diff --git a/app/src/main/java/eu/faircode/email/AdapterAccount.java b/app/src/main/java/eu/faircode/email/AdapterAccount.java index f4fdc865d0..52cefc671d 100644 --- a/app/src/main/java/eu/faircode/email/AdapterAccount.java +++ b/app/src/main/java/eu/faircode/email/AdapterAccount.java @@ -89,6 +89,8 @@ public class AdapterAccount extends RecyclerView.Adapter threads = new ArrayList<>(); + private void monitorAccount(final EntityAccount account, final ServiceState state) throws NoSuchProviderException { + Log.i(Helper.TAG, account.name + " start"); + + final DB db = DB.getInstance(this); final ExecutorService executor = Executors.newSingleThreadExecutor(); - Log.i(Helper.TAG, account.name + " start"); + Properties props = MessageHelper.getSessionProperties(); + props.setProperty("mail.imaps.peek", "true"); + props.setProperty("mail.mime.address.strict", "false"); + props.setProperty("mail.mime.decodetext.strict", "false"); + //props.put("mail.imaps.minidletime", "5000"); + + boolean debug = PreferenceManager.getDefaultSharedPreferences(this).getBoolean("debug", false); + if (debug) + System.setProperty("mail.socket.debug", "true"); - final DB db = DB.getInstance(ServiceSynchronize.this); + final Session isession = Session.getInstance(props, null); + isession.setDebug(debug); + // adb -t 1 logcat | grep "fairemail\|System.out" int backoff = CONNECT_BACKOFF_START; while (state.running) { - IMAPStore istore = null; + final IMAPStore istore = (IMAPStore) isession.getStore("imaps"); + final Map folders = new HashMap<>(); + List noops = new ArrayList<>(); + List idlers = new ArrayList<>(); try { - Properties props = MessageHelper.getSessionProperties(); - props.setProperty("mail.imaps.peek", "true"); - props.setProperty("mail.mime.address.strict", "false"); - props.setProperty("mail.mime.decodetext.strict", "false"); - //props.put("mail.imaps.minidletime", "5000"); - - boolean debug = PreferenceManager.getDefaultSharedPreferences(this).getBoolean("debug", false); - final Session isession = Session.getInstance(props, null); - isession.setDebug(debug); - // adb -t 1 logcat | grep "eu.faircode.email\|System.out" - - istore = (IMAPStore) isession.getStore("imaps"); - final IMAPStore fstore = istore; - - // Listen for events + // Listen for store events istore.addStoreListener(new StoreListener() { @Override public void notification(StoreEvent e) { Log.i(Helper.TAG, account.name + " event: " + e.getMessage()); db.account().setAccountError(account.id, e.getMessage()); + + + synchronized (state) { + state.notifyAll(); + } } }); + + // Listen for folder events istore.addFolderListener(new FolderAdapter() { @Override public void folderCreated(FolderEvent e) { @@ -382,243 +392,344 @@ public class ServiceSynchronize extends LifecycleService { } }); - // Listen for connection changes + // Listen for connection events istore.addConnectionListener(new ConnectionAdapter() { - Map mapFolder = new HashMap<>(); @Override public void opened(ConnectionEvent e) { Log.i(Helper.TAG, account.name + " opened"); + } - db.account().setAccountState(account.id, "connected"); - db.account().setAccountError(account.id, null); + @Override + public void disconnected(ConnectionEvent e) { + Log.e(Helper.TAG, account.name + " disconnected event"); + } - try { - synchronizeFolders(account, fstore); + @Override + public void closed(ConnectionEvent e) { + Log.e(Helper.TAG, account.name + " closed event"); + } + }); + + // Initiate connection + Log.i(Helper.TAG, account.name + " connect"); + db.account().setAccountState(account.id, "connecting"); + istore.connect(account.host, account.port, account.user, account.password); - for (final EntityFolder folder : db.folder().getFolders(account.id, true)) { - Log.i(Helper.TAG, account.name + " sync folder " + folder.name); + backoff = CONNECT_BACKOFF_START; + db.account().setAccountState(account.id, "connected"); + db.account().setAccountError(account.id, null); - // Monitor folders - Thread t = new Thread(new Runnable() { - @Override - public void run() { - IMAPFolder ifolder = null; - try { - Log.i(Helper.TAG, folder.name + " start"); + // Update folder list + try { + synchronizeFolders(account, istore); + } catch (MessagingException ex) { + // Don't show to user + throw new IllegalStateException("synchronize folders", ex); + } - db.folder().setFolderState(folder.id, "connecting"); + // Synchronize folders + for (final EntityFolder folder : db.folder().getFolders(account.id, true)) + try { + Log.i(Helper.TAG, account.name + " sync folder " + folder.name); - ifolder = (IMAPFolder) fstore.getFolder(folder.name); - ifolder.open(Folder.READ_WRITE); + db.folder().setFolderState(folder.id, "connecting"); - db.folder().setFolderState(folder.id, "connected"); - db.folder().setFolderError(folder.id, null); + final IMAPFolder ifolder = (IMAPFolder) istore.getFolder(folder.name); + ifolder.open(Folder.READ_WRITE); + folders.put(folder, ifolder); + + db.folder().setFolderState(folder.id, "connected"); + db.folder().setFolderError(folder.id, null); + + // Listen for new and deleted messages + ifolder.addMessageCountListener(new MessageCountAdapter() { + @Override + public void messagesAdded(MessageCountEvent e) { + synchronized (lock) { + try { + Log.i(Helper.TAG, folder.name + " messages added"); + for (Message imessage : e.getMessages()) + try { + synchronizeMessage(folder, ifolder, (IMAPMessage) imessage); + } catch (MessageRemovedException ex) { + Log.w(Helper.TAG, folder.name + " " + ex + "\n" + Log.getStackTraceString(ex)); - synchronized (mapFolder) { - mapFolder.put(folder.id, ifolder); } + } catch (Throwable ex) { + Log.e(Helper.TAG, folder.name + " " + ex + "\n" + Log.getStackTraceString(ex)); + reportError(account.name, folder.name, ex); - monitorFolder(account, folder, fstore, ifolder, state); + db.folder().setFolderError(folder.id, Helper.formatThrowable(ex)); - } catch (Throwable ex) { - // MessagingException - // - message: connection failure - // - event: Too many simultaneous connections. (Failure) - // TODO: retry? + synchronized (state) { + state.notifyAll(); + } + } + } + } - Log.e(Helper.TAG, folder.name + " " + ex + "\n" + Log.getStackTraceString(ex)); - reportError(account.name, folder.name, ex); + @Override + public void messagesRemoved(MessageCountEvent e) { + synchronized (lock) { + try { + Log.i(Helper.TAG, folder.name + " messages removed"); + for (Message imessage : e.getMessages()) + try { + long uid = ifolder.getUID(imessage); - db.folder().setFolderError(folder.id, Helper.formatThrowable(ex)); + DB db = DB.getInstance(ServiceSynchronize.this); + int count = db.message().deleteMessage(folder.id, uid); - // Check connection - synchronized (state) { - state.notifyAll(); - } - } finally { - if (ifolder != null && ifolder.isOpen()) { - try { - ifolder.close(false); - } catch (MessagingException ex) { - Log.w(Helper.TAG, folder.name + " " + ex + "\n" + Log.getStackTraceString(ex)); - } + 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().setFolderState(folder.id, null); + db.folder().setFolderError(folder.id, Helper.formatThrowable(ex)); - Log.i(Helper.TAG, folder.name + " stopped"); + synchronized (state) { + state.notifyAll(); } } - }, "sync.folder." + folder.id); - t.start(); - threads.add(t); + } } + }); - // Listen for folder operations - IntentFilter f = new IntentFilter(ACTION_PROCESS_OPERATIONS); - f.addDataType("account/" + account.id); - LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(ServiceSynchronize.this); - lbm.registerReceiver(processReceiver, f); + // Fetch e-mail + synchronizeMessages(folder, ifolder); - // Run folder operations - Log.i(Helper.TAG, "listen process folder"); - for (final EntityFolder folder : db.folder().getFolders(account.id)) - if (!EntityFolder.OUTBOX.equals(folder.type)) - lbm.sendBroadcast(new Intent(ACTION_PROCESS_OPERATIONS) - .setType("account/" + account.id) - .putExtra("folder", folder.id)); + // Flags (like "seen") at the remote could be changed while synchronizing - } catch (Throwable ex) { - Log.e(Helper.TAG, account.name + " " + ex + "\n" + Log.getStackTraceString(ex)); - reportError(account.name, null, ex); + // Listen for changed messages + ifolder.addMessageChangedListener(new MessageChangedListener() { + @Override + public void messageChanged(MessageChangedEvent e) { + synchronized (lock) { + try { + try { + Log.i(Helper.TAG, folder.name + " message changed"); + synchronizeMessage(folder, ifolder, (IMAPMessage) e.getMessage()); + } 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.account().setAccountError(account.id, Helper.formatThrowable(ex)); + db.folder().setFolderError(folder.id, Helper.formatThrowable(ex)); - // Check connection - synchronized (state) { - state.notifyAll(); + synchronized (state) { + state.notifyAll(); + } + } + } } - } - } + }); + + // Keep folder connection alive + Thread noop = new Thread(new Runnable() { + @Override + public void run() { + try { + Log.i(Helper.TAG, folder.name + " start noop"); + while (state.running && ifolder.isOpen()) { + Log.i(Helper.TAG, folder.name + " request NOOP"); + ifolder.doCommand(new IMAPFolder.ProtocolCommand() { + public Object doCommand(IMAPProtocol p) throws ProtocolException { + Log.i(Helper.TAG, ifolder.getName() + " start NOOP"); + p.simpleCommand("NOOP", null); + Log.i(Helper.TAG, ifolder.getName() + " end NOOP"); + return null; + } + }); - @Override - public void disconnected(ConnectionEvent e) { - Log.e(Helper.TAG, account.name + " disconnected event"); + try { + Thread.sleep(FOLDER_NOOP_INTERVAL); + } catch (InterruptedException ex) { + Log.w(Helper.TAG, folder.name + " noop " + ex.getMessage()); + } + } + } catch (Throwable ex) { + Log.e(Helper.TAG, folder.name + " " + ex + "\n" + Log.getStackTraceString(ex)); + reportError(account.name, folder.name, ex); - db.account().setAccountState(account.id, null); + db.folder().setFolderError(folder.id, Helper.formatThrowable(ex)); - LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(ServiceSynchronize.this); - lbm.unregisterReceiver(processReceiver); + synchronized (state) { + state.notifyAll(); + } + } finally { + Log.i(Helper.TAG, folder.name + " end noop"); + } + } + }, "sync.noop." + folder.id); + noop.start(); + noops.add(noop); + + // Receive folder events + Thread idle = new Thread(new Runnable() { + @Override + public void run() { + try { + Log.i(Helper.TAG, folder.name + " start idle"); + while (state.running && ifolder.isOpen()) { + Log.i(Helper.TAG, folder.name + " do idle"); + ifolder.idle(false); + Log.i(Helper.TAG, folder.name + " done idle"); + } + } catch (Throwable ex) { + Log.e(Helper.TAG, folder.name + " " + ex + "\n" + Log.getStackTraceString(ex)); + reportError(account.name, folder.name, ex); - synchronized (mapFolder) { - mapFolder.clear(); - } + db.folder().setFolderError(folder.id, Helper.formatThrowable(ex)); - // Check connection - synchronized (state) { - state.notifyAll(); - } + synchronized (state) { + state.notifyAll(); + } + } finally { + Log.i(Helper.TAG, folder.name + " end idle"); + } + } + }, "sync.idle." + folder.id); + idle.start(); + idlers.add(idle); + } catch (MessagingException ex) { + // Don't show to user + throw new FolderClosedException(folders.get(folder), "start folder", ex); + } catch (IOException ex) { + // Don't show to user + throw new FolderClosedException(folders.get(folder), "start folder", ex); } + BroadcastReceiver processReceiver = new BroadcastReceiver() { @Override - public void closed(ConnectionEvent e) { - Log.e(Helper.TAG, account.name + " closed event"); - - db.account().setAccountState(account.id, null); + public void onReceive(Context context, Intent intent) { + final long fid = intent.getLongExtra("folder", -1); + //Log.v(Helper.TAG, "run operations folder=" + fid); - LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(ServiceSynchronize.this); - lbm.unregisterReceiver(processReceiver); - - synchronized (mapFolder) { - mapFolder.clear(); - } - - // Check connection - synchronized (state) { - state.notifyAll(); - } - } + try { + executor.submit(new Runnable() { + @Override + public void run() { + // Get folder + EntityFolder folder = null; + IMAPFolder ifolder = null; + for (EntityFolder f : folders.keySet()) + if (f.id == fid) { + folder = f; + ifolder = folders.get(f); + break; + } - private BroadcastReceiver processReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - final long fid = intent.getLongExtra("folder", -1); + final boolean shouldClose = (ifolder == null); - IMAPFolder ifolder; - synchronized (mapFolder) { - ifolder = mapFolder.get(fid); - } - final boolean shouldClose = (ifolder == null); - final IMAPFolder ffolder = ifolder; + try { + if (folder == null) + throw new IllegalArgumentException("Unknown folder=" + fid); - Log.v(Helper.TAG, "run operations folder=" + fid + " offline=" + shouldClose); - try { - executor.submit(new Runnable() { - @Override - public void run() { - DB db = DB.getInstance(ServiceSynchronize.this); - EntityFolder folder = db.folder().getFolder(fid); - IMAPFolder ifolder = ffolder; - try { - Log.v(Helper.TAG, folder.name + " start operations"); + if (shouldClose) + Log.v(Helper.TAG, folder.name + " start operations offline=" + shouldClose); - if (ifolder == null) { - // Prevent unnecessary folder connections - if (db.operation().getOperationCount(fid) == 0) - return; + if (ifolder == null) { + // Prevent unnecessary folder connections + if (db.operation().getOperationCount(fid) == 0) + return; - ifolder = (IMAPFolder) fstore.getFolder(folder.name); - ifolder.open(Folder.READ_WRITE); - } + ifolder = (IMAPFolder) istore.getFolder(folder.name); + ifolder.open(Folder.READ_WRITE); + } - processOperations(folder, isession, fstore, ifolder); - } catch (Throwable ex) { - Log.e(Helper.TAG, folder.name + " " + ex + "\n" + Log.getStackTraceString(ex)); - reportError(account.name, folder.name, ex); - } finally { - if (shouldClose) - if (ifolder != null && ifolder.isOpen()) { - try { - ifolder.close(false); - } catch (MessagingException ex) { - Log.w(Helper.TAG, folder.name + " " + ex + "\n" + Log.getStackTraceString(ex)); - } + processOperations(folder, isession, istore, ifolder); + } catch (Throwable ex) { + Log.e(Helper.TAG, folder.name + " " + ex + "\n" + Log.getStackTraceString(ex)); + reportError(account.name, folder.name, ex); + } finally { + if (shouldClose) + if (ifolder != null && ifolder.isOpen()) { + try { + ifolder.close(false); + } catch (MessagingException ex) { + Log.w(Helper.TAG, folder.name + " " + ex + "\n" + Log.getStackTraceString(ex)); } - Log.v(Helper.TAG, folder.name + " stop operations"); - } + } + //Log.v(Helper.TAG, folder.name + " stop operations"); } - }); - } catch (RejectedExecutionException ex) { - Log.w(Helper.TAG, ex + "\n" + Log.getStackTraceString(ex)); - } + } + }); + } catch (RejectedExecutionException ex) { + Log.w(Helper.TAG, ex + "\n" + Log.getStackTraceString(ex)); } - }; - }); - - // Initiate connection - Log.i(Helper.TAG, account.name + " connect"); - db.account().setAccountState(account.id, "connecting"); - istore.connect(account.host, account.port, account.user, account.password); - backoff = CONNECT_BACKOFF_START; + } + }; - // Keep alive - boolean connected = false; - do { - try { + // Listen for folder operations + IntentFilter f = new IntentFilter(ACTION_PROCESS_OPERATIONS); + f.addDataType("account/" + account.id); + LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(ServiceSynchronize.this); + lbm.registerReceiver(processReceiver, f); + try { + // Process pending folder operations + Log.i(Helper.TAG, "listen process folder"); + for (final EntityFolder folder : folders.keySet()) + if (!EntityFolder.OUTBOX.equals(folder.type)) + lbm.sendBroadcast(new Intent(ACTION_PROCESS_OPERATIONS) + .setType("account/" + account.id) + .putExtra("folder", folder.id)); + + // Keep store alive + while (state.running && istore.isConnected()) { + Log.i(Helper.TAG, "Checking folders"); + for (EntityFolder folder : folders.keySet()) + if (!folders.get(folder).isOpen()) + throw new FolderClosedException(folders.get(folder)); + + // Wait for stop or folder error Log.i(Helper.TAG, account.name + " wait"); synchronized (state) { - state.wait(); + state.wait(STORE_NOOP_INTERVAL); } Log.i(Helper.TAG, account.name + " waited"); - } catch (InterruptedException ex) { - Log.w(Helper.TAG, account.name + " wait " + ex.toString()); - } - if (state.running) { - Log.i(Helper.TAG, account.name + " NOOP"); - connected = istore.isConnected(); } - } while (state.running && connected); - - if (state.running) - Log.w(Helper.TAG, account.name + " not connected anymore"); - else - Log.i(Helper.TAG, account.name + " not running anymore"); - + Log.i(Helper.TAG, account.name + " done running=" + state.running); + } finally { + lbm.unregisterReceiver(processReceiver); + } } catch (Throwable ex) { Log.e(Helper.TAG, account.name + " " + ex + "\n" + Log.getStackTraceString(ex)); reportError(account.name, null, ex); db.account().setAccountError(account.id, Helper.formatThrowable(ex)); } finally { + // Close store Log.i(Helper.TAG, account.name + " closing"); + db.account().setAccountState(account.id, "closing"); try { - // This can take 20 seconds + // This can take some time istore.close(); } catch (MessagingException ex) { Log.w(Helper.TAG, account.name + " " + ex + "\n" + Log.getStackTraceString(ex)); + } finally { + Log.i(Helper.TAG, account.name + " closed"); + db.account().setAccountState(account.id, null); + for (EntityFolder folder : folders.keySet()) + db.folder().setFolderState(folder.id, null); + } + + // Stop noop + for (Thread noop : noops) { + noop.interrupt(); + join(noop); + } + + // Stop idle + for (Thread idle : idlers) { + idle.interrupt(); + join(idle); } - Log.i(Helper.TAG, account.name + " closed"); } if (state.running) { @@ -634,158 +745,9 @@ public class ServiceSynchronize extends LifecycleService { } } - db.account().setAccountState(account.id, null); - - for (Thread t : threads) - join(t); - threads.clear(); - executor.shutdown(); - Log.i(Helper.TAG, account.name + " stopped"); } - private void monitorFolder( - final EntityAccount account, final EntityFolder folder, - final IMAPStore istore, final IMAPFolder ifolder, - final ServiceState state) throws MessagingException, JSONException, IOException { - - final DB db = DB.getInstance(ServiceSynchronize.this); - - // Listen for new and deleted messages - ifolder.addMessageCountListener(new MessageCountAdapter() { - @Override - public void messagesAdded(MessageCountEvent e) { - synchronized (lock) { - try { - Log.i(Helper.TAG, folder.name + " messages added"); - for (Message imessage : e.getMessages()) - try { - synchronizeMessage(folder, ifolder, (IMAPMessage) imessage); - } 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)); - - // Check connection - synchronized (state) { - state.notifyAll(); - } - } - } - } - - @Override - public void messagesRemoved(MessageCountEvent e) { - synchronized (lock) { - try { - 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)); - - // Check connection - synchronized (state) { - state.notifyAll(); - } - } - } - } - }); - - // Fetch e-mail - synchronizeMessages(folder, ifolder); - - // 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 { - try { - Log.i(Helper.TAG, folder.name + " message changed"); - synchronizeMessage(folder, ifolder, (IMAPMessage) e.getMessage()); - } 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)); - - // Check connection - synchronized (state) { - state.notifyAll(); - } - } - } - } - }); - - // Keep alive - Log.i(Helper.TAG, folder.name + " start"); - try { - Thread thread = new Thread(new Runnable() { - @Override - public void run() { - try { - boolean open; - do { - try { - Thread.sleep(NOOP_INTERVAL); - } catch (InterruptedException ex) { - Log.w(Helper.TAG, folder.name + " noop " + ex.getMessage()); - } - open = ifolder.isOpen(); - if (open) - noop(folder, ifolder); - } while (open); - } 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 { - // Check connection - synchronized (state) { - state.notifyAll(); - } - } - } - }, "sync.noop." + folder.id); - thread.start(); - - // Idle - while (state.running) { - Log.i(Helper.TAG, folder.name + " start idle"); - ifolder.idle(false); - Log.i(Helper.TAG, folder.name + " end idle"); - } - } finally { - Log.i(Helper.TAG, folder.name + " end"); - } - } - private void processOperations(EntityFolder folder, Session isession, IMAPStore istore, IMAPFolder ifolder) throws MessagingException, JSONException, IOException { synchronized (lock) { try { @@ -1141,7 +1103,7 @@ public class ServiceSynchronize extends LifecycleService { } } - private void synchronizeMessages(EntityFolder folder, IMAPFolder ifolder) throws MessagingException, JSONException, IOException { + private void synchronizeMessages(EntityFolder folder, IMAPFolder ifolder) throws MessagingException, IOException { try { Log.v(Helper.TAG, folder.name + " start sync after=" + folder.after); @@ -1218,8 +1180,8 @@ public class ServiceSynchronize extends LifecycleService { } } - private int synchronizeMessage(EntityFolder folder, IMAPFolder ifolder, IMAPMessage imessage) throws MessagingException, JSONException, IOException { - long uid = -1; + private int synchronizeMessage(EntityFolder folder, IMAPFolder ifolder, IMAPMessage imessage) throws MessagingException, IOException { + long uid; try { FetchProfile fp = new FetchProfile(); fp.add(UIDFolder.FetchProfileItem.UID); @@ -1227,7 +1189,7 @@ public class ServiceSynchronize extends LifecycleService { ifolder.fetch(new Message[]{imessage}, fp); uid = ifolder.getUID(imessage); - Log.v(Helper.TAG, folder.name + " start sync uid=" + uid); + //Log.v(Helper.TAG, folder.name + " start sync uid=" + uid); if (imessage.isExpunged()) { Log.i(Helper.TAG, folder.name + " expunged uid=" + uid); @@ -1277,10 +1239,10 @@ public class ServiceSynchronize extends LifecycleService { message.seen = seen; message.ui_seen = seen; db.message().updateMessage(message); - Log.v(Helper.TAG, folder.name + " updated id=" + message.id + " uid=" + message.uid + " seen=" + seen); + Log.i(Helper.TAG, folder.name + " updated id=" + message.id + " uid=" + message.uid + " seen=" + seen); result = -1; } else - Log.v(Helper.TAG, folder.name + " unchanged id=" + message.id + " uid=" + message.uid); + ; //Log.v(Helper.TAG, folder.name + " unchanged id=" + message.id + " uid=" + message.uid); } db.setTransactionSuccessful(); @@ -1327,7 +1289,7 @@ public class ServiceSynchronize extends LifecycleService { message.ui_hide = false; message.id = db.message().insertMessage(message); - Log.v(Helper.TAG, folder.name + " added id=" + message.id + " uid=" + message.uid); + Log.i(Helper.TAG, folder.name + " added id=" + message.id + " uid=" + message.uid); int sequence = 0; for (EntityAttachment attachment : helper.getAttachments()) { @@ -1342,25 +1304,13 @@ public class ServiceSynchronize extends LifecycleService { return 1; } finally { - Log.v(Helper.TAG, folder.name + " end sync uid=" + uid); + //Log.v(Helper.TAG, folder.name + " end sync uid=" + uid); } } - private void noop(EntityFolder folder, final IMAPFolder ifolder) throws MessagingException { - Log.i(Helper.TAG, folder.name + " request NOOP"); - ifolder.doCommand(new IMAPFolder.ProtocolCommand() { - public Object doCommand(IMAPProtocol p) throws ProtocolException { - Log.i(Helper.TAG, ifolder.getName() + " start NOOP"); - p.simpleCommand("NOOP", null); - Log.i(Helper.TAG, ifolder.getName() + " end NOOP"); - return null; - } - }); - } - private class ServiceManager extends ConnectivityManager.NetworkCallback { private ServiceState state = new ServiceState(); - private boolean connected = false; + private boolean running = false; private Thread main; private EntityFolder outbox = null; private ExecutorService lifecycle = Executors.newSingleThreadExecutor(); @@ -1370,12 +1320,15 @@ public class ServiceSynchronize extends LifecycleService { public void onAvailable(Network network) { Log.i(Helper.TAG, "Network available " + network); - if (!connected) { - Log.i(Helper.TAG, "Network not connected"); - connected = true; + if (running) + Log.i(Helper.TAG, "Service already running"); + else { + Log.i(Helper.TAG, "Service not running"); + running = true; lifecycle.submit(new Runnable() { @Override public void run() { + Log.i(Helper.TAG, "Starting service"); start(); } }); @@ -1386,28 +1339,30 @@ public class ServiceSynchronize extends LifecycleService { public void onLost(Network network) { Log.i(Helper.TAG, "Network lost " + network); - if (connected) { - Log.i(Helper.TAG, "Network connected"); + if (running) { + Log.i(Helper.TAG, "Service running"); ConnectivityManager cm = getSystemService(ConnectivityManager.class); NetworkInfo ni = cm.getActiveNetworkInfo(); - if (ni != null) - Log.i(Helper.TAG, "Network active=" + ni); + Log.i(Helper.TAG, "Network active=" + (ni == null ? null : ni.toString())); if (ni == null || !ni.isConnected()) { Log.i(Helper.TAG, "Network disconnected=" + ni); - connected = false; + running = false; lifecycle.submit(new Runnable() { @Override public void run() { - stop(); + Log.i(Helper.TAG, "Stopping service"); + stop(true); } }); } - } + } else + Log.i(Helper.TAG, "Service not running"); } - public void start() { + private void start() { synchronized (state) { state.running = true; + state.disconnected = false; } main = new Thread(new Runnable() { @@ -1480,14 +1435,16 @@ public class ServiceSynchronize extends LifecycleService { main.start(); } - public void stop() { + private void stop(boolean disconnected) { if (main != null) { synchronized (state) { state.running = false; + state.disconnected = disconnected; state.notifyAll(); } - main.interrupt(); // stop backoff + // stop wait or backoff + main.interrupt(); join(main); main = null; @@ -1564,7 +1521,7 @@ public class ServiceSynchronize extends LifecycleService { public void quit() { Log.i(Helper.TAG, "Service quit"); - serviceManager.stop(); + serviceManager.stop(false); Log.i(Helper.TAG, "Service quited"); stopSelf(); } @@ -1613,5 +1570,6 @@ public class ServiceSynchronize extends LifecycleService { private class ServiceState { boolean running = false; + boolean disconnected = false; } } diff --git a/app/src/main/res/drawable-hdpi/baseline_close_black_18.png b/app/src/main/res/drawable-hdpi/baseline_close_black_18.png new file mode 100755 index 0000000000..f80273a4dd Binary files /dev/null and b/app/src/main/res/drawable-hdpi/baseline_close_black_18.png differ diff --git a/app/src/main/res/drawable-hdpi/baseline_close_black_24.png b/app/src/main/res/drawable-hdpi/baseline_close_black_24.png new file mode 100755 index 0000000000..858996e483 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/baseline_close_black_24.png differ diff --git a/app/src/main/res/drawable-hdpi/baseline_close_black_36.png b/app/src/main/res/drawable-hdpi/baseline_close_black_36.png new file mode 100755 index 0000000000..bb6db33716 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/baseline_close_black_36.png differ diff --git a/app/src/main/res/drawable-hdpi/baseline_close_black_48.png b/app/src/main/res/drawable-hdpi/baseline_close_black_48.png new file mode 100755 index 0000000000..bba526c5de Binary files /dev/null and b/app/src/main/res/drawable-hdpi/baseline_close_black_48.png differ diff --git a/app/src/main/res/drawable-hdpi/baseline_close_white_18.png b/app/src/main/res/drawable-hdpi/baseline_close_white_18.png new file mode 100755 index 0000000000..de001ab0b5 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/baseline_close_white_18.png differ diff --git a/app/src/main/res/drawable-hdpi/baseline_close_white_24.png b/app/src/main/res/drawable-hdpi/baseline_close_white_24.png new file mode 100755 index 0000000000..c8598b6133 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/baseline_close_white_24.png differ diff --git a/app/src/main/res/drawable-hdpi/baseline_close_white_36.png b/app/src/main/res/drawable-hdpi/baseline_close_white_36.png new file mode 100755 index 0000000000..0419ecc522 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/baseline_close_white_36.png differ diff --git a/app/src/main/res/drawable-hdpi/baseline_close_white_48.png b/app/src/main/res/drawable-hdpi/baseline_close_white_48.png new file mode 100755 index 0000000000..3af97b1df7 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/baseline_close_white_48.png differ diff --git a/app/src/main/res/drawable-mdpi/baseline_close_black_18.png b/app/src/main/res/drawable-mdpi/baseline_close_black_18.png new file mode 100755 index 0000000000..aa51b150a9 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/baseline_close_black_18.png differ diff --git a/app/src/main/res/drawable-mdpi/baseline_close_black_24.png b/app/src/main/res/drawable-mdpi/baseline_close_black_24.png new file mode 100755 index 0000000000..a52f5eb938 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/baseline_close_black_24.png differ diff --git a/app/src/main/res/drawable-mdpi/baseline_close_black_36.png b/app/src/main/res/drawable-mdpi/baseline_close_black_36.png new file mode 100755 index 0000000000..858996e483 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/baseline_close_black_36.png differ diff --git a/app/src/main/res/drawable-mdpi/baseline_close_black_48.png b/app/src/main/res/drawable-mdpi/baseline_close_black_48.png new file mode 100755 index 0000000000..0369a905ad Binary files /dev/null and b/app/src/main/res/drawable-mdpi/baseline_close_black_48.png differ diff --git a/app/src/main/res/drawable-mdpi/baseline_close_white_18.png b/app/src/main/res/drawable-mdpi/baseline_close_white_18.png new file mode 100755 index 0000000000..1f7ab28517 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/baseline_close_white_18.png differ diff --git a/app/src/main/res/drawable-mdpi/baseline_close_white_24.png b/app/src/main/res/drawable-mdpi/baseline_close_white_24.png new file mode 100755 index 0000000000..08d8109923 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/baseline_close_white_24.png differ diff --git a/app/src/main/res/drawable-mdpi/baseline_close_white_36.png b/app/src/main/res/drawable-mdpi/baseline_close_white_36.png new file mode 100755 index 0000000000..c8598b6133 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/baseline_close_white_36.png differ diff --git a/app/src/main/res/drawable-mdpi/baseline_close_white_48.png b/app/src/main/res/drawable-mdpi/baseline_close_white_48.png new file mode 100755 index 0000000000..4419c31ab3 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/baseline_close_white_48.png differ diff --git a/app/src/main/res/drawable-xhdpi/baseline_close_black_18.png b/app/src/main/res/drawable-xhdpi/baseline_close_black_18.png new file mode 100755 index 0000000000..858996e483 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/baseline_close_black_18.png differ diff --git a/app/src/main/res/drawable-xhdpi/baseline_close_black_24.png b/app/src/main/res/drawable-xhdpi/baseline_close_black_24.png new file mode 100755 index 0000000000..0369a905ad Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/baseline_close_black_24.png differ diff --git a/app/src/main/res/drawable-xhdpi/baseline_close_black_36.png b/app/src/main/res/drawable-xhdpi/baseline_close_black_36.png new file mode 100755 index 0000000000..bba526c5de Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/baseline_close_black_36.png differ diff --git a/app/src/main/res/drawable-xhdpi/baseline_close_black_48.png b/app/src/main/res/drawable-xhdpi/baseline_close_black_48.png new file mode 100755 index 0000000000..f8c6a0c691 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/baseline_close_black_48.png differ diff --git a/app/src/main/res/drawable-xhdpi/baseline_close_white_18.png b/app/src/main/res/drawable-xhdpi/baseline_close_white_18.png new file mode 100755 index 0000000000..c8598b6133 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/baseline_close_white_18.png differ diff --git a/app/src/main/res/drawable-xhdpi/baseline_close_white_24.png b/app/src/main/res/drawable-xhdpi/baseline_close_white_24.png new file mode 100755 index 0000000000..4419c31ab3 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/baseline_close_white_24.png differ diff --git a/app/src/main/res/drawable-xhdpi/baseline_close_white_36.png b/app/src/main/res/drawable-xhdpi/baseline_close_white_36.png new file mode 100755 index 0000000000..3af97b1df7 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/baseline_close_white_36.png differ diff --git a/app/src/main/res/drawable-xhdpi/baseline_close_white_48.png b/app/src/main/res/drawable-xhdpi/baseline_close_white_48.png new file mode 100755 index 0000000000..f5a903fd73 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/baseline_close_white_48.png differ diff --git a/app/src/main/res/drawable-xxhdpi/baseline_close_black_18.png b/app/src/main/res/drawable-xxhdpi/baseline_close_black_18.png new file mode 100755 index 0000000000..bb6db33716 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/baseline_close_black_18.png differ diff --git a/app/src/main/res/drawable-xxhdpi/baseline_close_black_24.png b/app/src/main/res/drawable-xxhdpi/baseline_close_black_24.png new file mode 100755 index 0000000000..bba526c5de Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/baseline_close_black_24.png differ diff --git a/app/src/main/res/drawable-xxhdpi/baseline_close_black_36.png b/app/src/main/res/drawable-xxhdpi/baseline_close_black_36.png new file mode 100755 index 0000000000..06876a2d4b Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/baseline_close_black_36.png differ diff --git a/app/src/main/res/drawable-xxhdpi/baseline_close_black_48.png b/app/src/main/res/drawable-xxhdpi/baseline_close_black_48.png new file mode 100755 index 0000000000..9b84716a99 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/baseline_close_black_48.png differ diff --git a/app/src/main/res/drawable-xxhdpi/baseline_close_white_18.png b/app/src/main/res/drawable-xxhdpi/baseline_close_white_18.png new file mode 100755 index 0000000000..0419ecc522 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/baseline_close_white_18.png differ diff --git a/app/src/main/res/drawable-xxhdpi/baseline_close_white_24.png b/app/src/main/res/drawable-xxhdpi/baseline_close_white_24.png new file mode 100755 index 0000000000..3af97b1df7 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/baseline_close_white_24.png differ diff --git a/app/src/main/res/drawable-xxhdpi/baseline_close_white_36.png b/app/src/main/res/drawable-xxhdpi/baseline_close_white_36.png new file mode 100755 index 0000000000..3d430758d6 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/baseline_close_white_36.png differ diff --git a/app/src/main/res/drawable-xxhdpi/baseline_close_white_48.png b/app/src/main/res/drawable-xxhdpi/baseline_close_white_48.png new file mode 100755 index 0000000000..412081ebf3 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/baseline_close_white_48.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/baseline_close_black_18.png b/app/src/main/res/drawable-xxxhdpi/baseline_close_black_18.png new file mode 100755 index 0000000000..bba526c5de Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/baseline_close_black_18.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/baseline_close_black_24.png b/app/src/main/res/drawable-xxxhdpi/baseline_close_black_24.png new file mode 100755 index 0000000000..f8c6a0c691 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/baseline_close_black_24.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/baseline_close_black_36.png b/app/src/main/res/drawable-xxxhdpi/baseline_close_black_36.png new file mode 100755 index 0000000000..9b84716a99 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/baseline_close_black_36.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/baseline_close_black_48.png b/app/src/main/res/drawable-xxxhdpi/baseline_close_black_48.png new file mode 100755 index 0000000000..6d1fb36e46 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/baseline_close_black_48.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/baseline_close_white_18.png b/app/src/main/res/drawable-xxxhdpi/baseline_close_white_18.png new file mode 100755 index 0000000000..3af97b1df7 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/baseline_close_white_18.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/baseline_close_white_24.png b/app/src/main/res/drawable-xxxhdpi/baseline_close_white_24.png new file mode 100755 index 0000000000..f5a903fd73 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/baseline_close_white_24.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/baseline_close_white_36.png b/app/src/main/res/drawable-xxxhdpi/baseline_close_white_36.png new file mode 100755 index 0000000000..412081ebf3 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/baseline_close_white_36.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/baseline_close_white_48.png b/app/src/main/res/drawable-xxxhdpi/baseline_close_white_48.png new file mode 100755 index 0000000000..0c02f3556c Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/baseline_close_white_48.png differ diff --git a/app/src/main/res/drawable/baseline_close_24.xml b/app/src/main/res/drawable/baseline_close_24.xml new file mode 100755 index 0000000000..3f9973200c --- /dev/null +++ b/app/src/main/res/drawable/baseline_close_24.xml @@ -0,0 +1,10 @@ + + +