diff --git a/app/src/main/java/eu/faircode/email/AdapterMessage.java b/app/src/main/java/eu/faircode/email/AdapterMessage.java index 1674e201d8..2d1eead87a 100644 --- a/app/src/main/java/eu/faircode/email/AdapterMessage.java +++ b/app/src/main/java/eu/faircode/email/AdapterMessage.java @@ -96,6 +96,7 @@ import androidx.core.content.ContextCompat; import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentTransaction; import androidx.lifecycle.LifecycleOwner; +import androidx.lifecycle.LiveData; import androidx.lifecycle.Observer; import androidx.localbroadcastmanager.content.LocalBroadcastManager; import androidx.paging.AsyncPagedListDiffer; @@ -188,6 +189,9 @@ public class AdapterMessage extends RecyclerView.Adapter> liveAttachments = null; + private Observer> observerAttachments = null; + ViewHolder(View itemView) { super(itemView); @@ -400,25 +404,16 @@ public class AdapterMessage extends RecyclerView.Adapter>() { - @Override - public void onChanged(List operations) { - String text = message.error + - "\n" + message.uid + "/" + message.id + " " + df.format(new Date(message.received)) + - "\n" + (message.ui_hide ? "HIDDEN " : "") + - "seen=" + message.seen + "/" + message.ui_seen + "/" + message.unseen + - " found=" + message.ui_found + - "\n" + message.msgid + - "\n" + message.thread; - if (operations != null) - for (EntityOperation op : operations) - text += "\n" + op.id + ":" + op.name + " " + df.format(new Date(op.created)); + String text = message.error + + "\n" + message.uid + "/" + message.id + " " + df.format(new Date(message.received)) + + "\n" + (message.ui_hide ? "HIDDEN " : "") + + "seen=" + message.seen + "/" + message.ui_seen + "/" + message.unseen + + " found=" + message.ui_found + + "\n" + message.msgid + + "\n" + message.thread; - tvError.setText(text); - tvError.setVisibility(View.VISIBLE); - } - }); + tvError.setText(text); + tvError.setVisibility(View.VISIBLE); } else { tvError.setText(message.error); tvError.setVisibility(message.error == null ? View.GONE : View.VISIBLE); @@ -451,9 +446,6 @@ public class AdapterMessage extends RecyclerView.Adapter 0 && show_expanded ? View.VISIBLE : View.GONE); grpExpanded.setVisibility(viewType == ViewType.THREAD && show_expanded ? View.VISIBLE : View.GONE); - db.folder().liveSystemFolders(message.account).removeObservers(owner); - db.attachment().liveAttachments(message.id).removeObservers(owner); - bnvActions.setTag(null); if (show_expanded) { @@ -488,9 +480,18 @@ public class AdapterMessage extends RecyclerView.Adapter>() { + Bundle sargs = new Bundle(); + sargs.putLong("account", message.account); + + new SimpleTask>() { @Override - public void onChanged(@Nullable List folders) { + protected List onLoad(Context context, Bundle args) { + long account = args.getLong("account"); + return DB.getInstance(context).folder().getSystemFolders(account); + } + + @Override + protected void onLoaded(Bundle args, List folders) { boolean hasJunk = false; boolean hasTrash = false; boolean hasArchive = false; @@ -524,30 +525,43 @@ public class AdapterMessage extends RecyclerView.Adapter>() { - @Override - public void onChanged(@Nullable List attachments) { - if (attachments == null) - attachments = new ArrayList<>(); + observerAttachments = new Observer>() { + @Override + public void onChanged(@Nullable List attachments) { + if (attachments == null) + attachments = new ArrayList<>(); - adapter.set(attachments); + adapter.set(attachments); - if (message.content) { - Bundle args = new Bundle(); - args.putSerializable("message", message); - bodyTask.load(context, owner, args); - } - } - }); + if (message.content) { + Bundle args = new Bundle(); + args.putSerializable("message", message); + bodyTask.load(context, owner, args); + } + } + }; + liveAttachments = db.attachment().liveAttachments(message.id); + liveAttachments.observe(owner, observerAttachments); } itemView.setActivated(selectionTracker != null && selectionTracker.isSelected(message.id)); } + void unbind() { + if (liveAttachments != null) { + liveAttachments.removeObserver(observerAttachments); + liveAttachments = null; + } + } + @Override public void onClick(View view) { int pos = getAdapterPosition(); @@ -1040,10 +1054,14 @@ public class AdapterMessage extends RecyclerView.Adapter>() { + new SimpleTask>() { @Override - public void onChanged(List answers) { + protected List onLoad(Context context, Bundle args) { + return DB.getInstance(context).answer().getAnswers(); + } + + @Override + protected void onLoaded(Bundle args, List answers) { if (answers == null || answers.size() == 0) { Snackbar snackbar = Snackbar.make( itemView, @@ -1094,10 +1112,13 @@ public class AdapterMessage extends RecyclerView.Adapter selectionTracker) { this.selectionTracker = selectionTracker; } diff --git a/app/src/main/java/eu/faircode/email/DaoFolder.java b/app/src/main/java/eu/faircode/email/DaoFolder.java index 3554681b4f..9b68245e1d 100644 --- a/app/src/main/java/eu/faircode/email/DaoFolder.java +++ b/app/src/main/java/eu/faircode/email/DaoFolder.java @@ -97,6 +97,11 @@ public interface DaoFolder { @Query("SELECT * FROM folder ORDER BY account, name") List getFolders(); + @Query("SELECT * FROM folder" + + " WHERE folder.account = :account" + + " AND type <> '" + EntityFolder.USER + "'") + List getSystemFolders(long account); + @Query("SELECT * FROM folder WHERE id = :id") EntityFolder getFolder(Long id); diff --git a/app/src/main/java/eu/faircode/email/FragmentCompose.java b/app/src/main/java/eu/faircode/email/FragmentCompose.java index 5b5898fd23..ae84a86329 100644 --- a/app/src/main/java/eu/faircode/email/FragmentCompose.java +++ b/app/src/main/java/eu/faircode/email/FragmentCompose.java @@ -105,6 +105,7 @@ import androidx.core.content.ContextCompat; import androidx.cursoradapter.widget.SimpleCursorAdapter; import androidx.fragment.app.FragmentTransaction; import androidx.lifecycle.Lifecycle; +import androidx.lifecycle.LiveData; import androidx.lifecycle.Observer; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; @@ -1347,8 +1348,9 @@ public class FragmentCompose extends FragmentEx { final DB db = DB.getInstance(getContext()); - db.account().liveAccounts(true).removeObservers(getViewLifecycleOwner()); db.account().liveAccounts(true).observe(getViewLifecycleOwner(), new Observer>() { + private LiveData> liveIdentities = null; + @Override public void onChanged(List accounts) { if (accounts == null) @@ -1374,8 +1376,12 @@ public class FragmentCompose extends FragmentEx { public void onItemSelected(AdapterView parent, View view, int position, long id) { EntityAccount account = (EntityAccount) parent.getAdapter().getItem(position); - db.identity().liveIdentities(account.id, true).removeObservers(getViewLifecycleOwner()); - db.identity().liveIdentities(account.id, true).observe(getViewLifecycleOwner(), new Observer>() { + if (liveIdentities == null) + liveIdentities = db.identity().liveIdentities(account.id, true); + else + liveIdentities.removeObservers(getViewLifecycleOwner()); + + liveIdentities.observe(getViewLifecycleOwner(), new Observer>() { @Override public void onChanged(@Nullable List identities) { if (identities == null) @@ -1449,7 +1455,6 @@ public class FragmentCompose extends FragmentEx { } }); - db.attachment().liveAttachments(result.draft.id).removeObservers(getViewLifecycleOwner()); db.attachment().liveAttachments(result.draft.id).observe(getViewLifecycleOwner(), new Observer>() { @Override @@ -1462,7 +1467,6 @@ public class FragmentCompose extends FragmentEx { } }); - db.message().liveMessage(result.draft.id).removeObservers(getViewLifecycleOwner()); db.message().liveMessage(result.draft.id).observe(getViewLifecycleOwner(), new Observer() { @Override public void onChanged(final EntityMessage draft) { diff --git a/app/src/main/java/eu/faircode/email/FragmentMessages.java b/app/src/main/java/eu/faircode/email/FragmentMessages.java index 5db983551e..c224695c89 100644 --- a/app/src/main/java/eu/faircode/email/FragmentMessages.java +++ b/app/src/main/java/eu/faircode/email/FragmentMessages.java @@ -202,6 +202,7 @@ public class FragmentMessages extends FragmentEx { Bundle args = new Bundle(); args.putLong("account", account); args.putLong("folder", folder); + new SimpleTask() { @Override protected Void onLoad(Context context, Bundle args) { diff --git a/app/src/main/java/eu/faircode/email/ServiceSynchronize.java b/app/src/main/java/eu/faircode/email/ServiceSynchronize.java index e92bac8045..69c857ee3f 100644 --- a/app/src/main/java/eu/faircode/email/ServiceSynchronize.java +++ b/app/src/main/java/eu/faircode/email/ServiceSynchronize.java @@ -124,6 +124,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.core.content.ContextCompat; import androidx.lifecycle.LifecycleService; +import androidx.lifecycle.LiveData; import androidx.lifecycle.Observer; import static android.os.Process.THREAD_PRIORITY_BACKGROUND; @@ -1075,94 +1076,111 @@ public class ServiceSynchronize extends LifecycleService { // Observe operations Handler handler = new Handler(getMainLooper()) { - private List handling = new ArrayList<>(); - private final PowerManager.WakeLock wlFolder = pm.newWakeLock( - PowerManager.PARTIAL_WAKE_LOCK, BuildConfig.APPLICATION_ID + ":folder." + folder.id); - private final ExecutorService executor = Executors.newSingleThreadExecutor(Helper.backgroundThreadFactory); + private LiveData> liveOperations; @Override public void handleMessage(android.os.Message msg) { - Log.i(Helper.TAG, folder.name + " observe=" + msg.what); - if (msg.what == 0) - db.operation().liveOperations(folder.id).removeObservers(ServiceSynchronize.this); - else - db.operation().liveOperations(folder.id).observe(ServiceSynchronize.this, new Observer>() { - @Override - public void onChanged(List operations) { - boolean process = false; - List current = new ArrayList<>(); - for (EntityOperation op : operations) { - if (!handling.contains(op.id)) - process = true; - current.add(op.id); - } - handling = current; + Log.i(Helper.TAG, account.name + "/" + folder.name + " observe=" + msg.what); + try { + if (msg.what == 0) + liveOperations.removeObserver(observer); + else { + liveOperations = db.operation().liveOperations(folder.id); + liveOperations.observe(ServiceSynchronize.this, observer); + } + } catch (Throwable ex) { + Log.e(Helper.TAG, ex + "\n" + Log.getStackTraceString(ex)); + } + } - if (handling.size() > 0 && process) { - Log.i(Helper.TAG, folder.name + " operations=" + operations.size()); - executor.submit(new Runnable() { - @Override - public void run() { - try { - wlFolder.acquire(); - Log.i(Helper.TAG, folder.name + " process"); + private Observer> observer = new Observer>() { + private List handling = new ArrayList<>(); + private final ExecutorService executor = Executors.newSingleThreadExecutor(Helper.backgroundThreadFactory); + private final PowerManager.WakeLock wlFolder = pm.newWakeLock( + PowerManager.PARTIAL_WAKE_LOCK, BuildConfig.APPLICATION_ID + ":folder." + folder.id); - // Get folder - IMAPFolder ifolder = null; - for (EntityFolder f : folders.keySet()) - if (f.id.equals(folder.id)) { - ifolder = folders.get(f); // null when polling - break; - } + @Override + public void onChanged(List operations) { + boolean process = false; + List current = new ArrayList<>(); + for (EntityOperation op : operations) { + if (!handling.contains(op.id)) + process = true; + current.add(op.id); + } + handling = current; - final boolean shouldClose = (ifolder == null); + if (handling.size() > 0 && process) { + Log.i(Helper.TAG, folder.name + " operations=" + operations.size()); + executor.submit(new Runnable() { + @Override + public void run() { + try { + wlFolder.acquire(); + Log.i(Helper.TAG, folder.name + " process"); - try { - Log.i(Helper.TAG, folder.name + " run " + (shouldClose ? "offline" : "online")); + // Get folder + IMAPFolder ifolder = null; + for (EntityFolder f : folders.keySet()) + if (f.id.equals(folder.id)) { + ifolder = folders.get(f); // null when polling + break; + } - if (ifolder == null) { - // Prevent unnecessary folder connections - if (db.operation().getOperationCount(folder.id, null) == 0) - return; + final boolean shouldClose = (ifolder == null); - db.folder().setFolderState(folder.id, "connecting"); + try { + Log.i(Helper.TAG, folder.name + " run " + (shouldClose ? "offline" : "online")); - ifolder = (IMAPFolder) istore.getFolder(folder.name); - ifolder.open(Folder.READ_WRITE); + if (ifolder == null) { + // Prevent unnecessary folder connections + if (db.operation().getOperationCount(folder.id, null) == 0) + return; - db.folder().setFolderState(folder.id, "connected"); - db.folder().setFolderError(folder.id, null); - } + db.folder().setFolderState(folder.id, "connecting"); - processOperations(account, folder, isession, istore, ifolder, state); + ifolder = (IMAPFolder) istore.getFolder(folder.name); + ifolder.open(Folder.READ_WRITE); - } catch (Throwable ex) { - Log.e(Helper.TAG, folder.name + " " + ex + "\n" + Log.getStackTraceString(ex)); - reportError(account, folder, ex); - db.folder().setFolderError(folder.id, Helper.formatThrowable(ex)); - state.error(); - } finally { - if (shouldClose) { - if (ifolder != null && ifolder.isOpen()) { - db.folder().setFolderState(folder.id, "closing"); - try { - ifolder.close(false); - } catch (MessagingException ex) { - Log.w(Helper.TAG, folder.name + " " + ex + "\n" + Log.getStackTraceString(ex)); - } - } - db.folder().setFolderState(folder.id, null); + db.folder().setFolderState(folder.id, "connected"); + db.folder().setFolderError(folder.id, null); + } + + processOperations(account, folder, isession, istore, ifolder, state); + + } catch (Throwable ex) { + Log.e(Helper.TAG, folder.name + " " + ex + "\n" + Log.getStackTraceString(ex)); + reportError(account, folder, ex); + db.folder().setFolderError(folder.id, Helper.formatThrowable(ex)); + state.error(); + } finally { + if (shouldClose) { + if (ifolder != null && ifolder.isOpen()) { + db.folder().setFolderState(folder.id, "closing"); + try { + ifolder.close(false); + } catch (MessagingException ex) { + Log.w(Helper.TAG, folder.name + " " + ex + "\n" + Log.getStackTraceString(ex)); } } - } finally { - wlFolder.release(); + db.folder().setFolderState(folder.id, null); } } - }); + } finally { + wlFolder.release(); + } } - } - }); - } + }); + } + } + + @Override + public boolean equals(@Nullable Object obj) { + boolean eq = super.equals(obj); + Log.i(Helper.TAG, account.name + "/" + folder.name + " equal=" + eq + " observer=" + observer + " other=" + obj); + return eq; + } + }; }; // Start watching for operations @@ -2423,60 +2441,63 @@ public class ServiceSynchronize extends LifecycleService { db.folder().setFolderError(outbox.id, null); handler = new Handler(Looper.getMainLooper()) { + private LiveData> liveOperations; + @Override public void handleMessage(android.os.Message msg) { Log.i(Helper.TAG, outbox.name + " observe=" + msg.what); - if (msg.what == 0) - db.operation().liveOperations(outbox.id).removeObservers(ServiceSynchronize.this); + liveOperations.removeObserver(observer); else { - db.operation().liveOperations(outbox.id).observe(ServiceSynchronize.this, new Observer>() { - private List handling = new ArrayList<>(); - private ExecutorService executor = Executors.newSingleThreadExecutor(Helper.backgroundThreadFactory); - - @Override - public void onChanged(List operations) { - boolean process = false; - List current = new ArrayList<>(); - for (EntityOperation op : operations) { - if (!handling.contains(op.id)) - process = true; - current.add(op.id); - } - handling = current; - - if (handling.size() > 0 && process) { - Log.i(Helper.TAG, outbox.name + " operations=" + operations.size()); - executor.submit(new Runnable() { - PowerManager pm = getSystemService(PowerManager.class); - PowerManager.WakeLock wl = pm.newWakeLock( - PowerManager.PARTIAL_WAKE_LOCK, BuildConfig.APPLICATION_ID + ":outbox"); - - @Override - public void run() { - try { - wl.acquire(); - Log.i(Helper.TAG, outbox.name + " process"); - - db.folder().setFolderSyncState(outbox.id, "syncing"); - processOperations(null, outbox, null, null, null, state); - db.folder().setFolderError(outbox.id, null); - } catch (Throwable ex) { - Log.e(Helper.TAG, outbox.name + " " + ex + "\n" + Log.getStackTraceString(ex)); - reportError(null, outbox, ex); - db.folder().setFolderError(outbox.id, Helper.formatThrowable(ex)); - } finally { - db.folder().setFolderSyncState(outbox.id, null); - wl.release(); - EntityLog.log(ServiceSynchronize.this, "Outbox wake lock=" + wl.isHeld()); - } - } - }); - } - } - }); + liveOperations = db.operation().liveOperations(outbox.id); + liveOperations.observe(ServiceSynchronize.this, observer); } } + + private Observer> observer = new Observer>() { + private List handling = new ArrayList<>(); + private ExecutorService executor = Executors.newSingleThreadExecutor(Helper.backgroundThreadFactory); + PowerManager pm = getSystemService(PowerManager.class); + PowerManager.WakeLock wl = pm.newWakeLock( + PowerManager.PARTIAL_WAKE_LOCK, BuildConfig.APPLICATION_ID + ":outbox"); + + @Override + public void onChanged(List operations) { + boolean process = false; + List current = new ArrayList<>(); + for (EntityOperation op : operations) { + if (!handling.contains(op.id)) + process = true; + current.add(op.id); + } + handling = current; + + if (handling.size() > 0 && process) { + Log.i(Helper.TAG, outbox.name + " operations=" + operations.size()); + executor.submit(new Runnable() { + @Override + public void run() { + try { + wl.acquire(); + Log.i(Helper.TAG, outbox.name + " process"); + + db.folder().setFolderSyncState(outbox.id, "syncing"); + processOperations(null, outbox, null, null, null, state); + db.folder().setFolderError(outbox.id, null); + } catch (Throwable ex) { + Log.e(Helper.TAG, outbox.name + " " + ex + "\n" + Log.getStackTraceString(ex)); + reportError(null, outbox, ex); + db.folder().setFolderError(outbox.id, Helper.formatThrowable(ex)); + } finally { + db.folder().setFolderSyncState(outbox.id, null); + wl.release(); + EntityLog.log(ServiceSynchronize.this, "Outbox wake lock=" + wl.isHeld()); + } + } + }); + } + } + }; }; handler.sendEmptyMessage(1); db.folder().setFolderState(outbox.id, "connected"); diff --git a/app/src/main/java/eu/faircode/email/ServiceTileUnseen.java b/app/src/main/java/eu/faircode/email/ServiceTileUnseen.java index 21a00d0043..74ed500027 100644 --- a/app/src/main/java/eu/faircode/email/ServiceTileUnseen.java +++ b/app/src/main/java/eu/faircode/email/ServiceTileUnseen.java @@ -31,11 +31,13 @@ import android.util.Log; import java.util.List; import androidx.lifecycle.LifecycleService; +import androidx.lifecycle.LiveData; import androidx.lifecycle.Observer; @TargetApi(Build.VERSION_CODES.N) public class ServiceTileUnseen extends TileService { - LifecycleService owner = new LifecycleService(); + private LifecycleService owner = new LifecycleService(); + private LiveData> liveMessages; @Override public void onCreate() { @@ -63,9 +65,8 @@ public class ServiceTileUnseen extends TileService { public void onStartListening() { Log.i(Helper.TAG, "Start tile unseen"); - - DB db = DB.getInstance(this); - db.message().liveUnseenUnified().observe(owner, new Observer>() { + liveMessages = DB.getInstance(this).message().liveUnseenUnified(); + liveMessages.observe(owner, new Observer>() { @Override public void onChanged(List messages) { Log.i(Helper.TAG, "Update tile unseen=" + messages.size()); @@ -85,9 +86,8 @@ public class ServiceTileUnseen extends TileService { public void onStopListening() { Log.i(Helper.TAG, "Stop tile unseen"); - - DB db = DB.getInstance(this); - db.message().liveUnseenUnified().removeObservers(owner); + if (liveMessages != null) + liveMessages.removeObservers(owner); } public void onClick() {