From 2ffeb6c9b4321046791d4732a2c4c354146173d7 Mon Sep 17 00:00:00 2001 From: Niedermann IT-Dienstleistungen Date: Fri, 24 Aug 2018 11:16:40 +0200 Subject: [PATCH 0001/1834] #248 Refresh from server when in view mode --- .../android/fragment/NotePreviewFragment.java | 36 +++++++++++++++++++ .../main/res/layout/activity_single_note.xml | 13 +++++-- 2 files changed, 47 insertions(+), 2 deletions(-) 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 9209e2ec0..0d14d6a29 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 @@ -2,6 +2,7 @@ package it.niedermann.owncloud.notes.android.fragment; import android.os.Bundle; import android.support.annotation.Nullable; +import android.support.v4.widget.SwipeRefreshLayout; import android.text.method.LinkMovementMethod; import android.util.Log; import android.view.LayoutInflater; @@ -9,6 +10,7 @@ import android.view.Menu; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; +import android.widget.Toast; import com.yydcdut.rxmarkdown.RxMDTextView; import com.yydcdut.rxmarkdown.RxMarkdown; @@ -17,13 +19,21 @@ import com.yydcdut.rxmarkdown.syntax.text.TextFactory; import butterknife.BindView; import butterknife.ButterKnife; import it.niedermann.owncloud.notes.R; +import it.niedermann.owncloud.notes.persistence.NoteSQLiteOpenHelper; +import it.niedermann.owncloud.notes.util.ICallback; import it.niedermann.owncloud.notes.util.MarkDownUtil; +import it.niedermann.owncloud.notes.util.NotesClientUtil; import rx.Subscriber; import rx.android.schedulers.AndroidSchedulers; import rx.schedulers.Schedulers; public class NotePreviewFragment extends BaseNoteFragment { + private NoteSQLiteOpenHelper db = null; + + @BindView(R.id.swiperefreshlayout) + SwipeRefreshLayout swipeRefreshLayout; + @BindView(R.id.single_note_content) RxMDTextView noteContent; @@ -91,6 +101,32 @@ public class NotePreviewFragment extends BaseNoteFragment { }); noteContent.setText(content); noteContent.setMovementMethod(LinkMovementMethod.getInstance()); + + db = NoteSQLiteOpenHelper.getInstance(getActivity().getApplicationContext()); + // Pull to Refresh + swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() { + @Override + public void onRefresh() { + if (db.getNoteServerSyncHelper().isSyncPossible()) { + swipeRefreshLayout.setRefreshing(true); + db.getNoteServerSyncHelper().addCallbackPull( new ICallback() { + @Override + public void onFinish() { + noteContent.setText(db.getNote(note.getId()).getContent(), TextView.BufferType.SPANNABLE); + swipeRefreshLayout.setRefreshing(false); + } + + @Override + public void onScheduled() { + } + }); + db.getNoteServerSyncHelper().scheduleSync(false); + } else { + swipeRefreshLayout.setRefreshing(false); + Toast.makeText(getActivity().getApplicationContext(), getString(R.string.error_sync, getString(NotesClientUtil.LoginStatus.NO_NETWORK.str)), Toast.LENGTH_LONG).show(); + } + } + }); } @Override diff --git a/app/src/main/res/layout/activity_single_note.xml b/app/src/main/res/layout/activity_single_note.xml index e40af2efa..ff3cbfb7a 100644 --- a/app/src/main/res/layout/activity_single_note.xml +++ b/app/src/main/res/layout/activity_single_note.xml @@ -1,6 +1,14 @@ - + - \ No newline at end of file + + \ No newline at end of file -- GitLab From 5485d88481b33c9d97dd381ee3193db54e14ceca Mon Sep 17 00:00:00 2001 From: stefan-niedermann Date: Fri, 8 Feb 2019 11:34:15 +0100 Subject: [PATCH 0002/1834] #248 Refresh from server when in view mode - Merge master --- .../android/fragment/NotePreviewFragment.java | 1 + .../main/res/layout/activity_single_note.xml | 34 +++++++++---------- 2 files changed, 18 insertions(+), 17 deletions(-) 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 41696a13a..07aecb0db 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 @@ -18,6 +18,7 @@ import com.yydcdut.rxmarkdown.RxMDTextView; import com.yydcdut.rxmarkdown.RxMarkdown; import androidx.annotation.Nullable; +import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; import butterknife.BindView; import butterknife.ButterKnife; import it.niedermann.owncloud.notes.R; diff --git a/app/src/main/res/layout/activity_single_note.xml b/app/src/main/res/layout/activity_single_note.xml index ff3cbfb7a..c4e0834a5 100644 --- a/app/src/main/res/layout/activity_single_note.xml +++ b/app/src/main/res/layout/activity_single_note.xml @@ -1,6 +1,6 @@ - - - + android:layout_height="match_parent" + android:orientation="vertical" + tools:context="it.niedermann.owncloud.notes.android.activity.EditNoteActivity"> - - \ No newline at end of file + + + \ No newline at end of file -- GitLab From 4fc08ada662f56c34df366aa113b9540d211fdb9 Mon Sep 17 00:00:00 2001 From: stefan-niedermann Date: Fri, 19 Jul 2019 14:15:31 +0200 Subject: [PATCH 0003/1834] #378 Enhance category handling Removed AlwaysAutoCompleteView in favor of a recylcerview --- .../fragment/CategoryDialogFragment.java | 156 ++++-------------- .../owncloud/notes/model/CategoryAdapter.java | 71 ++++++++ .../notes/model/NavigationAdapter.java | 23 +-- .../res/layout/dialog_change_category.xml | 15 +- .../layout/dialog_change_category_single.xml | 11 ++ 5 files changed, 133 insertions(+), 143 deletions(-) create mode 100644 app/src/main/java/it/niedermann/owncloud/notes/model/CategoryAdapter.java create mode 100644 app/src/main/res/layout/dialog_change_category_single.xml 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 e27d45e9e..e81ec836a 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 @@ -3,25 +3,22 @@ package it.niedermann.owncloud.notes.android.fragment; import android.app.AlertDialog; import android.app.Dialog; import android.app.DialogFragment; -import android.app.Fragment; -import android.content.Context; -import android.content.DialogInterface; import android.os.AsyncTask; import android.os.Bundle; -import android.util.Log; import android.view.View; import android.view.WindowManager; -import android.widget.ArrayAdapter; -import android.widget.Filter; +import android.widget.EditText; + +import androidx.recyclerview.widget.RecyclerView; import java.util.ArrayList; import java.util.List; +import java.util.Objects; -import androidx.annotation.NonNull; import butterknife.BindView; import butterknife.ButterKnife; import it.niedermann.owncloud.notes.R; -import it.niedermann.owncloud.notes.android.AlwaysAutoCompleteTextView; +import it.niedermann.owncloud.notes.model.CategoryAdapter; import it.niedermann.owncloud.notes.model.NavigationAdapter; import it.niedermann.owncloud.notes.persistence.NoteSQLiteOpenHelper; @@ -46,42 +43,40 @@ public class CategoryDialogFragment extends DialogFragment { public static final String PARAM_CATEGORY = "category"; - @BindView(R.id.editCategory) - AlwaysAutoCompleteTextView textCategory; - private FolderArrayAdapter adapter; + @BindView(R.id.search) + EditText editCategory; + @BindView(R.id.recycler_view) + RecyclerView recyclerView; + private CategoryAdapter adapter; @Override public Dialog onCreateDialog(Bundle savedInstanceState) { View dialogView = getActivity().getLayoutInflater().inflate(R.layout.dialog_change_category, null); ButterKnife.bind(this, dialogView); if (savedInstanceState == null) { - textCategory.setText(getArguments().getString(PARAM_CATEGORY)); + editCategory.setText(getArguments().getString(PARAM_CATEGORY)); } - adapter = new FolderArrayAdapter(getActivity(), android.R.layout.simple_spinner_dropdown_item); - textCategory.setAdapter(adapter); + adapter = new CategoryAdapter(); + recyclerView.setAdapter(adapter); new LoadCategoriesTask().execute(); return new AlertDialog.Builder(getActivity(), R.style.ocAlertDialog) .setTitle(R.string.change_category_title) .setView(dialogView) .setCancelable(true) - .setPositiveButton(R.string.action_edit_save, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - CategoryDialogListener listener; - Fragment target = getTargetFragment(); - if (target instanceof CategoryDialogListener) { - listener = (CategoryDialogListener) target; - } else { - listener = (CategoryDialogListener) getActivity(); - } - listener.onCategoryChosen(textCategory.getText().toString()); - } - }) - .setNegativeButton(R.string.simple_cancel, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - // do nothing - } + +// @Override +// public void onClick(DialogInterface dialog, int which) { +// CategoryDialogListener listener; +// Fragment target = getTargetFragment(); +// if (target instanceof CategoryDialogListener) { +// listener = (CategoryDialogListener) target; +// } else { +// listener = (CategoryDialogListener) getActivity(); +// } +//// listener.onCategoryChosen(textCategory.getText().toString()); +// } + .setNegativeButton(R.string.simple_cancel, (dialog, which) -> { + // do nothing }) .create(); } @@ -89,11 +84,7 @@ public class CategoryDialogFragment extends DialogFragment { @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); - 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"); - } + Objects.requireNonNull(getDialog().getWindow()).setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE); } @@ -113,90 +104,13 @@ public class CategoryDialogFragment extends DialogFragment { @Override protected void onPostExecute(List categories) { - adapter.setData(categories); - if (textCategory.getText().length() == 0) { - textCategory.showFullDropDown(); - } else { - textCategory.dismissDropDown(); - } - } - } - - - private static class FolderArrayAdapter extends ArrayAdapter { - - private List originalData = new ArrayList<>(); - private Filter filter; - - private FolderArrayAdapter(@NonNull Context context, int resource) { - super(context, resource); - } - - public void setData(List data) { - originalData = data; - clear(); - addAll(data); - } - - @NonNull - @Override - public Filter getFilter() { - if (filter == null) { - filter = new FolderFilter(); - } - return filter; - } - - /* This implementation is based on ArrayAdapter.ArrayFilter */ - private class FolderFilter extends Filter { - @Override - protected FilterResults performFiltering(CharSequence prefix) { - final FilterResults results = new FilterResults(); - - if (prefix == null || prefix.length() == 0) { - final ArrayList list = new ArrayList<>(originalData); - results.values = list; - results.count = list.size(); - } else { - final String prefixString = prefix.toString().toLowerCase(); - final int count = originalData.size(); - final ArrayList newValues = new ArrayList<>(); - - for (int i = 0; i < count; i++) { - final String value = originalData.get(i); - final String valueText = value.toLowerCase(); - - // First match against the whole, non-splitted value - if (valueText.startsWith(prefixString)) { - newValues.add(value); - } else { - final String[] words = valueText.split("/"); - for (String word : words) { - if (word.startsWith(prefixString)) { - newValues.add(value); - break; - } - } - } - } - - results.values = newValues; - results.count = newValues.size(); - } - - return results; - } - - @Override - protected void publishResults(CharSequence constraint, FilterResults results) { - clear(); - addAll((List) results.values); - if (results.count > 0) { - notifyDataSetChanged(); - } else { - notifyDataSetInvalidated(); - } - } + adapter.setCategoryList(categories); +//TODO show creation entry +// if (textCategory.getText().length() == 0) { +// textCategory.showFullDropDown(); +// } else { +// textCategory.dismissDropDown(); +// } } } } diff --git a/app/src/main/java/it/niedermann/owncloud/notes/model/CategoryAdapter.java b/app/src/main/java/it/niedermann/owncloud/notes/model/CategoryAdapter.java new file mode 100644 index 000000000..a8209153f --- /dev/null +++ b/app/src/main/java/it/niedermann/owncloud/notes/model/CategoryAdapter.java @@ -0,0 +1,71 @@ +package it.niedermann.owncloud.notes.model; + +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import java.util.ArrayList; +import java.util.List; + +import butterknife.BindView; +import butterknife.ButterKnife; +import it.niedermann.owncloud.notes.R; + +public class CategoryAdapter extends RecyclerView.Adapter { + + private List categoryList; + + public CategoryAdapter() { + this.categoryList = new ArrayList<>(); + } + + /** + * Updates the item list and notifies respective view to update. + * + * @param categoryList List of items to be set + */ + public void setCategoryList(@NonNull List categoryList) { + this.categoryList = categoryList; + notifyDataSetChanged(); + } + + /** + * Adds the given note to the top of the list. + * + * @param category Category that should be added. + */ + public void add(@NonNull String category) { + categoryList.add(0, category); + notifyItemInserted(0); + notifyItemChanged(0); + } + + @Override + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + return new CategoryViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.dialog_change_category_single, parent, false)); + } + + @Override + public void onBindViewHolder(final RecyclerView.ViewHolder holder, int position) { + ((CategoryViewHolder) holder).title.setText(categoryList.get(position)); + } + + @Override + public int getItemCount() { + return categoryList.size(); + } + + static class CategoryViewHolder extends RecyclerView.ViewHolder { + @BindView(R.id.title) + TextView title; + + CategoryViewHolder(View view) { + super(view); + ButterKnife.bind(this, view); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/it/niedermann/owncloud/notes/model/NavigationAdapter.java b/app/src/main/java/it/niedermann/owncloud/notes/model/NavigationAdapter.java index a59a6bcf2..657c039dd 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/model/NavigationAdapter.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/model/NavigationAdapter.java @@ -1,16 +1,17 @@ package it.niedermann.owncloud.notes.model; import android.graphics.Color; -import androidx.annotation.DrawableRes; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.recyclerview.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; import android.widget.TextView; +import androidx.annotation.DrawableRes; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.recyclerview.widget.RecyclerView; + import java.util.ArrayList; import java.util.List; @@ -71,18 +72,8 @@ public class NavigationAdapter extends RecyclerView.Adapter clickListener.onIconClick(currentItem)); + itemView.setOnClickListener(view -> clickListener.onItemClick(currentItem)); } void assignItem(@NonNull NavigationItem item) { diff --git a/app/src/main/res/layout/dialog_change_category.xml b/app/src/main/res/layout/dialog_change_category.xml index 2c238806f..13a27d749 100644 --- a/app/src/main/res/layout/dialog_change_category.xml +++ b/app/src/main/res/layout/dialog_change_category.xml @@ -1,16 +1,19 @@ - + + + android:scrollbars="vertical" /> \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_change_category_single.xml b/app/src/main/res/layout/dialog_change_category_single.xml new file mode 100644 index 000000000..9a845a994 --- /dev/null +++ b/app/src/main/res/layout/dialog_change_category_single.xml @@ -0,0 +1,11 @@ + + + + \ No newline at end of file -- GitLab From 18aad1613d8c38edefcf4e3eb9f4744949027d94 Mon Sep 17 00:00:00 2001 From: stefan-niedermann Date: Thu, 3 Oct 2019 19:39:12 +0200 Subject: [PATCH 0004/1834] Setup account table and include SSO lib --- app/build.gradle | 2 + .../activity/NotesListViewActivity.java | 58 +++++++++-- .../NoteListWidgetConfiguration.java | 9 +- .../appwidget/NoteListWidgetFactory.java | 2 +- .../fragment/CategoryDialogFragment.java | 5 +- .../owncloud/notes/model/DBNote.java | 12 ++- .../owncloud/notes/model/LocalAccount.java | 61 ++++++++++++ .../persistence/NoteSQLiteOpenHelper.java | 95 +++++++++++++++---- .../persistence/NoteServerSyncHelper.java | 2 +- .../owncloud/notes/util/NotesClient.java | 3 +- build.gradle | 2 +- 11 files changed, 211 insertions(+), 40 deletions(-) create mode 100644 app/src/main/java/it/niedermann/owncloud/notes/model/LocalAccount.java diff --git a/app/build.gradle b/app/build.gradle index bba86c51e..e54f16b1e 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -34,6 +34,8 @@ android { dependencies { implementation project(':cert4android') + implementation 'com.github.nextcloud:Android-SingleSignOn:master-SNAPSHOT' + implementation 'io.reactivex:rxandroid:1.2.1' implementation 'io.reactivex:rxjava:1.3.8' implementation 'com.yydcdut:markdown-processor:0.1.3' 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 fce441a7d..c7349196d 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 @@ -42,6 +42,14 @@ import com.bumptech.glide.Glide; 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.exceptions.AndroidGetAccountsPermissionNotGranted; +import com.nextcloud.android.sso.exceptions.NextcloudFilesAppAccountNotFoundException; +import com.nextcloud.android.sso.exceptions.NextcloudFilesAppNotInstalledException; +import com.nextcloud.android.sso.exceptions.NoCurrentAccountSelectedException; +import com.nextcloud.android.sso.helper.SingleAccountHelper; +import com.nextcloud.android.sso.model.SingleSignOnAccount; +import com.nextcloud.android.sso.ui.UiExceptionManager; import java.util.ArrayList; import java.util.List; @@ -54,10 +62,10 @@ import it.niedermann.owncloud.notes.model.Category; import it.niedermann.owncloud.notes.model.DBNote; import it.niedermann.owncloud.notes.model.Item; import it.niedermann.owncloud.notes.model.ItemAdapter; +import it.niedermann.owncloud.notes.model.LocalAccount; import it.niedermann.owncloud.notes.model.NavigationAdapter; import it.niedermann.owncloud.notes.persistence.LoadNotesListTask; import it.niedermann.owncloud.notes.persistence.NoteSQLiteOpenHelper; -import it.niedermann.owncloud.notes.persistence.NoteServerSyncHelper; import it.niedermann.owncloud.notes.util.ExceptionHandler; import it.niedermann.owncloud.notes.util.ICallback; import it.niedermann.owncloud.notes.util.NoteUtil; @@ -80,6 +88,7 @@ public class NotesListViewActivity extends AppCompatActivity implements ItemAdap private static final String SAVED_STATE_NAVIGATION_OPEN = "navigationOpen"; private final static int create_note_cmd = 0; + private final static int add_account = 4; private final static int show_single_note_cmd = 1; private final static int server_settings = 2; private final static int about = 3; @@ -130,7 +139,7 @@ public class NotesListViewActivity extends AppCompatActivity implements ItemAdap if (!shortcutManager.isRateLimitingActive()) { List newShortcuts = new ArrayList<>(); - for (DBNote note : db.getRecentNotes()) { + for (DBNote note : db.getRecentNotes(0)) { Intent intent = new Intent(getApplicationContext(), EditNoteActivity.class); intent.putExtra(EditNoteActivity.PARAM_NOTE_ID, note.getId()); intent.setAction(ACTION_SHORTCUT); @@ -158,11 +167,32 @@ public class NotesListViewActivity extends AppCompatActivity implements ItemAdap protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Thread.currentThread().setUncaughtExceptionHandler(new ExceptionHandler(this)); - // First Run Wizard - if (!NoteServerSyncHelper.isConfigured(this)) { - Intent settingsIntent = new Intent(this, SettingsActivity.class); - startActivityForResult(settingsIntent, server_settings); + + + db = NoteSQLiteOpenHelper.getInstance(this); + + try { + Log.v("Notes", "current sso account " + SingleAccountHelper.getCurrentSingleSignOnAccount(getApplicationContext()).name); + } catch (NextcloudFilesAppAccountNotFoundException e) { + e.printStackTrace(); + } catch (NoCurrentAccountSelectedException e) { + if (!db.hasAccounts()) { + try { + AccountImporter.pickNewAccount(this); + } catch (NextcloudFilesAppNotInstalledException e1) { + UiExceptionManager.showDialogForException(this, e); + Log.w(NotesListViewActivity.class.toString(), "============================================================="); + Log.w(NotesListViewActivity.class.toString(), "Nextcloud app is not installed. Cannot choose account"); + e.printStackTrace(); + } catch (AndroidGetAccountsPermissionNotGranted e2) { + AccountImporter.requestAndroidAccountPermissionsAndPickAccount(this); + } + } else { + LocalAccount localAccount = db.getAccount(1); + SingleAccountHelper.setCurrentAccount(getApplicationContext(), localAccount.getAccountName()); + } } + String categoryAdapterSelectedItem = ADAPTER_KEY_RECENT; if (savedInstanceState == null) { if (ACTION_RECENT.equals(getIntent().getAction())) { @@ -180,8 +210,6 @@ public class NotesListViewActivity extends AppCompatActivity implements ItemAdap setContentView(R.layout.drawer_layout); ButterKnife.bind(this); - db = NoteSQLiteOpenHelper.getInstance(this); - setupActionBar(); setupNotesList(); setupNavigationList(categoryAdapterSelectedItem); @@ -305,7 +333,7 @@ public class NotesListViewActivity extends AppCompatActivity implements ItemAdap private class LoadCategoryListTask extends AsyncTask> { @Override protected List doInBackground(Void... voids) { - List categories = db.getCategories(); + List categories = db.getCategories(0); if (!categories.isEmpty() && categories.get(0).label.isEmpty()) { itemUncategorized = categories.get(0); itemUncategorized.label = getString(R.string.action_uncategorized); @@ -314,7 +342,7 @@ public class NotesListViewActivity extends AppCompatActivity implements ItemAdap itemUncategorized = null; } - Map favorites = db.getFavoritesCount(); + Map favorites = db.getFavoritesCount(0); int numFavorites = favorites.containsKey("1") ? favorites.get("1") : 0; int numNonFavorites = favorites.containsKey("0") ? favorites.get("0") : 0; itemFavorites.count = numFavorites; @@ -611,6 +639,14 @@ public class NotesListViewActivity extends AppCompatActivity implements ItemAdap */ @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + + AccountImporter.onActivityResult(requestCode, resultCode, data, this, (SingleSignOnAccount account) -> { + Log.v("Notes", "Added account: " + "name:" + account.name + ", " + account.url + ", userId" + account.userId); + db.addAccount(account.url, account.userId); + SingleAccountHelper.setCurrentAccount(getApplicationContext(), account.name); + }); + // Check which request we're responding to if (requestCode == create_note_cmd) { // Make sure the request was successful @@ -634,6 +670,8 @@ 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 if (requestCode == add_account) { + } } diff --git a/app/src/main/java/it/niedermann/owncloud/notes/android/appwidget/NoteListWidgetConfiguration.java b/app/src/main/java/it/niedermann/owncloud/notes/android/appwidget/NoteListWidgetConfiguration.java index 92fda7310..226b71482 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/android/appwidget/NoteListWidgetConfiguration.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/android/appwidget/NoteListWidgetConfiguration.java @@ -7,12 +7,13 @@ import android.content.SharedPreferences; import android.os.AsyncTask; import android.os.Bundle; import android.preference.PreferenceManager; +import android.util.Log; +import android.widget.Toast; + import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; -import android.util.Log; -import android.widget.Toast; import java.util.ArrayList; import java.util.List; @@ -124,7 +125,7 @@ public class NoteListWidgetConfiguration extends AppCompatActivity { @Override protected List doInBackground(Void... voids) { NavigationAdapter.NavigationItem itemUncategorized; - List categories = db.getCategories(); + List categories = db.getCategories(0); if (!categories.isEmpty() && categories.get(0).label.isEmpty()) { itemUncategorized = categories.get(0); @@ -132,7 +133,7 @@ public class NoteListWidgetConfiguration extends AppCompatActivity { itemUncategorized.icon = NavigationAdapter.ICON_NOFOLDER; } - Map favorites = db.getFavoritesCount(); + Map favorites = db.getFavoritesCount(0); int numFavorites = favorites.containsKey("1") ? favorites.get("1") : 0; int numNonFavorites = favorites.containsKey("0") ? favorites.get("0") : 0; itemFavorites.count = numFavorites; diff --git a/app/src/main/java/it/niedermann/owncloud/notes/android/appwidget/NoteListWidgetFactory.java b/app/src/main/java/it/niedermann/owncloud/notes/android/appwidget/NoteListWidgetFactory.java index 4776acfa9..c55325232 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/android/appwidget/NoteListWidgetFactory.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/android/appwidget/NoteListWidgetFactory.java @@ -45,7 +45,7 @@ public class NoteListWidgetFactory implements RemoteViewsService.RemoteViewsFact @Override public void onDataSetChanged() { if (displayMode == NoteListWidget.NLW_DISPLAY_ALL) { - dbNotes = db.getNotes(); + dbNotes = db.getNotes(0); } else if (displayMode == NoteListWidget.NLW_DISPLAY_STARRED) { dbNotes = db.searchNotes(null,null, true); } else if (displayMode == NoteListWidget.NLW_DISPLAY_CATEGORY) { 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 e27d45e9e..7d9a90685 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 @@ -14,10 +14,11 @@ import android.view.WindowManager; import android.widget.ArrayAdapter; import android.widget.Filter; +import androidx.annotation.NonNull; + import java.util.ArrayList; import java.util.List; -import androidx.annotation.NonNull; import butterknife.BindView; import butterknife.ButterKnife; import it.niedermann.owncloud.notes.R; @@ -101,7 +102,7 @@ public class CategoryDialogFragment extends DialogFragment { @Override protected List doInBackground(Void... voids) { NoteSQLiteOpenHelper db = NoteSQLiteOpenHelper.getInstance(getActivity()); - List items = db.getCategories(); + List items = db.getCategories(0); List categories = new ArrayList<>(); for (NavigationAdapter.NavigationItem item : items) { if (!item.label.isEmpty()) { diff --git a/app/src/main/java/it/niedermann/owncloud/notes/model/DBNote.java b/app/src/main/java/it/niedermann/owncloud/notes/model/DBNote.java index 4ce7012ed..73e148b9c 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/model/DBNote.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/model/DBNote.java @@ -12,6 +12,7 @@ import it.niedermann.owncloud.notes.util.NoteUtil; public class DBNote extends CloudNote implements Item, Serializable { private long id; + private long accountId; private DBStatus status; private String excerpt = ""; @@ -26,6 +27,10 @@ public class DBNote extends CloudNote implements Item, Serializable { return id; } + public long getAccountId() { + return accountId; + } + public DBStatus getStatus() { return status; } @@ -58,6 +63,11 @@ public class DBNote extends CloudNote implements Item, Serializable { @Override public String toString() { - return "#" + getId() + "/" + super.toString() + " " + getStatus(); + return "DBNote{" + + "id=" + id + + ", accountId=" + accountId + + ", status=" + status + + ", excerpt='" + excerpt + '\'' + + '}'; } } diff --git a/app/src/main/java/it/niedermann/owncloud/notes/model/LocalAccount.java b/app/src/main/java/it/niedermann/owncloud/notes/model/LocalAccount.java new file mode 100644 index 000000000..1b30bcbfd --- /dev/null +++ b/app/src/main/java/it/niedermann/owncloud/notes/model/LocalAccount.java @@ -0,0 +1,61 @@ +package it.niedermann.owncloud.notes.model; + +public class LocalAccount { + + private long id; + private String userName; + private String accountName; + private String url; + private String displayName; + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public String getUserName() { + return userName; + } + + public void setUserName(String userName) { + this.userName = userName; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public String getDisplayName() { + return displayName; + } + + public void setDisplayName(String displayName) { + this.displayName = displayName; + } + + public String getAccountName() { + return accountName; + } + + public void setAccountName(String accountName) { + this.accountName = accountName; + } + + @Override + public String toString() { + return "LocalAccount{" + + "id=" + id + + ", userName='" + userName + '\'' + + ", accountName='" + accountName + '\'' + + ", url='" + url + '\'' + + ", displayName='" + displayName + '\'' + + '}'; + } +} 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 43477dd34..09f8c501e 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 @@ -5,6 +5,7 @@ import android.content.Context; import android.content.Intent; import android.content.pm.ShortcutManager; import android.database.Cursor; +import android.database.DatabaseUtils; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.os.Build; @@ -28,6 +29,7 @@ import it.niedermann.owncloud.notes.android.appwidget.SingleNoteWidget; import it.niedermann.owncloud.notes.model.CloudNote; 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.ICallback; import it.niedermann.owncloud.notes.util.NoteUtil; @@ -37,11 +39,19 @@ import it.niedermann.owncloud.notes.util.NoteUtil; */ public class NoteSQLiteOpenHelper extends SQLiteOpenHelper { - private static final int database_version = 8; + private static final int database_version = 9; private static final String database_name = "OWNCLOUD_NOTES"; private static final String table_notes = "NOTES"; + private static final String table_accounts = "ACCOUNTS"; private static final String key_id = "ID"; + private static final String key_account_id = "ACCOUNT_ID"; private static final String key_remote_id = "REMOTEID"; + + private static final String key_url = "URL"; + private static final String key_account_name = "ACCOUNT_NAME"; + private static final String key_username = "USERNAME"; + private static final String key_display_name = "DISPLAY_NAME"; + private static final String key_status = "STATUS"; private static final String key_title = "TITLE"; private static final String key_modified = "MODIFIED"; @@ -49,6 +59,7 @@ public class NoteSQLiteOpenHelper extends SQLiteOpenHelper { private static final String key_favorite = "FAVORITE"; private static final String key_category = "CATEGORY"; private static final String key_etag = "ETAG"; + private static final String[] columns = {key_id, key_remote_id, key_status, key_title, key_modified, key_content, key_favorite, key_category, key_etag}; private static final String default_order = key_favorite + " DESC, " + key_modified + " DESC"; @@ -81,13 +92,15 @@ public class NoteSQLiteOpenHelper extends SQLiteOpenHelper { */ @Override public void onCreate(SQLiteDatabase db) { - createTable(db, table_notes); + createNotesTable(db, table_notes); + createAccountTable(db, table_accounts); } - private void createTable(SQLiteDatabase db, String tableName) { + private void createNotesTable(SQLiteDatabase db, String tableName) { db.execSQL("CREATE TABLE " + tableName + " ( " + key_id + " INTEGER PRIMARY KEY AUTOINCREMENT, " + key_remote_id + " INTEGER, " + + key_account_id + " INTEGER, " + key_status + " VARCHAR(50), " + key_title + " TEXT, " + key_modified + " INTEGER DEFAULT 0, " + @@ -95,7 +108,17 @@ public class NoteSQLiteOpenHelper extends SQLiteOpenHelper { key_favorite + " INTEGER DEFAULT 0, " + key_category + " TEXT NOT NULL DEFAULT '', " + key_etag + " TEXT)"); - createIndexes(db); + createNotesIndexes(db); + } + + private void createAccountTable(SQLiteDatabase db, String tableName) { + db.execSQL("CREATE TABLE " + tableName + " ( " + + key_id + " INTEGER PRIMARY KEY AUTOINCREMENT, " + + key_url + " TEXT, " + + key_username + " TEXT, " + + key_account_name + " TEXT, " + + key_display_name + " TEXT)"); + createAccountIndexes(db); } @@ -119,16 +142,20 @@ public class NoteSQLiteOpenHelper extends SQLiteOpenHelper { 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"); - createIndexes(db); + createNotesIndexes(db); } if (oldVersion < 8) { final String table_temp = "NOTES_TEMP"; - createTable(db, table_temp); + createNotesTable(db, table_temp); db.execSQL(String.format("INSERT INTO %s(%s,%s,%s,%s,%s,%s,%s,%s,%s) ", table_temp, key_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,strftime('%%s',%s),%s,%s,%s,%s FROM %s", key_id, key_remote_id, key_status, key_title, key_modified, key_content, key_favorite, key_category, key_etag, table_notes)); db.execSQL(String.format("DROP TABLE %s", table_notes)); db.execSQL(String.format("ALTER TABLE %s RENAME TO %s", table_temp, table_notes)); } + if (oldVersion < 9) { + db.execSQL("ALTER TABLE " + table_notes + " ADD COLUMN " + key_account_id + " INTEGER NOT NULL DEFAULT 0"); + createAccountTable(db, table_accounts); + } } @Override @@ -154,7 +181,7 @@ public class NoteSQLiteOpenHelper extends SQLiteOpenHelper { c.close(); } - private void createIndexes(SQLiteDatabase db) { + private void createNotesIndexes(SQLiteDatabase db) { createIndex(db, table_notes, key_remote_id); createIndex(db, table_notes, key_status); createIndex(db, table_notes, key_favorite); @@ -162,6 +189,9 @@ public class NoteSQLiteOpenHelper extends SQLiteOpenHelper { createIndex(db, table_notes, key_modified); } + private void createAccountIndexes(SQLiteDatabase db) { + } + 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 + ")"); @@ -308,14 +338,14 @@ public class NoteSQLiteOpenHelper extends SQLiteOpenHelper { */ @NonNull @WorkerThread - public List getNotes() { - return getNotesCustom(key_status + " != ?", new String[]{DBStatus.LOCAL_DELETED.getTitle()}, default_order); + public List getNotes(long accountId) { + return getNotesCustom(key_status + " != ? AND " + key_account_id + " = ?", new String[]{DBStatus.LOCAL_DELETED.getTitle(), "" + accountId}, default_order); } @NonNull @WorkerThread - public List getRecentNotes() { - return getNotesCustom(key_status + " != ?", new String[]{DBStatus.LOCAL_DELETED.getTitle()}, key_modified + " DESC", "4"); + public List getRecentNotes(long accountId) { + return getNotesCustom(key_status + " != ? AND " + key_account_id + " = ?", new String[]{DBStatus.LOCAL_DELETED.getTitle(), "" + accountId}, key_modified + " DESC", "4"); } /** @@ -364,19 +394,19 @@ public class NoteSQLiteOpenHelper extends SQLiteOpenHelper { */ @NonNull @WorkerThread - public List getLocalModifiedNotes() { - return getNotesCustom(key_status + " != ?", new String[]{DBStatus.VOID.getTitle()}, null); + public List getLocalModifiedNotes(long accountId) { + return getNotesCustom(key_status + " != ? AND " + key_account_id + " = ?", new String[]{DBStatus.VOID.getTitle(), "" + accountId}, null); } @NonNull @WorkerThread - public Map getFavoritesCount() { + public Map getFavoritesCount(long accountId) { SQLiteDatabase db = getReadableDatabase(); Cursor cursor = db.query( table_notes, new String[]{key_favorite, "COUNT(*)"}, - key_status + " != ?", - new String[]{DBStatus.LOCAL_DELETED.getTitle()}, + key_status + " != ? AND " + key_account_id + " = ?", + new String[]{DBStatus.LOCAL_DELETED.getTitle(), "" + accountId}, key_favorite, null, key_favorite); @@ -390,13 +420,13 @@ public class NoteSQLiteOpenHelper extends SQLiteOpenHelper { @NonNull @WorkerThread - public List getCategories() { + public List getCategories(long accountId) { SQLiteDatabase db = getReadableDatabase(); Cursor cursor = db.query( table_notes, new String[]{key_category, "COUNT(*)"}, - key_status + " != ?", - new String[]{DBStatus.LOCAL_DELETED.getTitle()}, + key_status + " != ? AND " + key_account_id + " = ?", + new String[]{DBStatus.LOCAL_DELETED.getTitle(), "" + accountId}, key_category, null, key_category); @@ -596,4 +626,31 @@ public class NoteSQLiteOpenHelper extends SQLiteOpenHelper { intent.setAction("android.appwidget.action.APPWIDGET_UPDATE"); getContext().sendBroadcast(intent); } + + public boolean hasAccounts() { + return DatabaseUtils.queryNumEntries(getReadableDatabase(), table_accounts) > 0; + } + + public long addAccount(String url, String username) { + SQLiteDatabase db = this.getWritableDatabase(); + ContentValues values = new ContentValues(); + values.put(key_url, url); + values.put(key_username, username); + return db.insert(table_accounts, null, values); + } + + public LocalAccount getAccount(int i) { + SQLiteDatabase db = getReadableDatabase(); + Cursor cursor = db.query(table_accounts, new String[]{key_id, key_url, key_account_name, key_username, key_display_name}, key_id + " = ?", new String[]{i + ""}, null, null, null, null); + LocalAccount account = new LocalAccount(); + while (cursor.moveToNext()) { + account.setId(cursor.getLong(0)); + account.setUrl(cursor.getString(1)); + account.setAccountName(cursor.getString(2)); + account.setUserName(cursor.getString(3)); + account.setDisplayName(cursor.getString(4)); + } + cursor.close(); + return account; + } } 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 a5d04a7ba..9e18a9a57 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 @@ -304,7 +304,7 @@ public class NoteServerSyncHelper { */ private void pushLocalChanges() { Log.d(getClass().getSimpleName(), "pushLocalChanges()"); - List notes = dbHelper.getLocalModifiedNotes(); + List notes = dbHelper.getLocalModifiedNotes(0); for (DBNote note : notes) { Log.d(getClass().getSimpleName(), " Process Local Note: " + note); try { 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 87c3b6e5f..bb7c9ebbf 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 @@ -1,9 +1,10 @@ package it.niedermann.owncloud.notes.util; -import androidx.annotation.WorkerThread; import android.util.Base64; import android.util.Log; +import androidx.annotation.WorkerThread; + import org.json.JSONException; import org.json.JSONObject; diff --git a/build.gradle b/build.gradle index f64da1e5e..7d70d889b 100644 --- a/build.gradle +++ b/build.gradle @@ -19,6 +19,6 @@ allprojects { repositories { google() jcenter() - + maven { url "https://jitpack.io" } } } -- GitLab From 7b1f409637ab9a2345048edd65a890d2ef47c349 Mon Sep 17 00:00:00 2001 From: stefan-niedermann Date: Fri, 4 Oct 2019 10:11:42 +0200 Subject: [PATCH 0005/1834] Enable Network requests via SSO --- .../owncloud/notes/model/DBNote.java | 3 +- .../persistence/NoteSQLiteOpenHelper.java | 13 +- .../persistence/NoteServerSyncHelper.java | 13 +- .../owncloud/notes/util/NotesClient.java | 204 ++++++++++++------ 4 files changed, 153 insertions(+), 80 deletions(-) diff --git a/app/src/main/java/it/niedermann/owncloud/notes/model/DBNote.java b/app/src/main/java/it/niedermann/owncloud/notes/model/DBNote.java index 73e148b9c..9f1e5520e 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/model/DBNote.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/model/DBNote.java @@ -16,11 +16,12 @@ public class DBNote extends CloudNote implements Item, Serializable { private DBStatus status; private String excerpt = ""; - public DBNote(long id, long remoteId, Calendar modified, String title, String content, boolean favorite, String category, String etag, DBStatus status) { + public DBNote(long id, long remoteId, Calendar modified, String title, String content, boolean favorite, String category, String etag, DBStatus status, long accountId) { super(remoteId, modified, title, content, favorite, category, etag); this.id = id; setExcerpt(content); this.status = status; + this.accountId = accountId; } public long getId() { 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 09f8c501e..708df47da 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 @@ -219,7 +219,8 @@ public class NoteSQLiteOpenHelper extends SQLiteOpenHelper { */ @SuppressWarnings("UnusedReturnValue") public long addNoteAndSync(CloudNote note) { - DBNote dbNote = new DBNote(0, 0, note.getModified(), note.getTitle(), note.getContent(), note.isFavorite(), note.getCategory(), note.getEtag(), DBStatus.LOCAL_EDITED); + // FIXME hardcoded accountId + DBNote dbNote = new DBNote(0, 0, note.getModified(), note.getTitle(), note.getContent(), note.isFavorite(), note.getCategory(), note.getEtag(), DBStatus.LOCAL_EDITED, 1); long id = addNote(dbNote); notifyNotesChanged(); getNoteServerSyncHelper().scheduleSync(true); @@ -241,6 +242,7 @@ public class NoteSQLiteOpenHelper extends SQLiteOpenHelper { values.put(key_id, dbNote.getId()); } values.put(key_status, dbNote.getStatus().getTitle()); + values.put(key_account_id, dbNote.getAccountId()); } else { values.put(key_status, DBStatus.VOID.getTitle()); } @@ -307,7 +309,8 @@ public class NoteSQLiteOpenHelper extends SQLiteOpenHelper { private DBNote getNoteFromCursor(@NonNull Cursor cursor) { 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))); + // FIXME hardcoded accountId + 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)), 1); } public void debugPrintFullDB() { @@ -479,9 +482,11 @@ public class NoteSQLiteOpenHelper extends SQLiteOpenHelper { //debugPrintFullDB(); DBNote newNote; if (newContent == null) { - newNote = new DBNote(oldNote.getId(), oldNote.getRemoteId(), oldNote.getModified(), oldNote.getTitle(), oldNote.getContent(), oldNote.isFavorite(), oldNote.getCategory(), oldNote.getEtag(), DBStatus.LOCAL_EDITED); + // FIXME hardcoded accountId + newNote = new DBNote(oldNote.getId(), oldNote.getRemoteId(), oldNote.getModified(), oldNote.getTitle(), oldNote.getContent(), oldNote.isFavorite(), oldNote.getCategory(), oldNote.getEtag(), DBStatus.LOCAL_EDITED, 1); } else { - newNote = new DBNote(oldNote.getId(), oldNote.getRemoteId(), Calendar.getInstance(), NoteUtil.generateNonEmptyNoteTitle(newContent, getContext()), newContent, oldNote.isFavorite(), oldNote.getCategory(), oldNote.getEtag(), DBStatus.LOCAL_EDITED); + // FIXME hardcoded accountId + newNote = new DBNote(oldNote.getId(), oldNote.getRemoteId(), Calendar.getInstance(), NoteUtil.generateNonEmptyNoteTitle(newContent, getContext()), newContent, oldNote.isFavorite(), oldNote.getCategory(), oldNote.getEtag(), DBStatus.LOCAL_EDITED, 1); } SQLiteDatabase db = this.getWritableDatabase(); ContentValues values = new ContentValues(); 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 9e18a9a57..efcdc40a2 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 @@ -286,7 +286,7 @@ public class NoteServerSyncHelper { @Override protected LoginStatus doInBackground(Void... voids) { - client = createNotesClient(); // recreate NoteClients on every sync in case the connection settings was changed + client = createNotesClient(appContext); // recreate NoteClients on every sync in case the connection settings was changed Log.i(getClass().getSimpleName(), "STARTING SYNCHRONIZATION"); //dbHelper.debugPrintFullDB(); LoginStatus status = LoginStatus.OK; @@ -304,7 +304,8 @@ public class NoteServerSyncHelper { */ private void pushLocalChanges() { Log.d(getClass().getSimpleName(), "pushLocalChanges()"); - List notes = dbHelper.getLocalModifiedNotes(0); + // FIXME hardcoded accountId + List notes = dbHelper.getLocalModifiedNotes(1); for (DBNote note : notes) { Log.d(getClass().getSimpleName(), " Process Local Note: " + note); try { @@ -333,7 +334,7 @@ public class NoteServerSyncHelper { if (note.getRemoteId() > 0) { Log.v(getClass().getSimpleName(), " ...delete (from server and local)"); try { - client.deleteNote(customCertManager, note.getRemoteId()); + client.deleteNote(note.getRemoteId()); } catch (FileNotFoundException e) { Log.v(getClass().getSimpleName(), " ...Note does not exist on server (anymore?) -> delete locally"); } @@ -364,7 +365,7 @@ public class NoteServerSyncHelper { LoginStatus status; try { Map idMap = dbHelper.getIdMap(); - ServerResponse.NotesResponse response = client.getNotes(customCertManager, lastModified, lastETag); + ServerResponse.NotesResponse response = client.getNotes(lastModified, lastETag); List remoteNotes = response.getNotes(); Set remoteIDs = new HashSet<>(); // pull remote changes: update or create each remote note @@ -443,11 +444,11 @@ public class NoteServerSyncHelper { } } - private NotesClient createNotesClient() { + private NotesClient createNotesClient(Context context) { SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(appContext.getApplicationContext()); String url = preferences.getString(SettingsActivity.SETTINGS_URL, SettingsActivity.DEFAULT_SETTINGS); String username = preferences.getString(SettingsActivity.SETTINGS_USERNAME, SettingsActivity.DEFAULT_SETTINGS); String password = preferences.getString(SettingsActivity.SETTINGS_PASSWORD, SettingsActivity.DEFAULT_SETTINGS); - return new NotesClient(url, username, password); + return new NotesClient(context, url, username, password); } } 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 bb7c9ebbf..6b989ea6a 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 @@ -1,22 +1,31 @@ package it.niedermann.owncloud.notes.util; -import android.util.Base64; +import android.content.Context; import android.util.Log; import androidx.annotation.WorkerThread; +import com.google.gson.GsonBuilder; +import com.nextcloud.android.sso.aidl.NextcloudRequest; +import com.nextcloud.android.sso.api.NextcloudAPI; +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 org.json.JSONException; import org.json.JSONObject; import java.io.BufferedReader; import java.io.IOException; +import java.io.InputStream; import java.io.InputStreamReader; -import java.io.OutputStream; -import java.net.HttpURLConnection; -import java.net.MalformedURLException; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import at.bitfire.cert4android.CustomCertManager; -import it.niedermann.owncloud.notes.BuildConfig; import it.niedermann.owncloud.notes.model.CloudNote; import it.niedermann.owncloud.notes.util.ServerResponse.NoteResponse; import it.niedermann.owncloud.notes.util.ServerResponse.NotesResponse; @@ -24,6 +33,8 @@ import it.niedermann.owncloud.notes.util.ServerResponse.NotesResponse; @WorkerThread public class NotesClient { + private NextcloudAPI mNextcloudAPI; + /** * This entity class is used to return relevant data of the HTTP reponse. */ @@ -63,22 +74,34 @@ public class NotesClient { public static final String JSON_ETAG = "etag"; public static final String JSON_MODIFIED = "modified"; private static final String application_json = "application/json"; - private String url = ""; - private String username = ""; - private String password = ""; - - public NotesClient(String url, String username, String password) { - this.url = url; - this.username = username; - this.password = password; + + public NotesClient(Context context, String url, String username, String password) { + try { + SingleSignOnAccount ssoAccount = SingleAccountHelper.getCurrentSingleSignOnAccount(context); + mNextcloudAPI = new NextcloudAPI(context, ssoAccount, new GsonBuilder().create(), new NextcloudAPI.ApiConnectedListener() { + @Override + public void onConnected() { + + } + + @Override + public void onError(Exception ex) { + + } + }); + } catch (NextcloudFilesAppAccountNotFoundException e) { + // TODO handle errors + } catch (NoCurrentAccountSelectedException e) { + // TODO handle errors + } } - public NotesResponse getNotes(CustomCertManager ccm, long lastModified, String lastETag) throws JSONException, IOException { + public NotesResponse getNotes(long lastModified, String lastETag) throws JSONException, IOException { String url = "notes"; if (lastModified > 0) { url += "?pruneBefore=" + lastModified; } - return new NotesResponse(requestServer(ccm, url, METHOD_GET, null, lastETag)); + return new NotesResponse(requestServer(url, METHOD_GET, null, lastETag)); } /** @@ -90,17 +113,17 @@ public class NotesClient { * @throws IOException */ @SuppressWarnings("unused") - public NoteResponse getNoteById(CustomCertManager ccm, long id) throws JSONException, IOException { - return new NoteResponse(requestServer(ccm, "notes/" + id, METHOD_GET, null, null)); + public NoteResponse getNoteById(long id) throws JSONException, IOException { + return new NoteResponse(requestServer("notes/" + id, METHOD_GET, null, null)); } - private NoteResponse putNote(CustomCertManager ccm, CloudNote note, String path, String method) throws JSONException, IOException { + private NoteResponse putNote(CloudNote note, String path, String method) throws JSONException, IOException { JSONObject paramObject = new JSONObject(); paramObject.accumulate(JSON_CONTENT, note.getContent()); paramObject.accumulate(JSON_MODIFIED, note.getModified().getTimeInMillis() / 1000); paramObject.accumulate(JSON_FAVORITE, note.isFavorite()); paramObject.accumulate(JSON_CATEGORY, note.getCategory()); - return new NoteResponse(requestServer(ccm, path, method, paramObject, null)); + return new NoteResponse(requestServer(path, method, paramObject, null)); } @@ -113,15 +136,15 @@ public class NotesClient { * @throws IOException */ public NoteResponse createNote(CustomCertManager ccm, CloudNote note) throws JSONException, IOException { - return putNote(ccm, note, "notes", METHOD_POST); + return putNote(note, "notes", METHOD_POST); } public NoteResponse editNote(CustomCertManager ccm, CloudNote note) throws JSONException, IOException { - return putNote(ccm, note, "notes/" + note.getRemoteId(), METHOD_PUT); + return putNote(note, "notes/" + note.getRemoteId(), METHOD_PUT); } - public void deleteNote(CustomCertManager ccm, long noteId) throws IOException { - this.requestServer(ccm, "notes/" + noteId, METHOD_DELETE, null, null); + public void deleteNote(long noteId) throws IOException { + this.requestServer("notes/" + noteId, METHOD_DELETE, null, null); } /** @@ -131,59 +154,102 @@ public class NotesClient { * @param method GET, POST, DELETE or PUT * @param params JSON Object which shall be transferred to the server. * @return Body of answer - * @throws MalformedURLException - * @throws IOException */ - private ResponseData requestServer(CustomCertManager ccm, String target, String method, JSONObject params, String lastETag) - throws IOException { - StringBuffer result = new StringBuffer(); - // setup connection - String targetURL = url + "index.php/apps/notes/api/v0.2/" + target; - HttpURLConnection con = SupportUtil.getHttpURLConnection(ccm, targetURL); - con.setRequestMethod(method); - con.setRequestProperty( - "Authorization", - "Basic " + Base64.encodeToString((username + ":" + password).getBytes(), Base64.NO_WRAP)); - // https://github.com/square/retrofit/issues/805#issuecomment-93426183 - con.setRequestProperty( "Connection", "Close"); - con.setRequestProperty("User-Agent", "nextcloud-notes/" + BuildConfig.VERSION_NAME + " (Android)"); - if (lastETag != null && METHOD_GET.equals(method)) { - con.setRequestProperty("If-None-Match", lastETag); - } - con.setConnectTimeout(10 * 1000); // 10 seconds - Log.d(getClass().getSimpleName(), method + " " + targetURL); - // send request data (optional) - byte[] paramData = null; + private ResponseData requestServer(String target, String method, JSONObject params, String lastETag) { + + + NextcloudRequest.Builder requestBuilder = new NextcloudRequest.Builder() + .setMethod(method) + .setUrl("/index.php/apps/notes/api/v0.2/" + target); + + Log.v("Notes", "NextcloudRequest Params: " + params); + + Map> header = new HashMap<>(); if (params != null) { - paramData = params.toString().getBytes(); - Log.d(getClass().getSimpleName(), "Params: " + params); - con.setFixedLengthStreamingMode(paramData.length); - con.setRequestProperty("Content-Type", application_json); - con.setDoOutput(true); - OutputStream os = con.getOutputStream(); - os.write(paramData); - os.flush(); - os.close(); + header.put("Content-Type", Collections.singletonList(application_json)); + requestBuilder.setRequestBody(params.toString()); } - // read response data - int responseCode = con.getResponseCode(); - Log.d(getClass().getSimpleName(), "HTTP response code: " + responseCode); - - if (responseCode == HttpURLConnection.HTTP_NOT_MODIFIED) { - throw new ServerResponse.NotModifiedException(); + if (lastETag != null && METHOD_GET.equals(method)) { + header.put("If-None-Match", Collections.singletonList(lastETag)); + requestBuilder.setHeader(header); } - BufferedReader rd = new BufferedReader(new InputStreamReader(con.getInputStream())); - String line; - while ((line = rd.readLine()) != null) { - result.append(line); + NextcloudRequest nextcloudRequest = requestBuilder.build(); + + StringBuilder result = new StringBuilder(); + + try { + InputStream inputStream = mNextcloudAPI.performNetworkRequest(nextcloudRequest); + Log.v("Notes", "NextcloudRequest: " + nextcloudRequest.toString()); + BufferedReader rd = new BufferedReader(new InputStreamReader(inputStream)); + String line; + while ((line = rd.readLine()) != null) { + result.append(line); + } + inputStream.close(); + } catch (Exception e) { + e.printStackTrace(); + } + String etag = ""; + if (nextcloudRequest.getHeader().get("ETag") != null) { + etag = nextcloudRequest.getHeader().get("ETag").get(0); } - // create response object - String etag = con.getHeaderField("ETag"); - long lastModified = con.getHeaderFieldDate("Last-Modified", 0) / 1000; - Log.i(getClass().getSimpleName(), "Result length: " + result.length() + (paramData == null ? "" : "; Request length: " + paramData.length)); - Log.d(getClass().getSimpleName(), "ETag: " + etag + "; Last-Modified: " + lastModified + " (" + con.getHeaderField("Last-Modified") + ")"); + 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 + ")"); // return these header fields since they should only be saved after successful processing the result! return new ResponseData(result.toString(), etag, lastModified); + + +// StringBuffer result = new StringBuffer(); +// // setup connection +// String targetURL = url + "index.php/apps/notes/api/v0.2/" + target; +// HttpURLConnection con = SupportUtil.getHttpURLConnection(ccm, targetURL); +// con.setRequestMethod(method); +// con.setRequestProperty( +// "Authorization", +// "Basic " + Base64.encodeToString((username + ":" + password).getBytes(), Base64.NO_WRAP)); +// // https://github.com/square/retrofit/issues/805#issuecomment-93426183 +// con.setRequestProperty("Connection", "Close"); +// con.setRequestProperty("User-Agent", "nextcloud-notes/" + BuildConfig.VERSION_NAME + " (Android)"); +// if (lastETag != null && METHOD_GET.equals(method)) { +// con.setRequestProperty("If-None-Match", lastETag); +// } +// con.setConnectTimeout(10 * 1000); // 10 seconds +// Log.d(getClass().getSimpleName(), method + " " + targetURL); +// // send request data (optional) +// byte[] paramData = null; +// if (params != null) { +// paramData = params.toString().getBytes(); +// Log.d(getClass().getSimpleName(), "Params: " + params); +// con.setFixedLengthStreamingMode(paramData.length); +// con.setRequestProperty("Content-Type", application_json); +// con.setDoOutput(true); +// OutputStream os = con.getOutputStream(); +// os.write(paramData); +// os.flush(); +// os.close(); +// } +// // read response data +// int responseCode = con.getResponseCode(); +// Log.d(getClass().getSimpleName(), "HTTP response code: " + responseCode); +// +// if (responseCode == HttpURLConnection.HTTP_NOT_MODIFIED) { +// throw new ServerResponse.NotModifiedException(); +// } +// +// BufferedReader rd = new BufferedReader(new InputStreamReader(con.getInputStream())); +// String line; +// while ((line = rd.readLine()) != null) { +// result.append(line); +// } +// // create response object +// String etag = con.getHeaderField("ETag"); +// long lastModified = con.getHeaderFieldDate("Last-Modified", 0) / 1000; +// Log.i(getClass().getSimpleName(), "Result length: " + result.length() + (paramData == null ? "" : "; Request length: " + paramData.length)); +// Log.d(getClass().getSimpleName(), "ETag: " + etag + "; Last-Modified: " + lastModified + " (" + con.getHeaderField("Last-Modified") + ")"); +// // return these header fields since they should only be saved after successful processing the result! +// return new ResponseData(result.toString(), etag, lastModified); } } -- GitLab From 738843fbcbec5cf0efcd1bd5c2507c1675ef6178 Mon Sep 17 00:00:00 2001 From: stefan-niedermann Date: Fri, 4 Oct 2019 10:19:23 +0200 Subject: [PATCH 0006/1834] Make account switchable in Navigation Drawer --- .../activity/NotesListViewActivity.java | 22 ++++++++++++++----- .../persistence/NoteSQLiteOpenHelper.java | 9 ++++++++ 2 files changed, 25 insertions(+), 6 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 c7349196d..6d1bf7978 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 @@ -88,7 +88,6 @@ public class NotesListViewActivity extends AppCompatActivity implements ItemAdap private static final String SAVED_STATE_NAVIGATION_OPEN = "navigationOpen"; private final static int create_note_cmd = 0; - private final static int add_account = 4; private final static int show_single_note_cmd = 1; private final static int server_settings = 2; private final static int about = 3; @@ -445,8 +444,16 @@ public class NotesListViewActivity extends AppCompatActivity implements ItemAdap this.updateUsernameInDrawer(); final NotesListViewActivity that = this; this.headerView.setOnClickListener((View v) -> { - Intent settingsIntent = new Intent(that, SettingsActivity.class); - startActivityForResult(settingsIntent, server_settings); + try { + AccountImporter.pickNewAccount(this); + } catch (NextcloudFilesAppNotInstalledException e1) { + UiExceptionManager.showDialogForException(this, e1); + Log.w(NotesListViewActivity.class.toString(), "============================================================="); + Log.w(NotesListViewActivity.class.toString(), "Nextcloud app is not installed. Cannot choose account"); + e1.printStackTrace(); + } catch (AndroidGetAccountsPermissionNotGranted e2) { + AccountImporter.requestAndroidAccountPermissionsAndPickAccount(this); + } }); adapterMenu.setItems(itemsMenu); @@ -643,7 +650,12 @@ public class NotesListViewActivity extends AppCompatActivity implements ItemAdap AccountImporter.onActivityResult(requestCode, resultCode, data, this, (SingleSignOnAccount account) -> { Log.v("Notes", "Added account: " + "name:" + account.name + ", " + account.url + ", userId" + account.userId); - db.addAccount(account.url, account.userId); + if(db.hasAccounts()) { + // FIXME hardcoded accountId + db.setAccount(1, account.url, account.userId); + } else { + db.addAccount(account.url, account.userId); + } SingleAccountHelper.setCurrentAccount(getApplicationContext(), account.name); }); @@ -670,8 +682,6 @@ 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 if (requestCode == add_account) { - } } 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 708df47da..56c5b46a4 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 @@ -658,4 +658,13 @@ public class NoteSQLiteOpenHelper extends SQLiteOpenHelper { cursor.close(); return account; } + + public void setAccount(int id, String url, String username) { + SQLiteDatabase db = this.getWritableDatabase(); + ContentValues values = new ContentValues(); + values.put(key_id, id); + values.put(key_url, url); + values.put(key_username, username); + db.update(table_accounts, values, key_id + " = ?", new String[] {id + ""}); + } } -- GitLab From b52fda7ba5608773ec34be49b47f434e2c66d485 Mon Sep 17 00:00:00 2001 From: stefan-niedermann Date: Fri, 4 Oct 2019 11:04:36 +0200 Subject: [PATCH 0007/1834] Completely remove cert4droid and old login --- app/build.gradle | 2 - .../activity/NotesListViewActivity.java | 48 +- .../android/activity/SettingsActivity.java | 979 ++++++++---------- .../android/fragment/PreferencesFragment.java | 3 +- .../persistence/NoteSQLiteOpenHelper.java | 5 + .../persistence/NoteServerSyncHelper.java | 95 +- .../owncloud/notes/util/NotesClient.java | 69 +- .../owncloud/notes/util/NotesClientUtil.java | 122 +-- .../owncloud/notes/util/SupportUtil.java | 57 - app/src/main/res/values/dimens.xml | 1 + 10 files changed, 506 insertions(+), 875 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index e54f16b1e..c8d282fe0 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -32,8 +32,6 @@ android { } dependencies { - implementation project(':cert4android') - implementation 'com.github.nextcloud:Android-SingleSignOn:master-SNAPSHOT' implementation 'io.reactivex:rxandroid:1.2.1' 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 6d1bf7978..ba45369cd 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 @@ -2,7 +2,6 @@ package it.niedermann.owncloud.notes.android.activity; import android.app.SearchManager; import android.content.Intent; -import android.content.SharedPreferences; import android.content.pm.ShortcutInfo; import android.content.pm.ShortcutManager; import android.content.res.Configuration; @@ -12,7 +11,6 @@ import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; import android.os.Handler; -import android.preference.PreferenceManager; import android.util.Log; import android.view.Menu; import android.view.MenuItem; @@ -51,6 +49,8 @@ import com.nextcloud.android.sso.helper.SingleAccountHelper; import com.nextcloud.android.sso.model.SingleSignOnAccount; import com.nextcloud.android.sso.ui.UiExceptionManager; +import java.net.MalformedURLException; +import java.net.URL; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -192,6 +192,9 @@ public class NotesListViewActivity extends AppCompatActivity implements ItemAdap } } + setContentView(R.layout.drawer_layout); + ButterKnife.bind(this); + String categoryAdapterSelectedItem = ADAPTER_KEY_RECENT; if (savedInstanceState == null) { if (ACTION_RECENT.equals(getIntent().getAction())) { @@ -206,9 +209,6 @@ public class NotesListViewActivity extends AppCompatActivity implements ItemAdap categoryAdapterSelectedItem = savedInstanceState.getString(SAVED_STATE_NAVIGATION_ADAPTER_SLECTION); } - setContentView(R.layout.drawer_layout); - ButterKnife.bind(this); - setupActionBar(); setupNotesList(); setupNavigationList(categoryAdapterSelectedItem); @@ -428,9 +428,8 @@ public class NotesListViewActivity extends AppCompatActivity implements ItemAdap Intent aboutIntent = new Intent(getApplicationContext(), AboutActivity.class); startActivityForResult(aboutIntent, about); } else if (item == itemTrashbin) { - SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(getApplicationContext()); - String url = preferences.getString(SettingsActivity.SETTINGS_URL, SettingsActivity.DEFAULT_SETTINGS); - startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url + "index.php/apps/files/?dir=/&view=trashbin"))); + // FIXME hardcoded accountId + startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(db.getAccount(1).getUrl() + "index.php/apps/files/?dir=/&view=trashbin"))); } } @@ -686,22 +685,25 @@ public class NotesListViewActivity extends AppCompatActivity implements ItemAdap } private void updateUsernameInDrawer() { - SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(getApplicationContext()); - String username = preferences.getString(SettingsActivity.SETTINGS_USERNAME, SettingsActivity.DEFAULT_SETTINGS); - String url = preferences.getString(SettingsActivity.SETTINGS_URL, SettingsActivity.DEFAULT_SETTINGS); - if (url != null) { - String croppedUrl = url.replace("https://", "").replace("http://", ""); - if (!SettingsActivity.DEFAULT_SETTINGS.equals(username) && !SettingsActivity.DEFAULT_SETTINGS.equals(url)) { - this.account.setText(username + "@" + croppedUrl.substring(0, croppedUrl.length() - 1)); - Glide - .with(this) - .load(url + "/index.php/avatar/" + Uri.encode(username) + "/64") - .error(R.mipmap.ic_launcher) - .apply(RequestOptions.circleCropTransform()) - .into(this.currentAccountImage); + try { + SingleSignOnAccount a = SingleAccountHelper.getCurrentSingleSignOnAccount(getApplicationContext()); + String url = a.url; + if (url != null) { + String croppedUrl = new URL(url).getHost(); + this.account.setText(a.userId + "@" + croppedUrl); + Glide + .with(this) + .load(url + "/index.php/avatar/" + Uri.encode(a.userId) + "/64") + .error(R.mipmap.ic_launcher) + .apply(RequestOptions.circleCropTransform()) + .into(this.currentAccountImage); + } else { + Log.w(NotesListViewActivity.class.getSimpleName(), "url is null"); } - } else { - Log.w(NotesListViewActivity.class.getSimpleName(), "url is null"); + } catch (NextcloudFilesAppAccountNotFoundException | NoCurrentAccountSelectedException e) { + e.printStackTrace(); + } catch (MalformedURLException e) { + e.printStackTrace(); } } diff --git a/app/src/main/java/it/niedermann/owncloud/notes/android/activity/SettingsActivity.java b/app/src/main/java/it/niedermann/owncloud/notes/android/activity/SettingsActivity.java index db805b150..98a6a752e 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/android/activity/SettingsActivity.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/android/activity/SettingsActivity.java @@ -1,59 +1,6 @@ package it.niedermann.owncloud.notes.android.activity; -import android.content.Intent; -import android.content.SharedPreferences; -import android.graphics.drawable.Drawable; -import android.net.http.SslCertificate; -import android.net.http.SslError; -import android.os.AsyncTask; -import android.os.Build; -import android.os.Bundle; -import android.os.Handler; -import android.preference.PreferenceManager; -import android.text.Editable; -import android.text.TextWatcher; -import android.util.Log; -import android.view.KeyEvent; -import android.view.View; -import android.webkit.SslErrorHandler; -import android.webkit.WebSettings; -import android.webkit.WebView; -import android.webkit.WebViewClient; -import android.widget.Button; -import android.widget.EditText; -import android.widget.ProgressBar; -import android.widget.TextView; -import android.widget.Toast; - import androidx.appcompat.app.AppCompatActivity; -import androidx.core.content.ContextCompat; - -import com.google.android.material.snackbar.Snackbar; -import com.google.android.material.textfield.TextInputLayout; - -import java.io.ByteArrayInputStream; -import java.net.URLDecoder; -import java.security.cert.Certificate; -import java.security.cert.CertificateException; -import java.security.cert.CertificateFactory; -import java.security.cert.X509Certificate; -import java.util.HashMap; -import java.util.Locale; -import java.util.Map; - -import at.bitfire.cert4android.CustomCertManager; -import at.bitfire.cert4android.IOnCertificateDecision; -import butterknife.BindView; -import butterknife.ButterKnife; -import it.niedermann.owncloud.notes.R; -import it.niedermann.owncloud.notes.persistence.NoteSQLiteOpenHelper; -import it.niedermann.owncloud.notes.persistence.NoteServerSyncHelper; -import it.niedermann.owncloud.notes.util.ExceptionHandler; -import it.niedermann.owncloud.notes.util.NotesClientUtil; -import it.niedermann.owncloud.notes.util.NotesClientUtil.LoginStatus; - -import static android.os.Process.killProcess; -import static android.os.Process.myPid; /** * Allows to set Settings like URL, Username and Password for Server-Synchronization @@ -61,484 +8,454 @@ import static android.os.Process.myPid; */ public class SettingsActivity extends AppCompatActivity { - public static final String SETTINGS_URL = "settingsUrl"; - public static final String SETTINGS_USERNAME = "settingsUsername"; - public static final String SETTINGS_PASSWORD = "settingsPassword"; +// public static final String SETTINGS_URL = "settingsUrl"; +// public static final String SETTINGS_USERNAME = "settingsUsername"; +// public static final String SETTINGS_PASSWORD = "settingsPassword"; public static final String SETTINGS_KEY_ETAG = "notes_last_etag"; public static final String SETTINGS_KEY_LAST_MODIFIED = "notes_last_modified"; - public static final String DEFAULT_SETTINGS = ""; - public static final int CREDENTIALS_CHANGED = 3; - - public static final String LOGIN_URL_DATA_KEY_VALUE_SEPARATOR = ":"; - public static final String WEBDAV_PATH_4_0_AND_LATER = "/remote.php/webdav"; - - private SharedPreferences preferences = null; - - @BindView(R.id.settings_url) - EditText field_url; - @BindView(R.id.settings_username_wrapper) - TextInputLayout username_wrapper; - @BindView(R.id.settings_username) - EditText field_username; - @BindView(R.id.settings_password) - EditText field_password; - @BindView(R.id.settings_password_wrapper) - TextInputLayout password_wrapper; - @BindView(R.id.settings_submit) - Button btn_submit; - @BindView(R.id.settings_url_warn_http) - View urlWarnHttp; - private String old_password = ""; - - private WebView webView; - - private boolean first_run = false; - private boolean useWebLogin = true; - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - Thread.currentThread().setUncaughtExceptionHandler(new ExceptionHandler(this)); - setContentView(R.layout.activity_settings); - ButterKnife.bind(this); - - preferences = PreferenceManager - .getDefaultSharedPreferences(getApplicationContext()); - - if (!NoteServerSyncHelper.isConfigured(this)) { - first_run = true; - if (getSupportActionBar() != null) { - getSupportActionBar().setDisplayHomeAsUpEnabled(false); - } - } - - setupListener(); - - // Load current Preferences - field_url.setText(preferences.getString(SETTINGS_URL, DEFAULT_SETTINGS)); - field_username.setText(preferences.getString(SETTINGS_USERNAME, DEFAULT_SETTINGS)); - old_password = preferences.getString(SETTINGS_PASSWORD, DEFAULT_SETTINGS); - - field_password.setOnEditorActionListener(new TextView.OnEditorActionListener() { - @Override - public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { - login(); - return true; - } - }); - field_password.setOnFocusChangeListener(new View.OnFocusChangeListener() { - @Override - public void onFocusChange(View v, boolean hasFocus) { - setPasswordHint(hasFocus); - } - }); - setPasswordHint(false); - - handleSubmitButtonEnabled(); - } - - private void setupListener() { - field_url.setOnFocusChangeListener(new View.OnFocusChangeListener() { - @Override - public void onFocusChange(View v, boolean hasFocus) { - new URLValidatorAsyncTask().execute(NotesClientUtil.formatURL(field_url.getText().toString())); - } - }); - field_url.addTextChangedListener(new TextWatcher() { - @Override - public void beforeTextChanged(CharSequence s, int start, int count, int after) { - } - - @Override - public void onTextChanged(CharSequence s, int start, int before, int count) { - String url = NotesClientUtil.formatURL(field_url.getText().toString()); - - if (NotesClientUtil.isHttp(url)) { - urlWarnHttp.setVisibility(View.VISIBLE); - } else { - urlWarnHttp.setVisibility(View.GONE); - } - - handleSubmitButtonEnabled(); - } - - @Override - public void afterTextChanged(Editable s) { - } - }); - - field_username.addTextChangedListener(new TextWatcher() { - @Override - public void beforeTextChanged(CharSequence s, int start, int count, int after) { - - } - - @Override - public void onTextChanged(CharSequence s, int start, int before, int count) { - handleSubmitButtonEnabled(); - } - - @Override - public void afterTextChanged(Editable s) { - - } - }); - - btn_submit.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - login(); - } - }); - } - - private void setPasswordHint(boolean hasFocus) { - boolean unchangedHint = !hasFocus && field_password.getText().toString().isEmpty() && !old_password.isEmpty(); - password_wrapper.setHint(getString(unchangedHint ? R.string.settings_password_unchanged : R.string.settings_password)); - } - - - @Override - protected void onResume() { - super.onResume(); - - // Occurs in this scenario: User opens the app but doesn't configure the server settings, they then add the Create Note widget to home screen and configure - // server settings there. The stale SettingsActivity is then displayed hence finish() here to close it down. - if ((first_run) && (NoteServerSyncHelper.isConfigured(this))) { - finish(); - } - } - - /** - * Prevent pressing back button on first run - */ - @Override - public void onBackPressed() { - if (!first_run) { - super.onBackPressed(); - } - } - - private void legacyLogin() { - String url = field_url.getText().toString().trim(); - String username = field_username.getText().toString(); - String password = field_password.getText().toString(); - - if (password.isEmpty()) { - password = old_password; - } - - url = NotesClientUtil.formatURL(url); - - new LoginValidatorAsyncTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, url, username, password); - } - - private void login() { - if (useWebLogin) { - webLogin(); - } else { - legacyLogin(); - } - } - - /** - * Obtain the X509Certificate from SslError - * - * @param error SslError - * @return X509Certificate from error - */ - public static X509Certificate getX509CertificateFromError(SslError error) { - Bundle bundle = SslCertificate.saveState(error.getCertificate()); - X509Certificate x509Certificate; - byte[] bytes = bundle.getByteArray("x509-certificate"); - if (bytes == null) { - x509Certificate = null; - } else { - try { - CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); - Certificate cert = certFactory.generateCertificate(new ByteArrayInputStream(bytes)); - x509Certificate = (X509Certificate) cert; - } catch (CertificateException e) { - x509Certificate = null; - } - } - return x509Certificate; - } - - private void webLogin() { - setContentView(R.layout.activity_settings_webview); - webView = findViewById(R.id.login_webview); - webView.setVisibility(View.GONE); - - final ProgressBar progressBar = findViewById(R.id.login_webview_progress_bar); - - WebSettings settings = webView.getSettings(); - settings.setAllowFileAccess(false); - settings.setJavaScriptEnabled(true); - settings.setDomStorageEnabled(true); - settings.setUserAgentString(getWebLoginUserAgent()); - settings.setSaveFormData(false); - settings.setSavePassword(false); - - Map headers = new HashMap<>(); - headers.put("OCS-APIREQUEST", "true"); - - - webView.loadUrl(normalizeUrlSuffix(NotesClientUtil.formatURL(field_url.getText().toString())) + "index.php/login/flow", headers); - - webView.setWebViewClient(new WebViewClient() { - @Override - public boolean shouldOverrideUrlLoading(WebView view, String url) { - if (url.startsWith("nc://login/")) { - parseAndLoginFromWebView(url); - return true; - } - return false; - } - - @Override - public void onPageFinished(WebView view, String url) { - super.onPageFinished(view, url); - - progressBar.setVisibility(View.GONE); - webView.setVisibility(View.VISIBLE); - } - - @Override - public void onReceivedSslError(WebView view, final SslErrorHandler handler, SslError error) { - X509Certificate cert = getX509CertificateFromError(error); - - try { - final boolean[] accepted = new boolean[1]; - NoteServerSyncHelper.getInstance(NoteSQLiteOpenHelper.getInstance(getApplicationContext())) - .checkCertificate(cert.getEncoded(), true, new IOnCertificateDecision.Stub() { - @Override - public void accept() { - Log.d("Note", "cert accepted"); - handler.proceed(); - accepted[0] = true; - } - - @Override - public void reject() { - Log.d("Note", "cert rejected"); - handler.cancel(); - killProcess(myPid()); - } - }); - } catch (Exception e) { - Log.e("Note", "Cert could not be verified"); - handler.proceed(); - } - } - - }); - - // show snackbar after 60s to switch back to old login method - new Handler().postDelayed(() -> { - Snackbar.make(webView, R.string.fallback_weblogin_text, Snackbar.LENGTH_INDEFINITE) - .setAction(R.string.fallback_weblogin_back, (View.OnClickListener) v -> initLegacyLogin(field_url.getText().toString())).show(); - }, 45 * 1000); - } - - private String getWebLoginUserAgent() { - return Build.MANUFACTURER.substring(0, 1).toUpperCase(Locale.getDefault()) + - Build.MANUFACTURER.substring(1).toLowerCase(Locale.getDefault()) + " " + Build.MODEL; - } - - private void parseAndLoginFromWebView(String dataString) { - String prefix = "nc://login/"; - LoginUrlInfo loginUrlInfo = parseLoginDataUrl(prefix, dataString); - - if (loginUrlInfo != null) { - String url = normalizeUrlSuffix(loginUrlInfo.serverAddress); - - new LoginValidatorAsyncTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, url, loginUrlInfo.username, - loginUrlInfo.password); - } - } - - /** - * parses a URI string and returns a login data object with the information from the URI string. - * - * @param prefix URI beginning, e.g. cloud://login/ - * @param dataString the complete URI - * @return login data - * @throws IllegalArgumentException when - */ - private LoginUrlInfo parseLoginDataUrl(String prefix, String dataString) throws IllegalArgumentException { - if (dataString.length() < prefix.length()) { - throw new IllegalArgumentException("Invalid login URL detected"); - } - LoginUrlInfo loginUrlInfo = new LoginUrlInfo(); - - // format is basically xxx://login/server:xxx&user:xxx&password while all variables are optional - String data = dataString.substring(prefix.length()); - - // parse data - String[] values = data.split("&"); - - if (values.length < 1 || values.length > 3) { - // error illegal number of URL elements detected - throw new IllegalArgumentException("Illegal number of login URL elements detected: " + values.length); - } - - for (String value : values) { - if (value.startsWith("user" + LOGIN_URL_DATA_KEY_VALUE_SEPARATOR)) { - loginUrlInfo.username = URLDecoder.decode( - value.substring(("user" + LOGIN_URL_DATA_KEY_VALUE_SEPARATOR).length())); - } else if (value.startsWith("password" + LOGIN_URL_DATA_KEY_VALUE_SEPARATOR)) { - loginUrlInfo.password = URLDecoder.decode( - value.substring(("password" + LOGIN_URL_DATA_KEY_VALUE_SEPARATOR).length())); - } else if (value.startsWith("server" + LOGIN_URL_DATA_KEY_VALUE_SEPARATOR)) { - loginUrlInfo.serverAddress = URLDecoder.decode( - value.substring(("server" + LOGIN_URL_DATA_KEY_VALUE_SEPARATOR).length())); - } else { - // error illegal URL element detected - throw new IllegalArgumentException("Illegal magic login URL element detected: " + value); - } - } - - return loginUrlInfo; - } - - private String normalizeUrlSuffix(String url) { - if (url.toLowerCase(Locale.ROOT).endsWith(WEBDAV_PATH_4_0_AND_LATER)) { - return url.substring(0, url.length() - WEBDAV_PATH_4_0_AND_LATER.length()); - } - - if (!url.endsWith("/")) { - return url + "/"; - } - - return url; - } - - private void initLegacyLogin(String oldUrl) { - useWebLogin = false; - new URLValidatorAsyncTask().execute(NotesClientUtil.formatURL(field_url.getText().toString())); - - webView.setVisibility(View.INVISIBLE); - setContentView(R.layout.activity_settings); - - ButterKnife.bind(this); - setupListener(); - - field_url.setText(oldUrl); - username_wrapper.setVisibility(View.VISIBLE); - password_wrapper.setVisibility(View.VISIBLE); - } - - private void handleSubmitButtonEnabled() { - // drawable[2] is not null if url is valid, see URLValidatorAsyncTask::onPostExecute - if (useWebLogin || field_url.getCompoundDrawables()[2] != null && (username_wrapper.getVisibility() == View.GONE || - (username_wrapper.getVisibility() == View.VISIBLE && field_username.getText().length() > 0))) { - btn_submit.setEnabled(true); - } else { - btn_submit.setEnabled(false); - } - } - - /************************************ Async Tasks ************************************/ - - /** - * Checks if the given URL returns a valid status code and sets the Check next to the URL-Input Field to visible. - * Created by stefan on 23.09.15. - */ - private class URLValidatorAsyncTask extends AsyncTask { - - @Override - protected void onPreExecute() { - btn_submit.setEnabled(false); - field_url.setCompoundDrawables(null, null, null, null); - } - - @Override - protected Boolean doInBackground(String... params) { - CustomCertManager ccm = NoteServerSyncHelper.getInstance(NoteSQLiteOpenHelper.getInstance(getApplicationContext())).getCustomCertManager(); - return NotesClientUtil.isValidURL(ccm, params[0]); - } - - @Override - protected void onPostExecute(Boolean o) { - if (o) { - Drawable actionDoneDark = ContextCompat.getDrawable(getApplicationContext(), R.drawable.ic_check_grey600_24dp); - actionDoneDark.setBounds(0, 0, actionDoneDark.getIntrinsicWidth(), actionDoneDark.getIntrinsicHeight()); - field_url.setCompoundDrawables(null, null, actionDoneDark, null); - } else { - field_url.setCompoundDrawables(null, null, null, null); - } - handleSubmitButtonEnabled(); - } - } - - /** - * If Log-In-Credentials are correct, save Credentials to Shared Preferences and finish First Run Wizard. - */ - private class LoginValidatorAsyncTask extends AsyncTask { - String url, username, password; - - @Override - protected void onPreExecute() { - setInputsEnabled(false); - btn_submit.setText(R.string.settings_submitting); - } - - /** - * @param params url, username and password - * @return isValidLogin Boolean - */ - @Override - protected LoginStatus doInBackground(String... params) { - url = params[0]; - username = params[1]; - password = params[2]; - CustomCertManager ccm = NoteServerSyncHelper.getInstance(NoteSQLiteOpenHelper.getInstance(getApplicationContext())).getCustomCertManager(); - return NotesClientUtil.isValidLogin(ccm, url, username, password); - } - - @Override - protected void onPostExecute(LoginStatus status) { - if (LoginStatus.OK.equals(status)) { - SharedPreferences.Editor editor = preferences.edit(); - editor.putString(SETTINGS_URL, url); - editor.putString(SETTINGS_USERNAME, username); - editor.putString(SETTINGS_PASSWORD, password); - editor.remove(SETTINGS_KEY_ETAG); - editor.remove(SETTINGS_KEY_LAST_MODIFIED); - editor.apply(); - - final Intent data = new Intent(); - data.putExtra(NotesListViewActivity.CREDENTIALS_CHANGED, CREDENTIALS_CHANGED); - setResult(RESULT_OK, data); - finish(); - } else { - Log.e("Note", "invalid login"); - btn_submit.setText(R.string.settings_submit); - setInputsEnabled(true); - Toast.makeText(getApplicationContext(), getString(R.string.error_invalid_login, getString(status.str)), Toast.LENGTH_LONG).show(); - } - } - - /** - * Sets all Input-Fields and Buttons to enabled or disabled depending on the given boolean. - * - * @param enabled - boolean - */ - private void setInputsEnabled(boolean enabled) { - btn_submit.setEnabled(enabled); - field_url.setEnabled(enabled); - field_username.setEnabled(enabled); - field_password.setEnabled(enabled); - } - } - - /** - * Data object holding the login url fields. - */ - public class LoginUrlInfo { - String serverAddress; - String username; - String password; - } +// public static final String DEFAULT_SETTINGS = ""; +// public static final int CREDENTIALS_CHANGED = 3; +// +// public static final String LOGIN_URL_DATA_KEY_VALUE_SEPARATOR = ":"; +// public static final String WEBDAV_PATH_4_0_AND_LATER = "/remote.php/webdav"; +// +// private SharedPreferences preferences = null; +// +// @BindView(R.id.settings_url) +// EditText field_url; +// @BindView(R.id.settings_username_wrapper) +// TextInputLayout username_wrapper; +// @BindView(R.id.settings_username) +// EditText field_username; +// @BindView(R.id.settings_password) +// EditText field_password; +// @BindView(R.id.settings_password_wrapper) +// TextInputLayout password_wrapper; +// @BindView(R.id.settings_submit) +// Button btn_submit; +// @BindView(R.id.settings_url_warn_http) +// View urlWarnHttp; +// private String old_password = ""; +// +// private WebView webView; +// +// private boolean first_run = false; +// private boolean useWebLogin = true; +// +// @Override +// public void onCreate(Bundle savedInstanceState) { +// super.onCreate(savedInstanceState); +// Thread.currentThread().setUncaughtExceptionHandler(new ExceptionHandler(this)); +// setContentView(R.layout.activity_settings); +// ButterKnife.bind(this); +// +// preferences = PreferenceManager +// .getDefaultSharedPreferences(getApplicationContext()); +// +// if (!NoteServerSyncHelper.isConfigured(this)) { +// first_run = true; +// if (getSupportActionBar() != null) { +// getSupportActionBar().setDisplayHomeAsUpEnabled(false); +// } +// } +// +// setupListener(); +// +// // Load current Preferences +// field_url.setText(preferences.getString(SETTINGS_URL, DEFAULT_SETTINGS)); +// field_username.setText(preferences.getString(SETTINGS_USERNAME, DEFAULT_SETTINGS)); +// old_password = preferences.getString(SETTINGS_PASSWORD, DEFAULT_SETTINGS); +// +// field_password.setOnEditorActionListener(new TextView.OnEditorActionListener() { +// @Override +// public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { +// login(); +// return true; +// } +// }); +// field_password.setOnFocusChangeListener(new View.OnFocusChangeListener() { +// @Override +// public void onFocusChange(View v, boolean hasFocus) { +// setPasswordHint(hasFocus); +// } +// }); +// setPasswordHint(false); +// +// handleSubmitButtonEnabled(); +// } +// +// private void setupListener() { +// field_url.setOnFocusChangeListener(new View.OnFocusChangeListener() { +// @Override +// public void onFocusChange(View v, boolean hasFocus) { +// new URLValidatorAsyncTask().execute(NotesClientUtil.formatURL(field_url.getText().toString())); +// } +// }); +// field_url.addTextChangedListener(new TextWatcher() { +// @Override +// public void beforeTextChanged(CharSequence s, int start, int count, int after) { +// } +// +// @Override +// public void onTextChanged(CharSequence s, int start, int before, int count) { +// String url = NotesClientUtil.formatURL(field_url.getText().toString()); +// +// if (NotesClientUtil.isHttp(url)) { +// urlWarnHttp.setVisibility(View.VISIBLE); +// } else { +// urlWarnHttp.setVisibility(View.GONE); +// } +// +// handleSubmitButtonEnabled(); +// } +// +// @Override +// public void afterTextChanged(Editable s) { +// } +// }); +// +// field_username.addTextChangedListener(new TextWatcher() { +// @Override +// public void beforeTextChanged(CharSequence s, int start, int count, int after) { +// +// } +// +// @Override +// public void onTextChanged(CharSequence s, int start, int before, int count) { +// handleSubmitButtonEnabled(); +// } +// +// @Override +// public void afterTextChanged(Editable s) { +// +// } +// }); +// +// btn_submit.setOnClickListener(new View.OnClickListener() { +// @Override +// public void onClick(View v) { +// login(); +// } +// }); +// } +// +// private void setPasswordHint(boolean hasFocus) { +// boolean unchangedHint = !hasFocus && field_password.getText().toString().isEmpty() && !old_password.isEmpty(); +// password_wrapper.setHint(getString(unchangedHint ? R.string.settings_password_unchanged : R.string.settings_password)); +// } +// +// +// @Override +// protected void onResume() { +// super.onResume(); +// +// // Occurs in this scenario: User opens the app but doesn't configure the server settings, they then add the Create Note widget to home screen and configure +// // server settings there. The stale SettingsActivity is then displayed hence finish() here to close it down. +// if ((first_run) && (NoteServerSyncHelper.isConfigured(this))) { +// finish(); +// } +// } +// +// /** +// * Prevent pressing back button on first run +// */ +// @Override +// public void onBackPressed() { +// if (!first_run) { +// super.onBackPressed(); +// } +// } +// +// private void legacyLogin() { +// String url = field_url.getText().toString().trim(); +// String username = field_username.getText().toString(); +// String password = field_password.getText().toString(); +// +// if (password.isEmpty()) { +// password = old_password; +// } +// +// url = NotesClientUtil.formatURL(url); +// +// new LoginValidatorAsyncTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, url, username, password); +// } +// +// private void login() { +// if (useWebLogin) { +// webLogin(); +// } else { +// legacyLogin(); +// } +// } +// +// /** +// * Obtain the X509Certificate from SslError +// * +// * @param error SslError +// * @return X509Certificate from error +// */ +// public static X509Certificate getX509CertificateFromError(SslError error) { +// Bundle bundle = SslCertificate.saveState(error.getCertificate()); +// X509Certificate x509Certificate; +// byte[] bytes = bundle.getByteArray("x509-certificate"); +// if (bytes == null) { +// x509Certificate = null; +// } else { +// try { +// CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); +// Certificate cert = certFactory.generateCertificate(new ByteArrayInputStream(bytes)); +// x509Certificate = (X509Certificate) cert; +// } catch (CertificateException e) { +// x509Certificate = null; +// } +// } +// return x509Certificate; +// } +// +// private void webLogin() { +// setContentView(R.layout.activity_settings_webview); +// webView = findViewById(R.id.login_webview); +// webView.setVisibility(View.GONE); +// +// final ProgressBar progressBar = findViewById(R.id.login_webview_progress_bar); +// +// WebSettings settings = webView.getSettings(); +// settings.setAllowFileAccess(false); +// settings.setJavaScriptEnabled(true); +// settings.setDomStorageEnabled(true); +// settings.setUserAgentString(getWebLoginUserAgent()); +// settings.setSaveFormData(false); +// settings.setSavePassword(false); +// +// Map headers = new HashMap<>(); +// headers.put("OCS-APIREQUEST", "true"); +// +// +// webView.loadUrl(normalizeUrlSuffix(NotesClientUtil.formatURL(field_url.getText().toString())) + "index.php/login/flow", headers); +// +// webView.setWebViewClient(new WebViewClient() { +// @Override +// public boolean shouldOverrideUrlLoading(WebView view, String url) { +// if (url.startsWith("nc://login/")) { +// parseAndLoginFromWebView(url); +// return true; +// } +// return false; +// } +// +// @Override +// public void onPageFinished(WebView view, String url) { +// super.onPageFinished(view, url); +// +// progressBar.setVisibility(View.GONE); +// webView.setVisibility(View.VISIBLE); +// } +// }); +// +// // show snackbar after 60s to switch back to old login method +// new Handler().postDelayed(() -> { +// Snackbar.make(webView, R.string.fallback_weblogin_text, Snackbar.LENGTH_INDEFINITE) +// .setAction(R.string.fallback_weblogin_back, (View.OnClickListener) v -> initLegacyLogin(field_url.getText().toString())).show(); +// }, 45 * 1000); +// } +// +// private String getWebLoginUserAgent() { +// return Build.MANUFACTURER.substring(0, 1).toUpperCase(Locale.getDefault()) + +// Build.MANUFACTURER.substring(1).toLowerCase(Locale.getDefault()) + " " + Build.MODEL; +// } +// +// private void parseAndLoginFromWebView(String dataString) { +// String prefix = "nc://login/"; +// LoginUrlInfo loginUrlInfo = parseLoginDataUrl(prefix, dataString); +// +// if (loginUrlInfo != null) { +// String url = normalizeUrlSuffix(loginUrlInfo.serverAddress); +// +// new LoginValidatorAsyncTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, url, loginUrlInfo.username, +// loginUrlInfo.password); +// } +// } +// +// /** +// * parses a URI string and returns a login data object with the information from the URI string. +// * +// * @param prefix URI beginning, e.g. cloud://login/ +// * @param dataString the complete URI +// * @return login data +// * @throws IllegalArgumentException when +// */ +// private LoginUrlInfo parseLoginDataUrl(String prefix, String dataString) throws IllegalArgumentException { +// if (dataString.length() < prefix.length()) { +// throw new IllegalArgumentException("Invalid login URL detected"); +// } +// LoginUrlInfo loginUrlInfo = new LoginUrlInfo(); +// +// // format is basically xxx://login/server:xxx&user:xxx&password while all variables are optional +// String data = dataString.substring(prefix.length()); +// +// // parse data +// String[] values = data.split("&"); +// +// if (values.length < 1 || values.length > 3) { +// // error illegal number of URL elements detected +// throw new IllegalArgumentException("Illegal number of login URL elements detected: " + values.length); +// } +// +// for (String value : values) { +// if (value.startsWith("user" + LOGIN_URL_DATA_KEY_VALUE_SEPARATOR)) { +// loginUrlInfo.username = URLDecoder.decode( +// value.substring(("user" + LOGIN_URL_DATA_KEY_VALUE_SEPARATOR).length())); +// } else if (value.startsWith("password" + LOGIN_URL_DATA_KEY_VALUE_SEPARATOR)) { +// loginUrlInfo.password = URLDecoder.decode( +// value.substring(("password" + LOGIN_URL_DATA_KEY_VALUE_SEPARATOR).length())); +// } else if (value.startsWith("server" + LOGIN_URL_DATA_KEY_VALUE_SEPARATOR)) { +// loginUrlInfo.serverAddress = URLDecoder.decode( +// value.substring(("server" + LOGIN_URL_DATA_KEY_VALUE_SEPARATOR).length())); +// } else { +// // error illegal URL element detected +// throw new IllegalArgumentException("Illegal magic login URL element detected: " + value); +// } +// } +// +// return loginUrlInfo; +// } +// +// private String normalizeUrlSuffix(String url) { +// if (url.toLowerCase(Locale.ROOT).endsWith(WEBDAV_PATH_4_0_AND_LATER)) { +// return url.substring(0, url.length() - WEBDAV_PATH_4_0_AND_LATER.length()); +// } +// +// if (!url.endsWith("/")) { +// return url + "/"; +// } +// +// return url; +// } +// +// private void initLegacyLogin(String oldUrl) { +// useWebLogin = false; +// new URLValidatorAsyncTask().execute(NotesClientUtil.formatURL(field_url.getText().toString())); +// +// webView.setVisibility(View.INVISIBLE); +// setContentView(R.layout.activity_settings); +// +// ButterKnife.bind(this); +// setupListener(); +// +// field_url.setText(oldUrl); +// username_wrapper.setVisibility(View.VISIBLE); +// password_wrapper.setVisibility(View.VISIBLE); +// } +// +// private void handleSubmitButtonEnabled() { +// // drawable[2] is not null if url is valid, see URLValidatorAsyncTask::onPostExecute +// if (useWebLogin || field_url.getCompoundDrawables()[2] != null && (username_wrapper.getVisibility() == View.GONE || +// (username_wrapper.getVisibility() == View.VISIBLE && field_username.getText().length() > 0))) { +// btn_submit.setEnabled(true); +// } else { +// btn_submit.setEnabled(false); +// } +// } +// +// /************************************ Async Tasks ************************************/ +// +// /** +// * Checks if the given URL returns a valid status code and sets the Check next to the URL-Input Field to visible. +// * Created by stefan on 23.09.15. +// */ +// private class URLValidatorAsyncTask extends AsyncTask { +// +// @Override +// protected void onPreExecute() { +// btn_submit.setEnabled(false); +// field_url.setCompoundDrawables(null, null, null, null); +// } +// +// @Override +// protected Boolean doInBackground(String... params) { +// CustomCertManager ccm = NoteServerSyncHelper.getInstance(NoteSQLiteOpenHelper.getInstance(getApplicationContext())).getCustomCertManager(); +// return NotesClientUtil.isValidURL(ccm, params[0]); +// } +// +// @Override +// protected void onPostExecute(Boolean o) { +// if (o) { +// Drawable actionDoneDark = ContextCompat.getDrawable(getApplicationContext(), R.drawable.ic_check_grey600_24dp); +// actionDoneDark.setBounds(0, 0, actionDoneDark.getIntrinsicWidth(), actionDoneDark.getIntrinsicHeight()); +// field_url.setCompoundDrawables(null, null, actionDoneDark, null); +// } else { +// field_url.setCompoundDrawables(null, null, null, null); +// } +// handleSubmitButtonEnabled(); +// } +// } +// +// /** +// * If Log-In-Credentials are correct, save Credentials to Shared Preferences and finish First Run Wizard. +// */ +// private class LoginValidatorAsyncTask extends AsyncTask { +// String url, username, password; +// +// @Override +// protected void onPreExecute() { +// setInputsEnabled(false); +// btn_submit.setText(R.string.settings_submitting); +// } +// +// /** +// * @param params url, username and password +// * @return isValidLogin Boolean +// */ +// @Override +// protected LoginStatus doInBackground(String... params) { +// url = params[0]; +// username = params[1]; +// password = params[2]; +// return NotesClientUtil.isValidLogin(url, username, password); +// } +// +// @Override +// protected void onPostExecute(LoginStatus status) { +// if (LoginStatus.OK.equals(status)) { +// SharedPreferences.Editor editor = preferences.edit(); +// editor.putString(SETTINGS_URL, url); +// editor.putString(SETTINGS_USERNAME, username); +// editor.putString(SETTINGS_PASSWORD, password); +// editor.remove(SETTINGS_KEY_ETAG); +// editor.remove(SETTINGS_KEY_LAST_MODIFIED); +// editor.apply(); +// +// final Intent data = new Intent(); +// data.putExtra(NotesListViewActivity.CREDENTIALS_CHANGED, CREDENTIALS_CHANGED); +// setResult(RESULT_OK, data); +// finish(); +// } else { +// Log.e("Note", "invalid login"); +// btn_submit.setText(R.string.settings_submit); +// setInputsEnabled(true); +// Toast.makeText(getApplicationContext(), getString(R.string.error_invalid_login, getString(status.str)), Toast.LENGTH_LONG).show(); +// } +// } +// +// /** +// * Sets all Input-Fields and Buttons to enabled or disabled depending on the given boolean. +// * +// * @param enabled - boolean +// */ +// private void setInputsEnabled(boolean enabled) { +// btn_submit.setEnabled(enabled); +// field_url.setEnabled(enabled); +// field_username.setEnabled(enabled); +// field_password.setEnabled(enabled); +// } +// } +// +// /** +// * Data object holding the login url fields. +// */ +// public class LoginUrlInfo { +// String serverAddress; +// String username; +// String password; +// } } 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 ad8591158..99d6a62d8 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 @@ -9,7 +9,7 @@ import android.util.Log; import android.widget.Toast; import androidx.annotation.Nullable; -import at.bitfire.cert4android.CustomCertManager; + import it.niedermann.owncloud.notes.R; import it.niedermann.owncloud.notes.util.Notes; @@ -21,7 +21,6 @@ public class PreferencesFragment extends PreferenceFragment { Preference resetTrust = findPreference(getString(R.string.pref_key_reset_trust)); resetTrust.setOnPreferenceClickListener((Preference preference) -> { - CustomCertManager.Companion.resetCertificates(getActivity()); Toast.makeText(getActivity(), getString(R.string.settings_cert_reset_toast), Toast.LENGTH_SHORT).show(); 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 56c5b46a4..f0ba44065 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 @@ -155,6 +155,9 @@ public class NoteSQLiteOpenHelper extends SQLiteOpenHelper { if (oldVersion < 9) { db.execSQL("ALTER TABLE " + table_notes + " ADD COLUMN " + key_account_id + " INTEGER NOT NULL DEFAULT 0"); 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"}); } } @@ -244,7 +247,9 @@ public class NoteSQLiteOpenHelper extends SQLiteOpenHelper { values.put(key_status, dbNote.getStatus().getTitle()); values.put(key_account_id, dbNote.getAccountId()); } else { + // FIXME hardcoded accountId values.put(key_status, DBStatus.VOID.getTitle()); + values.put(key_account_id, 1); } if (note.getRemoteId() > 0) { values.put(key_remote_id, note.getRemoteId()); 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 efcdc40a2..d8280521a 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 @@ -1,24 +1,23 @@ package it.niedermann.owncloud.notes.persistence; import android.content.BroadcastReceiver; -import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; -import android.content.ServiceConnection; import android.content.SharedPreferences; import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.os.AsyncTask; -import android.os.IBinder; -import android.os.RemoteException; import android.preference.PreferenceManager; import android.util.Log; import android.widget.Toast; +import com.nextcloud.android.sso.exceptions.NextcloudFilesAppAccountNotFoundException; +import com.nextcloud.android.sso.exceptions.NoCurrentAccountSelectedException; +import com.nextcloud.android.sso.helper.SingleAccountHelper; + import org.json.JSONException; -import java.io.FileNotFoundException; import java.io.IOException; import java.util.ArrayList; import java.util.HashSet; @@ -26,10 +25,6 @@ import java.util.List; import java.util.Map; import java.util.Set; -import at.bitfire.cert4android.CustomCertManager; -import at.bitfire.cert4android.CustomCertService; -import at.bitfire.cert4android.ICustomCertService; -import at.bitfire.cert4android.IOnCertificateDecision; import it.niedermann.owncloud.notes.R; import it.niedermann.owncloud.notes.android.activity.SettingsActivity; import it.niedermann.owncloud.notes.model.CloudNote; @@ -39,7 +34,6 @@ import it.niedermann.owncloud.notes.util.ICallback; import it.niedermann.owncloud.notes.util.NotesClient; import it.niedermann.owncloud.notes.util.NotesClientUtil.LoginStatus; import it.niedermann.owncloud.notes.util.ServerResponse; -import it.niedermann.owncloud.notes.util.SupportUtil; /** * Helps to synchronize the Database to the Server. @@ -66,9 +60,6 @@ public class NoteServerSyncHelper { private final NoteSQLiteOpenHelper dbHelper; private final Context appContext; - private CustomCertManager customCertManager; - private ICustomCertService iCustomCertService; - // Track network connection changes using a BroadcastReceiver private boolean networkConnected = false; private String syncOnlyOnWifiKey; @@ -94,24 +85,6 @@ public class NoteServerSyncHelper { } }; - private boolean cert4androidReady = false; - private final ServiceConnection certService = new ServiceConnection() { - @Override - public void onServiceConnected(ComponentName componentName, IBinder iBinder) { - iCustomCertService = ICustomCertService.Stub.asInterface(iBinder); - cert4androidReady = true; - if (isSyncPossible()) { - scheduleSync(false); - } - } - - @Override - public void onServiceDisconnected(ComponentName componentName) { - cert4androidReady = false; - iCustomCertService = null; - } - }; - // current state of the synchronization private boolean syncActive = false; private boolean syncScheduled = false; @@ -125,12 +98,6 @@ public class NoteServerSyncHelper { this.dbHelper = db; this.appContext = db.getContext().getApplicationContext(); this.syncOnlyOnWifiKey = appContext.getResources().getString(R.string.pref_key_wifi_only); - new Thread() { - @Override - public void run() { - customCertManager = SupportUtil.getCertManager(appContext); - } - }.start(); // Registers BroadcastReceiver to track network connection changes. appContext.registerReceiver(networkReceiver, new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)); @@ -140,42 +107,35 @@ public class NoteServerSyncHelper { syncOnlyOnWifi = prefs.getBoolean(syncOnlyOnWifiKey, false); updateNetworkStatus(); - // bind to certifciate service to block sync attempts if service is not ready - appContext.bindService(new Intent(appContext, CustomCertService.class), certService, Context.BIND_AUTO_CREATE); } @Override protected void finalize() throws Throwable { appContext.unregisterReceiver(networkReceiver); - appContext.unbindService(certService); - if (customCertManager != null) { - customCertManager.close(); - } super.finalize(); } public static boolean isConfigured(Context context) { - return !PreferenceManager.getDefaultSharedPreferences(context).getString(SettingsActivity.SETTINGS_URL, SettingsActivity.DEFAULT_SETTINGS).isEmpty(); + try { + SingleAccountHelper.getCurrentSingleSignOnAccount(context); + return true; + } catch (NextcloudFilesAppAccountNotFoundException e) { + return false; + } catch (NoCurrentAccountSelectedException e) { + return false; + } } /** * Synchronization is only possible, if there is an active network connection and - * Cert4Android service is available. + * SingleSignOn is available * NoteServerSyncHelper observes changes in the network connection. * The current state can be retrieved with this method. * * @return true if sync is possible, otherwise false. */ public boolean isSyncPossible() { - return networkConnected && isConfigured(appContext) && cert4androidReady; - } - - public CustomCertManager getCustomCertManager() { - return customCertManager; - } - - public void checkCertificate(byte[] cert, boolean foreground, IOnCertificateDecision callback) throws RemoteException { - iCustomCertService.checkTrusted(cert, true, foreground, callback); + return networkConnected && isConfigured(appContext); } /** @@ -211,7 +171,6 @@ public class NoteServerSyncHelper { */ public void scheduleSync(boolean onlyLocalChanges) { Log.d(getClass().getSimpleName(), "Sync requested (" + (onlyLocalChanges ? "onlyLocalChanges" : "full") + "; " + (syncActive ? "sync active" : "sync NOT active") + ") ..."); - Log.d(getClass().getSimpleName(), "(network:" + networkConnected + "; conf:" + isConfigured(appContext) + "; cert4android:" + cert4androidReady + ")"); if (isSyncPossible() && (!syncActive || onlyLocalChanges)) { Log.d(getClass().getSimpleName(), "... starting now"); SyncTask syncTask = new SyncTask(onlyLocalChanges); @@ -286,7 +245,7 @@ public class NoteServerSyncHelper { @Override protected LoginStatus doInBackground(Void... voids) { - client = createNotesClient(appContext); // recreate NoteClients on every sync in case the connection settings was changed + client = new NotesClient(appContext); // recreate NoteClients on every sync in case the connection settings was changed Log.i(getClass().getSimpleName(), "STARTING SYNCHRONIZATION"); //dbHelper.debugPrintFullDB(); LoginStatus status = LoginStatus.OK; @@ -316,28 +275,20 @@ public class NoteServerSyncHelper { // if note is not new, try to edit it. if (note.getRemoteId() > 0) { Log.v(getClass().getSimpleName(), " ...try to edit"); - try { - remoteNote = client.editNote(customCertManager, note).getNote(); - } catch (FileNotFoundException e) { - // Note does not exists anymore - } + remoteNote = client.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"); - remoteNote = client.createNote(customCertManager, note).getNote(); + remoteNote = client.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)"); - try { - client.deleteNote(note.getRemoteId()); - } catch (FileNotFoundException e) { - Log.v(getClass().getSimpleName(), " ...Note does not exist on server (anymore?) -> delete locally"); - } + client.deleteNote(note.getRemoteId()); } else { Log.v(getClass().getSimpleName(), " ...delete (only local, since it was not synchronized)"); } @@ -347,7 +298,7 @@ public class NoteServerSyncHelper { default: throw new IllegalStateException("Unknown State of Note: " + note); } - } catch (IOException | JSONException e) { + } catch (JSONException e) { Log.e(getClass().getSimpleName(), "Exception", e); exceptions.add(e); } @@ -443,12 +394,4 @@ public class NoteServerSyncHelper { } } } - - private NotesClient createNotesClient(Context context) { - SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(appContext.getApplicationContext()); - String url = preferences.getString(SettingsActivity.SETTINGS_URL, SettingsActivity.DEFAULT_SETTINGS); - String username = preferences.getString(SettingsActivity.SETTINGS_USERNAME, SettingsActivity.DEFAULT_SETTINGS); - String password = preferences.getString(SettingsActivity.SETTINGS_PASSWORD, SettingsActivity.DEFAULT_SETTINGS); - return new NotesClient(context, url, username, password); - } } 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 6b989ea6a..45202aba0 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 @@ -25,7 +25,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import at.bitfire.cert4android.CustomCertManager; import it.niedermann.owncloud.notes.model.CloudNote; import it.niedermann.owncloud.notes.util.ServerResponse.NoteResponse; import it.niedermann.owncloud.notes.util.ServerResponse.NotesResponse; @@ -75,7 +74,7 @@ public class NotesClient { public static final String JSON_MODIFIED = "modified"; private static final String application_json = "application/json"; - public NotesClient(Context context, String url, String username, String password) { + public NotesClient(Context context) { try { SingleSignOnAccount ssoAccount = SingleAccountHelper.getCurrentSingleSignOnAccount(context); mNextcloudAPI = new NextcloudAPI(context, ssoAccount, new GsonBuilder().create(), new NextcloudAPI.ApiConnectedListener() { @@ -109,15 +108,13 @@ public class NotesClient { * * @param id long - ID of the wanted note * @return Requested Note - * @throws JSONException - * @throws IOException */ @SuppressWarnings("unused") - public NoteResponse getNoteById(long id) throws JSONException, IOException { + 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, IOException { + private NoteResponse putNote(CloudNote note, String path, String method) throws JSONException { JSONObject paramObject = new JSONObject(); paramObject.accumulate(JSON_CONTENT, note.getContent()); paramObject.accumulate(JSON_MODIFIED, note.getModified().getTimeInMillis() / 1000); @@ -133,17 +130,16 @@ public class NotesClient { * @param note {@link CloudNote} - the new Note * @return Created Note including generated Title, ID and lastModified-Date * @throws JSONException - * @throws IOException */ - public NoteResponse createNote(CustomCertManager ccm, CloudNote note) throws JSONException, IOException { + public NoteResponse createNote(CloudNote note) throws JSONException { return putNote(note, "notes", METHOD_POST); } - public NoteResponse editNote(CustomCertManager ccm, CloudNote note) throws JSONException, IOException { + public NoteResponse editNote(CloudNote note) throws JSONException { return putNote(note, "notes/" + note.getRemoteId(), METHOD_PUT); } - public void deleteNote(long noteId) throws IOException { + public void deleteNote(long noteId) { this.requestServer("notes/" + noteId, METHOD_DELETE, null, null); } @@ -156,8 +152,6 @@ public class NotesClient { * @return Body of answer */ private ResponseData requestServer(String target, String method, JSONObject params, String lastETag) { - - NextcloudRequest.Builder requestBuilder = new NextcloudRequest.Builder() .setMethod(method) .setUrl("/index.php/apps/notes/api/v0.2/" + target); @@ -200,56 +194,5 @@ public class NotesClient { Log.d(getClass().getSimpleName(), "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); - - -// StringBuffer result = new StringBuffer(); -// // setup connection -// String targetURL = url + "index.php/apps/notes/api/v0.2/" + target; -// HttpURLConnection con = SupportUtil.getHttpURLConnection(ccm, targetURL); -// con.setRequestMethod(method); -// con.setRequestProperty( -// "Authorization", -// "Basic " + Base64.encodeToString((username + ":" + password).getBytes(), Base64.NO_WRAP)); -// // https://github.com/square/retrofit/issues/805#issuecomment-93426183 -// con.setRequestProperty("Connection", "Close"); -// con.setRequestProperty("User-Agent", "nextcloud-notes/" + BuildConfig.VERSION_NAME + " (Android)"); -// if (lastETag != null && METHOD_GET.equals(method)) { -// con.setRequestProperty("If-None-Match", lastETag); -// } -// con.setConnectTimeout(10 * 1000); // 10 seconds -// Log.d(getClass().getSimpleName(), method + " " + targetURL); -// // send request data (optional) -// byte[] paramData = null; -// if (params != null) { -// paramData = params.toString().getBytes(); -// Log.d(getClass().getSimpleName(), "Params: " + params); -// con.setFixedLengthStreamingMode(paramData.length); -// con.setRequestProperty("Content-Type", application_json); -// con.setDoOutput(true); -// OutputStream os = con.getOutputStream(); -// os.write(paramData); -// os.flush(); -// os.close(); -// } -// // read response data -// int responseCode = con.getResponseCode(); -// Log.d(getClass().getSimpleName(), "HTTP response code: " + responseCode); -// -// if (responseCode == HttpURLConnection.HTTP_NOT_MODIFIED) { -// throw new ServerResponse.NotModifiedException(); -// } -// -// BufferedReader rd = new BufferedReader(new InputStreamReader(con.getInputStream())); -// String line; -// while ((line = rd.readLine()) != null) { -// result.append(line); -// } -// // create response object -// String etag = con.getHeaderField("ETag"); -// long lastModified = con.getHeaderFieldDate("Last-Modified", 0) / 1000; -// Log.i(getClass().getSimpleName(), "Result length: " + result.length() + (paramData == null ? "" : "; Request length: " + paramData.length)); -// Log.d(getClass().getSimpleName(), "ETag: " + etag + "; Last-Modified: " + lastModified + " (" + con.getHeaderField("Last-Modified") + ")"); -// // 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 ef192eae9..97eb0bdb6 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 @@ -1,21 +1,7 @@ package it.niedermann.owncloud.notes.util; import androidx.annotation.StringRes; -import android.util.Base64; -import android.util.Log; -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.net.HttpURLConnection; -import java.net.MalformedURLException; -import java.net.SocketTimeoutException; - -import at.bitfire.cert4android.CustomCertManager; import it.niedermann.owncloud.notes.R; /** @@ -26,11 +12,9 @@ public class NotesClientUtil { public enum LoginStatus { OK(0), - AUTH_FAILED(R.string.error_username_password_invalid), CONNECTION_FAILED(R.string.error_io), NO_NETWORK(R.string.error_no_network), - JSON_FAILED(R.string.error_json), - SERVER_FAILED(R.string.error_server); + JSON_FAILED(R.string.error_json); @StringRes public final int str; @@ -40,108 +24,4 @@ public class NotesClientUtil { } } - /** - * Checks if the given url String starts with http:// or https:// - * - * @param url String - * @return true, if the given String is only http - */ - public static boolean isHttp(String url) { - return url != null && url.length() > 4 && url.startsWith("http") && url.charAt(4) != 's'; - } - - /** - * Strips the api part from the path of a given url, handles trailing slash and missing protocol - * - * @param url String - * @return formatted URL - */ - public static String formatURL(String url) { - if (!url.endsWith("/")) { - url += "/"; - } - if (!url.startsWith("http://") && !url.startsWith("https://")) { - url = "https://" + url; - } - String[] replacements = new String[]{"notes/", "v0.2/", "api/", "notes/", "apps/", "index.php/"}; - for (String replacement : replacements) { - if (url.endsWith(replacement)) { - url = url.substring(0, url.length() - replacement.length()); - } - } - return url; - } - - /** - * @param url String - * @param username String - * @param password String - * @return Username and Password are a valid Login-Combination for the given URL. - */ - public static LoginStatus isValidLogin(CustomCertManager ccm, String url, String username, String password) { - try { - String targetURL = url + "index.php/apps/notes/api/v0.2/notes"; - HttpURLConnection con = SupportUtil.getHttpURLConnection(ccm, targetURL); - con.setRequestMethod("GET"); - con.setRequestProperty( - "Authorization", - "Basic " - + new String(Base64.encode((username + ":" - + password).getBytes(), Base64.NO_WRAP))); - con.setConnectTimeout(10 * 1000); // 10 seconds - con.connect(); - - Log.v(NotesClientUtil.class.getSimpleName(), "Establishing connection to server"); - if (con.getResponseCode() == 200) { - Log.v(NotesClientUtil.class.getSimpleName(), "" + con.getResponseMessage()); - StringBuilder result = new StringBuilder(); - BufferedReader rd = new BufferedReader(new InputStreamReader(con.getInputStream())); - String line; - while ((line = rd.readLine()) != null) { - result.append(line); - } - Log.v(NotesClientUtil.class.getSimpleName(), result.toString()); - new JSONArray(result.toString()); - return LoginStatus.OK; - } else if (con.getResponseCode() >= 401 && con.getResponseCode() <= 403) { - return LoginStatus.AUTH_FAILED; - } else { - return LoginStatus.SERVER_FAILED; - } - } catch (MalformedURLException | SocketTimeoutException e) { - Log.e(NotesClientUtil.class.getSimpleName(), "Exception", e); - return LoginStatus.CONNECTION_FAILED; - } catch (IOException e) { - Log.e(NotesClientUtil.class.getSimpleName(), "Exception", e); - return LoginStatus.CONNECTION_FAILED; - } catch (JSONException e) { - Log.e(NotesClientUtil.class.getSimpleName(), "Exception", e); - return LoginStatus.JSON_FAILED; - } - } - - /** - * Pings a server and checks if there is a installed ownCloud instance - * - * @param url String URL to server - * @return true if there is a installed instance, false if not - */ - public static boolean isValidURL(CustomCertManager ccm, String url) { - StringBuilder result = new StringBuilder(); - try { - HttpURLConnection con = SupportUtil.getHttpURLConnection(ccm, url + "status.php"); - con.setRequestMethod(NotesClient.METHOD_GET); - con.setConnectTimeout(10 * 1000); // 10 seconds - BufferedReader rd = new BufferedReader(new InputStreamReader(con.getInputStream())); - String line; - while ((line = rd.readLine()) != null) { - result.append(line); - } - JSONObject response = new JSONObject(result.toString()); - return response.getBoolean("installed"); - } catch (IOException | JSONException | NullPointerException e) { - return false; - } - } - } \ No newline at end of file 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 e443c7c08..4fcea7920 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 @@ -1,31 +1,11 @@ package it.niedermann.owncloud.notes.util; -import android.content.Context; -import android.content.SharedPreferences; import android.os.Build; -import android.preference.PreferenceManager; import android.text.Html; import android.text.Spanned; import android.text.method.LinkMovementMethod; -import android.util.Log; import android.widget.TextView; -import androidx.annotation.WorkerThread; - -import java.io.IOException; -import java.net.HttpURLConnection; -import java.net.MalformedURLException; -import java.net.URL; -import java.security.KeyManagementException; -import java.security.NoSuchAlgorithmException; - -import javax.net.ssl.HttpsURLConnection; -import javax.net.ssl.SSLContext; -import javax.net.ssl.TrustManager; - -import at.bitfire.cert4android.CustomCertManager; -import it.niedermann.owncloud.notes.R; - /** * Some helper functionality in alike the Android support library. * Currently, it offers methods for working with HTML string resources. @@ -59,41 +39,4 @@ public class SupportUtil { view.setText(SupportUtil.fromHtml(view.getResources().getString(stringId, formatArgs))); view.setMovementMethod(LinkMovementMethod.getInstance()); } - - /** - * Create a new {@link HttpURLConnection} for strUrl. - * If protocol equals https, then install CustomCertManager in {@link SSLContext}. - * - * @param ccm - * @param strUrl - * @return HttpURLConnection with custom trust manager - * @throws MalformedURLException - * @throws IOException - */ - public static HttpURLConnection getHttpURLConnection(CustomCertManager ccm, String strUrl) throws MalformedURLException, IOException { - URL url = new URL(strUrl); - HttpURLConnection httpCon = (HttpURLConnection) url.openConnection(); - if (ccm != null && url.getProtocol().equals("https")) { - HttpsURLConnection httpsCon = (HttpsURLConnection) httpCon; - httpsCon.setHostnameVerifier(ccm.hostnameVerifier(httpsCon.getHostnameVerifier())); - try { - SSLContext sslContext = SSLContext.getInstance("TLS"); - sslContext.init(null, new TrustManager[]{ccm}, null); - httpsCon.setSSLSocketFactory(sslContext.getSocketFactory()); - } catch (NoSuchAlgorithmException e) { - Log.e(SupportUtil.class.getSimpleName(), "Exception", e); - // ignore, use default TrustManager - } catch (KeyManagementException e) { - Log.e(SupportUtil.class.getSimpleName(), "Exception", e); - // ignore, use default TrustManager - } - } - return httpCon; - } - - @WorkerThread - public static CustomCertManager getCertManager(Context ctx) { - SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(ctx); - return new CustomCertManager(ctx, preferences.getBoolean(ctx.getString(R.string.pref_key_trust_system_certs), true), true, true); - } } diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index f2ce0d854..073a4feec 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -2,6 +2,7 @@ + 16dp 16dp 16dp -- GitLab From a7b1173575910d0e8f087fe5d27676e8f4c2491b Mon Sep 17 00:00:00 2001 From: stefan-niedermann Date: Fri, 4 Oct 2019 11:05:50 +0200 Subject: [PATCH 0008/1834] Fix trashbin link --- .../owncloud/notes/android/activity/NotesListViewActivity.java | 2 +- 1 file changed, 1 insertion(+), 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 ba45369cd..0f113e6ba 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 @@ -429,7 +429,7 @@ public class NotesListViewActivity extends AppCompatActivity implements ItemAdap startActivityForResult(aboutIntent, about); } else if (item == itemTrashbin) { // FIXME hardcoded accountId - startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(db.getAccount(1).getUrl() + "index.php/apps/files/?dir=/&view=trashbin"))); + startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(db.getAccount(1).getUrl() + "/index.php/apps/files/?dir=/&view=trashbin"))); } } -- GitLab From 74a3f46335ed4dd81e400a70b67a3f64637ea1b0 Mon Sep 17 00:00:00 2001 From: stefan-niedermann Date: Fri, 4 Oct 2019 11:08:56 +0200 Subject: [PATCH 0009/1834] Fix categories and favorites --- .../notes/android/activity/NotesListViewActivity.java | 6 ++++-- .../android/appwidget/NoteListWidgetConfiguration.java | 6 ++++-- .../notes/android/fragment/CategoryDialogFragment.java | 3 ++- 3 files changed, 10 insertions(+), 5 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 0f113e6ba..81b285c66 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 @@ -332,7 +332,8 @@ public class NotesListViewActivity extends AppCompatActivity implements ItemAdap private class LoadCategoryListTask extends AsyncTask> { @Override protected List doInBackground(Void... voids) { - List categories = db.getCategories(0); + // FIXME hardcoded accountId + List categories = db.getCategories(1); if (!categories.isEmpty() && categories.get(0).label.isEmpty()) { itemUncategorized = categories.get(0); itemUncategorized.label = getString(R.string.action_uncategorized); @@ -341,7 +342,8 @@ public class NotesListViewActivity extends AppCompatActivity implements ItemAdap itemUncategorized = null; } - Map favorites = db.getFavoritesCount(0); + // FIXME hardcoded accountId + Map favorites = db.getFavoritesCount(1); int numFavorites = favorites.containsKey("1") ? favorites.get("1") : 0; int numNonFavorites = favorites.containsKey("0") ? favorites.get("0") : 0; itemFavorites.count = numFavorites; diff --git a/app/src/main/java/it/niedermann/owncloud/notes/android/appwidget/NoteListWidgetConfiguration.java b/app/src/main/java/it/niedermann/owncloud/notes/android/appwidget/NoteListWidgetConfiguration.java index 226b71482..1923d60c0 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/android/appwidget/NoteListWidgetConfiguration.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/android/appwidget/NoteListWidgetConfiguration.java @@ -125,7 +125,8 @@ public class NoteListWidgetConfiguration extends AppCompatActivity { @Override protected List doInBackground(Void... voids) { NavigationAdapter.NavigationItem itemUncategorized; - List categories = db.getCategories(0); + // FIXME hardcoded accountId + List categories = db.getCategories(1); if (!categories.isEmpty() && categories.get(0).label.isEmpty()) { itemUncategorized = categories.get(0); @@ -133,7 +134,8 @@ public class NoteListWidgetConfiguration extends AppCompatActivity { itemUncategorized.icon = NavigationAdapter.ICON_NOFOLDER; } - Map favorites = db.getFavoritesCount(0); + // FIXME hardcoded accountId + Map favorites = db.getFavoritesCount(1); int numFavorites = favorites.containsKey("1") ? favorites.get("1") : 0; int numNonFavorites = favorites.containsKey("0") ? favorites.get("0") : 0; itemFavorites.count = numFavorites; 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 7d9a90685..c68f8aa11 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 @@ -102,7 +102,8 @@ public class CategoryDialogFragment extends DialogFragment { @Override protected List doInBackground(Void... voids) { NoteSQLiteOpenHelper db = NoteSQLiteOpenHelper.getInstance(getActivity()); - List items = db.getCategories(0); + // FIXME hardcoded accountId + List items = db.getCategories(1); List categories = new ArrayList<>(); for (NavigationAdapter.NavigationItem item : items) { if (!item.label.isEmpty()) { -- GitLab From 69c15be3bd2578029bf29544c8d6b8c283caff30 Mon Sep 17 00:00:00 2001 From: stefan-niedermann Date: Fri, 4 Oct 2019 11:13:00 +0200 Subject: [PATCH 0010/1834] Do not reinstanziate NotesClient on each request --- .../notes/persistence/NoteServerSyncHelper.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) 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 d8280521a..c2dce3d33 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 @@ -88,6 +88,7 @@ public class NoteServerSyncHelper { // current state of the synchronization private boolean syncActive = false; private boolean syncScheduled = false; + private NotesClient notesClient; // list of callbacks for both parts of synchronziation private List callbacksPush = new ArrayList<>(); @@ -101,6 +102,7 @@ public class NoteServerSyncHelper { // Registers BroadcastReceiver to track network connection changes. appContext.registerReceiver(networkReceiver, new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)); + notesClient = new NotesClient(appContext); SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this.appContext); prefs.registerOnSharedPreferenceChangeListener(onSharedPreferenceChangeListener); @@ -223,7 +225,6 @@ public class NoteServerSyncHelper { private class SyncTask extends AsyncTask { private final boolean onlyLocalChanges; private final List callbacks = new ArrayList<>(); - private NotesClient client; private List exceptions = new ArrayList<>(); public SyncTask(boolean onlyLocalChanges) { @@ -245,7 +246,6 @@ public class NoteServerSyncHelper { @Override protected LoginStatus doInBackground(Void... voids) { - client = new NotesClient(appContext); // recreate NoteClients on every sync in case the connection settings was changed Log.i(getClass().getSimpleName(), "STARTING SYNCHRONIZATION"); //dbHelper.debugPrintFullDB(); LoginStatus status = LoginStatus.OK; @@ -275,20 +275,20 @@ public class NoteServerSyncHelper { // if note is not new, try to edit it. if (note.getRemoteId() > 0) { Log.v(getClass().getSimpleName(), " ...try to edit"); - remoteNote = client.editNote(note).getNote(); + 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"); - remoteNote = client.createNote(note).getNote(); + 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)"); - client.deleteNote(note.getRemoteId()); + notesClient.deleteNote(note.getRemoteId()); } else { Log.v(getClass().getSimpleName(), " ...delete (only local, since it was not synchronized)"); } @@ -316,7 +316,7 @@ public class NoteServerSyncHelper { LoginStatus status; try { Map idMap = dbHelper.getIdMap(); - ServerResponse.NotesResponse response = client.getNotes(lastModified, lastETag); + ServerResponse.NotesResponse response = notesClient.getNotes(lastModified, lastETag); List remoteNotes = response.getNotes(); Set remoteIDs = new HashSet<>(); // pull remote changes: update or create each remote note -- GitLab From 0fbf02840e8e54acbfcc812fa1f71c72faceea48 Mon Sep 17 00:00:00 2001 From: stefan-niedermann Date: Fri, 4 Oct 2019 12:54:50 +0200 Subject: [PATCH 0011/1834] Use accountId --- app/src/main/AndroidManifest.xml | 2 +- .../android/activity/AccountActivity.java | 88 ++++ .../activity/NotesListViewActivity.java | 38 +- .../android/activity/SettingsActivity.java | 461 ------------------ .../appwidget/NoteListWidgetFactory.java | 19 +- .../appwidget/SingleNoteWidgetFactory.java | 11 +- .../android/fragment/BaseNoteFragment.java | 20 +- .../notes/persistence/LoadNotesListTask.java | 13 +- .../persistence/NoteSQLiteOpenHelper.java | 95 ++-- .../persistence/NoteServerSyncHelper.java | 26 +- .../owncloud/notes/util/NotesClient.java | 7 +- app/src/main/res/layout/activity_account.xml | 16 + app/src/main/res/layout/activity_settings.xml | 77 --- .../res/layout/activity_settings_webview.xml | 20 - app/src/main/res/layout/item_account.xml | 32 ++ app/src/main/res/xml/preferences.xml | 2 +- 16 files changed, 283 insertions(+), 644 deletions(-) create mode 100644 app/src/main/java/it/niedermann/owncloud/notes/android/activity/AccountActivity.java delete mode 100644 app/src/main/java/it/niedermann/owncloud/notes/android/activity/SettingsActivity.java create mode 100644 app/src/main/res/layout/activity_account.xml delete mode 100644 app/src/main/res/layout/activity_settings.xml delete mode 100644 app/src/main/res/layout/activity_settings_webview.xml create mode 100644 app/src/main/res/layout/item_account.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 3af99f24c..1c3436894 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -46,7 +46,7 @@ diff --git a/app/src/main/java/it/niedermann/owncloud/notes/android/activity/AccountActivity.java b/app/src/main/java/it/niedermann/owncloud/notes/android/activity/AccountActivity.java new file mode 100644 index 000000000..4b4aa997e --- /dev/null +++ b/app/src/main/java/it/niedermann/owncloud/notes/android/activity/AccountActivity.java @@ -0,0 +1,88 @@ +package it.niedermann.owncloud.notes.android.activity; + +import android.content.Intent; +import android.os.Bundle; +import android.util.Log; +import android.view.View; +import android.widget.Button; +import android.widget.LinearLayout; +import android.widget.TextView; + +import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatActivity; + +import com.nextcloud.android.sso.AccountImporter; +import com.nextcloud.android.sso.exceptions.AndroidGetAccountsPermissionNotGranted; +import com.nextcloud.android.sso.exceptions.NextcloudFilesAppNotInstalledException; +import com.nextcloud.android.sso.helper.SingleAccountHelper; +import com.nextcloud.android.sso.model.SingleSignOnAccount; +import com.nextcloud.android.sso.ui.UiExceptionManager; + +import butterknife.BindView; +import butterknife.ButterKnife; +import it.niedermann.owncloud.notes.R; +import it.niedermann.owncloud.notes.model.LocalAccount; +import it.niedermann.owncloud.notes.persistence.NoteSQLiteOpenHelper; +import it.niedermann.owncloud.notes.util.ExceptionHandler; + +/** + * Allows switching the account + * Created by stefan on 22.09.15. + */ +public class AccountActivity extends AppCompatActivity { + public static final String SETTINGS_KEY_ETAG = "notes_last_etag"; + public static final String SETTINGS_KEY_LAST_MODIFIED = "notes_last_modified"; + + private NoteSQLiteOpenHelper db; + + @BindView(R.id.accountsLayout) + LinearLayout accountsLayout; + + @BindView(R.id.addAccount) + Button addAccount; + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + Thread.currentThread().setUncaughtExceptionHandler(new ExceptionHandler(this)); + + setContentView(R.layout.activity_account); + ButterKnife.bind(this); + + db = NoteSQLiteOpenHelper.getInstance(this); + + addAccount.setOnClickListener((btn) -> { + try { + AccountImporter.pickNewAccount(this); + } catch (NextcloudFilesAppNotInstalledException e1) { + UiExceptionManager.showDialogForException(this, e1); + Log.w(NotesListViewActivity.class.toString(), "============================================================="); + Log.w(NotesListViewActivity.class.toString(), "Nextcloud app is not installed. Cannot choose account"); + e1.printStackTrace(); + } catch (AndroidGetAccountsPermissionNotGranted e2) { + AccountImporter.requestAndroidAccountPermissionsAndPickAccount(this); + } + }); + + for(LocalAccount account: db.getAccounts()) { + View v = getLayoutInflater().inflate(R.layout.item_account, null); + ((TextView) v.findViewById(R.id.accountItemLabel)).setText(account.getUserName()); + v.setOnClickListener(clickedView -> { + SingleAccountHelper.setCurrentAccount(getApplicationContext(), account.getAccountName()); + finish(); + }); + accountsLayout.addView(v); + } + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { + super.onActivityResult(requestCode, resultCode, data); + + AccountImporter.onActivityResult(requestCode, resultCode, data, this, (SingleSignOnAccount account) -> { + Log.v("Notes", "Added account: " + "name:" + account.name + ", " + account.url + ", userId" + account.userId); + db.addAccount(account.url, account.userId, account.name); + SingleAccountHelper.setCurrentAccount(getApplicationContext(), account.name); + }); + } +} 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 81b285c66..4d2dc823f 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,13 +76,11 @@ import static it.niedermann.owncloud.notes.android.activity.EditNoteActivity.ACT public class NotesListViewActivity extends AppCompatActivity implements ItemAdapter.NoteClickListener { public static final String CREATED_NOTE = "it.niedermann.owncloud.notes.created_notes"; - public static final String CREDENTIALS_CHANGED = "it.niedermann.owncloud.notes.CREDENTIALS_CHANGED"; public static final String ADAPTER_KEY_RECENT = "recent"; public static final String ADAPTER_KEY_STARRED = "starred"; public static final String ACTION_FAVORITES = "it.niedermann.owncloud.notes.favorites"; public static final String ACTION_RECENT = "it.niedermann.owncloud.notes.recent"; - private static final String SAVED_STATE_NAVIGATION_SELECTION = "navigationSelection"; private static final String SAVED_STATE_NAVIGATION_ADAPTER_SLECTION = "navigationAdapterSelection"; private static final String SAVED_STATE_NAVIGATION_OPEN = "navigationOpen"; @@ -92,6 +90,7 @@ public class NotesListViewActivity extends AppCompatActivity implements ItemAdap private final static int server_settings = 2; private final static int about = 3; + private LocalAccount localAccount; @BindView(R.id.notesListActivityActionBar) Toolbar toolbar; @@ -138,7 +137,7 @@ public class NotesListViewActivity extends AppCompatActivity implements ItemAdap if (!shortcutManager.isRateLimitingActive()) { List newShortcuts = new ArrayList<>(); - for (DBNote note : db.getRecentNotes(0)) { + for (DBNote note : db.getRecentNotes(localAccount.getId())) { Intent intent = new Intent(getApplicationContext(), EditNoteActivity.class); intent.putExtra(EditNoteActivity.PARAM_NOTE_ID, note.getId()); intent.setAction(ACTION_SHORTCUT); @@ -171,24 +170,25 @@ public class NotesListViewActivity extends AppCompatActivity implements ItemAdap db = NoteSQLiteOpenHelper.getInstance(this); try { - Log.v("Notes", "current sso account " + SingleAccountHelper.getCurrentSingleSignOnAccount(getApplicationContext()).name); + localAccount = db.getLocalAccountByAccountName(SingleAccountHelper.getCurrentSingleSignOnAccount(getApplicationContext()).name); + Log.v("Notes", "current sso account " + localAccount.getAccountName()); } catch (NextcloudFilesAppAccountNotFoundException e) { e.printStackTrace(); } catch (NoCurrentAccountSelectedException e) { - if (!db.hasAccounts()) { + if (db.hasAccounts()) { + localAccount = db.getAccount(1); + SingleAccountHelper.setCurrentAccount(getApplicationContext(), localAccount.getAccountName()); + } else { try { AccountImporter.pickNewAccount(this); } catch (NextcloudFilesAppNotInstalledException e1) { - UiExceptionManager.showDialogForException(this, e); + UiExceptionManager.showDialogForException(this, e1); Log.w(NotesListViewActivity.class.toString(), "============================================================="); Log.w(NotesListViewActivity.class.toString(), "Nextcloud app is not installed. Cannot choose account"); e.printStackTrace(); } catch (AndroidGetAccountsPermissionNotGranted e2) { AccountImporter.requestAndroidAccountPermissionsAndPickAccount(this); } - } else { - LocalAccount localAccount = db.getAccount(1); - SingleAccountHelper.setCurrentAccount(getApplicationContext(), localAccount.getAccountName()); } } @@ -332,8 +332,7 @@ public class NotesListViewActivity extends AppCompatActivity implements ItemAdap private class LoadCategoryListTask extends AsyncTask> { @Override protected List doInBackground(Void... voids) { - // FIXME hardcoded accountId - List categories = db.getCategories(1); + List categories = db.getCategories(localAccount.getId()); if (!categories.isEmpty() && categories.get(0).label.isEmpty()) { itemUncategorized = categories.get(0); itemUncategorized.label = getString(R.string.action_uncategorized); @@ -342,8 +341,7 @@ public class NotesListViewActivity extends AppCompatActivity implements ItemAdap itemUncategorized = null; } - // FIXME hardcoded accountId - Map favorites = db.getFavoritesCount(1); + Map favorites = db.getFavoritesCount(localAccount.getId()); int numFavorites = favorites.containsKey("1") ? favorites.get("1") : 0; int numNonFavorites = favorites.containsKey("0") ? favorites.get("0") : 0; itemFavorites.count = numFavorites; @@ -430,8 +428,7 @@ public class NotesListViewActivity extends AppCompatActivity implements ItemAdap Intent aboutIntent = new Intent(getApplicationContext(), AboutActivity.class); startActivityForResult(aboutIntent, about); } else if (item == itemTrashbin) { - // FIXME hardcoded accountId - startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(db.getAccount(1).getUrl() + "/index.php/apps/files/?dir=/&view=trashbin"))); + startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(localAccount.getUrl() + "/index.php/apps/files/?dir=/&view=trashbin"))); } } @@ -501,7 +498,7 @@ public class NotesListViewActivity extends AppCompatActivity implements ItemAdap Log.v("Note", "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); + db.addNoteAndSync(dbNote.getAccountId(), dbNote); refreshLists(); Snackbar.make(swipeRefreshLayout, R.string.action_note_restored, Snackbar.LENGTH_SHORT) .show(); @@ -565,7 +562,7 @@ public class NotesListViewActivity extends AppCompatActivity implements ItemAdap listView.scrollToPosition(0); } }; - new LoadNotesListTask(getApplicationContext(), callback, navigationSelection, query).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + new LoadNotesListTask(localAccount.getId(), getApplicationContext(), callback, navigationSelection, query).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); new LoadCategoryListTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); } @@ -651,12 +648,7 @@ public class NotesListViewActivity extends AppCompatActivity implements ItemAdap AccountImporter.onActivityResult(requestCode, resultCode, data, this, (SingleSignOnAccount account) -> { Log.v("Notes", "Added account: " + "name:" + account.name + ", " + account.url + ", userId" + account.userId); - if(db.hasAccounts()) { - // FIXME hardcoded accountId - db.setAccount(1, account.url, account.userId); - } else { - db.addAccount(account.url, account.userId); - } + db.addAccount(account.url, account.userId, account.name); SingleAccountHelper.setCurrentAccount(getApplicationContext(), account.name); }); diff --git a/app/src/main/java/it/niedermann/owncloud/notes/android/activity/SettingsActivity.java b/app/src/main/java/it/niedermann/owncloud/notes/android/activity/SettingsActivity.java deleted file mode 100644 index 98a6a752e..000000000 --- a/app/src/main/java/it/niedermann/owncloud/notes/android/activity/SettingsActivity.java +++ /dev/null @@ -1,461 +0,0 @@ -package it.niedermann.owncloud.notes.android.activity; - -import androidx.appcompat.app.AppCompatActivity; - -/** - * Allows to set Settings like URL, Username and Password for Server-Synchronization - * Created by stefan on 22.09.15. - */ -public class SettingsActivity extends AppCompatActivity { - -// public static final String SETTINGS_URL = "settingsUrl"; -// public static final String SETTINGS_USERNAME = "settingsUsername"; -// public static final String SETTINGS_PASSWORD = "settingsPassword"; - public static final String SETTINGS_KEY_ETAG = "notes_last_etag"; - public static final String SETTINGS_KEY_LAST_MODIFIED = "notes_last_modified"; -// public static final String DEFAULT_SETTINGS = ""; -// public static final int CREDENTIALS_CHANGED = 3; -// -// public static final String LOGIN_URL_DATA_KEY_VALUE_SEPARATOR = ":"; -// public static final String WEBDAV_PATH_4_0_AND_LATER = "/remote.php/webdav"; -// -// private SharedPreferences preferences = null; -// -// @BindView(R.id.settings_url) -// EditText field_url; -// @BindView(R.id.settings_username_wrapper) -// TextInputLayout username_wrapper; -// @BindView(R.id.settings_username) -// EditText field_username; -// @BindView(R.id.settings_password) -// EditText field_password; -// @BindView(R.id.settings_password_wrapper) -// TextInputLayout password_wrapper; -// @BindView(R.id.settings_submit) -// Button btn_submit; -// @BindView(R.id.settings_url_warn_http) -// View urlWarnHttp; -// private String old_password = ""; -// -// private WebView webView; -// -// private boolean first_run = false; -// private boolean useWebLogin = true; -// -// @Override -// public void onCreate(Bundle savedInstanceState) { -// super.onCreate(savedInstanceState); -// Thread.currentThread().setUncaughtExceptionHandler(new ExceptionHandler(this)); -// setContentView(R.layout.activity_settings); -// ButterKnife.bind(this); -// -// preferences = PreferenceManager -// .getDefaultSharedPreferences(getApplicationContext()); -// -// if (!NoteServerSyncHelper.isConfigured(this)) { -// first_run = true; -// if (getSupportActionBar() != null) { -// getSupportActionBar().setDisplayHomeAsUpEnabled(false); -// } -// } -// -// setupListener(); -// -// // Load current Preferences -// field_url.setText(preferences.getString(SETTINGS_URL, DEFAULT_SETTINGS)); -// field_username.setText(preferences.getString(SETTINGS_USERNAME, DEFAULT_SETTINGS)); -// old_password = preferences.getString(SETTINGS_PASSWORD, DEFAULT_SETTINGS); -// -// field_password.setOnEditorActionListener(new TextView.OnEditorActionListener() { -// @Override -// public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { -// login(); -// return true; -// } -// }); -// field_password.setOnFocusChangeListener(new View.OnFocusChangeListener() { -// @Override -// public void onFocusChange(View v, boolean hasFocus) { -// setPasswordHint(hasFocus); -// } -// }); -// setPasswordHint(false); -// -// handleSubmitButtonEnabled(); -// } -// -// private void setupListener() { -// field_url.setOnFocusChangeListener(new View.OnFocusChangeListener() { -// @Override -// public void onFocusChange(View v, boolean hasFocus) { -// new URLValidatorAsyncTask().execute(NotesClientUtil.formatURL(field_url.getText().toString())); -// } -// }); -// field_url.addTextChangedListener(new TextWatcher() { -// @Override -// public void beforeTextChanged(CharSequence s, int start, int count, int after) { -// } -// -// @Override -// public void onTextChanged(CharSequence s, int start, int before, int count) { -// String url = NotesClientUtil.formatURL(field_url.getText().toString()); -// -// if (NotesClientUtil.isHttp(url)) { -// urlWarnHttp.setVisibility(View.VISIBLE); -// } else { -// urlWarnHttp.setVisibility(View.GONE); -// } -// -// handleSubmitButtonEnabled(); -// } -// -// @Override -// public void afterTextChanged(Editable s) { -// } -// }); -// -// field_username.addTextChangedListener(new TextWatcher() { -// @Override -// public void beforeTextChanged(CharSequence s, int start, int count, int after) { -// -// } -// -// @Override -// public void onTextChanged(CharSequence s, int start, int before, int count) { -// handleSubmitButtonEnabled(); -// } -// -// @Override -// public void afterTextChanged(Editable s) { -// -// } -// }); -// -// btn_submit.setOnClickListener(new View.OnClickListener() { -// @Override -// public void onClick(View v) { -// login(); -// } -// }); -// } -// -// private void setPasswordHint(boolean hasFocus) { -// boolean unchangedHint = !hasFocus && field_password.getText().toString().isEmpty() && !old_password.isEmpty(); -// password_wrapper.setHint(getString(unchangedHint ? R.string.settings_password_unchanged : R.string.settings_password)); -// } -// -// -// @Override -// protected void onResume() { -// super.onResume(); -// -// // Occurs in this scenario: User opens the app but doesn't configure the server settings, they then add the Create Note widget to home screen and configure -// // server settings there. The stale SettingsActivity is then displayed hence finish() here to close it down. -// if ((first_run) && (NoteServerSyncHelper.isConfigured(this))) { -// finish(); -// } -// } -// -// /** -// * Prevent pressing back button on first run -// */ -// @Override -// public void onBackPressed() { -// if (!first_run) { -// super.onBackPressed(); -// } -// } -// -// private void legacyLogin() { -// String url = field_url.getText().toString().trim(); -// String username = field_username.getText().toString(); -// String password = field_password.getText().toString(); -// -// if (password.isEmpty()) { -// password = old_password; -// } -// -// url = NotesClientUtil.formatURL(url); -// -// new LoginValidatorAsyncTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, url, username, password); -// } -// -// private void login() { -// if (useWebLogin) { -// webLogin(); -// } else { -// legacyLogin(); -// } -// } -// -// /** -// * Obtain the X509Certificate from SslError -// * -// * @param error SslError -// * @return X509Certificate from error -// */ -// public static X509Certificate getX509CertificateFromError(SslError error) { -// Bundle bundle = SslCertificate.saveState(error.getCertificate()); -// X509Certificate x509Certificate; -// byte[] bytes = bundle.getByteArray("x509-certificate"); -// if (bytes == null) { -// x509Certificate = null; -// } else { -// try { -// CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); -// Certificate cert = certFactory.generateCertificate(new ByteArrayInputStream(bytes)); -// x509Certificate = (X509Certificate) cert; -// } catch (CertificateException e) { -// x509Certificate = null; -// } -// } -// return x509Certificate; -// } -// -// private void webLogin() { -// setContentView(R.layout.activity_settings_webview); -// webView = findViewById(R.id.login_webview); -// webView.setVisibility(View.GONE); -// -// final ProgressBar progressBar = findViewById(R.id.login_webview_progress_bar); -// -// WebSettings settings = webView.getSettings(); -// settings.setAllowFileAccess(false); -// settings.setJavaScriptEnabled(true); -// settings.setDomStorageEnabled(true); -// settings.setUserAgentString(getWebLoginUserAgent()); -// settings.setSaveFormData(false); -// settings.setSavePassword(false); -// -// Map headers = new HashMap<>(); -// headers.put("OCS-APIREQUEST", "true"); -// -// -// webView.loadUrl(normalizeUrlSuffix(NotesClientUtil.formatURL(field_url.getText().toString())) + "index.php/login/flow", headers); -// -// webView.setWebViewClient(new WebViewClient() { -// @Override -// public boolean shouldOverrideUrlLoading(WebView view, String url) { -// if (url.startsWith("nc://login/")) { -// parseAndLoginFromWebView(url); -// return true; -// } -// return false; -// } -// -// @Override -// public void onPageFinished(WebView view, String url) { -// super.onPageFinished(view, url); -// -// progressBar.setVisibility(View.GONE); -// webView.setVisibility(View.VISIBLE); -// } -// }); -// -// // show snackbar after 60s to switch back to old login method -// new Handler().postDelayed(() -> { -// Snackbar.make(webView, R.string.fallback_weblogin_text, Snackbar.LENGTH_INDEFINITE) -// .setAction(R.string.fallback_weblogin_back, (View.OnClickListener) v -> initLegacyLogin(field_url.getText().toString())).show(); -// }, 45 * 1000); -// } -// -// private String getWebLoginUserAgent() { -// return Build.MANUFACTURER.substring(0, 1).toUpperCase(Locale.getDefault()) + -// Build.MANUFACTURER.substring(1).toLowerCase(Locale.getDefault()) + " " + Build.MODEL; -// } -// -// private void parseAndLoginFromWebView(String dataString) { -// String prefix = "nc://login/"; -// LoginUrlInfo loginUrlInfo = parseLoginDataUrl(prefix, dataString); -// -// if (loginUrlInfo != null) { -// String url = normalizeUrlSuffix(loginUrlInfo.serverAddress); -// -// new LoginValidatorAsyncTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, url, loginUrlInfo.username, -// loginUrlInfo.password); -// } -// } -// -// /** -// * parses a URI string and returns a login data object with the information from the URI string. -// * -// * @param prefix URI beginning, e.g. cloud://login/ -// * @param dataString the complete URI -// * @return login data -// * @throws IllegalArgumentException when -// */ -// private LoginUrlInfo parseLoginDataUrl(String prefix, String dataString) throws IllegalArgumentException { -// if (dataString.length() < prefix.length()) { -// throw new IllegalArgumentException("Invalid login URL detected"); -// } -// LoginUrlInfo loginUrlInfo = new LoginUrlInfo(); -// -// // format is basically xxx://login/server:xxx&user:xxx&password while all variables are optional -// String data = dataString.substring(prefix.length()); -// -// // parse data -// String[] values = data.split("&"); -// -// if (values.length < 1 || values.length > 3) { -// // error illegal number of URL elements detected -// throw new IllegalArgumentException("Illegal number of login URL elements detected: " + values.length); -// } -// -// for (String value : values) { -// if (value.startsWith("user" + LOGIN_URL_DATA_KEY_VALUE_SEPARATOR)) { -// loginUrlInfo.username = URLDecoder.decode( -// value.substring(("user" + LOGIN_URL_DATA_KEY_VALUE_SEPARATOR).length())); -// } else if (value.startsWith("password" + LOGIN_URL_DATA_KEY_VALUE_SEPARATOR)) { -// loginUrlInfo.password = URLDecoder.decode( -// value.substring(("password" + LOGIN_URL_DATA_KEY_VALUE_SEPARATOR).length())); -// } else if (value.startsWith("server" + LOGIN_URL_DATA_KEY_VALUE_SEPARATOR)) { -// loginUrlInfo.serverAddress = URLDecoder.decode( -// value.substring(("server" + LOGIN_URL_DATA_KEY_VALUE_SEPARATOR).length())); -// } else { -// // error illegal URL element detected -// throw new IllegalArgumentException("Illegal magic login URL element detected: " + value); -// } -// } -// -// return loginUrlInfo; -// } -// -// private String normalizeUrlSuffix(String url) { -// if (url.toLowerCase(Locale.ROOT).endsWith(WEBDAV_PATH_4_0_AND_LATER)) { -// return url.substring(0, url.length() - WEBDAV_PATH_4_0_AND_LATER.length()); -// } -// -// if (!url.endsWith("/")) { -// return url + "/"; -// } -// -// return url; -// } -// -// private void initLegacyLogin(String oldUrl) { -// useWebLogin = false; -// new URLValidatorAsyncTask().execute(NotesClientUtil.formatURL(field_url.getText().toString())); -// -// webView.setVisibility(View.INVISIBLE); -// setContentView(R.layout.activity_settings); -// -// ButterKnife.bind(this); -// setupListener(); -// -// field_url.setText(oldUrl); -// username_wrapper.setVisibility(View.VISIBLE); -// password_wrapper.setVisibility(View.VISIBLE); -// } -// -// private void handleSubmitButtonEnabled() { -// // drawable[2] is not null if url is valid, see URLValidatorAsyncTask::onPostExecute -// if (useWebLogin || field_url.getCompoundDrawables()[2] != null && (username_wrapper.getVisibility() == View.GONE || -// (username_wrapper.getVisibility() == View.VISIBLE && field_username.getText().length() > 0))) { -// btn_submit.setEnabled(true); -// } else { -// btn_submit.setEnabled(false); -// } -// } -// -// /************************************ Async Tasks ************************************/ -// -// /** -// * Checks if the given URL returns a valid status code and sets the Check next to the URL-Input Field to visible. -// * Created by stefan on 23.09.15. -// */ -// private class URLValidatorAsyncTask extends AsyncTask { -// -// @Override -// protected void onPreExecute() { -// btn_submit.setEnabled(false); -// field_url.setCompoundDrawables(null, null, null, null); -// } -// -// @Override -// protected Boolean doInBackground(String... params) { -// CustomCertManager ccm = NoteServerSyncHelper.getInstance(NoteSQLiteOpenHelper.getInstance(getApplicationContext())).getCustomCertManager(); -// return NotesClientUtil.isValidURL(ccm, params[0]); -// } -// -// @Override -// protected void onPostExecute(Boolean o) { -// if (o) { -// Drawable actionDoneDark = ContextCompat.getDrawable(getApplicationContext(), R.drawable.ic_check_grey600_24dp); -// actionDoneDark.setBounds(0, 0, actionDoneDark.getIntrinsicWidth(), actionDoneDark.getIntrinsicHeight()); -// field_url.setCompoundDrawables(null, null, actionDoneDark, null); -// } else { -// field_url.setCompoundDrawables(null, null, null, null); -// } -// handleSubmitButtonEnabled(); -// } -// } -// -// /** -// * If Log-In-Credentials are correct, save Credentials to Shared Preferences and finish First Run Wizard. -// */ -// private class LoginValidatorAsyncTask extends AsyncTask { -// String url, username, password; -// -// @Override -// protected void onPreExecute() { -// setInputsEnabled(false); -// btn_submit.setText(R.string.settings_submitting); -// } -// -// /** -// * @param params url, username and password -// * @return isValidLogin Boolean -// */ -// @Override -// protected LoginStatus doInBackground(String... params) { -// url = params[0]; -// username = params[1]; -// password = params[2]; -// return NotesClientUtil.isValidLogin(url, username, password); -// } -// -// @Override -// protected void onPostExecute(LoginStatus status) { -// if (LoginStatus.OK.equals(status)) { -// SharedPreferences.Editor editor = preferences.edit(); -// editor.putString(SETTINGS_URL, url); -// editor.putString(SETTINGS_USERNAME, username); -// editor.putString(SETTINGS_PASSWORD, password); -// editor.remove(SETTINGS_KEY_ETAG); -// editor.remove(SETTINGS_KEY_LAST_MODIFIED); -// editor.apply(); -// -// final Intent data = new Intent(); -// data.putExtra(NotesListViewActivity.CREDENTIALS_CHANGED, CREDENTIALS_CHANGED); -// setResult(RESULT_OK, data); -// finish(); -// } else { -// Log.e("Note", "invalid login"); -// btn_submit.setText(R.string.settings_submit); -// setInputsEnabled(true); -// Toast.makeText(getApplicationContext(), getString(R.string.error_invalid_login, getString(status.str)), Toast.LENGTH_LONG).show(); -// } -// } -// -// /** -// * Sets all Input-Fields and Buttons to enabled or disabled depending on the given boolean. -// * -// * @param enabled - boolean -// */ -// private void setInputsEnabled(boolean enabled) { -// btn_submit.setEnabled(enabled); -// field_url.setEnabled(enabled); -// field_username.setEnabled(enabled); -// field_password.setEnabled(enabled); -// } -// } -// -// /** -// * Data object holding the login url fields. -// */ -// public class LoginUrlInfo { -// String serverAddress; -// String username; -// String password; -// } -} diff --git a/app/src/main/java/it/niedermann/owncloud/notes/android/appwidget/NoteListWidgetFactory.java b/app/src/main/java/it/niedermann/owncloud/notes/android/appwidget/NoteListWidgetFactory.java index c55325232..90a36da02 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/android/appwidget/NoteListWidgetFactory.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/android/appwidget/NoteListWidgetFactory.java @@ -10,6 +10,10 @@ import android.preference.PreferenceManager; import android.widget.RemoteViews; import android.widget.RemoteViewsService; +import com.nextcloud.android.sso.exceptions.NextcloudFilesAppAccountNotFoundException; +import com.nextcloud.android.sso.exceptions.NoCurrentAccountSelectedException; +import com.nextcloud.android.sso.helper.SingleAccountHelper; + import java.util.List; import it.niedermann.owncloud.notes.R; @@ -26,15 +30,22 @@ public class NoteListWidgetFactory implements RemoteViewsService.RemoteViewsFact private final SharedPreferences sp; private NoteSQLiteOpenHelper db; private List dbNotes; + private long accountId; NoteListWidgetFactory(Context context, Intent intent) { this.context = context; appWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, - AppWidgetManager.INVALID_APPWIDGET_ID); + AppWidgetManager.INVALID_APPWIDGET_ID); sp = PreferenceManager.getDefaultSharedPreferences(this.context); displayMode = sp.getInt(NoteListWidget.WIDGET_MODE_KEY + appWidgetId, -1); darkTheme = sp.getBoolean(NoteListWidget.DARK_THEME_KEY + appWidgetId, false); category = sp.getString(NoteListWidget.WIDGET_CATEGORY_KEY + appWidgetId, ""); + + try { + accountId = db.getLocalAccountByAccountName(SingleAccountHelper.getCurrentSingleSignOnAccount(context).name).getId(); + } catch (NextcloudFilesAppAccountNotFoundException | NoCurrentAccountSelectedException e) { + e.printStackTrace(); + } } @Override @@ -45,11 +56,11 @@ public class NoteListWidgetFactory implements RemoteViewsService.RemoteViewsFact @Override public void onDataSetChanged() { if (displayMode == NoteListWidget.NLW_DISPLAY_ALL) { - dbNotes = db.getNotes(0); + dbNotes = db.getNotes(accountId); } else if (displayMode == NoteListWidget.NLW_DISPLAY_STARRED) { - dbNotes = db.searchNotes(null,null, true); + dbNotes = db.searchNotes(accountId, null, null, true); } else if (displayMode == NoteListWidget.NLW_DISPLAY_CATEGORY) { - dbNotes = db.searchNotes(null, category, null); + dbNotes = db.searchNotes(accountId, null, category, null); } } diff --git a/app/src/main/java/it/niedermann/owncloud/notes/android/appwidget/SingleNoteWidgetFactory.java b/app/src/main/java/it/niedermann/owncloud/notes/android/appwidget/SingleNoteWidgetFactory.java index 9841d48c5..3e027089b 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/android/appwidget/SingleNoteWidgetFactory.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/android/appwidget/SingleNoteWidgetFactory.java @@ -10,6 +10,9 @@ import android.util.Log; import android.widget.RemoteViews; import android.widget.RemoteViewsService; +import com.nextcloud.android.sso.exceptions.NextcloudFilesAppAccountNotFoundException; +import com.nextcloud.android.sso.exceptions.NoCurrentAccountSelectedException; +import com.nextcloud.android.sso.helper.SingleAccountHelper; import com.yydcdut.markdown.MarkdownProcessor; import com.yydcdut.markdown.syntax.text.TextFactory; @@ -24,6 +27,7 @@ public class SingleNoteWidgetFactory implements RemoteViewsService.RemoteViewsFa private MarkdownProcessor markdownProcessor; private final Context context; private final int appWidgetId; + private long accountId; private NoteSQLiteOpenHelper db; private DBNote note; @@ -41,6 +45,11 @@ public class SingleNoteWidgetFactory implements RemoteViewsService.RemoteViewsFa markdownProcessor = new MarkdownProcessor(this.context); markdownProcessor.factory(TextFactory.create()); markdownProcessor.config(MarkDownUtil.getMarkDownConfiguration(this.context, darkTheme).build()); + try { + accountId = db.getLocalAccountByAccountName(SingleAccountHelper.getCurrentSingleSignOnAccount(context).name).getId(); + } catch (NextcloudFilesAppAccountNotFoundException | NoCurrentAccountSelectedException e) { + e.printStackTrace(); + } } @Override @@ -54,7 +63,7 @@ public class SingleNoteWidgetFactory implements RemoteViewsService.RemoteViewsFa long noteID = sp.getLong(SingleNoteWidget.WIDGET_KEY + appWidgetId, -1); if (noteID >= 0) { - note = db.getNote(noteID); + note = db.getNote(accountId, noteID); if (note == null) { Log.e(TAG, "Error: note not found"); 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 78abe70d4..5286ae404 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 @@ -28,10 +28,15 @@ import androidx.appcompat.widget.ShareActionProvider; import androidx.core.view.MenuItemCompat; import androidx.core.view.ViewCompat; +import com.nextcloud.android.sso.exceptions.NextcloudFilesAppAccountNotFoundException; +import com.nextcloud.android.sso.exceptions.NoCurrentAccountSelectedException; +import com.nextcloud.android.sso.helper.SingleAccountHelper; + import it.niedermann.owncloud.notes.R; import it.niedermann.owncloud.notes.android.activity.EditNoteActivity; import it.niedermann.owncloud.notes.model.CloudNote; import it.niedermann.owncloud.notes.model.DBNote; +import it.niedermann.owncloud.notes.model.LocalAccount; import it.niedermann.owncloud.notes.persistence.NoteSQLiteOpenHelper; import it.niedermann.owncloud.notes.util.DisplayUtils; import it.niedermann.owncloud.notes.util.ICallback; @@ -58,6 +63,8 @@ public abstract class BaseNoteFragment extends Fragment implements CategoryDialo protected String searchQuery = null; + private LocalAccount localAccount; + protected DBNote note; @Nullable private DBNote originalNote; @@ -75,6 +82,11 @@ public abstract class BaseNoteFragment extends Fragment implements CategoryDialo searchQuery = savedInstanceState.getString("searchQuery", ""); } + try { + this.localAccount = db.getLocalAccountByAccountName(SingleAccountHelper.getCurrentSingleSignOnAccount(getActivity().getApplicationContext()).name); + } catch (NextcloudFilesAppAccountNotFoundException | NoCurrentAccountSelectedException e) { + e.printStackTrace(); + } } protected void setActiveTextView(TextView textView) { @@ -88,13 +100,13 @@ public abstract class BaseNoteFragment extends Fragment implements CategoryDialo isNew = true; long id = getArguments().getLong(PARAM_NOTE_ID); if (id > 0) { - note = originalNote = db.getNote(id); + 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(db.addNoteAndSync(cloudNote)); + note = db.getNote(localAccount.getId(), db.addNoteAndSync(note.getAccountId(), cloudNote)); originalNote = null; } } else { @@ -234,7 +246,7 @@ public abstract class BaseNoteFragment extends Fragment implements CategoryDialo if (originalNote == null) { db.deleteNoteAndSync(note.getId()); } else { - db.updateNoteAndSync(originalNote, null, null); + db.updateNoteAndSync(localAccount.getId(), originalNote, null, null); } listener.close(); return true; @@ -315,7 +327,7 @@ public abstract class BaseNoteFragment extends Fragment implements CategoryDialo if (note.getContent().equals(newContent)) { Log.v(getClass().getSimpleName(), "... not saving, since nothing has changed"); } else { - note = db.updateNoteAndSync(note, newContent, callback); + note = db.updateNoteAndSync(localAccount.getId(), note, newContent, callback); listener.onNoteUpdated(note); } } diff --git a/app/src/main/java/it/niedermann/owncloud/notes/persistence/LoadNotesListTask.java b/app/src/main/java/it/niedermann/owncloud/notes/persistence/LoadNotesListTask.java index c70df6823..a8e00b358 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/persistence/LoadNotesListTask.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/persistence/LoadNotesListTask.java @@ -2,14 +2,15 @@ package it.niedermann.owncloud.notes.persistence; import android.content.Context; import android.os.AsyncTask; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.annotation.WorkerThread; import android.text.Html; import android.text.SpannableString; import android.text.TextUtils; import android.text.style.ForegroundColorSpan; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.WorkerThread; + import java.util.ArrayList; import java.util.Calendar; import java.util.List; @@ -29,18 +30,20 @@ public class LoadNotesListTask extends AsyncTask> { private final NotesLoadedListener callback; private final Category category; private final CharSequence searchQuery; - public LoadNotesListTask(@NonNull Context context, @NonNull NotesLoadedListener callback, @NonNull Category category, @Nullable CharSequence searchQuery) { + private final long accountId; + public LoadNotesListTask(long accountId, @NonNull Context context, @NonNull NotesLoadedListener callback, @NonNull Category category, @Nullable CharSequence searchQuery) { this.context = context; this.callback = callback; this.category = category; this.searchQuery = searchQuery; + this.accountId = accountId; } @Override protected List doInBackground(Void... voids) { List noteList; NoteSQLiteOpenHelper db = NoteSQLiteOpenHelper.getInstance(context); - noteList = db.searchNotes(searchQuery, category.category, category.favorite); + noteList = db.searchNotes(accountId, searchQuery, category.category, category.favorite); if (category.category == null) { return fillListByTime(noteList); 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 f0ba44065..31dfd254a 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 @@ -116,7 +116,7 @@ public class NoteSQLiteOpenHelper extends SQLiteOpenHelper { key_id + " INTEGER PRIMARY KEY AUTOINCREMENT, " + key_url + " TEXT, " + key_username + " TEXT, " + - key_account_name + " TEXT, " + + key_account_name + " TEXT UNIQUE, " + key_display_name + " TEXT)"); createAccountIndexes(db); } @@ -210,9 +210,9 @@ public class NoteSQLiteOpenHelper extends SQLiteOpenHelper { * @param content String */ @SuppressWarnings("UnusedReturnValue") - public long addNoteAndSync(String content, String category, boolean favorite) { + 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(note); + return addNoteAndSync(accountId, note); } /** @@ -221,10 +221,9 @@ public class NoteSQLiteOpenHelper extends SQLiteOpenHelper { * @param note Note */ @SuppressWarnings("UnusedReturnValue") - public long addNoteAndSync(CloudNote note) { - // FIXME hardcoded accountId - DBNote dbNote = new DBNote(0, 0, note.getModified(), note.getTitle(), note.getContent(), note.isFavorite(), note.getCategory(), note.getEtag(), DBStatus.LOCAL_EDITED, 1); - long id = addNote(dbNote); + 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); notifyNotesChanged(); getNoteServerSyncHelper().scheduleSync(true); return id; @@ -236,7 +235,7 @@ public class NoteSQLiteOpenHelper extends SQLiteOpenHelper { * * @param note Note to be added. Remotely created Notes must be of type CloudNote and locally created Notes must be of Type DBNote (with DBStatus.LOCAL_EDITED)! */ - long addNote(CloudNote note) { + long addNote(long accountId, CloudNote note) { SQLiteDatabase db = this.getWritableDatabase(); ContentValues values = new ContentValues(); if (note instanceof DBNote) { @@ -247,9 +246,8 @@ public class NoteSQLiteOpenHelper extends SQLiteOpenHelper { values.put(key_status, dbNote.getStatus().getTitle()); values.put(key_account_id, dbNote.getAccountId()); } else { - // FIXME hardcoded accountId values.put(key_status, DBStatus.VOID.getTitle()); - values.put(key_account_id, 1); + values.put(key_account_id, accountId); } if (note.getRemoteId() > 0) { values.put(key_remote_id, note.getRemoteId()); @@ -269,8 +267,8 @@ public class NoteSQLiteOpenHelper extends SQLiteOpenHelper { * @param id int - ID of the requested Note * @return requested Note */ - public DBNote getNote(long id) { - List notes = getNotesCustom(key_id + " = ? AND " + key_status + " != ?", new String[]{String.valueOf(id), DBStatus.LOCAL_DELETED.getTitle()}, null); + public DBNote getNote(long accountId, long id) { + List notes = getNotesCustom(accountId, key_id + " = ? AND " + key_status + " != ?", new String[]{String.valueOf(id), DBStatus.LOCAL_DELETED.getTitle()}, null); return notes.isEmpty() ? null : notes.get(0); } @@ -284,13 +282,13 @@ public class NoteSQLiteOpenHelper extends SQLiteOpenHelper { */ @NonNull @WorkerThread - private List getNotesCustom(@NonNull String selection, @NonNull String[] selectionArgs, @Nullable String orderBy) { - return this.getNotesCustom(selection, selectionArgs, orderBy, null); + private List getNotesCustom(long accountId, @NonNull String selection, @NonNull String[] selectionArgs, @Nullable String orderBy) { + return this.getNotesCustom(accountId, selection, selectionArgs, orderBy, null); } @NonNull @WorkerThread - private List getNotesCustom(@NonNull String selection, @NonNull String[] selectionArgs, @Nullable String orderBy, @Nullable String limit) { + 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("Note", selection + " ---- " + selectionArgs[0] + " " + selectionArgs[1] + " " + selectionArgs[2]); @@ -298,7 +296,7 @@ public class NoteSQLiteOpenHelper extends SQLiteOpenHelper { Cursor cursor = db.query(table_notes, columns, selection, selectionArgs, null, null, orderBy, limit); List notes = new ArrayList<>(); while (cursor.moveToNext()) { - notes.add(getNoteFromCursor(cursor)); + notes.add(getNoteFromCursor(accountId, cursor)); } cursor.close(); return notes; @@ -311,15 +309,14 @@ public class NoteSQLiteOpenHelper extends SQLiteOpenHelper { * @return DBNote */ @NonNull - private DBNote getNoteFromCursor(@NonNull Cursor cursor) { + private DBNote getNoteFromCursor(long accountId, @NonNull Cursor cursor) { Calendar modified = Calendar.getInstance(); modified.setTimeInMillis(cursor.getLong(4) * 1000); - // FIXME hardcoded accountId - 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)), 1); + 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() { - List notes = getNotesCustom("", new String[]{}, default_order); + public void debugPrintFullDB(long accountId) { + List notes = getNotesCustom(accountId, "", new String[]{}, default_order); Log.v(getClass().getSimpleName(), "Full Database (" + notes.size() + " notes):"); for (DBNote note : notes) { Log.v(getClass().getSimpleName(), " " + note); @@ -347,13 +344,13 @@ public class NoteSQLiteOpenHelper extends SQLiteOpenHelper { @NonNull @WorkerThread public List getNotes(long accountId) { - return getNotesCustom(key_status + " != ? AND " + key_account_id + " = ?", new String[]{DBStatus.LOCAL_DELETED.getTitle(), "" + accountId}, default_order); + 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) { - return getNotesCustom(key_status + " != ? AND " + key_account_id + " = ?", new String[]{DBStatus.LOCAL_DELETED.getTitle(), "" + accountId}, key_modified + " DESC", "4"); + return getNotesCustom(accountId, key_status + " != ? AND " + key_account_id + " = ?", new String[]{DBStatus.LOCAL_DELETED.getTitle(), "" + accountId}, key_modified + " DESC", "4"); } /** @@ -363,7 +360,7 @@ public class NoteSQLiteOpenHelper extends SQLiteOpenHelper { */ @NonNull @WorkerThread - public List searchNotes(@Nullable CharSequence query, @Nullable String category, @Nullable Boolean favorite) { + public List searchNotes(long accountId, @Nullable CharSequence query, @Nullable String category, @Nullable Boolean favorite) { List where = new ArrayList<>(); List args = new ArrayList<>(); @@ -392,7 +389,7 @@ public class NoteSQLiteOpenHelper extends SQLiteOpenHelper { } String order = category == null ? default_order : key_category + ", " + key_title; - return getNotesCustom(TextUtils.join(" AND ", where), args.toArray(new String[]{}), order); + return getNotesCustom(accountId, TextUtils.join(" AND ", where), args.toArray(new String[]{}), order); } /** @@ -403,7 +400,7 @@ public class NoteSQLiteOpenHelper extends SQLiteOpenHelper { @NonNull @WorkerThread public List getLocalModifiedNotes(long accountId) { - return getNotesCustom(key_status + " != ? AND " + key_account_id + " = ?", new String[]{DBStatus.VOID.getTitle(), "" + accountId}, null); + return getNotesCustom(accountId, key_status + " != ? AND " + key_account_id + " = ?", new String[]{DBStatus.VOID.getTitle(), "" + accountId}, null); } @NonNull @@ -483,15 +480,13 @@ public class NoteSQLiteOpenHelper extends SQLiteOpenHelper { * @param callback When the synchronization is finished, this callback will be invoked (optional). * @return changed note if differs from database, otherwise the old note. */ - public DBNote updateNoteAndSync(@NonNull DBNote oldNote, @Nullable String newContent, @Nullable ICallback callback) { + public DBNote updateNoteAndSync(long accountId, @NonNull DBNote oldNote, @Nullable String newContent, @Nullable ICallback callback) { //debugPrintFullDB(); DBNote newNote; if (newContent == null) { - // FIXME hardcoded accountId - newNote = new DBNote(oldNote.getId(), oldNote.getRemoteId(), oldNote.getModified(), oldNote.getTitle(), oldNote.getContent(), oldNote.isFavorite(), oldNote.getCategory(), oldNote.getEtag(), DBStatus.LOCAL_EDITED, 1); + newNote = new DBNote(oldNote.getId(), oldNote.getRemoteId(), oldNote.getModified(), oldNote.getTitle(), oldNote.getContent(), oldNote.isFavorite(), oldNote.getCategory(), oldNote.getEtag(), DBStatus.LOCAL_EDITED, accountId); } else { - // FIXME hardcoded accountId - newNote = new DBNote(oldNote.getId(), oldNote.getRemoteId(), Calendar.getInstance(), NoteUtil.generateNonEmptyNoteTitle(newContent, getContext()), newContent, oldNote.isFavorite(), oldNote.getCategory(), oldNote.getEtag(), DBStatus.LOCAL_EDITED, 1); + newNote = new DBNote(oldNote.getId(), oldNote.getRemoteId(), Calendar.getInstance(), NoteUtil.generateNonEmptyNoteTitle(newContent, getContext()), newContent, oldNote.isFavorite(), oldNote.getCategory(), oldNote.getEtag(), DBStatus.LOCAL_EDITED, accountId); } SQLiteDatabase db = this.getWritableDatabase(); ContentValues values = new ContentValues(); @@ -641,11 +636,12 @@ public class NoteSQLiteOpenHelper extends SQLiteOpenHelper { return DatabaseUtils.queryNumEntries(getReadableDatabase(), table_accounts) > 0; } - public long addAccount(String url, String username) { + public long addAccount(String url, String username, String accountName) { SQLiteDatabase db = this.getWritableDatabase(); ContentValues values = new ContentValues(); values.put(key_url, url); values.put(key_username, username); + values.put(key_account_name, accountName); return db.insert(table_accounts, null, values); } @@ -664,12 +660,45 @@ public class NoteSQLiteOpenHelper extends SQLiteOpenHelper { return account; } - public void setAccount(int id, String url, String username) { + public List getAccounts() { + SQLiteDatabase db = getReadableDatabase(); + Cursor cursor = db.query(table_accounts, new String[]{key_id, key_url, key_account_name, key_username, key_display_name}, null, null, null, null, null); + List accounts = new ArrayList<>(); + while (cursor.moveToNext()) { + LocalAccount account = new LocalAccount(); + account.setId(cursor.getLong(0)); + account.setUrl(cursor.getString(1)); + account.setAccountName(cursor.getString(2)); + account.setUserName(cursor.getString(3)); + account.setDisplayName(cursor.getString(4)); + accounts.add(account); + } + cursor.close(); + return accounts; + } + + public void setAccount(int id, String url, String username, String accountName) { SQLiteDatabase db = this.getWritableDatabase(); ContentValues values = new ContentValues(); values.put(key_id, id); values.put(key_url, url); + values.put(key_account_name, accountName); values.put(key_username, username); db.update(table_accounts, values, key_id + " = ?", new String[] {id + ""}); } + + public LocalAccount getLocalAccountByAccountName(String accountName) { + SQLiteDatabase db = getReadableDatabase(); + Cursor cursor = db.query(table_accounts, new String[]{key_id, key_url, key_account_name, key_username, key_display_name}, key_account_name + " = ?", new String[]{accountName}, null, null, null, null); + LocalAccount account = new LocalAccount(); + while (cursor.moveToNext()) { + account.setId(cursor.getLong(0)); + account.setUrl(cursor.getString(1)); + account.setAccountName(cursor.getString(2)); + account.setUserName(cursor.getString(3)); + account.setDisplayName(cursor.getString(4)); + } + cursor.close(); + return account; + } } 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 c2dce3d33..1d8973407 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 @@ -26,10 +26,11 @@ import java.util.Map; import java.util.Set; import it.niedermann.owncloud.notes.R; -import it.niedermann.owncloud.notes.android.activity.SettingsActivity; +import it.niedermann.owncloud.notes.android.activity.AccountActivity; import it.niedermann.owncloud.notes.model.CloudNote; 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.util.ICallback; import it.niedermann.owncloud.notes.util.NotesClient; import it.niedermann.owncloud.notes.util.NotesClientUtil.LoginStatus; @@ -59,6 +60,7 @@ public class NoteServerSyncHelper { private final NoteSQLiteOpenHelper dbHelper; private final Context appContext; + private LocalAccount localAccount; // Track network connection changes using a BroadcastReceiver private boolean networkConnected = false; @@ -98,6 +100,11 @@ public class NoteServerSyncHelper { private NoteServerSyncHelper(NoteSQLiteOpenHelper db) { this.dbHelper = db; this.appContext = db.getContext().getApplicationContext(); + try { + this.localAccount = db.getLocalAccountByAccountName(SingleAccountHelper.getCurrentSingleSignOnAccount(appContext).name); + } catch (NextcloudFilesAppAccountNotFoundException | NoCurrentAccountSelectedException e) { + e.printStackTrace(); + } this.syncOnlyOnWifiKey = appContext.getResources().getString(R.string.pref_key_wifi_only); // Registers BroadcastReceiver to track network connection changes. @@ -263,8 +270,7 @@ public class NoteServerSyncHelper { */ private void pushLocalChanges() { Log.d(getClass().getSimpleName(), "pushLocalChanges()"); - // FIXME hardcoded accountId - List notes = dbHelper.getLocalModifiedNotes(1); + List notes = dbHelper.getLocalModifiedNotes(localAccount.getId()); for (DBNote note : notes) { Log.d(getClass().getSimpleName(), " Process Local Note: " + note); try { @@ -311,8 +317,8 @@ public class NoteServerSyncHelper { private LoginStatus pullRemoteChanges() { Log.d(getClass().getSimpleName(), "pullRemoteChanges()"); SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(appContext); - String lastETag = preferences.getString(SettingsActivity.SETTINGS_KEY_ETAG, null); - long lastModified = preferences.getLong(SettingsActivity.SETTINGS_KEY_LAST_MODIFIED, 0); + String lastETag = preferences.getString(AccountActivity.SETTINGS_KEY_ETAG, null); + long lastModified = preferences.getLong(AccountActivity.SETTINGS_KEY_LAST_MODIFIED, 0); LoginStatus status; try { Map idMap = dbHelper.getIdMap(); @@ -330,7 +336,7 @@ public class NoteServerSyncHelper { dbHelper.updateNote(idMap.get(remoteNote.getRemoteId()), remoteNote, null); } else { Log.v(getClass().getSimpleName(), " ... create"); - dbHelper.addNote(remoteNote); + dbHelper.addNote(localAccount.getId(), remoteNote); } } Log.d(getClass().getSimpleName(), " Remove remotely deleted Notes (only those without local changes)"); @@ -347,15 +353,15 @@ public class NoteServerSyncHelper { SharedPreferences.Editor editor = preferences.edit(); String etag = response.getETag(); if (etag != null && !etag.isEmpty()) { - editor.putString(SettingsActivity.SETTINGS_KEY_ETAG, etag); + editor.putString(AccountActivity.SETTINGS_KEY_ETAG, etag); } else { - editor.remove(SettingsActivity.SETTINGS_KEY_ETAG); + editor.remove(AccountActivity.SETTINGS_KEY_ETAG); } long modified = response.getLastModified(); if (modified != 0) { - editor.putLong(SettingsActivity.SETTINGS_KEY_LAST_MODIFIED, modified); + editor.putLong(AccountActivity.SETTINGS_KEY_LAST_MODIFIED, modified); } else { - editor.remove(SettingsActivity.SETTINGS_KEY_LAST_MODIFIED); + editor.remove(AccountActivity.SETTINGS_KEY_LAST_MODIFIED); } editor.apply(); } catch (ServerResponse.NotModifiedException e) { 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 45202aba0..4f42dee87 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 @@ -80,12 +80,12 @@ public class NotesClient { mNextcloudAPI = new NextcloudAPI(context, ssoAccount, new GsonBuilder().create(), new NextcloudAPI.ApiConnectedListener() { @Override public void onConnected() { - + Log.v("Notes", "SSO API connected"); } @Override public void onError(Exception ex) { - + ex.printStackTrace(); } }); } catch (NextcloudFilesAppAccountNotFoundException e) { @@ -156,8 +156,6 @@ public class NotesClient { .setMethod(method) .setUrl("/index.php/apps/notes/api/v0.2/" + target); - Log.v("Notes", "NextcloudRequest Params: " + params); - Map> header = new HashMap<>(); if (params != null) { header.put("Content-Type", Collections.singletonList(application_json)); @@ -173,6 +171,7 @@ public class NotesClient { StringBuilder result = new StringBuilder(); try { + Log.v("Notes", "NextcloudRequest: " + nextcloudRequest.toString()); InputStream inputStream = mNextcloudAPI.performNetworkRequest(nextcloudRequest); Log.v("Notes", "NextcloudRequest: " + nextcloudRequest.toString()); BufferedReader rd = new BufferedReader(new InputStreamReader(inputStream)); diff --git a/app/src/main/res/layout/activity_account.xml b/app/src/main/res/layout/activity_account.xml new file mode 100644 index 000000000..ebf385bb3 --- /dev/null +++ b/app/src/main/res/layout/activity_account.xml @@ -0,0 +1,16 @@ + + + +