From be54e018c49a3899acc2baf182ac6dea4382dbf5 Mon Sep 17 00:00:00 2001 From: Stefan Niedermann Date: Thu, 25 Nov 2021 14:02:21 +0100 Subject: #1165 Refactor PushNotifications Signed-off-by: Stefan Niedermann --- .../deck/persistence/sync/SyncManager.java | 11 +- .../sync/adapters/db/DataBaseAdapter.java | 4 +- .../persistence/sync/adapters/db/dao/BoardDao.java | 4 +- .../deck/ui/PushNotificationActivity.java | 85 +++++------- .../deck/ui/PushNotificationViewModel.java | 152 +++++++++++++++------ 5 files changed, 158 insertions(+), 98 deletions(-) (limited to 'app/src/main') diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/SyncManager.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/SyncManager.java index 7ce19fc16..ef170719f 100644 --- a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/SyncManager.java +++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/SyncManager.java @@ -30,6 +30,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; +import java.util.Optional; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -130,9 +131,9 @@ public class SyncManager { this.syncHelperFactory = syncHelperFactory; } - @AnyThread - public Board getBoardByAccountAndCardRemoteIdDirectly(long accountId, long cardRemoteId) { - return dataBaseAdapter.getBoardByAccountAndCardRemoteIdDirectly(accountId, cardRemoteId); + @WorkerThread + public Optional getBoardLocalIdByAccountAndCardRemoteIdDirectly(long accountId, long cardRemoteId) { + return Optional.ofNullable(dataBaseAdapter.getBoardLocalIdByAccountAndCardRemoteIdDirectly(accountId, cardRemoteId)); } @WorkerThread @@ -1590,8 +1591,8 @@ public class SyncManager { } @WorkerThread - public Card getCardByRemoteIDDirectly(long accountId, long remoteId) { - return dataBaseAdapter.getCardByRemoteIDDirectly(accountId, remoteId); + public Optional getCardByRemoteIDDirectly(long accountId, long remoteId) { + return Optional.ofNullable(dataBaseAdapter.getCardByRemoteIDDirectly(accountId, remoteId)); } public long createUser(long accountId, User user) { diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/DataBaseAdapter.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/DataBaseAdapter.java index cb31ff0b1..790d3a1d5 100644 --- a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/DataBaseAdapter.java +++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/DataBaseAdapter.java @@ -1113,8 +1113,8 @@ public class DataBaseAdapter { } @WorkerThread - public Board getBoardByAccountAndCardRemoteIdDirectly(long accountId, long cardRemoteId) { - return db.getBoardDao().getBoardByAccountAndCardRemoteIdDirectly(accountId, cardRemoteId); + public Long getBoardLocalIdByAccountAndCardRemoteIdDirectly(long accountId, long cardRemoteId) { + return db.getBoardDao().getBoardLocalIdByAccountAndCardRemoteIdDirectly(accountId, cardRemoteId); } @WorkerThread diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/BoardDao.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/BoardDao.java index 89846ac39..4fa7bf8f7 100644 --- a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/BoardDao.java +++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/BoardDao.java @@ -75,12 +75,12 @@ public interface BoardDao extends GenericDao { "WHERE c.id = :cardRemoteId and c.accountId = :accountId") LiveData getLocalBoardIdByCardRemoteIdAndAccountId(long cardRemoteId, long accountId); - @Query("SELECT b.* " + + @Query("SELECT b.localId " + "FROM card c " + "inner join stack s on s.localId = c.stackId " + "inner join board b on s.boardId = b.localId " + "WHERE c.id = :cardRemoteId and c.accountId = :accountId") - Board getBoardByAccountAndCardRemoteIdDirectly(long accountId, long cardRemoteId); + Long getBoardLocalIdByAccountAndCardRemoteIdDirectly(long accountId, long cardRemoteId); @Query("SELECT count(*) FROM board WHERE accountId = :accountId and archived = 1 and (deletedAt = 0 or deletedAt is null) and status <> 3") LiveData countArchivedBoards(long accountId); diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/PushNotificationActivity.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/PushNotificationActivity.java index 25a255aa1..b824ed305 100644 --- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/PushNotificationActivity.java +++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/PushNotificationActivity.java @@ -1,5 +1,9 @@ package it.niedermann.nextcloud.deck.ui; +import static it.niedermann.nextcloud.deck.ui.PushNotificationViewModel.KEY_MESSAGE; +import static it.niedermann.nextcloud.deck.ui.PushNotificationViewModel.KEY_SUBJECT; + +import android.annotation.SuppressLint; import android.content.Intent; import android.net.Uri; import android.text.TextUtils; @@ -7,7 +11,6 @@ import android.view.View; import androidx.annotation.ColorInt; import androidx.annotation.NonNull; -import androidx.annotation.UiThread; import androidx.appcompat.app.AppCompatActivity; import androidx.lifecycle.ViewModelProvider; @@ -17,13 +20,11 @@ import java.util.concurrent.Executors; import it.niedermann.android.util.ColorUtil; import it.niedermann.nextcloud.deck.DeckLog; import it.niedermann.nextcloud.deck.R; -import it.niedermann.nextcloud.deck.api.IResponseCallback; import it.niedermann.nextcloud.deck.databinding.ActivityPushNotificationBinding; import it.niedermann.nextcloud.deck.model.Account; import it.niedermann.nextcloud.deck.ui.card.EditActivity; import it.niedermann.nextcloud.deck.ui.exception.ExceptionDialogFragment; import it.niedermann.nextcloud.deck.ui.exception.ExceptionHandler; -import kotlin.Triple; /** * Warning: Do not move this class to another package or folder! @@ -35,13 +36,6 @@ public class PushNotificationActivity extends AppCompatActivity { private PushNotificationViewModel viewModel; private final ExecutorService executor = Executors.newSingleThreadExecutor(); - // Provided by Files app NotificationJob - private static final String KEY_SUBJECT = "subject"; - private static final String KEY_MESSAGE = "message"; - private static final String KEY_LINK = "link"; - private static final String KEY_ACCOUNT = "account"; - // Optional - private static final String KEY_CARD_REMOTE_ID = "objectId"; @Override protected void onResume() { @@ -70,59 +64,48 @@ public class PushNotificationActivity extends AppCompatActivity { binding.cancel.setOnClickListener((v) -> finish()); viewModel.getAccount().observe(this, this::applyBrandToSubmitButton); - executor.submit(() -> viewModel.getCardInformation( - intent.getStringExtra(KEY_ACCOUNT), - intent.getStringExtra(KEY_CARD_REMOTE_ID), - new IResponseCallback<>() { - @Override - public void onResponse(Triple response) { - runOnUiThread(() -> openCardOnSubmit(response.getFirst(), response.getSecond(), response.getThird())); - } - - @Override - public void onError(Throwable throwable) { - IResponseCallback.super.onError(throwable); - runOnUiThread(() -> fallbackToBrowser(intent.getStringExtra(KEY_LINK))); - final String params = "Error while receiving push notification:\n" - + KEY_SUBJECT + ": [" + intent.getStringExtra(KEY_SUBJECT) + "]\n" - + KEY_MESSAGE + ": [" + intent.getStringExtra(KEY_MESSAGE) + "]\n" - + KEY_LINK + ": [" + intent.getStringExtra(KEY_LINK) + "]\n" - + KEY_CARD_REMOTE_ID + ": [" + intent.getStringExtra(KEY_CARD_REMOTE_ID) + "]\n" - + KEY_ACCOUNT + ": [" + intent.getStringExtra(KEY_ACCOUNT) + "]"; - ExceptionDialogFragment.newInstance(new Exception(params, throwable), null).show(getSupportFragmentManager(), ExceptionDialogFragment.class.getSimpleName()); - } - })); + executor.submit(() -> viewModel.getCardInformation(intent.getExtras(), new PushNotificationViewModel.PushNotificationCallback() { + @Override + public void onResponse(@NonNull PushNotificationViewModel.CardInformation cardInformation) { + runOnUiThread(() -> openCardOnSubmit(cardInformation.account, cardInformation.localBoardId, cardInformation.localCardId)); + } + + @Override + public void fallbackToBrowser(@NonNull Uri uri) { + runOnUiThread(() -> PushNotificationActivity.this.fallbackToBrowser(uri)); + } + + @Override + @SuppressLint("MissingSuperCall") + public void onError(Throwable throwable) { + runOnUiThread(() -> displayError(throwable)); + } + })); } private void openCardOnSubmit(@NonNull Account account, long boardLocalId, long cardLocalId) { - binding.submit.setOnClickListener((v) -> launchEditActivity(account, boardLocalId, cardLocalId)); + binding.submit.setOnClickListener((v) -> { + DeckLog.info("Starting", EditActivity.class.getSimpleName(), "with [" + account + ", " + boardLocalId + ", " + cardLocalId + "]"); + startActivity(EditActivity.createEditCardIntent(this, account, boardLocalId, cardLocalId)); + finish(); + }); binding.submit.setText(R.string.simple_open); applyBrandToSubmitButton(account.getColor()); binding.submit.setEnabled(true); binding.progress.setVisibility(View.INVISIBLE); } - /** - * If anything goes wrong and we cannot open the card directly, we fall back to open the given link in the webbrowser - */ - private void fallbackToBrowser(String link) { + private void fallbackToBrowser(@NonNull Uri uri) { DeckLog.warn("Falling back to browser as notification handler."); - try { - final var uri = Uri.parse(link); - binding.submit.setOnClickListener((v) -> startActivity(new Intent(Intent.ACTION_VIEW, uri))); - binding.submit.setText(R.string.open_in_browser); - binding.submit.setEnabled(true); - binding.progress.setVisibility(View.INVISIBLE); - } catch (Throwable t) { - DeckLog.logError(t); - } + binding.submit.setOnClickListener((v) -> startActivity(new Intent(Intent.ACTION_VIEW, uri))); + binding.submit.setText(R.string.open_in_browser); + binding.submit.setEnabled(true); + binding.progress.setVisibility(View.INVISIBLE); } - @UiThread - private void launchEditActivity(@NonNull Account account, Long boardId, Long cardId) { - DeckLog.info("starting", EditActivity.class.getSimpleName(), "with [" + account + ", " + boardId + ", " + cardId + "]"); - startActivity(EditActivity.createEditCardIntent(this, account, boardId, cardId)); - finish(); + private void displayError(Throwable throwable) { + ExceptionDialogFragment.newInstance(throwable, null) + .show(getSupportFragmentManager(), ExceptionDialogFragment.class.getSimpleName()); } @Override diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/PushNotificationViewModel.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/PushNotificationViewModel.java index 42ef05d0c..612759b9b 100644 --- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/PushNotificationViewModel.java +++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/PushNotificationViewModel.java @@ -5,6 +5,8 @@ import static androidx.lifecycle.Transformations.map; import android.annotation.SuppressLint; import android.app.Application; +import android.net.Uri; +import android.os.Bundle; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -15,15 +17,24 @@ import androidx.lifecycle.MutableLiveData; import com.nextcloud.android.sso.helper.SingleAccountHelper; +import java.util.NoSuchElementException; +import java.util.Optional; + import it.niedermann.nextcloud.deck.DeckLog; import it.niedermann.nextcloud.deck.api.IResponseCallback; import it.niedermann.nextcloud.deck.api.ResponseCallback; import it.niedermann.nextcloud.deck.model.Account; import it.niedermann.nextcloud.deck.persistence.sync.SyncManager; -import kotlin.Triple; public class PushNotificationViewModel extends AndroidViewModel { + // Provided by Files app NotificationJob + static final String KEY_SUBJECT = "subject"; + static final String KEY_MESSAGE = "message"; + static final String KEY_LINK = "link"; + private static final String KEY_ACCOUNT = "account"; + private static final String KEY_CARD_REMOTE_ID = "objectId"; + private final SyncManager readAccountSyncManager; private final MutableLiveData account = new MutableLiveData<>(); @@ -33,80 +44,145 @@ public class PushNotificationViewModel extends AndroidViewModel { } @WorkerThread - public void getCardInformation( - @Nullable String accountString, - @Nullable String cardRemoteIdString, - @NonNull IResponseCallback> callback) { - if (cardRemoteIdString == null) { - callback.onError(new NumberFormatException("cardRemoteIdString is null")); + public void getCardInformation(@Nullable Bundle bundle, @NonNull PushNotificationCallback callback) { + if (bundle == null) { + callback.onError(new NullPointerException("Bundle is null")); return; } + + final var uri = getUriFromBundle(bundle); try { - final long cardRemoteId; - try { - DeckLog.verbose("cardRemoteIdString = ", cardRemoteIdString); - cardRemoteId = Long.parseLong(cardRemoteIdString); - } catch (NumberFormatException e) { - callback.onError(e); - return; - } - final var account = readAccountSyncManager.readAccountDirectly(accountString); - if (account == null) { - callback.onError(new RuntimeException("Given account for" + accountString + "is null.")); - return; - } + final long cardRemoteId = getCardRemoteId(bundle); + final var account = getAccount(bundle); SingleAccountHelper.setCurrentAccount(getApplication(), account.getName()); final var syncManager = new SyncManager(getApplication()); - DeckLog.verbose("account:", account); - final var board = syncManager.getBoardByAccountAndCardRemoteIdDirectly(account.getId(), cardRemoteId); - DeckLog.verbose("BoardLocalId:", board); - if (board == null) { - callback.onError(new RuntimeException("Given localBoardId for cardRemoteId" + cardRemoteId + "is null.")); - return; - } + final long boardLocalId = syncManager.getBoardLocalIdByAccountAndCardRemoteIdDirectly(account.getId(), cardRemoteId) + .orElseThrow(() -> new NoSuchElementException("Given localBoardId for cardRemoteId" + cardRemoteId + "is null.")); + DeckLog.verbose("boardLocalId:", boardLocalId); final var card = syncManager.getCardByRemoteIDDirectly(account.getId(), cardRemoteId); DeckLog.verbose("Card:", card); - if (card != null) { + if (card.isPresent()) { syncManager.synchronizeCard(new ResponseCallback<>(account) { @Override public void onResponse(Boolean response) { - callback.onResponse(new Triple<>(account, board.getLocalId(), card.getLocalId())); + callback.onResponse(new CardInformation(account, boardLocalId, card.get().getLocalId())); } @Override public void onError(Throwable throwable) { super.onError(throwable); - callback.onResponse(new Triple<>(account, board.getLocalId(), card.getLocalId())); + callback.onResponse(new CardInformation(account, boardLocalId, card.get().getLocalId())); } - }, card); + }, card.get()); } else { - DeckLog.info("Card is not yet available locally. Synchronize board with localId", board); + DeckLog.info("Card is not yet available locally. Synchronize board with localId", boardLocalId); - syncManager.synchronizeBoard(board.getLocalId(), new ResponseCallback<>(account) { + syncManager.synchronizeBoard(boardLocalId, new ResponseCallback<>(account) { @Override public void onResponse(Boolean response) { final var card = syncManager.getCardByRemoteIDDirectly(account.getId(), cardRemoteId); DeckLog.verbose("Card:", card); - if (card != null) { - callback.onResponse(new Triple<>(account, board.getLocalId(), card.getLocalId())); + if (card.isPresent()) { + callback.onResponse(new CardInformation(account, boardLocalId, card.get().getLocalId())); } else { - callback.onError(new RuntimeException("Something went wrong while synchronizing the card" + cardRemoteId + " (cardRemoteId). Given fullCard is null.")); + if(uri.isPresent()) { + callback.fallbackToBrowser(uri.get()); + } else { + callback.onError(generateException("Something went wrong while synchronizing the card" + cardRemoteId + " (cardRemoteId). Given fullCard is null.", bundle)); + } } } @SuppressLint("MissingSuperCall") @Override public void onError(Throwable throwable) { - callback.onError(new RuntimeException("Something went wrong while synchronizing the board with localId" + board)); + if(uri.isPresent()) { + callback.fallbackToBrowser(uri.get()); + } else { + callback.onError(generateException("Something went wrong while synchronizing the board with localId" + boardLocalId, bundle)); + } } }); } } catch (Throwable t) { - callback.onError(t); + if(uri.isPresent()) { + callback.fallbackToBrowser(uri.get()); + } else { + callback.onError(generateException("", bundle, t)); + } + } + } + + private long getCardRemoteId(@NonNull Bundle bundle) throws IllegalArgumentException { + final String cardRemoteIdString = bundle.getString(KEY_CARD_REMOTE_ID); + if (cardRemoteIdString == null) { + throw new IllegalArgumentException("cardRemoteIdString is null"); + } + DeckLog.verbose("cardRemoteIdString = ", cardRemoteIdString); + return Long.parseLong(cardRemoteIdString); + } + + private long getCardRemoteIdFromLink(@NonNull Bundle bundle) { + // TODO parse with ProjectUtils + return -1L; + } + + private Account getAccount(@NonNull Bundle bundle) throws NoSuchElementException { + final var accountString = bundle.getString(KEY_ACCOUNT); + final var account = readAccountSyncManager.readAccountDirectly(accountString); + if (account == null) { + throw new NoSuchElementException("Given account for" + accountString + "is null."); + } + DeckLog.verbose("account:", account); + return account; + } + + private Optional getUriFromBundle(@NonNull Bundle bundle) { + try { + return Optional.of(Uri.parse(bundle.getString(KEY_LINK))); + } catch (Throwable t) { + return Optional.empty(); + } + } + + private Exception generateException(@NonNull String message, @Nullable Bundle bundle) { + return generateException(message, bundle, null); + } + + private Exception generateException(@NonNull String message, @Nullable Bundle bundle, @Nullable Throwable cause) { + if (bundle == null) { + return new Exception("Bundle is null"); } + final var info = "Error while receiving push notification:\n" + + message + "\n" + + KEY_SUBJECT + ": [" + bundle.getString(KEY_SUBJECT) + "]\n" + + KEY_MESSAGE + ": [" + bundle.getString(KEY_MESSAGE) + "]\n" + + KEY_LINK + ": [" + bundle.getString(KEY_LINK) + "]\n" + + KEY_CARD_REMOTE_ID + ": [" + bundle.getString(KEY_CARD_REMOTE_ID) + "]\n" + + KEY_ACCOUNT + ": [" + bundle.getString(KEY_ACCOUNT) + "]"; + return cause == null + ? new Exception(info) + : new Exception(info, cause); } public LiveData getAccount() { return distinctUntilChanged(map(this.account, Account::getColor)); } + + public interface PushNotificationCallback extends IResponseCallback { + void fallbackToBrowser(@NonNull Uri uri); + } + + public static class CardInformation { + @NonNull + public final Account account; + public final long localBoardId; + public final long localCardId; + + public CardInformation(@NonNull Account account, long localBoardId, long localCardId) { + this.account = account; + this.localBoardId = localBoardId; + this.localCardId = localCardId; + } + } } -- cgit v1.2.3