From 4d2ea9c9c3ccefb6825a3ee3e8c7559704b2455b Mon Sep 17 00:00:00 2001 From: Distopico Vegan Date: Tue, 20 Nov 2018 01:49:44 -0500 Subject: [PATCH] style: indentation format --- .../org/dystopia/email/FragmentMessages.java | 1490 +++++++------- .../dystopia/email/ServiceSynchronize.java | 1772 +++++++---------- 2 files changed, 1421 insertions(+), 1841 deletions(-) diff --git a/app/src/main/java/org/dystopia/email/FragmentMessages.java b/app/src/main/java/org/dystopia/email/FragmentMessages.java index b8996f94..e2a92e42 100644 --- a/app/src/main/java/org/dystopia/email/FragmentMessages.java +++ b/app/src/main/java/org/dystopia/email/FragmentMessages.java @@ -1,24 +1,22 @@ package org.dystopia.email; /* - This file is part of FairEmail. - - FairEmail is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - FairEmail is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with FairEmail. If not, see . - - Copyright 2018, Marcel Bokhorst (M66B) - Copyright 2018, Distopico (dystopia project) and contributors -*/ + * This file is part of FairEmail. + * + * FairEmail is free software: you can redistribute it and/or modify it under the terms of the GNU + * General Public License as published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * FairEmail is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with FairEmail. If not, + * see . + * + * Copyright 2018, Marcel Bokhorst (M66B) Copyright 2018, Distopico (dystopia project) + * and contributors + */ import android.content.Context; import android.content.Intent; @@ -149,9 +147,7 @@ public class FragmentMessages extends FragmentEx { @Override @Nullable - public View onCreateView( - @NonNull LayoutInflater inflater, - @Nullable ViewGroup container, + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { view = (ViewGroup) inflater.inflate(R.layout.fragment_messages, container, false); @@ -177,439 +173,381 @@ public class FragmentMessages extends FragmentEx { // Wire controls - ibHintSwipe.setOnClickListener( - new View.OnClickListener() { - @Override - public void onClick(View v) { - prefs.edit().putBoolean("message_swipe", true).apply(); - grpHintSwipe.setVisibility(View.GONE); - } - }); + ibHintSwipe.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + prefs.edit().putBoolean("message_swipe", true).apply(); + grpHintSwipe.setVisibility(View.GONE); + } + }); - ibHintSelect.setOnClickListener( - new View.OnClickListener() { - @Override - public void onClick(View v) { - prefs.edit().putBoolean("message_select", true).apply(); - grpHintSelect.setVisibility(View.GONE); - } - }); + ibHintSelect.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + prefs.edit().putBoolean("message_select", true).apply(); + grpHintSelect.setVisibility(View.GONE); + } + }); - ibHintSupport.setOnClickListener( - new View.OnClickListener() { - @Override - public void onClick(View v) { - prefs.edit().putBoolean("app_support", true).apply(); - grpHintSupport.setVisibility(View.GONE); - } - }); + ibHintSupport.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + prefs.edit().putBoolean("app_support", true).apply(); + grpHintSupport.setVisibility(View.GONE); + } + }); rvMessage.setHasFixedSize(false); LinearLayoutManager llm = new LinearLayoutManager(getContext()); rvMessage.setLayoutManager(llm); - adapter = - new AdapterMessage( - getContext(), - getViewLifecycleOwner(), - getFragmentManager(), - viewType, - folder, - new AdapterMessage.IProperties() { - @Override - public void setExpanded(long id, boolean expand) { - if (expand) { - expanded.add(id); - handleExpand(id); - } else { - expanded.remove(id); - } - } + adapter = new AdapterMessage(getContext(), getViewLifecycleOwner(), getFragmentManager(), + viewType, folder, new AdapterMessage.IProperties() { + @Override + public void setExpanded(long id, boolean expand) { + if (expand) { + expanded.add(id); + handleExpand(id); + } else { + expanded.remove(id); + } + } - @Override - public void setDetails(long id, boolean show) { - if (show) { - details.add(id); - } else { - details.remove(id); - } - } + @Override + public void setDetails(long id, boolean show) { + if (show) { + details.add(id); + } else { + details.remove(id); + } + } - @Override - public void setHeaders(long id, boolean show) { - if (show) { - headers.add(id); - } else { - headers.remove(id); - } - } + @Override + public void setHeaders(long id, boolean show) { + if (show) { + headers.add(id); + } else { + headers.remove(id); + } + } - @Override - public void setImages(long id, boolean show) { - if (show) { - images.add(id); - } else { - images.remove(id); - } - } + @Override + public void setImages(long id, boolean show) { + if (show) { + images.add(id); + } else { + images.remove(id); + } + } - @Override - public boolean isExpanded(long id) { - return expanded.contains(id); - } + @Override + public boolean isExpanded(long id) { + return expanded.contains(id); + } - @Override - public boolean showDetails(long id) { - return details.contains(id); - } + @Override + public boolean showDetails(long id) { + return details.contains(id); + } - @Override - public boolean showHeaders(long id) { - return headers.contains(id); - } + @Override + public boolean showHeaders(long id) { + return headers.contains(id); + } - @Override - public boolean showImages(long id) { - return images.contains(id); - } - }); + @Override + public boolean showImages(long id) { + return images.contains(id); + } + }); rvMessage.setAdapter(adapter); if (viewType == AdapterMessage.ViewType.FOLDER) { - selectionTracker = - new SelectionTracker.Builder<>( - "messages-selection", - rvMessage, - new ItemKeyProviderMessage(rvMessage), - new ItemDetailsLookupMessage(rvMessage), - StorageStrategy.createLongStorage()) - .withSelectionPredicate(new SelectionPredicateMessage(rvMessage)) - .build(); + selectionTracker = new SelectionTracker.Builder<>("messages-selection", rvMessage, + new ItemKeyProviderMessage(rvMessage), new ItemDetailsLookupMessage(rvMessage), + StorageStrategy.createLongStorage()) + .withSelectionPredicate(new SelectionPredicateMessage(rvMessage)).build(); adapter.setSelectionTracker(selectionTracker); - selectionTracker.addObserver( - new SelectionTracker.SelectionObserver() { - @Override - public void onSelectionChanged() { - if (selectionTracker.hasSelection()) { - fabMove.show(); - } else { - fabMove.hide(); - } - } - }); + selectionTracker.addObserver(new SelectionTracker.SelectionObserver() { + @Override + public void onSelectionChanged() { + if (selectionTracker.hasSelection()) { + fabMove.show(); + } else { + fabMove.hide(); + } + } + }); } - new ItemTouchHelper( - new ItemTouchHelper.Callback() { - @Override - public int getMovementFlags( - RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) { - if (!prefs.getBoolean("swipe", true)) { - return 0; - } + new ItemTouchHelper(new ItemTouchHelper.Callback() { + @Override + public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) { + if (!prefs.getBoolean("swipe", true)) { + return 0; + } - if (selectionTracker != null && selectionTracker.hasSelection()) { - return 0; - } + if (selectionTracker != null && selectionTracker.hasSelection()) { + return 0; + } - int pos = viewHolder.getAdapterPosition(); - if (pos == RecyclerView.NO_POSITION) { - return 0; - } + int pos = viewHolder.getAdapterPosition(); + if (pos == RecyclerView.NO_POSITION) { + return 0; + } - TupleMessageEx message = - ((AdapterMessage) rvMessage.getAdapter()).getCurrentList().get(pos); - if (message == null - || expanded.contains(message.id) - || EntityFolder.OUTBOX.equals(message.folderType)) { - return 0; - } + TupleMessageEx message = + ((AdapterMessage) rvMessage.getAdapter()).getCurrentList().get(pos); + if (message == null || expanded.contains(message.id) + || EntityFolder.OUTBOX.equals(message.folderType)) { + return 0; + } - int flags = 0; - if (archives.contains(message.account)) { - flags |= ItemTouchHelper.RIGHT; - } - if (trashes.contains(message.account)) { - flags |= ItemTouchHelper.LEFT; - } + int flags = 0; + if (archives.contains(message.account)) { + flags |= ItemTouchHelper.RIGHT; + } + if (trashes.contains(message.account)) { + flags |= ItemTouchHelper.LEFT; + } - return makeMovementFlags(0, flags); - } + return makeMovementFlags(0, flags); + } - @Override - public boolean onMove( - RecyclerView recyclerView, - RecyclerView.ViewHolder viewHolder, - RecyclerView.ViewHolder target) { - return false; - } + @Override + public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, + RecyclerView.ViewHolder target) { + return false; + } - @Override - public void onChildDraw( - Canvas canvas, - RecyclerView recyclerView, - RecyclerView.ViewHolder viewHolder, - float dX, - float dY, - int actionState, - boolean isCurrentlyActive) { - int pos = viewHolder.getAdapterPosition(); - if (pos == RecyclerView.NO_POSITION) { - return; - } + @Override + public void onChildDraw(Canvas canvas, RecyclerView recyclerView, + RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, + boolean isCurrentlyActive) { + int pos = viewHolder.getAdapterPosition(); + if (pos == RecyclerView.NO_POSITION) { + return; + } - TupleMessageEx message = - ((AdapterMessage) rvMessage.getAdapter()).getCurrentList().get(pos); - if (message == null) { - return; - } + TupleMessageEx message = + ((AdapterMessage) rvMessage.getAdapter()).getCurrentList().get(pos); + if (message == null) { + return; + } - boolean inbox = - (EntityFolder.ARCHIVE.equals(message.folderType) - || EntityFolder.TRASH.equals(message.folderType)); - - View itemView = viewHolder.itemView; - int margin = Math.round(12 * (getResources().getDisplayMetrics().density)); - - if (dX > margin) { - // Right swipe - Drawable d = - getResources() - .getDrawable( - inbox - ? R.drawable.baseline_move_to_inbox_24 - : R.drawable.baseline_archive_24, - getContext().getTheme()); - int padding = (itemView.getHeight() - d.getIntrinsicHeight()); - d.setBounds( - itemView.getLeft() + margin, - itemView.getTop() + padding / 2, - itemView.getLeft() + margin + d.getIntrinsicWidth(), - itemView.getTop() + padding / 2 + d.getIntrinsicHeight()); - d.draw(canvas); - } else if (dX < -margin) { - // Left swipe - Drawable d = - getResources() - .getDrawable( - inbox - ? R.drawable.baseline_move_to_inbox_24 - : R.drawable.baseline_delete_24, - getContext().getTheme()); - int padding = (itemView.getHeight() - d.getIntrinsicHeight()); - d.setBounds( - itemView.getLeft() + itemView.getWidth() - d.getIntrinsicWidth() - margin, - itemView.getTop() + padding / 2, - itemView.getLeft() + itemView.getWidth() - margin, - itemView.getTop() + padding / 2 + d.getIntrinsicHeight()); - d.draw(canvas); - } + boolean inbox = (EntityFolder.ARCHIVE.equals(message.folderType) + || EntityFolder.TRASH.equals(message.folderType)); + + View itemView = viewHolder.itemView; + int margin = Math.round(12 * (getResources().getDisplayMetrics().density)); + + if (dX > margin) { + // Right swipe + Drawable d = getResources().getDrawable( + inbox ? R.drawable.baseline_move_to_inbox_24 : R.drawable.baseline_archive_24, + getContext().getTheme()); + int padding = (itemView.getHeight() - d.getIntrinsicHeight()); + d.setBounds(itemView.getLeft() + margin, itemView.getTop() + padding / 2, + itemView.getLeft() + margin + d.getIntrinsicWidth(), + itemView.getTop() + padding / 2 + d.getIntrinsicHeight()); + d.draw(canvas); + } else if (dX < -margin) { + // Left swipe + Drawable d = getResources().getDrawable( + inbox ? R.drawable.baseline_move_to_inbox_24 : R.drawable.baseline_delete_24, + getContext().getTheme()); + int padding = (itemView.getHeight() - d.getIntrinsicHeight()); + d.setBounds(itemView.getLeft() + itemView.getWidth() - d.getIntrinsicWidth() - margin, + itemView.getTop() + padding / 2, itemView.getLeft() + itemView.getWidth() - margin, + itemView.getTop() + padding / 2 + d.getIntrinsicHeight()); + d.draw(canvas); + } - super.onChildDraw( - canvas, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive); - } + super.onChildDraw(canvas, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive); + } - @Override - public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) { - int pos = viewHolder.getAdapterPosition(); - if (pos == RecyclerView.NO_POSITION) { - return; - } + @Override + public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) { + int pos = viewHolder.getAdapterPosition(); + if (pos == RecyclerView.NO_POSITION) { + return; + } - TupleMessageEx message = - ((AdapterMessage) rvMessage.getAdapter()).getCurrentList().get(pos); - if (message == null) { - return; - } - Log.i(Helper.TAG, "Swiped dir=" + direction + " message=" + message.id); + TupleMessageEx message = + ((AdapterMessage) rvMessage.getAdapter()).getCurrentList().get(pos); + if (message == null) { + return; + } + Log.i(Helper.TAG, "Swiped dir=" + direction + " message=" + message.id); - Bundle args = new Bundle(); - args.putLong("id", message.id); - args.putBoolean("thread", viewType != AdapterMessage.ViewType.THREAD); - args.putInt("direction", direction); + Bundle args = new Bundle(); + args.putLong("id", message.id); + args.putBoolean("thread", viewType != AdapterMessage.ViewType.THREAD); + args.putInt("direction", direction); - new SimpleTask() { - @Override - protected MessageTarget onLoad(Context context, Bundle args) { - long id = args.getLong("id"); - boolean thread = args.getBoolean("thread"); - int direction = args.getInt("direction"); + new SimpleTask() { + @Override + protected MessageTarget onLoad(Context context, Bundle args) { + long id = args.getLong("id"); + boolean thread = args.getBoolean("thread"); + int direction = args.getInt("direction"); - MessageTarget result = new MessageTarget(); - EntityFolder target = null; + MessageTarget result = new MessageTarget(); + EntityFolder target = null; - // Get target folder and hide message - DB db = DB.getInstance(context); - try { - db.beginTransaction(); + // Get target folder and hide message + DB db = DB.getInstance(context); + try { + db.beginTransaction(); - EntityMessage message = db.message().getMessage(id); + EntityMessage message = db.message().getMessage(id); - EntityFolder folder = db.folder().getFolder(message.folder); - if (EntityFolder.ARCHIVE.equals(folder.type) - || EntityFolder.TRASH.equals(folder.type)) { - target = db.folder().getFolderByType(message.account, EntityFolder.INBOX); - } else { - if (direction == ItemTouchHelper.RIGHT) { - target = - db.folder().getFolderByType(message.account, EntityFolder.ARCHIVE); - } - if (direction == ItemTouchHelper.LEFT || target == null) { - target = db.folder().getFolderByType(message.account, EntityFolder.TRASH); - } - if (target == null) { - target = db.folder().getFolderByType(message.account, EntityFolder.INBOX); - } - } + EntityFolder folder = db.folder().getFolder(message.folder); + if (EntityFolder.ARCHIVE.equals(folder.type) + || EntityFolder.TRASH.equals(folder.type)) { + target = db.folder().getFolderByType(message.account, EntityFolder.INBOX); + } else { + if (direction == ItemTouchHelper.RIGHT) { + target = db.folder().getFolderByType(message.account, EntityFolder.ARCHIVE); + } + if (direction == ItemTouchHelper.LEFT || target == null) { + target = db.folder().getFolderByType(message.account, EntityFolder.TRASH); + } + if (target == null) { + target = db.folder().getFolderByType(message.account, EntityFolder.INBOX); + } + } - result.target = target.name; - result.display = (target.display == null ? target.name : target.display); + result.target = target.name; + result.display = (target.display == null ? target.name : target.display); - if (thread) { - List messages = - db.message().getMessageByThread(message.account, message.thread); - for (EntityMessage threaded : messages) { - if (!threaded.ui_hide && threaded.folder.equals(message.folder)) { - result.ids.add(threaded.id); - } - } - } else { - result.ids.add(message.id); - } + if (thread) { + List messages = + db.message().getMessageByThread(message.account, message.thread); + for (EntityMessage threaded : messages) { + if (!threaded.ui_hide && threaded.folder.equals(message.folder)) { + result.ids.add(threaded.id); + } + } + } else { + result.ids.add(message.id); + } - for (long mid : result.ids) { - Log.i(Helper.TAG, "Move hide id=" + mid + " target=" + result.target); - db.message().setMessageUiHide(mid, true); - } + for (long mid : result.ids) { + Log.i(Helper.TAG, "Move hide id=" + mid + " target=" + result.target); + db.message().setMessageUiHide(mid, true); + } - db.setTransactionSuccessful(); - } finally { - db.endTransaction(); - } + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + } - return result; - } + return result; + } - @Override - protected void onLoaded(final Bundle args, final MessageTarget result) { - // Show undo snackbar - final Snackbar snackbar = - Snackbar.make( - view, - getString( - R.string.title_moving, - Helper.localizeFolderName(getContext(), result.display)), - Snackbar.LENGTH_INDEFINITE); - snackbar.setAction( - R.string.title_undo, - new View.OnClickListener() { - @Override - public void onClick(View v) { - snackbar.dismiss(); - - Bundle args = new Bundle(); - args.putSerializable("result", result); - - // Show message again - new SimpleTask() { - @Override - protected Void onLoad(Context context, Bundle args) { - MessageTarget result = - (MessageTarget) args.getSerializable("result"); - for (long id : result.ids) { - Log.i(Helper.TAG, "Move undo id=" + id); - DB.getInstance(context).message().setMessageUiHide(id, false); - } - return null; - } - - @Override - protected void onException(Bundle args, Throwable ex) { - super.onException(args, ex); - } - }.load(FragmentMessages.this, args); - } - }); - snackbar.show(); - - // Wait - new Handler() - .postDelayed( - new Runnable() { - @Override - public void run() { - Log.i(Helper.TAG, "Move timeout"); - - // Remove snackbar - if (snackbar.isShown()) { - snackbar.dismiss(); - } + @Override + protected void onLoaded(final Bundle args, final MessageTarget result) { + // Show undo snackbar + final Snackbar snackbar = Snackbar.make(view, + getString(R.string.title_moving, + Helper.localizeFolderName(getContext(), result.display)), + Snackbar.LENGTH_INDEFINITE); + snackbar.setAction(R.string.title_undo, new View.OnClickListener() { + @Override + public void onClick(View v) { + snackbar.dismiss(); - final Bundle args = new Bundle(); - args.putSerializable("result", result); - - // Process move in a thread - // - the fragment could be gone - executor.submit( - new Runnable() { - @Override - public void run() { - try { - MessageTarget result = - (MessageTarget) args.getSerializable("result"); - - DB db = DB.getInstance(snackbar.getContext()); - try { - db.beginTransaction(); - - for (long id : result.ids) { - EntityMessage message = db.message().getMessage(id); - if (message != null && message.ui_hide) { - Log.i( - Helper.TAG, - "Move id=" + id + " target=" + result.target); - EntityFolder folder = - db.folder() - .getFolderByName( - message.account, result.target); - EntityOperation.queue( - db, message, EntityOperation.MOVE, folder.id); - } - } - - db.setTransactionSuccessful(); - } finally { - db.endTransaction(); - } - - EntityOperation.process(snackbar.getContext()); - - } catch (Throwable ex) { - Log.e( - Helper.TAG, ex + "\n" + Log.getStackTraceString(ex)); - } - } - }); - } - }, - UNDO_TIMEOUT); + Bundle args = new Bundle(); + args.putSerializable("result", result); + + // Show message again + new SimpleTask() { + @Override + protected Void onLoad(Context context, Bundle args) { + MessageTarget result = (MessageTarget) args.getSerializable("result"); + for (long id : result.ids) { + Log.i(Helper.TAG, "Move undo id=" + id); + DB.getInstance(context).message().setMessageUiHide(id, false); + } + return null; } @Override protected void onException(Bundle args, Throwable ex) { - Helper.unexpectedError(getContext(), ex); + super.onException(args, ex); } }.load(FragmentMessages.this, args); } + }); + snackbar.show(); + + // Wait + new Handler().postDelayed(new Runnable() { + @Override + public void run() { + Log.i(Helper.TAG, "Move timeout"); + + // Remove snackbar + if (snackbar.isShown()) { + snackbar.dismiss(); + } + + final Bundle args = new Bundle(); + args.putSerializable("result", result); + + // Process move in a thread + // - the fragment could be gone + executor.submit(new Runnable() { + @Override + public void run() { + try { + MessageTarget result = (MessageTarget) args.getSerializable("result"); + + DB db = DB.getInstance(snackbar.getContext()); + try { + db.beginTransaction(); + + for (long id : result.ids) { + EntityMessage message = db.message().getMessage(id); + if (message != null && message.ui_hide) { + Log.i(Helper.TAG, "Move id=" + id + " target=" + result.target); + EntityFolder folder = + db.folder().getFolderByName(message.account, result.target); + EntityOperation.queue(db, message, EntityOperation.MOVE, folder.id); + } + } + + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + } - class MessageTarget implements Serializable { - List ids = new ArrayList<>(); - String target; - String display; + EntityOperation.process(snackbar.getContext()); + + } catch (Throwable ex) { + Log.e(Helper.TAG, ex + "\n" + Log.getStackTraceString(ex)); + } + } + }); } - }) - .attachToRecyclerView(rvMessage); + }, UNDO_TIMEOUT); + } + + @Override + protected void onException(Bundle args, Throwable ex) { + Helper.unexpectedError(getContext(), ex); + } + }.load(FragmentMessages.this, args); + } + + class MessageTarget implements Serializable { + List ids = new ArrayList<>(); + String target; + String display; + } + }).attachToRecyclerView(rvMessage); bottom_navigation.setOnNavigationItemSelectedListener( new BottomNavigationView.OnNavigationItemSelectedListener() { @@ -619,170 +557,154 @@ public class FragmentMessages extends FragmentEx { ViewModelMessages.Target target = (menuItem.getItemId() == R.id.action_prev ? pn[0] : pn[1]); LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(getContext()); - lbm.sendBroadcast( - new Intent(ActivityView.ACTION_VIEW_THREAD) - .putExtra("account", target.account) - .putExtra("thread", target.thread)); + lbm.sendBroadcast(new Intent(ActivityView.ACTION_VIEW_THREAD) + .putExtra("account", target.account).putExtra("thread", target.thread)); return true; } }); - fab.setOnClickListener( - new View.OnClickListener() { - @Override - public void onClick(View view) { - startActivity( - new Intent(getContext(), ActivityCompose.class) - .putExtra("action", "new") - .putExtra("account", (Long) fab.getTag())); - } - }); + fab.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + startActivity(new Intent(getContext(), ActivityCompose.class).putExtra("action", "new") + .putExtra("account", (Long) fab.getTag())); + } + }); + + fabMove.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Bundle args = new Bundle(); + args.putLong("folder", folder); - fabMove.setOnClickListener( - new View.OnClickListener() { + new SimpleTask>() { @Override - public void onClick(View v) { - Bundle args = new Bundle(); - args.putLong("folder", folder); + protected List onLoad(Context context, Bundle args) { + long folder = args.getLong("folder"); + DB db = DB.getInstance(context); + + EntityFolder source = db.folder().getFolder(folder); + List folders = db.folder().getFolders(source.account); + List targets = new ArrayList<>(); + for (EntityFolder f : folders) { + if (!f.id.equals(folder) && !EntityFolder.DRAFTS.equals(f.type)) { + targets.add(f); + } + } + + final Collator collator = Collator.getInstance(Locale.getDefault()); + collator.setStrength(Collator.SECONDARY); // Case insensitive, process accents + // etc - new SimpleTask>() { + Collections.sort(targets, new Comparator() { @Override - protected List onLoad(Context context, Bundle args) { - long folder = args.getLong("folder"); - DB db = DB.getInstance(context); - - EntityFolder source = db.folder().getFolder(folder); - List folders = db.folder().getFolders(source.account); - List targets = new ArrayList<>(); - for (EntityFolder f : folders) { - if (!f.id.equals(folder) && !EntityFolder.DRAFTS.equals(f.type)) { - targets.add(f); - } + public int compare(EntityFolder f1, EntityFolder f2) { + int s = Integer.compare(EntityFolder.FOLDER_SORT_ORDER.indexOf(f1.type), + EntityFolder.FOLDER_SORT_ORDER.indexOf(f2.type)); + if (s != 0) { + return s; } + return collator.compare(f1.name == null ? "" : f1.name, + f2.name == null ? "" : f2.name); + } + }); - final Collator collator = Collator.getInstance(Locale.getDefault()); - collator.setStrength(Collator.SECONDARY); // Case insensitive, process accents - // etc - - Collections.sort( - targets, - new Comparator() { - @Override - public int compare(EntityFolder f1, EntityFolder f2) { - int s = - Integer.compare( - EntityFolder.FOLDER_SORT_ORDER.indexOf(f1.type), - EntityFolder.FOLDER_SORT_ORDER.indexOf(f2.type)); - if (s != 0) { - return s; - } - return collator.compare( - f1.name == null ? "" : f1.name, f2.name == null ? "" : f2.name); - } - }); + return targets; + } - return targets; - } + @Override + protected void onLoaded(final Bundle args, List folders) { + PopupMenu popupMenu = new PopupMenu(getContext(), popupAnchor); + + int order = 0; + for (EntityFolder folder : folders) { + String name = + (folder.display == null ? Helper.localizeFolderName(getContext(), folder.name) + : folder.display); + popupMenu.getMenu().add(Menu.NONE, folder.id.intValue(), order++, name); + } + popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() { @Override - protected void onLoaded(final Bundle args, List folders) { - PopupMenu popupMenu = new PopupMenu(getContext(), popupAnchor); - - int order = 0; - for (EntityFolder folder : folders) { - String name = - (folder.display == null - ? Helper.localizeFolderName(getContext(), folder.name) - : folder.display); - popupMenu.getMenu().add(Menu.NONE, folder.id.intValue(), order++, name); + public boolean onMenuItemClick(final MenuItem target) { + MutableSelection selection = new MutableSelection<>(); + selectionTracker.copySelection(selection); + + long[] ids = new long[selection.size()]; + int i = 0; + for (Long id : selection) { + ids[i++] = id; } - popupMenu.setOnMenuItemClickListener( - new PopupMenu.OnMenuItemClickListener() { - @Override - public boolean onMenuItemClick(final MenuItem target) { - MutableSelection selection = new MutableSelection<>(); - selectionTracker.copySelection(selection); - - long[] ids = new long[selection.size()]; - int i = 0; - for (Long id : selection) { - ids[i++] = id; - } + selectionTracker.clearSelection(); - selectionTracker.clearSelection(); - - args.putLongArray("ids", ids); - args.putLong("target", target.getItemId()); - - new SimpleTask() { - @Override - protected Void onLoad(Context context, Bundle args) { - long[] ids = args.getLongArray("ids"); - long target = args.getLong("target"); - - DB db = DB.getInstance(context); - try { - db.beginTransaction(); - - for (long id : ids) { - EntityMessage message = db.message().getMessage(id); - List messages = - db.message() - .getMessageByThread(message.account, message.thread); - for (EntityMessage threaded : messages) { - if (threaded.folder.equals(message.folder)) { - db.message().setMessageUiHide(threaded.id, true); - EntityOperation.queue( - db, threaded, EntityOperation.MOVE, target); - } - } - } + args.putLongArray("ids", ids); + args.putLong("target", target.getItemId()); - db.setTransactionSuccessful(); - } finally { - db.endTransaction(); - } + new SimpleTask() { + @Override + protected Void onLoad(Context context, Bundle args) { + long[] ids = args.getLongArray("ids"); + long target = args.getLong("target"); - EntityOperation.process(context); + DB db = DB.getInstance(context); + try { + db.beginTransaction(); - return null; + for (long id : ids) { + EntityMessage message = db.message().getMessage(id); + List messages = + db.message().getMessageByThread(message.account, message.thread); + for (EntityMessage threaded : messages) { + if (threaded.folder.equals(message.folder)) { + db.message().setMessageUiHide(threaded.id, true); + EntityOperation.queue(db, threaded, EntityOperation.MOVE, target); } + } + } - @Override - protected void onException(Bundle args, Throwable ex) { - Helper.unexpectedError(getContext(), ex); - } - }.load(FragmentMessages.this, args); + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + } - return true; - } - }); + EntityOperation.process(context); - popupMenu.show(); - } + return null; + } - @Override - protected void onException(Bundle args, Throwable ex) { - Helper.unexpectedError(getContext(), ex); - } - }.load(FragmentMessages.this, args); - } - }); + @Override + protected void onException(Bundle args, Throwable ex) { + Helper.unexpectedError(getContext(), ex); + } + }.load(FragmentMessages.this, args); - ((ActivityBase) getActivity()) - .addBackPressedListener( - new ActivityBase.IBackPressedListener() { - @Override - public boolean onBackPressed() { - if (selectionTracker != null && selectionTracker.hasSelection()) { - selectionTracker.clearSelection(); - return true; - } - return false; + return true; } }); + popupMenu.show(); + } + + @Override + protected void onException(Bundle args, Throwable ex) { + Helper.unexpectedError(getContext(), ex); + } + }.load(FragmentMessages.this, args); + } + }); + + ((ActivityBase) getActivity()).addBackPressedListener(new ActivityBase.IBackPressedListener() { + @Override + public boolean onBackPressed() { + if (selectionTracker != null && selectionTracker.hasSelection()) { + selectionTracker.clearSelection(); + return true; + } + return false; + } + }); + // Initialize tvNoEmail.setVisibility(View.GONE); bottom_navigation.setVisibility(View.GONE); @@ -840,71 +762,61 @@ public class FragmentMessages extends FragmentEx { final DB db = DB.getInstance(getContext()); // Primary account - db.account() - .livePrimaryAccount() - .observe( - getViewLifecycleOwner(), - new Observer() { - @Override - public void onChanged(EntityAccount account) { - primary = (account == null ? -1 : account.id); - connected = (account != null && "connected".equals(account.state)); - getActivity().invalidateOptionsMenu(); - } - }); + db.account().livePrimaryAccount().observe(getViewLifecycleOwner(), + new Observer() { + @Override + public void onChanged(EntityAccount account) { + primary = (account == null ? -1 : account.id); + connected = (account != null && "connected".equals(account.state)); + getActivity().invalidateOptionsMenu(); + } + }); // Folder switch (viewType) { case UNIFIED: - db.folder() - .liveUnified() - .observe( - getViewLifecycleOwner(), - new Observer>() { - @Override - public void onChanged(List folders) { - int unseen = 0; - if (folders != null) { - for (TupleFolderEx folder : folders) { - unseen += folder.unseen; - } - } - String name = getString(R.string.title_folder_unified); - if (unseen > 0) { - setSubtitle(getString(R.string.title_folder_unseen, name, unseen)); - } else { - setSubtitle(name); - } + db.folder().liveUnified().observe(getViewLifecycleOwner(), + new Observer>() { + @Override + public void onChanged(List folders) { + int unseen = 0; + if (folders != null) { + for (TupleFolderEx folder : folders) { + unseen += folder.unseen; } - }); + } + String name = getString(R.string.title_folder_unified); + if (unseen > 0) { + setSubtitle(getString(R.string.title_folder_unseen, name, unseen)); + } else { + setSubtitle(name); + } + } + }); break; case FOLDER: - db.folder() - .liveFolderEx(folder) - .observe( - getViewLifecycleOwner(), - new Observer() { - @Override - public void onChanged(@Nullable TupleFolderEx folder) { - if (folder == null) { - setSubtitle(null); - } else { - String name = - (folder.display == null - ? Helper.localizeFolderName(getContext(), folder.name) - : folder.display); - if (folder.unseen > 0) { - setSubtitle(getString(R.string.title_folder_unseen, name, folder.unseen)); - } else { - setSubtitle(name); - } - - outbox = EntityFolder.OUTBOX.equals(folder.type); - getActivity().invalidateOptionsMenu(); - } + db.folder().liveFolderEx(folder).observe(getViewLifecycleOwner(), + new Observer() { + @Override + public void onChanged(@Nullable TupleFolderEx folder) { + if (folder == null) { + setSubtitle(null); + } else { + String name = + (folder.display == null ? Helper.localizeFolderName(getContext(), folder.name) + : folder.display); + if (folder.unseen > 0) { + setSubtitle(getString(R.string.title_folder_unseen, name, folder.unseen)); + } else { + setSubtitle(name); } - }); + + outbox = EntityFolder.OUTBOX.equals(folder.type); + getActivity().invalidateOptionsMenu(); + } + } + }); break; case THREAD: @@ -917,31 +829,28 @@ public class FragmentMessages extends FragmentEx { } // Folders and messages - db.folder() - .liveSystemFolders(account) - .observe( - getViewLifecycleOwner(), - new Observer>() { - @Override - public void onChanged(List folders) { - if (folders == null) { - folders = new ArrayList<>(); - } - - archives.clear(); - trashes.clear(); + db.folder().liveSystemFolders(account).observe(getViewLifecycleOwner(), + new Observer>() { + @Override + public void onChanged(List folders) { + if (folders == null) { + folders = new ArrayList<>(); + } - for (EntityFolder folder : folders) { - if (EntityFolder.ARCHIVE.equals(folder.type)) { - archives.add(folder.account); - } else if (EntityFolder.TRASH.equals(folder.type)) { - trashes.add(folder.account); - } - } + archives.clear(); + trashes.clear(); - loadMessages(); + for (EntityFolder folder : folders) { + if (EntityFolder.ARCHIVE.equals(folder.type)) { + archives.add(folder.account); + } else if (EntityFolder.TRASH.equals(folder.type)) { + trashes.add(folder.account); } - }); + } + + loadMessages(); + } + }); if (selectionTracker != null && selectionTracker.hasSelection()) { fabMove.show(); @@ -998,42 +907,41 @@ public class FragmentMessages extends FragmentEx { final MenuItem menuSearch = menu.findItem(R.id.menu_search); final SearchView searchView = (SearchView) menuSearch.getActionView(); searchView.setQueryHint(getString(R.string.title_search_hint)); - searchView.setOnQueryTextListener( - new SearchView.OnQueryTextListener() { - @Override - public boolean onQueryTextSubmit(String query) { - menuSearch.collapseActionView(); - - Bundle args = new Bundle(); - args.putLong("folder", folder); - args.putString("search", query); - - new SimpleTask() { - @Override - protected Void onLoad(Context context, Bundle args) { - DB.getInstance(context).message().deleteFoundMessages(); - return null; - } + searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() { + @Override + public boolean onQueryTextSubmit(String query) { + menuSearch.collapseActionView(); - @Override - protected void onLoaded(Bundle args, Void data) { - FragmentMessages fragment = new FragmentMessages(); - fragment.setArguments(args); + Bundle args = new Bundle(); + args.putLong("folder", folder); + args.putString("search", query); - FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction(); - fragmentTransaction.replace(R.id.content_frame, fragment).addToBackStack("search"); - fragmentTransaction.commit(); - } - }.load(FragmentMessages.this, args); - - return true; + new SimpleTask() { + @Override + protected Void onLoad(Context context, Bundle args) { + DB.getInstance(context).message().deleteFoundMessages(); + return null; } @Override - public boolean onQueryTextChange(String newText) { - return false; + protected void onLoaded(Bundle args, Void data) { + FragmentMessages fragment = new FragmentMessages(); + fragment.setArguments(args); + + FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction(); + fragmentTransaction.replace(R.id.content_frame, fragment).addToBackStack("search"); + fragmentTransaction.commit(); } - }); + }.load(FragmentMessages.this, args); + + return true; + } + + @Override + public boolean onQueryTextChange(String newText) { + return false; + } + }); super.onCreateOptionsMenu(menu, inflater); } @@ -1073,8 +981,7 @@ public class FragmentMessages extends FragmentEx { case R.id.menu_sort_on_unread: case R.id.menu_sort_on_starred: - prefs - .edit() + prefs.edit() .putString("sort", item.getItemId() == R.id.menu_sort_on_unread ? "unread" : "starred") .apply(); item.setChecked(true); @@ -1170,83 +1077,21 @@ public class FragmentMessages extends FragmentEx { switch (viewType) { case UNIFIED: - messages = - new LivePagedListBuilder<>( - db.message().pagedUnifiedInbox(sort, debug), LOCAL_PAGE_SIZE) - .build(); + messages = new LivePagedListBuilder<>(db.message().pagedUnifiedInbox(sort, debug), + LOCAL_PAGE_SIZE).build(); break; case FOLDER: if (searchCallback == null) { - searchCallback = - new BoundaryCallbackMessages( - this, - model, - new BoundaryCallbackMessages.IBoundaryCallbackMessages() { - @Override - public void onLoading() { - pbWait.setVisibility(View.VISIBLE); - } - - @Override - public void onLoaded() { - pbWait.setVisibility(View.GONE); - } - - @Override - public void onError(Context context, Throwable ex) { - if (getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.RESUMED)) { - new DialogBuilderLifecycle(getContext(), getViewLifecycleOwner()) - .setMessage(Helper.formatThrowable(ex)) - .setPositiveButton(android.R.string.cancel, null) - .create() - .show(); - } - } - }); - } - - PagedList.Config config = - new PagedList.Config.Builder() - .setPageSize(LOCAL_PAGE_SIZE) - .setInitialLoadSizeHint(LOCAL_PAGE_SIZE) - .setPrefetchDistance(REMOTE_PAGE_SIZE) - .build(); - LivePagedListBuilder builder = - new LivePagedListBuilder<>( - db.message().pagedFolder(folder, folderType, sort, false, debug), config); - if (browse) { - builder.setBoundaryCallback(searchCallback); - } - messages = builder.build(); - - break; - case THREAD: - messages = - new LivePagedListBuilder<>( - db.message().pagedThread(account, folder, thread, sort, debug), - LOCAL_PAGE_SIZE) - .build(); - break; - } - } else { - if (searchCallback == null) { - searchCallback = - new BoundaryCallbackMessages( - this, - model, + searchCallback = new BoundaryCallbackMessages(this, model, new BoundaryCallbackMessages.IBoundaryCallbackMessages() { @Override public void onLoading() { - tvNoEmail.setVisibility(View.GONE); pbWait.setVisibility(View.VISIBLE); } @Override public void onLoaded() { pbWait.setVisibility(View.GONE); - if (messages.getValue() == null || messages.getValue().size() == 0) { - tvNoEmail.setVisibility(View.VISIBLE); - } } @Override @@ -1254,119 +1099,156 @@ public class FragmentMessages extends FragmentEx { if (getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.RESUMED)) { new DialogBuilderLifecycle(getContext(), getViewLifecycleOwner()) .setMessage(Helper.formatThrowable(ex)) - .setPositiveButton(android.R.string.cancel, null) - .create() - .show(); + .setPositiveButton(android.R.string.cancel, null).create().show(); } } }); - } - PagedList.Config config = - new PagedList.Config.Builder() - .setPageSize(LOCAL_PAGE_SIZE) - .setInitialLoadSizeHint(LOCAL_PAGE_SIZE) - .setPrefetchDistance(REMOTE_PAGE_SIZE) - .build(); - LivePagedListBuilder builder = - new LivePagedListBuilder<>( - db.message().pagedFolder(folder, folderType, "time", true, false), config); - builder.setBoundaryCallback(searchCallback); - messages = builder.build(); - } + } - messages.observe( - getViewLifecycleOwner(), - new Observer>() { - @Override - public void onChanged(@Nullable PagedList messages) { - if (messages == null - || (viewType == AdapterMessage.ViewType.THREAD && messages.size() == 0)) { - finish(); - return; - } + PagedList.Config config = new PagedList.Config.Builder().setPageSize(LOCAL_PAGE_SIZE) + .setInitialLoadSizeHint(LOCAL_PAGE_SIZE).setPrefetchDistance(REMOTE_PAGE_SIZE) + .build(); + LivePagedListBuilder builder = new LivePagedListBuilder<>( + db.message().pagedFolder(folder, folderType, sort, false, debug), config); + if (browse) { + builder.setBoundaryCallback(searchCallback); + } + messages = builder.build(); - if (viewType == AdapterMessage.ViewType.THREAD) { - if (autoExpand) { - autoExpand = false; + break; + case THREAD: + messages = new LivePagedListBuilder<>( + db.message().pagedThread(account, folder, thread, sort, debug), LOCAL_PAGE_SIZE) + .build(); + break; + } + } else { + if (searchCallback == null) { + searchCallback = new BoundaryCallbackMessages(this, model, + new BoundaryCallbackMessages.IBoundaryCallbackMessages() { + @Override + public void onLoading() { + tvNoEmail.setVisibility(View.GONE); + pbWait.setVisibility(View.VISIBLE); + } - int unseen = 0; - TupleMessageEx single = null; - TupleMessageEx see = null; - for (int i = 0; i < messages.size(); i++) { - TupleMessageEx message = messages.get(i); - if (!EntityFolder.ARCHIVE.equals(message.folderType) - && !EntityFolder.SENT.equals(message.folderType) - && !EntityFolder.OUTBOX.equals(message.folderType)) { - autoCount++; - single = message; - if (!message.ui_seen) { - unseen++; - see = message; - } - } + @Override + public void onLoaded() { + pbWait.setVisibility(View.GONE); + if (messages.getValue() == null || messages.getValue().size() == 0) { + tvNoEmail.setVisibility(View.VISIBLE); } + } - // Auto expand when: - // - single, non archived/sent message - // - one unread, non archived/sent message in conversation - // - sole message - - TupleMessageEx expand = null; - if (autoCount == 1) { - expand = single; - } else if (unseen == 1) { - expand = see; - } else if (messages.size() == 1) { - expand = messages.get(0); - } - if (expand != null) { - expanded.add(expand.id); - handleExpand(expand.id); - } - } else if (autoCount > 0) { - int count = 0; - for (int i = 0; i < messages.size(); i++) { - TupleMessageEx message = messages.get(i); - if (!EntityFolder.ARCHIVE.equals(message.folderType) - && !EntityFolder.SENT.equals(message.folderType) - && !EntityFolder.OUTBOX.equals(message.folderType)) { - count++; - } + @Override + public void onError(Context context, Throwable ex) { + if (getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.RESUMED)) { + new DialogBuilderLifecycle(getContext(), getViewLifecycleOwner()) + .setMessage(Helper.formatThrowable(ex)) + .setPositiveButton(android.R.string.cancel, null).create().show(); } + } + }); + } + PagedList.Config config = new PagedList.Config.Builder().setPageSize(LOCAL_PAGE_SIZE) + .setInitialLoadSizeHint(LOCAL_PAGE_SIZE).setPrefetchDistance(REMOTE_PAGE_SIZE).build(); + LivePagedListBuilder builder = new LivePagedListBuilder<>( + db.message().pagedFolder(folder, folderType, "time", true, false), config); + builder.setBoundaryCallback(searchCallback); + messages = builder.build(); + } - // Auto close when: - // - no more non archived/sent messages + messages.observe(getViewLifecycleOwner(), new Observer>() { + @Override + public void onChanged(@Nullable PagedList messages) { + if (messages == null + || (viewType == AdapterMessage.ViewType.THREAD && messages.size() == 0)) { + finish(); + return; + } - if (count == 0) { - finish(); + if (viewType == AdapterMessage.ViewType.THREAD) { + if (autoExpand) { + autoExpand = false; + + int unseen = 0; + TupleMessageEx single = null; + TupleMessageEx see = null; + for (int i = 0; i < messages.size(); i++) { + TupleMessageEx message = messages.get(i); + if (!EntityFolder.ARCHIVE.equals(message.folderType) + && !EntityFolder.SENT.equals(message.folderType) + && !EntityFolder.OUTBOX.equals(message.folderType)) { + autoCount++; + single = message; + if (!message.ui_seen) { + unseen++; + see = message; } } - } else { - ViewModelMessages model = - ViewModelProviders.of(getActivity()).get(ViewModelMessages.class); - model.setMessages(messages); } - Log.i(Helper.TAG, "Submit messages=" + messages.size()); - adapter.submitList(messages); - - boolean searching = (searchCallback != null && searchCallback.isSearching()); - - if (!searching) { - pbWait.setVisibility(View.GONE); + // Auto expand when: + // - single, non archived/sent message + // - one unread, non archived/sent message in conversation + // - sole message + + TupleMessageEx expand = null; + if (autoCount == 1) { + expand = single; + } else if (unseen == 1) { + expand = see; + } else if (messages.size() == 1) { + expand = messages.get(0); } - grpReady.setVisibility(View.VISIBLE); - - if (messages.size() == 0) { - if (searchCallback == null) { - tvNoEmail.setVisibility(View.VISIBLE); + if (expand != null) { + expanded.add(expand.id); + handleExpand(expand.id); + } + } else if (autoCount > 0) { + int count = 0; + for (int i = 0; i < messages.size(); i++) { + TupleMessageEx message = messages.get(i); + if (!EntityFolder.ARCHIVE.equals(message.folderType) + && !EntityFolder.SENT.equals(message.folderType) + && !EntityFolder.OUTBOX.equals(message.folderType)) { + count++; } - rvMessage.setVisibility(View.GONE); - } else { - tvNoEmail.setVisibility(View.GONE); - rvMessage.setVisibility(View.VISIBLE); + } + + // Auto close when: + // - no more non archived/sent messages + + if (count == 0) { + finish(); } } + } else { + ViewModelMessages model = + ViewModelProviders.of(getActivity()).get(ViewModelMessages.class); + model.setMessages(messages); + } + + Log.i(Helper.TAG, "Submit messages=" + messages.size()); + adapter.submitList(messages); + + boolean searching = (searchCallback != null && searchCallback.isSearching()); + + if (!searching) { + pbWait.setVisibility(View.GONE); + } + grpReady.setVisibility(View.VISIBLE); + + if (messages.size() == 0) { + if (searchCallback == null) { + tvNoEmail.setVisibility(View.VISIBLE); + } + rvMessage.setVisibility(View.GONE); + } else { + tvNoEmail.setVisibility(View.GONE); + rvMessage.setVisibility(View.VISIBLE); + } + } }); } diff --git a/app/src/main/java/org/dystopia/email/ServiceSynchronize.java b/app/src/main/java/org/dystopia/email/ServiceSynchronize.java index 36eca16e..65d17f02 100644 --- a/app/src/main/java/org/dystopia/email/ServiceSynchronize.java +++ b/app/src/main/java/org/dystopia/email/ServiceSynchronize.java @@ -1,27 +1,24 @@ package org.dystopia.email; /* - This file is part of FairEmail. - - FairEmail is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - FairEmail is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with FairEmail. If not, see . - - Copyright 2018, Marcel Bokhorst (M66B) - Copyright 2018, Distopico (dystopia project) and contributors -*/ + * This file is part of FairEmail. + * + * FairEmail is free software: you can redistribute it and/or modify it under the terms of the GNU + * General Public License as published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * FairEmail is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with FairEmail. If not, + * see . + * + * Copyright 2018, Marcel Bokhorst (M66B) Copyright 2018, Distopico (dystopia project) + * and contributors + */ import static android.os.Process.THREAD_PRIORITY_BACKGROUND; - import android.Manifest; import android.app.AlarmManager; import android.app.Notification; @@ -160,111 +157,101 @@ public class ServiceSynchronize extends LifecycleService { DB db = DB.getInstance(this); - db.account() - .liveStats() - .observe( - this, - new Observer() { - @Override - public void onChanged(@Nullable TupleAccountStats stats) { - NotificationManager nm = getSystemService(NotificationManager.class); - nm.notify(NOTIFICATION_SYNCHRONIZE, getNotificationService(stats).build()); - } - }); + db.account().liveStats().observe(this, new Observer() { + @Override + public void onChanged(@Nullable TupleAccountStats stats) { + NotificationManager nm = getSystemService(NotificationManager.class); + nm.notify(NOTIFICATION_SYNCHRONIZE, getNotificationService(stats).build()); + } + }); - db.message() - .liveUnseenUnified() - .observe( - this, - new Observer>() { - private LongSparseArray> notifying = new LongSparseArray<>(); - private LongSparseArray accounts = new LongSparseArray<>(); + db.message().liveUnseenUnified().observe(this, new Observer>() { + private LongSparseArray> notifying = new LongSparseArray<>(); + private LongSparseArray accounts = new LongSparseArray<>(); - @Override - public void onChanged(List messages) { - NotificationManager nm = getSystemService(NotificationManager.class); - LongSparseArray> messagesByAccount = - new LongSparseArray<>(); - LongSparseArray> removed = notifying.clone(); + @Override + public void onChanged(List messages) { + NotificationManager nm = getSystemService(NotificationManager.class); + LongSparseArray> messagesByAccount = new LongSparseArray<>(); + LongSparseArray> removed = notifying.clone(); - // Update unseen for all account - setWidgetUnseen(messages); + // Update unseen for all account + setWidgetUnseen(messages); - if (messages.size() == 0) { - nm.cancelAll(); - return; - } + if (messages.size() == 0) { + nm.cancelAll(); + return; + } - // Organize messages per account - for (TupleNotification message : messages) { + // Organize messages per account + for (TupleNotification message : messages) { - Long accountKey = message.account; - List msgList = new ArrayList<>(); + Long accountKey = message.account; + List msgList = new ArrayList<>(); - if (messagesByAccount.indexOfKey(accountKey) != -1) { - msgList = messagesByAccount.get(accountKey); - } + if (messagesByAccount.indexOfKey(accountKey) != -1) { + msgList = messagesByAccount.get(accountKey); + } - if (accounts.indexOfKey(accountKey) == -1) { - accounts.put( - accountKey, - new Pair(message.accountName, message.accountColor)); - } + if (accounts.indexOfKey(accountKey) == -1) { + accounts.put(accountKey, + new Pair(message.accountName, message.accountColor)); + } - msgList.add(message); - messagesByAccount.put(accountKey, msgList); - } + msgList.add(message); + messagesByAccount.put(accountKey, msgList); + } - // Set and group notification per account - for (int i = 0; i < messagesByAccount.size(); i++) { - Long accountId = messagesByAccount.keyAt(i); - List messagesAccount = messagesByAccount.get(accountId); - List notifications = - getNotificationUnseen(messagesAccount, accounts.get(accountId)); - List all = new ArrayList<>(); - List added = new ArrayList<>(); - List toRemove = new ArrayList<>(); - String tag = "unseen-" + accountId; - - if (notifying.indexOfKey(accountId) != -1) { - toRemove = notifying.get(accountId); - } + // Set and group notification per account + for (int i = 0; i < messagesByAccount.size(); i++) { + Long accountId = messagesByAccount.keyAt(i); + List messagesAccount = messagesByAccount.get(accountId); + List notifications = + getNotificationUnseen(messagesAccount, accounts.get(accountId)); + List all = new ArrayList<>(); + List added = new ArrayList<>(); + List toRemove = new ArrayList<>(); + String tag = "unseen-" + accountId; + + if (notifying.indexOfKey(accountId) != -1) { + toRemove = notifying.get(accountId); + } - for (Notification notification : notifications) { - Integer id = (int) notification.extras.getLong("id", 0); + for (Notification notification : notifications) { + Integer id = (int) notification.extras.getLong("id", 0); - all.add(id); - if (toRemove.contains(id)) { - toRemove.remove(id); - } else if (id > 0) { - added.add(id); - } - } + all.add(id); + if (toRemove.contains(id)) { + toRemove.remove(id); + } else if (id > 0) { + added.add(id); + } + } - for (Notification notification : notifications) { - Integer id = (int) notification.extras.getLong("id", 0); + for (Notification notification : notifications) { + Integer id = (int) notification.extras.getLong("id", 0); - if ((id == 0 && added.size() > 0) || added.contains(id)) { - nm.notify(tag, id, notification); - } - } + if ((id == 0 && added.size() > 0) || added.contains(id)) { + nm.notify(tag, id, notification); + } + } - removed.put(accountId, toRemove); - notifying.put(accountId, all); - } + removed.put(accountId, toRemove); + notifying.put(accountId, all); + } - // Cancel old per account - for (int i = 0; i < removed.size(); i++) { - Long accountId = removed.keyAt(i); - List notifyRemove = removed.get(accountId); - String tag = "unseen-" + accountId; + // Cancel old per account + for (int i = 0; i < removed.size(); i++) { + Long accountId = removed.keyAt(i); + List notifyRemove = removed.get(accountId); + String tag = "unseen-" + accountId; - for (Integer id : notifyRemove) { - nm.cancel(tag, id); - } - } - } - }); + for (Integer id : notifyRemove) { + nm.cancel(tag, id); + } + } + } + }); } @Override @@ -310,8 +297,7 @@ public class ServiceSynchronize extends LifecycleService { return null; } }.load(this, new Bundle()); - } else if (action.startsWith("seen:") - || action.startsWith("trash:") + } else if (action.startsWith("seen:") || action.startsWith("trash:") || action.startsWith("ignored:")) { Bundle args = new Bundle(); args.putLong("id", Long.parseLong(action.split(":")[1])); @@ -370,41 +356,28 @@ public class ServiceSynchronize extends LifecycleService { // Build pending intent Intent intent = new Intent(this, ActivityView.class); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - PendingIntent pi = - PendingIntent.getActivity( - this, ActivityView.REQUEST_UNIFIED, intent, PendingIntent.FLAG_UPDATE_CURRENT); + PendingIntent pi = PendingIntent.getActivity(this, ActivityView.REQUEST_UNIFIED, intent, + PendingIntent.FLAG_UPDATE_CURRENT); // Build notification Notification.Builder builder = Helper.getNotificationBuilder(this, "service"); - builder - .setSmallIcon(R.drawable.baseline_compare_arrows_white_24) - .setContentTitle( - getResources() - .getQuantityString( - R.plurals.title_notification_synchronizing, stats.accounts, stats.accounts)) - .setContentIntent(pi) - .setAutoCancel(false) - .setShowWhen(false) - .setPriority(Notification.PRIORITY_MIN) - .setCategory(Notification.CATEGORY_STATUS) + builder.setSmallIcon(R.drawable.baseline_compare_arrows_white_24) + .setContentTitle(getResources().getQuantityString( + R.plurals.title_notification_synchronizing, stats.accounts, stats.accounts)) + .setContentIntent(pi).setAutoCancel(false).setShowWhen(false) + .setPriority(Notification.PRIORITY_MIN).setCategory(Notification.CATEGORY_STATUS) .setVisibility(Notification.VISIBILITY_SECRET); if (stats.operations > 0) { - builder.setStyle( - new Notification.BigTextStyle() - .setSummaryText( - getResources() - .getQuantityString( - R.plurals.title_notification_operations, - stats.operations, - stats.operations))); + builder + .setStyle(new Notification.BigTextStyle().setSummaryText(getResources().getQuantityString( + R.plurals.title_notification_operations, stats.operations, stats.operations))); } if (stats.unsent > 0) { - builder.setContentText( - getResources() - .getQuantityString(R.plurals.title_notification_unsent, stats.unsent, stats.unsent)); + builder.setContentText(getResources().getQuantityString(R.plurals.title_notification_unsent, + stats.unsent, stats.unsent)); } lastStats = stats; @@ -428,8 +401,7 @@ public class ServiceSynchronize extends LifecycleService { * @return Integer */ private Integer getNotificationColor(Integer accountColor) { - return accountColor != null - ? accountColor + return accountColor != null ? accountColor : ContextCompat.getColor(getBaseContext(), R.color.colorPrimary); } @@ -451,16 +423,15 @@ public class ServiceSynchronize extends LifecycleService { * @param size - number of new messages * @return Notification.Builder */ - private Notification.Builder getNotificationPublic( - String accountName, Integer accountColor, Integer size) { + private Notification.Builder getNotificationPublic(String accountName, Integer accountColor, + Integer size) { String channelId = "notification"; // Build pending intent Intent view = new Intent(this, ActivityView.class); view.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - PendingIntent piView = - PendingIntent.getActivity( - this, ActivityView.REQUEST_UNIFIED, view, PendingIntent.FLAG_UPDATE_CURRENT); + PendingIntent piView = PendingIntent.getActivity(this, ActivityView.REQUEST_UNIFIED, view, + PendingIntent.FLAG_UPDATE_CURRENT); Intent clear = new Intent(this, ServiceSynchronize.class); clear.setAction("clear"); @@ -472,18 +443,10 @@ public class ServiceSynchronize extends LifecycleService { // Public notification Notification.Builder pbuilder = Helper.getNotificationBuilder(this, channelId); - pbuilder - .setSmallIcon(R.drawable.ic_mail_icon) - .setContentTitle(summaryText) - .setContentText(accountName) - .setContentIntent(piView) - .setNumber(size) - .setShowWhen(true) - .setColor(accountColor) - .setDeleteIntent(piClear) - .setPriority(Notification.PRIORITY_DEFAULT) - .setCategory(Notification.CATEGORY_STATUS) - .setVisibility(Notification.VISIBILITY_PUBLIC); + pbuilder.setSmallIcon(R.drawable.ic_mail_icon).setContentTitle(summaryText) + .setContentText(accountName).setContentIntent(piView).setNumber(size).setShowWhen(true) + .setColor(accountColor).setDeleteIntent(piClear).setPriority(Notification.PRIORITY_DEFAULT) + .setCategory(Notification.CATEGORY_STATUS).setVisibility(Notification.VISIBILITY_PUBLIC); return pbuilder; } @@ -496,8 +459,8 @@ public class ServiceSynchronize extends LifecycleService { * @param messages - account messages * @return Notification.Builder */ - private Notification.Builder getNotificationGroup( - String accountName, Integer accountColor, List messages) { + private Notification.Builder getNotificationGroup(String accountName, Integer accountColor, + List messages) { // https://developer.android.com/training/notify-user/group SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); Integer groupColor = getNotificationColor(accountColor); @@ -508,9 +471,8 @@ public class ServiceSynchronize extends LifecycleService { // Build pending intent Intent view = new Intent(this, ActivityView.class); view.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - PendingIntent piView = - PendingIntent.getActivity( - this, ActivityView.REQUEST_UNIFIED, view, PendingIntent.FLAG_UPDATE_CURRENT); + PendingIntent piView = PendingIntent.getActivity(this, ActivityView.REQUEST_UNIFIED, view, + PendingIntent.FLAG_UPDATE_CURRENT); Intent clear = new Intent(this, ServiceSynchronize.class); clear.setAction("clear"); @@ -524,20 +486,11 @@ public class ServiceSynchronize extends LifecycleService { Notification.Builder pbuilder = getNotificationPublic(accountName, accountColor, size); Notification.Builder gbuilder = Helper.getNotificationBuilder(this, channelId); - gbuilder - .setSmallIcon(R.drawable.ic_mail_icon) - .setContentTitle(summaryText) - .setContentIntent(piView) - .setNumber(messages.size()) - .setShowWhen(true) - .setColor(groupColor) - .setDeleteIntent(piClear) - .setPriority(Notification.PRIORITY_DEFAULT) - .setCategory(Notification.CATEGORY_STATUS) - .setVisibility(Notification.VISIBILITY_PRIVATE) - .setGroup(groupKey) - .setGroupSummary(true) - .setPublicVersion(pbuilder.build()); + gbuilder.setSmallIcon(R.drawable.ic_mail_icon).setContentTitle(summaryText) + .setContentIntent(piView).setNumber(messages.size()).setShowWhen(true).setColor(groupColor) + .setDeleteIntent(piClear).setPriority(Notification.PRIORITY_DEFAULT) + .setCategory(Notification.CATEGORY_STATUS).setVisibility(Notification.VISIBILITY_PRIVATE) + .setGroup(groupKey).setGroupSummary(true).setPublicVersion(pbuilder.build()); if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { gbuilder.setSound(null); @@ -555,8 +508,7 @@ public class ServiceSynchronize extends LifecycleService { SimpleDateFormat.getDateTimeInstance(SimpleDateFormat.SHORT, SimpleDateFormat.SHORT); StringBuilder sb = new StringBuilder(); for (EntityMessage message : messages) { - sb.append("") - .append(MessageHelper.getFormattedAddresses(message.from, false)) + sb.append("").append(MessageHelper.getFormattedAddresses(message.from, false)) .append(""); if (!TextUtils.isEmpty(message.subject)) { @@ -567,10 +519,8 @@ public class ServiceSynchronize extends LifecycleService { sb.append("
"); } - Notification.BigTextStyle gstyle = - new Notification.BigTextStyle() - .bigText(Html.fromHtml(sb.toString())) - .setSummaryText(accountName); + Notification.BigTextStyle gstyle = new Notification.BigTextStyle() + .bigText(Html.fromHtml(sb.toString())).setSummaryText(accountName); gbuilder.setStyle(gstyle); @@ -612,9 +562,8 @@ public class ServiceSynchronize extends LifecycleService { thread.setAction("thread:" + message.thread); thread.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); thread.putExtra("account", message.account); - PendingIntent piContent = - PendingIntent.getActivity( - this, ActivityView.REQUEST_THREAD, thread, PendingIntent.FLAG_UPDATE_CURRENT); + PendingIntent piContent = PendingIntent.getActivity(this, ActivityView.REQUEST_THREAD, thread, + PendingIntent.FLAG_UPDATE_CURRENT); Intent ignored = new Intent(this, ServiceSynchronize.class); ignored.setAction("ignored:" + message.id); @@ -631,37 +580,24 @@ public class ServiceSynchronize extends LifecycleService { PendingIntent piTrash = PendingIntent.getService(this, PI_TRASH, trash, PendingIntent.FLAG_UPDATE_CURRENT); - Notification.Action.Builder actionSeen = - new Notification.Action.Builder( - Icon.createWithResource(this, R.drawable.baseline_visibility_24), - getString(R.string.title_seen), - piSeen); + Notification.Action.Builder actionSeen = new Notification.Action.Builder( + Icon.createWithResource(this, R.drawable.baseline_visibility_24), + getString(R.string.title_seen), piSeen); - Notification.Action.Builder actionTrash = - new Notification.Action.Builder( - Icon.createWithResource(this, R.drawable.baseline_delete_24), - getString(R.string.title_trash), - piTrash); + Notification.Action.Builder actionTrash = new Notification.Action.Builder( + Icon.createWithResource(this, R.drawable.baseline_delete_24), + getString(R.string.title_trash), piTrash); Notification.Builder mbuilder = Helper.getNotificationBuilder(this, channelId); Notification.InboxStyle mstyle = new Notification.InboxStyle(); - mbuilder - .addExtras(mArgs) - .setSmallIcon(R.drawable.ic_mail_icon) + mbuilder.addExtras(mArgs).setSmallIcon(R.drawable.ic_mail_icon) .setContentTitle(MessageHelper.getFormattedAddresses(message.from, true)) - .setContentIntent(piContent) - .setDeleteIntent(piDelete) - .setSound(uri) - .setColor(groupColor) + .setContentIntent(piContent).setDeleteIntent(piDelete).setSound(uri).setColor(groupColor) .setWhen(message.sent == null ? message.received : message.sent) - .setPriority(Notification.PRIORITY_DEFAULT) - .setCategory(Notification.CATEGORY_STATUS) - .setVisibility(Notification.VISIBILITY_PRIVATE) - .setGroup(groupKey) - .setGroupSummary(false) - .addAction(actionSeen.build()) - .addAction(actionTrash.build()); + .setPriority(Notification.PRIORITY_DEFAULT).setCategory(Notification.CATEGORY_STATUS) + .setVisibility(Notification.VISIBILITY_PRIVATE).setGroup(groupKey).setGroupSummary(false) + .addAction(actionSeen.build()).addAction(actionTrash.build()); if (!TextUtils.isEmpty(message.subject)) { mbuilder.setContentText(message.subject); @@ -672,8 +608,7 @@ public class ServiceSynchronize extends LifecycleService { mbuilder.setGroupAlertBehavior(Notification.GROUP_ALERT_CHILDREN); } - mstyle - .setBigContentTitle(MessageHelper.getFormattedAddresses(message.from, false)) + mstyle.setBigContentTitle(MessageHelper.getFormattedAddresses(message.from, false)) .setSummaryText(accountName); mbuilder.setStyle(mstyle); @@ -686,24 +621,17 @@ public class ServiceSynchronize extends LifecycleService { // Build pending intent Intent intent = new Intent(this, ActivityView.class); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - PendingIntent pi = - PendingIntent.getActivity( - this, ActivityView.REQUEST_ERROR, intent, PendingIntent.FLAG_UPDATE_CURRENT); + PendingIntent pi = PendingIntent.getActivity(this, ActivityView.REQUEST_ERROR, intent, + PendingIntent.FLAG_UPDATE_CURRENT); // Build notification Notification.Builder builder = Helper.getNotificationBuilder(this, "error"); - builder - .setSmallIcon(android.R.drawable.stat_notify_error) + builder.setSmallIcon(android.R.drawable.stat_notify_error) .setContentTitle(getString(R.string.title_notification_failed, action)) - .setContentText(Helper.formatThrowable(ex)) - .setContentIntent(pi) - .setAutoCancel(false) - .setOnlyAlertOnce(true) - .setShowWhen(true) - .setPriority(Notification.PRIORITY_MAX) - .setCategory(Notification.CATEGORY_ERROR) - .setVisibility(Notification.VISIBILITY_SECRET); + .setContentText(Helper.formatThrowable(ex)).setContentIntent(pi).setAutoCancel(false) + .setOnlyAlertOnce(true).setShowWhen(true).setPriority(Notification.PRIORITY_MAX) + .setCategory(Notification.CATEGORY_ERROR).setVisibility(Notification.VISIBILITY_SECRET); builder.setStyle(new Notification.BigTextStyle().bigText(ex.toString())); @@ -739,12 +667,9 @@ public class ServiceSynchronize extends LifecycleService { nm.notify(action, 1, getNotificationError(action, ex).build()); } - if (BuildConfig.DEBUG - && !(ex instanceof SendFailedException) - && !(ex instanceof MailConnectException) - && !(ex instanceof FolderClosedException) - && !(ex instanceof IllegalStateException) - && !(ex instanceof AuthenticationFailedException) + if (BuildConfig.DEBUG && !(ex instanceof SendFailedException) + && !(ex instanceof MailConnectException) && !(ex instanceof FolderClosedException) + && !(ex instanceof IllegalStateException) && !(ex instanceof AuthenticationFailedException) && // Also: Too many simultaneous connections !(ex instanceof StoreClosedException) && !(ex instanceof MessagingException && ex.getCause() instanceof UnknownHostException) @@ -761,10 +686,8 @@ public class ServiceSynchronize extends LifecycleService { private void monitorAccount(final EntityAccount account, final ServiceState state) throws NoSuchProviderException { final PowerManager pm = getSystemService(PowerManager.class); - final PowerManager.WakeLock wl0 = - pm.newWakeLock( - PowerManager.PARTIAL_WAKE_LOCK, - BuildConfig.APPLICATION_ID + ":account." + account.id + ".monitor"); + final PowerManager.WakeLock wl0 = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, + BuildConfig.APPLICATION_ID + ":account." + account.id + ".monitor"); try { wl0.acquire(); @@ -793,96 +716,89 @@ public class ServiceSynchronize extends LifecycleService { List idlers = new ArrayList<>(); try { // Listen for store events - istore.addStoreListener( - new StoreListener() { - PowerManager.WakeLock wl = - pm.newWakeLock( - PowerManager.PARTIAL_WAKE_LOCK, - BuildConfig.APPLICATION_ID + ":account." + account.id + ".store"); + istore.addStoreListener(new StoreListener() { + PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, + BuildConfig.APPLICATION_ID + ":account." + account.id + ".store"); - @Override - public void notification(StoreEvent e) { - try { - wl.acquire(); - Log.i(Helper.TAG, account.name + " event: " + e.getMessage()); - db.account().setAccountError(account.id, e.getMessage()); - state.thread.interrupt(); - yieldWakelock(); - } finally { - wl.release(); - } - } - }); + @Override + public void notification(StoreEvent e) { + try { + wl.acquire(); + Log.i(Helper.TAG, account.name + " event: " + e.getMessage()); + db.account().setAccountError(account.id, e.getMessage()); + state.thread.interrupt(); + yieldWakelock(); + } finally { + wl.release(); + } + } + }); // Listen for folder events - istore.addFolderListener( - new FolderAdapter() { - PowerManager.WakeLock wl = - pm.newWakeLock( - PowerManager.PARTIAL_WAKE_LOCK, - BuildConfig.APPLICATION_ID + ":account." + account.id + ".folder"); + istore.addFolderListener(new FolderAdapter() { + PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, + BuildConfig.APPLICATION_ID + ":account." + account.id + ".folder"); - @Override - public void folderCreated(FolderEvent e) { - try { - wl.acquire(); - Log.i(Helper.TAG, "Folder created=" + e.getFolder().getFullName()); - state.thread.interrupt(); - yieldWakelock(); - } finally { - wl.release(); - } - } + @Override + public void folderCreated(FolderEvent e) { + try { + wl.acquire(); + Log.i(Helper.TAG, "Folder created=" + e.getFolder().getFullName()); + state.thread.interrupt(); + yieldWakelock(); + } finally { + wl.release(); + } + } - @Override - public void folderRenamed(FolderEvent e) { - try { - wl.acquire(); - Log.i(Helper.TAG, "Folder renamed=" + e.getFolder()); + @Override + public void folderRenamed(FolderEvent e) { + try { + wl.acquire(); + Log.i(Helper.TAG, "Folder renamed=" + e.getFolder()); - String old = e.getFolder().getFullName(); - String name = e.getNewFolder().getFullName(); - int count = db.folder().renameFolder(account.id, old, name); - Log.i(Helper.TAG, "Renamed to " + name + " count=" + count); + String old = e.getFolder().getFullName(); + String name = e.getNewFolder().getFullName(); + int count = db.folder().renameFolder(account.id, old, name); + Log.i(Helper.TAG, "Renamed to " + name + " count=" + count); - state.thread.interrupt(); - yieldWakelock(); - } finally { - wl.release(); - } - } + state.thread.interrupt(); + yieldWakelock(); + } finally { + wl.release(); + } + } - @Override - public void folderDeleted(FolderEvent e) { - try { - wl.acquire(); - Log.i(Helper.TAG, "Folder deleted=" + e.getFolder().getFullName()); - state.thread.interrupt(); - yieldWakelock(); - } finally { - wl.release(); - } - } - }); + @Override + public void folderDeleted(FolderEvent e) { + try { + wl.acquire(); + Log.i(Helper.TAG, "Folder deleted=" + e.getFolder().getFullName()); + state.thread.interrupt(); + yieldWakelock(); + } finally { + wl.release(); + } + } + }); // Listen for connection events - istore.addConnectionListener( - new ConnectionAdapter() { - @Override - public void opened(ConnectionEvent e) { - Log.i(Helper.TAG, account.name + " opened"); - } + istore.addConnectionListener(new ConnectionAdapter() { + @Override + public void opened(ConnectionEvent e) { + Log.i(Helper.TAG, account.name + " opened"); + } - @Override - public void disconnected(ConnectionEvent e) { - Log.e(Helper.TAG, account.name + " disconnected event"); - } + @Override + public void disconnected(ConnectionEvent e) { + Log.e(Helper.TAG, account.name + " disconnected event"); + } - @Override - public void closed(ConnectionEvent e) { - Log.e(Helper.TAG, account.name + " closed event"); - } - }); + @Override + public void closed(ConnectionEvent e) { + Log.e(Helper.TAG, account.name + " closed event"); + } + }); // Initiate connection Log.i(Helper.TAG, account.name + " connect"); @@ -920,243 +836,159 @@ public class ServiceSynchronize extends LifecycleService { db.folder().setFolderError(folder.id, null); // Synchronize folder - Thread sync = - new Thread( - new Runnable() { - PowerManager.WakeLock wl = - pm.newWakeLock( - PowerManager.PARTIAL_WAKE_LOCK, - BuildConfig.APPLICATION_ID + ":account." + account.id + ".sync"); - - @Override - public void run() { + Thread sync = new Thread(new Runnable() { + PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, + BuildConfig.APPLICATION_ID + ":account." + account.id + ".sync"); + + @Override + public void run() { + try { + wl.acquire(); + + // Process pending operations + processOperations(folder, isession, istore, ifolder); + + // Listen for new and deleted messages + ifolder.addMessageCountListener(new MessageCountAdapter() { + @Override + public void messagesAdded(MessageCountEvent e) { + synchronized (lock) { + try { + wl.acquire(); + Log.i(Helper.TAG, folder.name + " messages added"); + + FetchProfile fp = new FetchProfile(); + fp.add(FetchProfile.Item.ENVELOPE); + fp.add(FetchProfile.Item.FLAGS); + fp.add(FetchProfile.Item.CONTENT_INFO); // body structure + fp.add(UIDFolder.FetchProfileItem.UID); + fp.add(IMAPFolder.FetchProfileItem.HEADERS); + fp.add(IMAPFolder.FetchProfileItem.MESSAGE); + fp.add(FetchProfile.Item.SIZE); + fp.add(IMAPFolder.FetchProfileItem.INTERNALDATE); + ifolder.fetch(e.getMessages(), fp); + + for (Message imessage : e.getMessages()) { + try { + long id; + try { + db.beginTransaction(); + id = synchronizeMessage(ServiceSynchronize.this, folder, ifolder, + (IMAPMessage) imessage, false); + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + } + downloadMessage(ServiceSynchronize.this, folder, ifolder, + (IMAPMessage) imessage, id); + } catch (MessageRemovedException ex) { + Log.w(Helper.TAG, + folder.name + " " + ex + "\n" + Log.getStackTraceString(ex)); + } catch (IOException ex) { + if (ex.getCause() instanceof MessageRemovedException) { + Log.w(Helper.TAG, + folder.name + " " + ex + "\n" + Log.getStackTraceString(ex)); + } else { + throw ex; + } + } + } + EntityOperation.process(ServiceSynchronize.this); // download small + // attachments + } catch (Throwable ex) { + Log.e(Helper.TAG, + folder.name + " " + ex + "\n" + Log.getStackTraceString(ex)); + reportError(account.name, folder.name, ex); + + db.folder().setFolderError(folder.id, Helper.formatThrowable(ex)); + + state.thread.interrupt(); + yieldWakelock(); + } finally { + wl.release(); + } + } + } + + @Override + public void messagesRemoved(MessageCountEvent e) { + synchronized (lock) { try { wl.acquire(); + Log.i(Helper.TAG, folder.name + " messages removed"); + for (Message imessage : e.getMessages()) { + try { + long uid = ifolder.getUID(imessage); - // Process pending operations - processOperations(folder, isession, istore, ifolder); - - // Listen for new and deleted messages - ifolder.addMessageCountListener( - new MessageCountAdapter() { - @Override - public void messagesAdded(MessageCountEvent e) { - synchronized (lock) { - try { - wl.acquire(); - Log.i(Helper.TAG, folder.name + " messages added"); - - FetchProfile fp = new FetchProfile(); - fp.add(FetchProfile.Item.ENVELOPE); - fp.add(FetchProfile.Item.FLAGS); - fp.add(FetchProfile.Item.CONTENT_INFO); // body structure - fp.add(UIDFolder.FetchProfileItem.UID); - fp.add(IMAPFolder.FetchProfileItem.HEADERS); - fp.add(IMAPFolder.FetchProfileItem.MESSAGE); - fp.add(FetchProfile.Item.SIZE); - fp.add(IMAPFolder.FetchProfileItem.INTERNALDATE); - ifolder.fetch(e.getMessages(), fp); - - for (Message imessage : e.getMessages()) { - try { - long id; - try { - db.beginTransaction(); - id = - synchronizeMessage( - ServiceSynchronize.this, - folder, - ifolder, - (IMAPMessage) imessage, - false); - db.setTransactionSuccessful(); - } finally { - db.endTransaction(); - } - downloadMessage( - ServiceSynchronize.this, - folder, - ifolder, - (IMAPMessage) imessage, - id); - } catch (MessageRemovedException ex) { - Log.w( - Helper.TAG, - folder.name - + " " - + ex - + "\n" - + Log.getStackTraceString(ex)); - } catch (IOException ex) { - if (ex.getCause() instanceof MessageRemovedException) { - Log.w( - Helper.TAG, - folder.name - + " " - + ex - + "\n" - + Log.getStackTraceString(ex)); - } else { - throw ex; - } - } - } - EntityOperation.process( - ServiceSynchronize.this); // download small attachments - } catch (Throwable ex) { - Log.e( - Helper.TAG, - folder.name - + " " - + ex - + "\n" - + Log.getStackTraceString(ex)); - reportError(account.name, folder.name, ex); - - db.folder() - .setFolderError(folder.id, Helper.formatThrowable(ex)); - - state.thread.interrupt(); - yieldWakelock(); - } finally { - wl.release(); - } - } - } - - @Override - public void messagesRemoved(MessageCountEvent e) { - synchronized (lock) { - try { - wl.acquire(); - Log.i(Helper.TAG, folder.name + " messages removed"); - for (Message imessage : e.getMessages()) { - try { - long uid = ifolder.getUID(imessage); - - DB db = DB.getInstance(ServiceSynchronize.this); - int count = db.message().deleteMessage(folder.id, uid); - - Log.i( - Helper.TAG, "Deleted uid=" + uid + " count=" + count); - } catch (MessageRemovedException ex) { - Log.w( - Helper.TAG, - folder.name - + " " - + ex - + "\n" - + Log.getStackTraceString(ex)); - } - } - } catch (Throwable ex) { - Log.e( - Helper.TAG, - folder.name - + " " - + ex - + "\n" - + Log.getStackTraceString(ex)); - reportError(account.name, folder.name, ex); - - db.folder() - .setFolderError(folder.id, Helper.formatThrowable(ex)); - - state.thread.interrupt(); - } finally { - wl.release(); - } - } - } - }); - - // Fetch e-mail - synchronizeMessages(account, folder, ifolder, state); - - // Flags (like "seen") at the remote could be - // changed while synchronizing - - // Listen for changed messages - ifolder.addMessageChangedListener( - new MessageChangedListener() { - @Override - public void messageChanged(MessageChangedEvent e) { - synchronized (lock) { - try { - wl.acquire(); - try { - Log.i(Helper.TAG, folder.name + " message changed"); - - FetchProfile fp = new FetchProfile(); - fp.add(UIDFolder.FetchProfileItem.UID); - fp.add(IMAPFolder.FetchProfileItem.FLAGS); - ifolder.fetch(new Message[] {e.getMessage()}, fp); - - long id; - try { - db.beginTransaction(); - id = - synchronizeMessage( - ServiceSynchronize.this, - folder, - ifolder, - (IMAPMessage) e.getMessage(), - false); - db.setTransactionSuccessful(); - } finally { - db.endTransaction(); - } - downloadMessage( - ServiceSynchronize.this, - folder, - ifolder, - (IMAPMessage) e.getMessage(), - id); - } catch (MessageRemovedException ex) { - Log.w( - Helper.TAG, - folder.name - + " " - + ex - + "\n" - + Log.getStackTraceString(ex)); - } catch (IOException ex) { - if (ex.getCause() instanceof MessageRemovedException) { - Log.w( - Helper.TAG, - folder.name - + " " - + ex - + "\n" - + Log.getStackTraceString(ex)); - } else { - throw ex; - } - } - } catch (Throwable ex) { - Log.e( - Helper.TAG, - folder.name - + " " - + ex - + "\n" - + Log.getStackTraceString(ex)); - reportError(account.name, folder.name, ex); - - db.folder() - .setFolderError(folder.id, Helper.formatThrowable(ex)); - - state.thread.interrupt(); - yieldWakelock(); - } finally { - wl.release(); - } - } - } - }); + DB db = DB.getInstance(ServiceSynchronize.this); + int count = db.message().deleteMessage(folder.id, uid); + + Log.i(Helper.TAG, "Deleted uid=" + uid + " count=" + count); + } catch (MessageRemovedException ex) { + Log.w(Helper.TAG, + folder.name + " " + ex + "\n" + Log.getStackTraceString(ex)); + } + } } catch (Throwable ex) { - Log.e( - Helper.TAG, + Log.e(Helper.TAG, + folder.name + " " + ex + "\n" + Log.getStackTraceString(ex)); + reportError(account.name, folder.name, ex); + + db.folder().setFolderError(folder.id, Helper.formatThrowable(ex)); + + state.thread.interrupt(); + } finally { + wl.release(); + } + } + } + }); + + // Fetch e-mail + synchronizeMessages(account, folder, ifolder, state); + + // Flags (like "seen") at the remote could be + // changed while synchronizing + + // Listen for changed messages + ifolder.addMessageChangedListener(new MessageChangedListener() { + @Override + public void messageChanged(MessageChangedEvent e) { + synchronized (lock) { + try { + wl.acquire(); + try { + Log.i(Helper.TAG, folder.name + " message changed"); + + FetchProfile fp = new FetchProfile(); + fp.add(UIDFolder.FetchProfileItem.UID); + fp.add(IMAPFolder.FetchProfileItem.FLAGS); + ifolder.fetch(new Message[] {e.getMessage()}, fp); + + long id; + try { + db.beginTransaction(); + id = synchronizeMessage(ServiceSynchronize.this, folder, ifolder, + (IMAPMessage) e.getMessage(), false); + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + } + downloadMessage(ServiceSynchronize.this, folder, ifolder, + (IMAPMessage) e.getMessage(), id); + } catch (MessageRemovedException ex) { + Log.w(Helper.TAG, + folder.name + " " + ex + "\n" + Log.getStackTraceString(ex)); + } catch (IOException ex) { + if (ex.getCause() instanceof MessageRemovedException) { + Log.w(Helper.TAG, + folder.name + " " + ex + "\n" + Log.getStackTraceString(ex)); + } else { + throw ex; + } + } + } catch (Throwable ex) { + Log.e(Helper.TAG, folder.name + " " + ex + "\n" + Log.getStackTraceString(ex)); reportError(account.name, folder.name, ex); @@ -1168,43 +1000,51 @@ public class ServiceSynchronize extends LifecycleService { wl.release(); } } - }, - "sync." + folder.id); + } + }); + } catch (Throwable ex) { + Log.e(Helper.TAG, folder.name + " " + ex + "\n" + Log.getStackTraceString(ex)); + reportError(account.name, folder.name, ex); + + db.folder().setFolderError(folder.id, Helper.formatThrowable(ex)); + + state.thread.interrupt(); + yieldWakelock(); + } finally { + wl.release(); + } + } + }, "sync." + folder.id); sync.start(); syncs.add(sync); // Idle folder if (capIdle) { - Thread idler = - new Thread( - new Runnable() { - @Override - public void run() { - try { - Log.i(Helper.TAG, folder.name + " start idle"); - while (state.running) { - Log.i(Helper.TAG, folder.name + " do idle"); - ifolder.idle(false); - // Log.i(Helper.TAG, folder.name + " - // done idle"); - } - } catch (FolderClosedException ignored) { - } catch (Throwable ex) { - Log.e( - Helper.TAG, - folder.name + " " + ex + "\n" + Log.getStackTraceString(ex)); - reportError(account.name, folder.name, ex); + Thread idler = new Thread(new Runnable() { + @Override + public void run() { + try { + Log.i(Helper.TAG, folder.name + " start idle"); + while (state.running) { + Log.i(Helper.TAG, folder.name + " do idle"); + ifolder.idle(false); + // Log.i(Helper.TAG, folder.name + " + // done idle"); + } + } catch (FolderClosedException ignored) { + } catch (Throwable ex) { + Log.e(Helper.TAG, folder.name + " " + ex + "\n" + Log.getStackTraceString(ex)); + reportError(account.name, folder.name, ex); - db.folder().setFolderError(folder.id, Helper.formatThrowable(ex)); + db.folder().setFolderError(folder.id, Helper.formatThrowable(ex)); - state.thread.interrupt(); - yieldWakelock(); - } finally { - Log.i(Helper.TAG, folder.name + " end idle"); - } - } - }, - "idler." + folder.id); + state.thread.interrupt(); + yieldWakelock(); + } finally { + Log.i(Helper.TAG, folder.name + " end idle"); + } + } + }, "idler." + folder.id); idler.start(); idlers.add(idler); } @@ -1214,104 +1054,93 @@ public class ServiceSynchronize extends LifecycleService { backoff = CONNECT_BACKOFF_START; // Process folder actions - BroadcastReceiver processFolder = - new BroadcastReceiver() { + BroadcastReceiver processFolder = new BroadcastReceiver() { + @Override + public void onReceive(Context context, final Intent intent) { + executor.submit(new Runnable() { + PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, + BuildConfig.APPLICATION_ID + ":account." + account.id + ".process"); + @Override - public void onReceive(Context context, final Intent intent) { - executor.submit( - new Runnable() { - PowerManager.WakeLock wl = - pm.newWakeLock( - PowerManager.PARTIAL_WAKE_LOCK, - BuildConfig.APPLICATION_ID + ":account." + account.id + ".process"); - - @Override - public void run() { - long fid = intent.getLongExtra("folder", -1); - try { - wl.acquire(); - Log.i(Helper.TAG, "Process folder=" + fid + " intent=" + intent); - - // Get folder - EntityFolder folder = null; - IMAPFolder ifolder = null; - for (EntityFolder f : folders.keySet()) { - if (f.id == fid) { - folder = f; - ifolder = folders.get(f); - break; - } - } + public void run() { + long fid = intent.getLongExtra("folder", -1); + try { + wl.acquire(); + Log.i(Helper.TAG, "Process folder=" + fid + " intent=" + intent); + + // Get folder + EntityFolder folder = null; + IMAPFolder ifolder = null; + for (EntityFolder f : folders.keySet()) { + if (f.id == fid) { + folder = f; + ifolder = folders.get(f); + break; + } + } - final boolean shouldClose = (folder == null); + final boolean shouldClose = (folder == null); - try { - if (folder == null) { - folder = db.folder().getFolder(fid); - } + try { + if (folder == null) { + folder = db.folder().getFolder(fid); + } - Log.i( - Helper.TAG, - folder.name + " run " + (shouldClose ? "offline" : "online")); + Log.i(Helper.TAG, + folder.name + " run " + (shouldClose ? "offline" : "online")); - if (ifolder == null) { - // Prevent unnecessary folder - // connections - if (ACTION_PROCESS_OPERATIONS.equals(intent.getAction())) { - if (db.operation().getOperationCount(fid) == 0) { - return; - } - } + if (ifolder == null) { + // Prevent unnecessary folder + // connections + if (ACTION_PROCESS_OPERATIONS.equals(intent.getAction())) { + if (db.operation().getOperationCount(fid) == 0) { + return; + } + } - db.folder().setFolderState(folder.id, "connecting"); + db.folder().setFolderState(folder.id, "connecting"); - ifolder = (IMAPFolder) istore.getFolder(folder.name); - ifolder.open(Folder.READ_WRITE); + ifolder = (IMAPFolder) istore.getFolder(folder.name); + ifolder.open(Folder.READ_WRITE); - db.folder().setFolderState(folder.id, "connected"); - db.folder().setFolderError(folder.id, null); - } + db.folder().setFolderState(folder.id, "connected"); + db.folder().setFolderError(folder.id, null); + } - if (ACTION_PROCESS_OPERATIONS.equals(intent.getAction())) { - processOperations(folder, isession, istore, ifolder); - } else if (ACTION_SYNCHRONIZE_FOLDER.equals(intent.getAction())) { - processOperations(folder, isession, istore, ifolder); - synchronizeMessages(account, folder, ifolder, state); - } + if (ACTION_PROCESS_OPERATIONS.equals(intent.getAction())) { + processOperations(folder, isession, istore, ifolder); + } else if (ACTION_SYNCHRONIZE_FOLDER.equals(intent.getAction())) { + processOperations(folder, isession, istore, ifolder); + synchronizeMessages(account, folder, ifolder, state); + } - } catch (Throwable ex) { - Log.e( - Helper.TAG, - folder.name + " " + ex + "\n" + Log.getStackTraceString(ex)); - reportError(account.name, folder.name, ex); + } catch (Throwable ex) { + Log.e(Helper.TAG, + folder.name + " " + ex + "\n" + Log.getStackTraceString(ex)); + reportError(account.name, folder.name, ex); - db.folder().setFolderError(folder.id, Helper.formatThrowable(ex)); - } 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); - } - } - } finally { - wl.release(); + db.folder().setFolderError(folder.id, Helper.formatThrowable(ex)); + } 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); + } + } + } finally { + wl.release(); + } } - }; + }); + } + }; // Listen for folder operations IntentFilter f = new IntentFilter(); @@ -1333,17 +1162,16 @@ public class ServiceSynchronize extends LifecycleService { } // Keep alive alarm receiver - BroadcastReceiver alarm = - new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - // Receiver runs on main thread - // Receiver has a wake lock for ~10 seconds - EntityLog.log(context, account.name + " keep alive wake lock=" + wl0.isHeld()); - state.thread.interrupt(); - yieldWakelock(); - } - }; + BroadcastReceiver alarm = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + // Receiver runs on main thread + // Receiver has a wake lock for ~10 seconds + EntityLog.log(context, account.name + " keep alive wake lock=" + wl0.isHeld()); + state.thread.interrupt(); + yieldWakelock(); + } + }; String id = BuildConfig.APPLICATION_ID + ".POLL." + account.id; PendingIntent pi = @@ -1356,10 +1184,8 @@ public class ServiceSynchronize extends LifecycleService { while (state.running) { // Schedule keep alive alarm EntityLog.log(this, account.name + " wait=" + account.poll_interval); - am.setAndAllowWhileIdle( - AlarmManager.RTC_WAKEUP, - System.currentTimeMillis() + account.poll_interval * 60 * 1000L, - pi); + am.setAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, + System.currentTimeMillis() + account.poll_interval * 60 * 1000L, pi); try { wl0.release(); @@ -1414,22 +1240,18 @@ public class ServiceSynchronize extends LifecycleService { // Close store try { - Thread t = - new Thread( - new Runnable() { - @Override - public void run() { - try { - EntityLog.log(ServiceSynchronize.this, account.name + " store closing"); - istore.close(); - EntityLog.log(ServiceSynchronize.this, account.name + " store closed"); - } catch (Throwable ex) { - Log.w( - Helper.TAG, - account.name + " " + ex + "\n" + Log.getStackTraceString(ex)); - } - } - }); + Thread t = new Thread(new Runnable() { + @Override + public void run() { + try { + EntityLog.log(ServiceSynchronize.this, account.name + " store closing"); + istore.close(); + EntityLog.log(ServiceSynchronize.this, account.name + " store closed"); + } catch (Throwable ex) { + Log.w(Helper.TAG, account.name + " " + ex + "\n" + Log.getStackTraceString(ex)); + } + } + }); t.start(); try { t.join(MessageHelper.NETWORK_TIMEOUT); @@ -1474,9 +1296,8 @@ public class ServiceSynchronize extends LifecycleService { } } - private void processOperations( - EntityFolder folder, Session isession, IMAPStore istore, IMAPFolder ifolder) - throws MessagingException, JSONException, IOException { + private void processOperations(EntityFolder folder, Session isession, IMAPStore istore, + IMAPFolder ifolder) throws MessagingException, JSONException, IOException { synchronized (lock) { try { Log.i(Helper.TAG, folder.name + " start process"); @@ -1486,17 +1307,8 @@ public class ServiceSynchronize extends LifecycleService { Log.i(Helper.TAG, folder.name + " pending operations=" + ops.size()); for (EntityOperation op : ops) { try { - Log.i( - Helper.TAG, - folder.name - + " start op=" - + op.id - + "/" - + op.name - + " msg=" - + op.message - + " args=" - + op.args); + Log.i(Helper.TAG, folder.name + " start op=" + op.id + "/" + op.name + " msg=" + + op.message + " args=" + op.args); EntityMessage message = db.message().getMessage(op.message); try { @@ -1506,11 +1318,9 @@ public class ServiceSynchronize extends LifecycleService { db.message().setMessageError(message.id, null); - if (message.uid == null - && (EntityOperation.SEEN.equals(op.name) - || EntityOperation.DELETE.equals(op.name) - || EntityOperation.MOVE.equals(op.name) - || EntityOperation.HEADERS.equals(op.name))) { + if (message.uid == null && (EntityOperation.SEEN.equals(op.name) + || EntityOperation.DELETE.equals(op.name) || EntityOperation.MOVE.equals(op.name) + || EntityOperation.HEADERS.equals(op.name))) { throw new IllegalArgumentException(op.name + " without uid " + op.args); } @@ -1550,8 +1360,7 @@ public class ServiceSynchronize extends LifecycleService { db.message().setMessageError(message.id, Helper.formatThrowable(ex)); } - if (ex instanceof MessageRemovedException - || ex instanceof FolderNotFoundException + if (ex instanceof MessageRemovedException || ex instanceof FolderNotFoundException || ex instanceof SendFailedException) { Log.w(Helper.TAG, "Unrecoverable " + ex + "\n" + Log.getStackTraceString(ex)); @@ -1579,9 +1388,8 @@ public class ServiceSynchronize extends LifecycleService { } } - private void doSeen( - EntityFolder folder, IMAPFolder ifolder, EntityMessage message, JSONArray jargs, DB db) - throws MessagingException, JSONException { + private void doSeen(EntityFolder folder, IMAPFolder ifolder, EntityMessage message, + JSONArray jargs, DB db) throws MessagingException, JSONException { // Mark message (un)seen boolean seen = jargs.getBoolean(0); if (message.seen == seen) { @@ -1598,9 +1406,8 @@ public class ServiceSynchronize extends LifecycleService { db.message().setMessageSeen(message.id, seen); } - private void doFlag( - EntityFolder folder, IMAPFolder ifolder, EntityMessage message, JSONArray jargs, DB db) - throws MessagingException, JSONException { + private void doFlag(EntityFolder folder, IMAPFolder ifolder, EntityMessage message, + JSONArray jargs, DB db) throws MessagingException, JSONException { // Star/unstar message boolean flagged = jargs.getBoolean(0); Message imessage = ifolder.getMessageByUID(message.uid); @@ -1613,13 +1420,8 @@ public class ServiceSynchronize extends LifecycleService { db.message().setMessageFlagged(message.id, flagged); } - private void doAdd( - EntityFolder folder, - Session isession, - IMAPFolder ifolder, - EntityMessage message, - JSONArray jargs, - DB db) + private void doAdd(EntityFolder folder, Session isession, IMAPFolder ifolder, + EntityMessage message, JSONArray jargs, DB db) throws MessagingException, JSONException, IOException { // Append message List attachments = db.attachment().getAttachments(message.id); @@ -1638,14 +1440,8 @@ public class ServiceSynchronize extends LifecycleService { } } - private void doMove( - EntityFolder folder, - Session isession, - IMAPStore istore, - IMAPFolder ifolder, - EntityMessage message, - JSONArray jargs, - DB db) + private void doMove(EntityFolder folder, Session isession, IMAPStore istore, IMAPFolder ifolder, + EntityMessage message, JSONArray jargs, DB db) throws JSONException, MessagingException, IOException { // Move message long id = jargs.getLong(0); @@ -1679,9 +1475,8 @@ public class ServiceSynchronize extends LifecycleService { } } - private void doDelete( - EntityFolder folder, IMAPFolder ifolder, EntityMessage message, JSONArray jargs, DB db) - throws MessagingException, JSONException { + private void doDelete(EntityFolder folder, IMAPFolder ifolder, EntityMessage message, + JSONArray jargs, DB db) throws MessagingException, JSONException { // Delete message Message imessage = ifolder.getMessageByUID(message.uid); if (imessage == null) { @@ -1741,8 +1536,7 @@ public class ServiceSynchronize extends LifecycleService { // Send message Address[] to = imessage.getAllRecipients(); itransport.sendMessage(imessage, to); - Log.i( - Helper.TAG, + Log.i(Helper.TAG, "Sent via " + ident.host + "/" + ident.user + " to " + TextUtils.join(", ", to)); try { @@ -1818,13 +1612,8 @@ public class ServiceSynchronize extends LifecycleService { db.message().setMessageContent(message.id, true); } - private void doAttachment( - EntityFolder folder, - EntityOperation op, - IMAPFolder ifolder, - EntityMessage message, - JSONArray jargs, - DB db) + private void doAttachment(EntityFolder folder, EntityOperation op, IMAPFolder ifolder, + EntityMessage message, JSONArray jargs, DB db) throws JSONException, MessagingException, IOException { // Download attachment int sequence = jargs.getInt(0); @@ -1905,9 +1694,8 @@ public class ServiceSynchronize extends LifecycleService { } } - private void synchronizeMessages( - EntityAccount account, EntityFolder folder, IMAPFolder ifolder, ServiceState state) - throws MessagingException, IOException { + private void synchronizeMessages(EntityAccount account, EntityFolder folder, IMAPFolder ifolder, + ServiceState state) throws MessagingException, IOException { DB db = DB.getInstance(this); try { Log.v(Helper.TAG, folder.name + " start sync after=" + folder.after); @@ -1940,14 +1728,8 @@ public class ServiceSynchronize extends LifecycleService { // Reduce list of local uids long search = SystemClock.elapsedRealtime(); Message[] imessages = ifolder.search(new ReceivedDateTerm(ComparisonTerm.GE, new Date(ago))); - Log.i( - Helper.TAG, - folder.name - + " remote count=" - + imessages.length - + " search=" - + (SystemClock.elapsedRealtime() - search) - + " ms"); + Log.i(Helper.TAG, folder.name + " remote count=" + imessages.length + " search=" + + (SystemClock.elapsedRealtime() - search) + " ms"); FetchProfile fp = new FetchProfile(); fp.add(UIDFolder.FetchProfileItem.UID); @@ -1955,8 +1737,7 @@ public class ServiceSynchronize extends LifecycleService { ifolder.fetch(imessages, fp); long fetch = SystemClock.elapsedRealtime(); - Log.i( - Helper.TAG, + Log.i(Helper.TAG, folder.name + " remote fetched=" + (SystemClock.elapsedRealtime() - fetch) + " ms"); for (Message imessage : imessages) { @@ -2013,14 +1794,8 @@ public class ServiceSynchronize extends LifecycleService { if (full.size() > 0) { long headers = SystemClock.elapsedRealtime(); ifolder.fetch(full.toArray(new Message[0]), fp); - Log.i( - Helper.TAG, - folder.name - + " fetched headers=" - + full.size() - + " " - + (SystemClock.elapsedRealtime() - headers) - + " ms"); + Log.i(Helper.TAG, folder.name + " fetched headers=" + full.size() + " " + + (SystemClock.elapsedRealtime() - headers) + " ms"); } for (int j = isub.length - 1; j >= 0; j--) { @@ -2095,9 +1870,8 @@ public class ServiceSynchronize extends LifecycleService { } } - static Long synchronizeMessage( - Context context, EntityFolder folder, IMAPFolder ifolder, IMAPMessage imessage, boolean found) - throws MessagingException, IOException { + static Long synchronizeMessage(Context context, EntityFolder folder, IMAPFolder ifolder, + IMAPMessage imessage, boolean found) throws MessagingException, IOException { long uid = ifolder.getUID(imessage); if (imessage.isExpunged()) { @@ -2128,43 +1902,19 @@ public class ServiceSynchronize extends LifecycleService { String reference = (refs.length == 1 && refs[0].indexOf(BuildConfig.APPLICATION_ID) > 0 ? refs[0] : msgid); Log.i(Helper.TAG, "Searching for " + msgid + " / " + reference); - for (EntityMessage dup : - db.message().getMessageByMsgId(folder.account, msgid, reference, found)) { + for (EntityMessage dup : db.message().getMessageByMsgId(folder.account, msgid, reference, + found)) { EntityFolder dfolder = db.folder().getFolder(dup.folder); boolean outbox = EntityFolder.OUTBOX.equals(dfolder.type); - Log.i( - Helper.TAG, - folder.name - + " found as id=" - + dup.id - + "/" - + dup.uid - + " folder=" - + dfolder.type - + ":" - + dup.folder - + "/" - + folder.type - + ":" - + folder.id - + " msgid=" - + dup.msgid - + " thread=" - + dup.thread); + Log.i(Helper.TAG, + folder.name + " found as id=" + dup.id + "/" + dup.uid + " folder=" + dfolder.type + ":" + + dup.folder + "/" + folder.type + ":" + folder.id + " msgid=" + dup.msgid + + " thread=" + dup.thread); if (dup.folder.equals(folder.id) || outbox) { String thread = helper.getThreadId(uid); - Log.i( - Helper.TAG, - folder.name - + " found as id=" - + dup.id - + "/" - + uid - + " msgid=" - + msgid - + " thread=" - + thread); + Log.i(Helper.TAG, folder.name + " found as id=" + dup.id + "/" + uid + " msgid=" + msgid + + " thread=" + thread); dup.folder = folder.id; dup.uid = uid; dup.msgid = msgid; @@ -2211,8 +1961,8 @@ public class ServiceSynchronize extends LifecycleService { message.ui_found = found; message.ui_ignored = false; - if (ContextCompat.checkSelfPermission(context, Manifest.permission.READ_CONTACTS) - == PackageManager.PERMISSION_GRANTED) { + if (ContextCompat.checkSelfPermission(context, + Manifest.permission.READ_CONTACTS) == PackageManager.PERMISSION_GRANTED) { try { if (message.from != null) { for (int i = 0; i < message.from.length; i++) { @@ -2220,16 +1970,11 @@ public class ServiceSynchronize extends LifecycleService { Cursor cursor = null; try { ContentResolver resolver = context.getContentResolver(); - cursor = - resolver.query( - ContactsContract.CommonDataKinds.Email.CONTENT_URI, - new String[] { - ContactsContract.CommonDataKinds.Photo.CONTACT_ID, - ContactsContract.Contacts.DISPLAY_NAME - }, - ContactsContract.CommonDataKinds.Email.ADDRESS + " = ?", - new String[] {email}, - null); + cursor = resolver.query(ContactsContract.CommonDataKinds.Email.CONTENT_URI, + new String[] {ContactsContract.CommonDataKinds.Photo.CONTACT_ID, + ContactsContract.Contacts.DISPLAY_NAME}, + ContactsContract.CommonDataKinds.Email.ADDRESS + " = ?", new String[] {email}, + null); if (cursor.moveToNext()) { int colContactId = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Photo.CONTACT_ID); @@ -2264,17 +2009,8 @@ public class ServiceSynchronize extends LifecycleService { int sequence = 1; for (EntityAttachment attachment : helper.getAttachments()) { - Log.i( - Helper.TAG, - folder.name - + " attachment seq=" - + sequence - + " name=" - + attachment.name - + " type=" - + attachment.type - + " cid=" - + attachment.cid); + Log.i(Helper.TAG, folder.name + " attachment seq=" + sequence + " name=" + attachment.name + + " type=" + attachment.type + " cid=" + attachment.cid); if (!TextUtils.isEmpty(attachment.cid) && db.attachment().getAttachment(message.id, attachment.cid) != null) { Log.i(Helper.TAG, "Skipping duplicated CID"); @@ -2289,8 +2025,7 @@ public class ServiceSynchronize extends LifecycleService { message.seen = seen; message.ui_seen = seen; db.message().updateMessage(message); - Log.i( - Helper.TAG, + Log.i(Helper.TAG, folder.name + " updated id=" + message.id + " uid=" + message.uid + " seen=" + seen); } @@ -2298,15 +2033,8 @@ public class ServiceSynchronize extends LifecycleService { message.flagged = flagged; message.ui_flagged = flagged; db.message().updateMessage(message); - Log.i( - Helper.TAG, - folder.name - + " updated id=" - + message.id - + " uid=" - + message.uid - + " flagged=" - + flagged); + Log.i(Helper.TAG, folder.name + " updated id=" + message.id + " uid=" + message.uid + + " flagged=" + flagged); } if (message.ui_hide) { @@ -2319,9 +2047,8 @@ public class ServiceSynchronize extends LifecycleService { return message.id; } - private static void downloadMessage( - Context context, EntityFolder folder, IMAPFolder ifolder, IMAPMessage imessage, long id) - throws MessagingException, IOException { + private static void downloadMessage(Context context, EntityFolder folder, IMAPFolder ifolder, + IMAPMessage imessage, long id) throws MessagingException, IOException { DB db = DB.getInstance(context); EntityMessage message = db.message().getMessage(id); if (message == null) { @@ -2370,8 +2097,7 @@ public class ServiceSynchronize extends LifecycleService { if (!metered || (message.size != null && message.size < MESSAGE_AUTO_DOWNLOAD_SIZE)) { message.write(context, helper.getHtml()); db.message().setMessageContent(message.id, true); - Log.i( - Helper.TAG, + Log.i(Helper.TAG, folder.name + " downloaded message id=" + message.id + " size=" + message.size); } } @@ -2387,15 +2113,8 @@ public class ServiceSynchronize extends LifecycleService { } attachment.part = iattachments.get(i).part; attachment.download(context, db); - Log.i( - Helper.TAG, - folder.name - + " downloaded message id=" - + message.id - + " attachment=" - + attachment.name - + " size=" - + message.size); + Log.i(Helper.TAG, folder.name + " downloaded message id=" + message.id + " attachment=" + + attachment.name + " size=" + message.size); } } } @@ -2415,20 +2134,18 @@ public class ServiceSynchronize extends LifecycleService { public void onAvailable(Network network) { ConnectivityManager cm = getSystemService(ConnectivityManager.class); NetworkInfo ni = cm.getNetworkInfo(network); - EntityLog.log( - ServiceSynchronize.this, + EntityLog.log(ServiceSynchronize.this, "Network available " + network + " running=" + running + " " + ni); if (!running) { running = true; - lifecycle.submit( - new Runnable() { - @Override - public void run() { - Log.i(Helper.TAG, "Starting service"); - start(); - } - }); + lifecycle.submit(new Runnable() { + @Override + public void run() { + Log.i(Helper.TAG, "Starting service"); + start(); + } + }); } } @@ -2439,19 +2156,18 @@ public class ServiceSynchronize extends LifecycleService { if (running) { ConnectivityManager cm = getSystemService(ConnectivityManager.class); NetworkInfo ani = (network == null ? null : cm.getActiveNetworkInfo()); - EntityLog.log( - ServiceSynchronize.this, "Network active=" + (ani == null ? null : ani.toString())); + EntityLog.log(ServiceSynchronize.this, + "Network active=" + (ani == null ? null : ani.toString())); if (ani == null || !ani.isConnected()) { EntityLog.log(ServiceSynchronize.this, "Network disconnected=" + ani); running = false; lastLost = new Date().getTime(); - lifecycle.submit( - new Runnable() { - @Override - public void run() { - stop(); - } - }); + lifecycle.submit(new Runnable() { + @Override + public void run() { + stop(); + } + }); } } } @@ -2460,123 +2176,113 @@ public class ServiceSynchronize extends LifecycleService { EntityLog.log(ServiceSynchronize.this, "Main start"); state = new ServiceState(); - state.thread = - new Thread( - new Runnable() { - private List threadState = new ArrayList<>(); - - @Override - public void run() { - PowerManager pm = getSystemService(PowerManager.class); - PowerManager.WakeLock wl = - pm.newWakeLock( - PowerManager.PARTIAL_WAKE_LOCK, BuildConfig.APPLICATION_ID + ":start"); - try { - wl.acquire(); - - DB db = DB.getInstance(ServiceSynchronize.this); - - outbox = db.folder().getOutbox(); - if (outbox == null) { - EntityLog.log(ServiceSynchronize.this, "No outbox, halt"); - stopSelf(); - return; - } + state.thread = new Thread(new Runnable() { + private List threadState = new ArrayList<>(); + + @Override + public void run() { + PowerManager pm = getSystemService(PowerManager.class); + PowerManager.WakeLock wl = + pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, BuildConfig.APPLICATION_ID + ":start"); + try { + wl.acquire(); - List accounts = db.account().getAccounts(true); - if (accounts.size() == 0) { - EntityLog.log(ServiceSynchronize.this, "No accounts, halt"); - stopSelf(); - return; - } + DB db = DB.getInstance(ServiceSynchronize.this); - long ago = new Date().getTime() - lastLost; - if (ago < RECONNECT_BACKOFF) { - try { - long backoff = RECONNECT_BACKOFF - ago; - EntityLog.log(ServiceSynchronize.this, "Main backoff=" + (backoff / 1000)); - Thread.sleep(backoff); - } catch (InterruptedException ex) { - Log.w(Helper.TAG, "main backoff " + ex.toString()); - return; - } - } + outbox = db.folder().getOutbox(); + if (outbox == null) { + EntityLog.log(ServiceSynchronize.this, "No outbox, halt"); + stopSelf(); + return; + } - // Start monitoring outbox - IntentFilter f = new IntentFilter(); - f.addAction(ACTION_SYNCHRONIZE_FOLDER); - f.addAction(ACTION_PROCESS_OPERATIONS); - f.addDataType("account/outbox"); - LocalBroadcastManager lbm = - LocalBroadcastManager.getInstance(ServiceSynchronize.this); - lbm.registerReceiver(outboxReceiver, f); + List accounts = db.account().getAccounts(true); + if (accounts.size() == 0) { + EntityLog.log(ServiceSynchronize.this, "No accounts, halt"); + stopSelf(); + return; + } - db.folder().setFolderState(outbox.id, "connected"); - db.folder().setFolderError(outbox.id, null); + long ago = new Date().getTime() - lastLost; + if (ago < RECONNECT_BACKOFF) { + try { + long backoff = RECONNECT_BACKOFF - ago; + EntityLog.log(ServiceSynchronize.this, "Main backoff=" + (backoff / 1000)); + Thread.sleep(backoff); + } catch (InterruptedException ex) { + Log.w(Helper.TAG, "main backoff " + ex.toString()); + return; + } + } - lbm.sendBroadcast( - new Intent(ACTION_PROCESS_OPERATIONS) - .setType("account/outbox") - .putExtra("folder", outbox.id)); - - // Start monitoring accounts - for (final EntityAccount account : accounts) { - Log.i(Helper.TAG, account.host + "/" + account.user + " run"); - final ServiceState astate = new ServiceState(); - astate.thread = - new Thread( - new Runnable() { - @Override - public void run() { - try { - monitorAccount(account, astate); - } catch (Throwable ex) { - // Fall-safe - Log.e(Helper.TAG, ex + "\n" + Log.getStackTraceString(ex)); - } - } - }, - "sync.account." + account.id); - astate.thread.start(); - threadState.add(astate); - } + // Start monitoring outbox + IntentFilter f = new IntentFilter(); + f.addAction(ACTION_SYNCHRONIZE_FOLDER); + f.addAction(ACTION_PROCESS_OPERATIONS); + f.addDataType("account/outbox"); + LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(ServiceSynchronize.this); + lbm.registerReceiver(outboxReceiver, f); + + db.folder().setFolderState(outbox.id, "connected"); + db.folder().setFolderError(outbox.id, null); + + lbm.sendBroadcast(new Intent(ACTION_PROCESS_OPERATIONS).setType("account/outbox") + .putExtra("folder", outbox.id)); + + // Start monitoring accounts + for (final EntityAccount account : accounts) { + Log.i(Helper.TAG, account.host + "/" + account.user + " run"); + final ServiceState astate = new ServiceState(); + astate.thread = new Thread(new Runnable() { + @Override + public void run() { + try { + monitorAccount(account, astate); + } catch (Throwable ex) { + // Fall-safe + Log.e(Helper.TAG, ex + "\n" + Log.getStackTraceString(ex)); + } + } + }, "sync.account." + account.id); + astate.thread.start(); + threadState.add(astate); + } - EntityLog.log(ServiceSynchronize.this, "Main started"); + EntityLog.log(ServiceSynchronize.this, "Main started"); - try { - yieldWakelock(); - wl.release(); - state.semaphore.acquire(); - } catch (InterruptedException ex) { - Log.w(Helper.TAG, "main wait " + ex.toString()); - } finally { - wl.acquire(); - } + try { + yieldWakelock(); + wl.release(); + state.semaphore.acquire(); + } catch (InterruptedException ex) { + Log.w(Helper.TAG, "main wait " + ex.toString()); + } finally { + wl.acquire(); + } - // Stop monitoring accounts - for (ServiceState astate : threadState) { - astate.running = false; - astate.semaphore.release(); - join(astate.thread); - } - threadState.clear(); + // Stop monitoring accounts + for (ServiceState astate : threadState) { + astate.running = false; + astate.semaphore.release(); + join(astate.thread); + } + threadState.clear(); - // Stop monitoring outbox - lbm.unregisterReceiver(outboxReceiver); - Log.i(Helper.TAG, outbox.name + " unlisten operations"); - db.folder().setFolderState(outbox.id, null); + // Stop monitoring outbox + lbm.unregisterReceiver(outboxReceiver); + Log.i(Helper.TAG, outbox.name + " unlisten operations"); + db.folder().setFolderState(outbox.id, null); - EntityLog.log(ServiceSynchronize.this, "Main exited"); - } catch (Throwable ex) { - // Fail-safe - Log.e(Helper.TAG, ex + "\n" + Log.getStackTraceString(ex)); - } finally { - wl.release(); - EntityLog.log(ServiceSynchronize.this, "Start wake lock=" + wl.isHeld()); - } - } - }, - "sync.main"); + EntityLog.log(ServiceSynchronize.this, "Main exited"); + } catch (Throwable ex) { + // Fail-safe + Log.e(Helper.TAG, ex + "\n" + Log.getStackTraceString(ex)); + } finally { + wl.release(); + EntityLog.log(ServiceSynchronize.this, "Start wake lock=" + wl.isHeld()); + } + } + }, "sync.main"); state.thread.setPriority(THREAD_PRIORITY_BACKGROUND); // will be inherited state.thread.start(); yieldWakelock(); @@ -2605,85 +2311,77 @@ public class ServiceSynchronize extends LifecycleService { private void queue_reload() { if (running) { - lifecycle.submit( - new Runnable() { - @Override - public void run() { - stop(); - start(); - } - }); + lifecycle.submit(new Runnable() { + @Override + public void run() { + stop(); + start(); + } + }); } } private void queue_start() { if (!running) { running = true; - lifecycle.submit( - new Runnable() { - @Override - public void run() { - start(); - } - }); + lifecycle.submit(new Runnable() { + @Override + public void run() { + start(); + } + }); } } private void queue_stop() { if (running) { running = false; - lifecycle.submit( - new Runnable() { - @Override - public void run() { - stop(); - stopSelf(); - } - }); + lifecycle.submit(new Runnable() { + @Override + public void run() { + stop(); + stopSelf(); + } + }); } } - private BroadcastReceiver outboxReceiver = - new BroadcastReceiver() { + private BroadcastReceiver outboxReceiver = new BroadcastReceiver() { + @Override + public void onReceive(final Context context, Intent intent) { + Log.v(Helper.TAG, outbox.name + " run operations"); + + executor.submit(new Runnable() { @Override - public void onReceive(final Context context, Intent intent) { - Log.v(Helper.TAG, outbox.name + " run operations"); - - executor.submit( - new Runnable() { - @Override - public void run() { - PowerManager pm = getSystemService(PowerManager.class); - PowerManager.WakeLock wl = - pm.newWakeLock( - PowerManager.PARTIAL_WAKE_LOCK, BuildConfig.APPLICATION_ID + ":outbox"); + public void run() { + PowerManager pm = getSystemService(PowerManager.class); + PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, + BuildConfig.APPLICATION_ID + ":outbox"); - try { - wl.acquire(); - DB db = DB.getInstance(context); - try { - Log.i(Helper.TAG, outbox.name + " start operations"); - db.folder().setFolderState(outbox.id, "syncing"); - processOperations(outbox, null, null, null); - db.folder().setFolderError(outbox.id, null); - } catch (Throwable ex) { - Log.e( - Helper.TAG, - outbox.name + " " + ex + "\n" + Log.getStackTraceString(ex)); - reportError(null, outbox.name, ex); - - db.folder().setFolderError(outbox.id, Helper.formatThrowable(ex)); - } finally { - Log.i(Helper.TAG, outbox.name + " end operations"); - db.folder().setFolderState(outbox.id, null); - } - } finally { - wl.release(); - EntityLog.log(ServiceSynchronize.this, "Outbox wake lock=" + wl.isHeld()); - } - } - }); + try { + wl.acquire(); + DB db = DB.getInstance(context); + try { + Log.i(Helper.TAG, outbox.name + " start operations"); + db.folder().setFolderState(outbox.id, "syncing"); + processOperations(outbox, null, null, null); + db.folder().setFolderError(outbox.id, null); + } catch (Throwable ex) { + Log.e(Helper.TAG, outbox.name + " " + ex + "\n" + Log.getStackTraceString(ex)); + reportError(null, outbox.name, ex); + + db.folder().setFolderError(outbox.id, Helper.formatThrowable(ex)); + } finally { + Log.i(Helper.TAG, outbox.name + " end operations"); + db.folder().setFolderState(outbox.id, null); + } + } finally { + wl.release(); + EntityLog.log(ServiceSynchronize.this, "Outbox wake lock=" + wl.isHeld()); + } } + }); + } }; } @@ -2717,21 +2415,21 @@ public class ServiceSynchronize extends LifecycleService { } public static void start(Context context) { - ContextCompat.startForegroundService( - context, new Intent(context, ServiceSynchronize.class).setAction("start")); + ContextCompat.startForegroundService(context, + new Intent(context, ServiceSynchronize.class).setAction("start")); } public static void stop(Context context) { - ContextCompat.startForegroundService( - context, new Intent(context, ServiceSynchronize.class).setAction("stop")); + ContextCompat.startForegroundService(context, + new Intent(context, ServiceSynchronize.class).setAction("stop")); } public static void reload(Context context, String reason) { SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); if (prefs.getBoolean("enabled", true)) { Log.i(Helper.TAG, "Reload because of '" + reason + "'"); - ContextCompat.startForegroundService( - context, new Intent(context, ServiceSynchronize.class).setAction("reload")); + ContextCompat.startForegroundService(context, + new Intent(context, ServiceSynchronize.class).setAction("reload")); } }