From c04ce42effb8bcf5092c67e28500210647cf45c6 Mon Sep 17 00:00:00 2001 From: M66B Date: Wed, 15 Aug 2018 19:34:22 +0000 Subject: [PATCH] Refactored account/folder monitoring --- .../eu/faircode/email/AdapterAccount.java | 2 + .../java/eu/faircode/email/EntityFolder.java | 3 +- .../eu/faircode/email/ServiceSynchronize.java | 758 +++++++++--------- .../drawable-hdpi/baseline_close_black_18.png | Bin 0 -> 220 bytes .../drawable-hdpi/baseline_close_black_24.png | Bin 0 -> 219 bytes .../drawable-hdpi/baseline_close_black_36.png | Bin 0 -> 319 bytes .../drawable-hdpi/baseline_close_black_48.png | Bin 0 -> 311 bytes .../drawable-hdpi/baseline_close_white_18.png | Bin 0 -> 228 bytes .../drawable-hdpi/baseline_close_white_24.png | Bin 0 -> 225 bytes .../drawable-hdpi/baseline_close_white_36.png | Bin 0 -> 312 bytes .../drawable-hdpi/baseline_close_white_48.png | Bin 0 -> 312 bytes .../drawable-mdpi/baseline_close_black_18.png | Bin 0 -> 143 bytes .../drawable-mdpi/baseline_close_black_24.png | Bin 0 -> 176 bytes .../drawable-mdpi/baseline_close_black_36.png | Bin 0 -> 219 bytes .../drawable-mdpi/baseline_close_black_48.png | Bin 0 -> 253 bytes .../drawable-mdpi/baseline_close_white_18.png | Bin 0 -> 146 bytes .../drawable-mdpi/baseline_close_white_24.png | Bin 0 -> 180 bytes .../drawable-mdpi/baseline_close_white_36.png | Bin 0 -> 225 bytes .../drawable-mdpi/baseline_close_white_48.png | Bin 0 -> 251 bytes .../baseline_close_black_18.png | Bin 0 -> 219 bytes .../baseline_close_black_24.png | Bin 0 -> 253 bytes .../baseline_close_black_36.png | Bin 0 -> 311 bytes .../baseline_close_black_48.png | Bin 0 -> 389 bytes .../baseline_close_white_18.png | Bin 0 -> 225 bytes .../baseline_close_white_24.png | Bin 0 -> 251 bytes .../baseline_close_white_36.png | Bin 0 -> 312 bytes .../baseline_close_white_48.png | Bin 0 -> 389 bytes .../baseline_close_black_18.png | Bin 0 -> 319 bytes .../baseline_close_black_24.png | Bin 0 -> 311 bytes .../baseline_close_black_36.png | Bin 0 -> 449 bytes .../baseline_close_black_48.png | Bin 0 -> 544 bytes .../baseline_close_white_18.png | Bin 0 -> 312 bytes .../baseline_close_white_24.png | Bin 0 -> 312 bytes .../baseline_close_white_36.png | Bin 0 -> 449 bytes .../baseline_close_white_48.png | Bin 0 -> 544 bytes .../baseline_close_black_18.png | Bin 0 -> 311 bytes .../baseline_close_black_24.png | Bin 0 -> 389 bytes .../baseline_close_black_36.png | Bin 0 -> 544 bytes .../baseline_close_black_48.png | Bin 0 -> 738 bytes .../baseline_close_white_18.png | Bin 0 -> 312 bytes .../baseline_close_white_24.png | Bin 0 -> 389 bytes .../baseline_close_white_36.png | Bin 0 -> 544 bytes .../baseline_close_white_48.png | Bin 0 -> 747 bytes .../main/res/drawable/baseline_close_24.xml | 10 + 44 files changed, 372 insertions(+), 401 deletions(-) create mode 100755 app/src/main/res/drawable-hdpi/baseline_close_black_18.png create mode 100755 app/src/main/res/drawable-hdpi/baseline_close_black_24.png create mode 100755 app/src/main/res/drawable-hdpi/baseline_close_black_36.png create mode 100755 app/src/main/res/drawable-hdpi/baseline_close_black_48.png create mode 100755 app/src/main/res/drawable-hdpi/baseline_close_white_18.png create mode 100755 app/src/main/res/drawable-hdpi/baseline_close_white_24.png create mode 100755 app/src/main/res/drawable-hdpi/baseline_close_white_36.png create mode 100755 app/src/main/res/drawable-hdpi/baseline_close_white_48.png create mode 100755 app/src/main/res/drawable-mdpi/baseline_close_black_18.png create mode 100755 app/src/main/res/drawable-mdpi/baseline_close_black_24.png create mode 100755 app/src/main/res/drawable-mdpi/baseline_close_black_36.png create mode 100755 app/src/main/res/drawable-mdpi/baseline_close_black_48.png create mode 100755 app/src/main/res/drawable-mdpi/baseline_close_white_18.png create mode 100755 app/src/main/res/drawable-mdpi/baseline_close_white_24.png create mode 100755 app/src/main/res/drawable-mdpi/baseline_close_white_36.png create mode 100755 app/src/main/res/drawable-mdpi/baseline_close_white_48.png create mode 100755 app/src/main/res/drawable-xhdpi/baseline_close_black_18.png create mode 100755 app/src/main/res/drawable-xhdpi/baseline_close_black_24.png create mode 100755 app/src/main/res/drawable-xhdpi/baseline_close_black_36.png create mode 100755 app/src/main/res/drawable-xhdpi/baseline_close_black_48.png create mode 100755 app/src/main/res/drawable-xhdpi/baseline_close_white_18.png create mode 100755 app/src/main/res/drawable-xhdpi/baseline_close_white_24.png create mode 100755 app/src/main/res/drawable-xhdpi/baseline_close_white_36.png create mode 100755 app/src/main/res/drawable-xhdpi/baseline_close_white_48.png create mode 100755 app/src/main/res/drawable-xxhdpi/baseline_close_black_18.png create mode 100755 app/src/main/res/drawable-xxhdpi/baseline_close_black_24.png create mode 100755 app/src/main/res/drawable-xxhdpi/baseline_close_black_36.png create mode 100755 app/src/main/res/drawable-xxhdpi/baseline_close_black_48.png create mode 100755 app/src/main/res/drawable-xxhdpi/baseline_close_white_18.png create mode 100755 app/src/main/res/drawable-xxhdpi/baseline_close_white_24.png create mode 100755 app/src/main/res/drawable-xxhdpi/baseline_close_white_36.png create mode 100755 app/src/main/res/drawable-xxhdpi/baseline_close_white_48.png create mode 100755 app/src/main/res/drawable-xxxhdpi/baseline_close_black_18.png create mode 100755 app/src/main/res/drawable-xxxhdpi/baseline_close_black_24.png create mode 100755 app/src/main/res/drawable-xxxhdpi/baseline_close_black_36.png create mode 100755 app/src/main/res/drawable-xxxhdpi/baseline_close_black_48.png create mode 100755 app/src/main/res/drawable-xxxhdpi/baseline_close_white_18.png create mode 100755 app/src/main/res/drawable-xxxhdpi/baseline_close_white_24.png create mode 100755 app/src/main/res/drawable-xxxhdpi/baseline_close_white_36.png create mode 100755 app/src/main/res/drawable-xxxhdpi/baseline_close_white_48.png create mode 100755 app/src/main/res/drawable/baseline_close_24.xml 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<>(); - final ExecutorService executor = Executors.newSingleThreadExecutor(); - + private void monitorAccount(final EntityAccount account, final ServiceState state) throws NoSuchProviderException { Log.i(Helper.TAG, account.name + " start"); - final DB db = DB.getInstance(ServiceSynchronize.this); + final DB db = DB.getInstance(this); + final ExecutorService executor = Executors.newSingleThreadExecutor(); + + 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 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,61 +392,263 @@ 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"); + } + + @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); + + backoff = CONNECT_BACKOFF_START; + db.account().setAccountState(account.id, "connected"); + db.account().setAccountError(account.id, null); + + // Update folder list + try { + synchronizeFolders(account, istore); + } catch (MessagingException ex) { + // Don't show to user + throw new IllegalStateException("synchronize folders", ex); + } + + // Synchronize folders + for (final EntityFolder folder : db.folder().getFolders(account.id, true)) + try { + Log.i(Helper.TAG, account.name + " sync folder " + folder.name); + + db.folder().setFolderState(folder.id, "connecting"); + + 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)); + + } + } 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)); + + 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)); + + 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)); + + 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; + } + }); + + 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.folder().setFolderError(folder.id, Helper.formatThrowable(ex)); + + 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); + + db.folder().setFolderError(folder.id, Helper.formatThrowable(ex)); + + 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 onReceive(Context context, Intent intent) { + final long fid = intent.getLongExtra("folder", -1); + //Log.v(Helper.TAG, "run operations folder=" + fid); try { - synchronizeFolders(account, fstore); + 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; + } - for (final EntityFolder folder : db.folder().getFolders(account.id, true)) { - Log.i(Helper.TAG, account.name + " sync folder " + folder.name); + final boolean shouldClose = (ifolder == null); - // Monitor folders - Thread t = new Thread(new Runnable() { - @Override - public void run() { - IMAPFolder ifolder = null; - try { - Log.i(Helper.TAG, folder.name + " start"); + try { + if (folder == null) + throw new IllegalArgumentException("Unknown folder=" + fid); - db.folder().setFolderState(folder.id, "connecting"); + if (shouldClose) + Log.v(Helper.TAG, folder.name + " start operations offline=" + shouldClose); - ifolder = (IMAPFolder) fstore.getFolder(folder.name); + if (ifolder == null) { + // Prevent unnecessary folder connections + if (db.operation().getOperationCount(fid) == 0) + return; + + ifolder = (IMAPFolder) istore.getFolder(folder.name); ifolder.open(Folder.READ_WRITE); + } - db.folder().setFolderState(folder.id, "connected"); - db.folder().setFolderError(folder.id, null); - - synchronized (mapFolder) { - mapFolder.put(folder.id, ifolder); - } - - monitorFolder(account, folder, fstore, ifolder, state); - - } catch (Throwable ex) { - // MessagingException - // - message: connection failure - // - event: Too many simultaneous connections. (Failure) - // TODO: retry? - - 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(); - } - } finally { + 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); @@ -444,181 +656,80 @@ public class ServiceSynchronize extends LifecycleService { Log.w(Helper.TAG, folder.name + " " + ex + "\n" + Log.getStackTraceString(ex)); } } - - db.folder().setFolderState(folder.id, null); - - Log.i(Helper.TAG, folder.name + " stopped"); - } + //Log.v(Helper.TAG, folder.name + " stop operations"); } - }, "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); - - // 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)); - - } 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)); - - // Check connection - synchronized (state) { - state.notifyAll(); - } + } + }); + } catch (RejectedExecutionException ex) { + Log.w(Helper.TAG, ex + "\n" + Log.getStackTraceString(ex)); } } + }; - @Override - public void disconnected(ConnectionEvent e) { - Log.e(Helper.TAG, account.name + " disconnected event"); + // 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)); - db.account().setAccountState(account.id, null); + // 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)); - LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(ServiceSynchronize.this); - lbm.unregisterReceiver(processReceiver); - - synchronized (mapFolder) { - mapFolder.clear(); - } - - // Check connection - synchronized (state) { - state.notifyAll(); - } - } - - @Override - public void closed(ConnectionEvent e) { - Log.e(Helper.TAG, account.name + " closed event"); - - db.account().setAccountState(account.id, null); - - LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(ServiceSynchronize.this); - lbm.unregisterReceiver(processReceiver); - - synchronized (mapFolder) { - mapFolder.clear(); - } - - // Check connection - synchronized (state) { - state.notifyAll(); - } - } - - private BroadcastReceiver processReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - final long fid = intent.getLongExtra("folder", -1); - - IMAPFolder ifolder; - synchronized (mapFolder) { - ifolder = mapFolder.get(fid); - } - final boolean shouldClose = (ifolder == null); - final IMAPFolder ffolder = ifolder; - - 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 (ifolder == null) { - // Prevent unnecessary folder connections - if (db.operation().getOperationCount(fid) == 0) - return; - - ifolder = (IMAPFolder) fstore.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)); - } - } - Log.v(Helper.TAG, folder.name + " stop operations"); - } - } - }); - } 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 { + // 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 0000000000000000000000000000000000000000..f80273a4dde97fcf217815f3bfd30c01a58c69bb GIT binary patch literal 220 zcmeAS@N?(olHy`uVBq!ia0vp^(jd&i0wmS%+S~(Di#=T&Ln;{GUSMQn3KVI(7|h|o zC?TTKnJ|fAOUIT?-!3*e33zp~Bs`j!vQCn};U|N|yRX@+vsLmm_-xtLTdP_(H_zM9 z*?uZgaKWj=E^}NN_Gd7@-5`4uhzjOjSS!f7!7D>;@!lhE6#hqj>lTlme#dd$vKhOc zovAlxKXcbvV!`sI@!sdtEc{+9{SyCj>Vt@g`xkG!?d{#Zc%KEccHz>-WxMQ_E)2eF TK5?B7(76nru6{1-oD!M|k0wldT1B8LpB2O2`kP61PQ?@cP8!{YuvhmOc z-(B++0%~8r^A3+RKC`AEb*J%(nmLnCe&MiDx^Q%1f@9SJE|q6I0=IO`R15pJybg5N zD3{gF3w#^pE%PY$YMgN4xsZ>#y*yp31Vc4%+b;TUJc&EtP+Y42Ax+j{Cm-7d)?L1|__W$4VKecDDzsXQ+CO0KGr}dV?4dMno39+)WNM2kDr9hJ6Px^JBK{j5$ejb8{;a z(Gp9Dcc9Raf>c9cPp}h(VqB2=%#lD_ij8q0%4Qw89OuD_*B`jVb#TTROTuMGB^bx$ zFVt-uW!#0*&yB32tcBw$d{ux7au3HPaWKA0-d9{&l5)5$iuO6Zv@R*8=^* zGtpJfQ;l{>-Hl$qH~;&wc(0w*SgQhRnnmj_69PDsm4;myXaGjRC>RB!007-ZW~r_~ Rz_I`U002ovPDHLkV1n~ug(v_3 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..bba526c5de6d301a894624376b2f30fe81a7c86e GIT binary patch literal 311 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY0wn)GsXhaw{&~7MhEy=Vy~fCP*nz?2V&fLe zaL%1MQ$0+X_J#@HxYUuv{fFo1pPdJodxE?)mrnV&(XZK0$yDm9{PD<86ULy$k*?mZ zOmmn#q?gxtOmDVm*i!gyuSRA-AmbAK^!vUm!h(I7=0x_UC>Zw5Jz`mRNwR=v$>wk21zf{!z7VpKQTCK}N>G zs94RHO=#D2E|lhc#GmEi^J4;#MY-@+bc81e1zbU)f0prgnu zIxo$T`GNo!3-iX8C+-C-@R8=cKC$7o$H8vr^R6lid~@IY-_iU!ZPVB6n`T#vmfrm} zq3=jfi=T6ERZ#K7OWYb4J@V$CkQ7&(=q%h?>w54>!jhT(XMHrMO`0inzl3FuubX3n c_uIV;TCDeiPA_;j0qAT7Pgg&ebxsLQ0BoIFwg3PC literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..c8598b6133402c8340f380933c524e1006f519c7 GIT binary patch literal 225 zcmV<703QE|P)Sm|Y(Fi((z49x@&WzPJROD`99q{scOE=lr&1wkAH=JFpIpadtQ99W5u zDMsZ76f+bbIVQ>%L`*LKQDEZFOd+Ops;H51{_Vu8q;h|{C7_rMd4r6d7@n!rMPLsY zB{T`CAL@5%C}zhF6U8*x{<{0t@Id+>`6(|G{pq&zjO@h63=^9_Y?*&zu%|X_=kZ_4 bvm`kJ!LV3i&qsp>00000NkvXXu0mjfvENx_ literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..0419ecc5227c71173779d8ea2ba8c9d6f21afb12 GIT binary patch literal 312 zcmV-80muG{P)4KySp)c`9iwFgknMgeRs)B;U81EtL@%W))k{>_m%lRJv$z)`(Z`t<;= z`a)d>QOcdjU8sC0jnM};mX8=4C?j`ME>kyhC#4*?J~fhF!?{{sQ9Dq|wd5Yu)I4mI zA*wg7qw72iI^iXl~Soxa_9*z0bS0M5ep>%0000< KMNUMnLSTYMf_~`$ literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..3af97b1df73d790974b9eb916b4a9fcf2b64eea8 GIT binary patch literal 312 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY0wn)GsXhauJY5_^Dj45hYh*i=AmR4V@oK5- zn>69zSqpAwH4BTKkuUfrw6B#c>c!6xxJ>IN6{k0&Z} z@*OymY0by&S-@~_(h-IYYCwUjjeR}3o(1#XupFOgb9}Omy6d9^i+NXy;^sZ}oF}YM z*{2@q*N|e#IO!>iN0+d|T~&35Pcr_W=GdGpw$VIq_&fEU^?{En=Pxc{I6j$a&O}28 zqu1vQ9g1GhQ{eRLd2-Fxsp$1P&v}#8CHvGX{pO`u&U<26Iq7MOhfQ5E%gaX&w!xkP619lNi|!81T5>SM&

+o^be3dijj`cn6ew?!Obfdt>Efq?K9H%=tdh|Z> zHffd67w?7tu6#A|edTjeHgd-*4ncP1L&+R&OV%j=&95q1bGUoAn}70Sv%dVq ayts{5T(${1MLY*Ofx*+&&t;ucLK6U88bxma literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..858996e483eb9c24bb22864fff8afedc26d64f12 GIT binary patch literal 219 zcmeAS@N?(olHy`uVBq!ia0vp^Dj>|k0wldT1B8LpB2O2`kP61PQ?@cP8!{YuvhmOc z-(B++0%~8r^A3+RKC`AEb*J%(nmLnCe&MiDx^Q%1f@9SJE|q6I0=IO`R15pJybg5N zD3{gF3w#^pE%PY$YMgN4xsZ>#y*yp31Vc4%+b;TUJc&EtP+Y42Ax+j{Cm-7d)?L1|__W$4V_Ti4{Y|SeejaW~Wj_ zg;A*N%*cGG*H>7$W1+Ow8#0O9emZXy3ujxIN#Ixb<8_4|uPe^3Nd9q0@f7=S#Ls@qQ@H|y5biZ4@U6|;+oCxd00000NkvXXu0mjf DO`2ya literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..1f7ab2851786e2e13600f719c747e4d5103a5ddb GIT binary patch literal 146 zcmeAS@N?(olHy`uVBq!ia0vp^LLkh+1|-AI^@RheNKY5XkP60R3C9Qj|Nmd-vZ7FB z$$_`_EL$E2UgisZ>~dm5-HgNvo|o;N6LTaIojIh#_>b_uXq%9A!(>8U7mwo(u0(;Q tZ6*TWq@FhVR(`o`>bOg2YOfm~(Bvz(7I*$Qw-IOwgQu&X%Q~loCIEd>HEsX^ literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..08d810992391502761a8cf4e5672755306a7d2b4 GIT binary patch literal 180 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM0wlfaz7_+iYEKu(kP619lNi|!81T53cLa&# z-1w_+kh58ur*+l5C%uQxEx zP^h6rn@N&WvQVgV$+9om3UVDPcULI+xE*+X_SOMihvFqGl)viVwafnh*y4Xw^3zo_ e^5-49#+-0qhm+2)FVa9~FnGH9xvXSm|Y(Fi((z49x@&WzPJROD`99q{scOE=lr&1wkAH=JFpIpadtQ99W5u zDMsZ76f+bbIVQ>%L`*LKQDEZFOd+Ops;H51{_Vu8q;h|{C7_rMd4r6d7@n!rMPLsY zB{T`CAL@5%C}zhF6U8*x{<{0t@Id+>`6(|G{pq&zjO@h63=^9_Y?*&zu%|X_=kZ_4 bvm`kJ!LV3i&qsp>00000NkvXXu0mjfvENx_ literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..4419c31ab3d5fc616f466698316a1b724dff7173 GIT binary patch literal 251 zcmV!36gH;NVP*#! zMT~F2&a4aGB#kvLV5N~fb-}_(QduwJE6K?c;VblbU!lkAib_&h{CN3=Wx21ApU+oV z@oJ@~!i*PsH90tiCiE4FFh(rJ*N@i(O&0WjBE60d4*!#-PSDG=f1~vu75=d6KdyT% zoDM6~So+OS@Y0)!Vw^43;q_tu=jr%hirX?641T{&TNwT|%`pG~002ovPDHLkV1hQ< BY%u@; literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..858996e483eb9c24bb22864fff8afedc26d64f12 GIT binary patch literal 219 zcmeAS@N?(olHy`uVBq!ia0vp^Dj>|k0wldT1B8LpB2O2`kP61PQ?@cP8!{YuvhmOc z-(B++0%~8r^A3+RKC`AEb*J%(nmLnCe&MiDx^Q%1f@9SJE|q6I0=IO`R15pJybg5N zD3{gF3w#^pE%PY$YMgN4xsZ>#y*yp31Vc4%+b;TUJc&EtP+Y42Ax+j{Cm-7d)?L1|__W$4V_Ti4{Y|SeejaW~Wj_ zg;A*N%*cGG*H>7$W1+Ow8#0O9emZXy3ujxIN#Ixb<8_4|uPe^3Nd9q0@f7=S#Ls@qQ@H|y5biZ4@U6|;+oCxd00000NkvXXu0mjf DO`2ya literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..bba526c5de6d301a894624376b2f30fe81a7c86e GIT binary patch literal 311 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY0wn)GsXhaw{&~7MhEy=Vy~fCP*nz?2V&fLe zaL%1MQ$0+X_J#@HxYUuv{fFo1pPdJodxE?)mrnV&(XZK0$yDm9{PD<86ULy$k*?mZ zOmmn#q?gxtOmDVm*i!gyuSRA-AmbAK^!vUm!h(I7=0x_UC>Zw5Jz`mRNwR=v$>wk21zf{!z7VpKQTCK}N>G zs94RHO=#Db(!f0rM4{h4!*-Mj;@7XztT2VMtr9#Gp*c}REzZ~m+gS3~42=3Mx7AqQdr*qmFh ziy7q|8s-c9QvQ}Nav@!q;jiiZvqCc@)eVA6t$AfKY=X)PR+O)4;@>8t`_1r9>7H}( z1rSm|Y(Fi((z49x@&WzPJROD`99q{scOE=lr&1wkAH=JFpIpadtQ99W5u zDMsZ76f+bbIVQ>%L`*LKQDEZFOd+Ops;H51{_Vu8q;h|{C7_rMd4r6d7@n!rMPLsY zB{T`CAL@5%C}zhF6U8*x{<{0t@Id+>`6(|G{pq&zjO@h63=^9_Y?*&zu%|X_=kZ_4 bvm`kJ!LV3i&qsp>00000NkvXXu0mjfvENx_ literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..4419c31ab3d5fc616f466698316a1b724dff7173 GIT binary patch literal 251 zcmV!36gH;NVP*#! zMT~F2&a4aGB#kvLV5N~fb-}_(QduwJE6K?c;VblbU!lkAib_&h{CN3=Wx21ApU+oV z@oJ@~!i*PsH90tiCiE4FFh(rJ*N@i(O&0WjBE60d4*!#-PSDG=f1~vu75=d6KdyT% zoDM6~So+OS@Y0)!Vw^43;q_tu=jr%hirX?641T{&TNwT|%`pG~002ovPDHLkV1hQ< BY%u@; literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..3af97b1df73d790974b9eb916b4a9fcf2b64eea8 GIT binary patch literal 312 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY0wn)GsXhauJY5_^Dj45hYh*i=AmR4V@oK5- zn>69zSqpAwH4BTKkuUfrw6B#c>c!6xxJ>IN6{k0&Z} z@*OymY0by&S-@~_(h-IYYCwUjjeR}3o(1#XupFOgb9}Omy6d9^i+NXy;^sZ}oF}YM z*{2@q*N|e#IO!>iN0+d|T~&35Pcr_W=GdGpw$VIq_&fEU^?{En=Pxc{I6j$a&O}28 zqu1vQ9g1GhQ{eRLd2-Fxsp$1P&v}#8CHvGX{pO`u&U<26Iq7MOhfQ5E%gaX&w!x=0V z+WnV3FS*t-|LtBfxh(f<|D+Jk*RQ=VZH#Q%w)DNa&DHfSxzc}|x$Ga#tSiizuwZXj zIOo2)jq&ygSN7C3wYC4+>~_DR@=(GH|9$)B8)S04U>D-~Thp|DzW;}>pMfgv93MOX zUD>!#@lng~AMu;k!o?3>Kd=7d*H4z$0yF$COfTm-uYTv#uX|5b*5}9a*P@v4AcnEV z=}(7o{bELtxczG>-w)|?)~BzMS-$gv%lauXuO=spvPEyasC4-5fr`28eSd_7`)hut zhso_aE17QRXy5klH|PENTk?!`BDW_#)ZABe>{9$fzyCkCdP9PPVFzKecDDzsXQ+CO0KGr}dV?4dMno39+)WNM2kDr9hJ6Px^JBK{j5$ejb8{;a z(Gp9Dcc9Raf>c9cPp}h(VqB2=%#lD_ij8q0%4Qw89OuD_*B`jVb#TTROTuMGB^bx$ zFVt-uW!#0*&yB32tcBw$d{ux7au3HPaWKA0-d9{&l5)5$iuO6Zv@R*8=^* zGtpJfQ;l{>-Hl$qH~;&wc(0w*SgQhRnnmj_69PDsm4;myXaGjRC>RB!007-ZW~r_~ Rz_I`U002ovPDHLkV1n~ug(v_3 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..bba526c5de6d301a894624376b2f30fe81a7c86e GIT binary patch literal 311 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY0wn)GsXhaw{&~7MhEy=Vy~fCP*nz?2V&fLe zaL%1MQ$0+X_J#@HxYUuv{fFo1pPdJodxE?)mrnV&(XZK0$yDm9{PD<86ULy$k*?mZ zOmmn#q?gxtOmDVm*i!gyuSRA-AmbAK^!vUm!h(I7=0x_UC>Zw5Jz`mRNwR=v$>wk21zf{!z7VpKQTCK}N>G zs94RHO=#Du60*k z3+9TQJ-8)ovskIl>y=AHJWj7!vP9kewQ;sEo7Ji66Io zl;5)JNK)qG_KdIJ)IYAd#J}pH|HDk1?&TkKrm<~ch}x3VBb$AmPx8%<|df*wujQ?GB zl|M@Ee-O|3=Qd;c!!?eF_htS`?v^*%Yx3aeoL_TJ=WlhZJ9x$W&b=uQwolyi@%h@Q zjN;lPv+F+#J3bw~%d8`3dF%0n8OMLwbW}RKr9bbzc=Ptf_PMt|ZPun|YNw)W8-P*6;OXk;vd$@?2>?aI+4cYc literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..9b84716a99b2dddd0f283099ae8ab9b7d278b319 GIT binary patch literal 544 zcmeAS@N?(olHy`uVBq!ia0vp^6F``S8Ays|{O<-*S^+*Gu0Wbdz$JXH1E`d*B*-tA zLH>x0w8E4B4<0_=e{Y}7jHd?}7#OE}x;TbZFuuK}=;s_L;Px={(B~C-43lo@H9ja{ zx%wdBE@Lp)3iTTf!eI_K)OKdwd1>9%Wmz@p=M(w6ORtv3UN61-O08*keUIu|{@VxE z?W-4S{AS7iZNhOSMvdYL98)4ym{QO2oU+NA-oV!B__fP%rg7sd(WVFM9G-j5ZrRP0 zx{rOkx00xa|3Tk)7p)7oV=E@H8+JOnd{R)T6qsPc;UUMeWGWM5X0C%n)LY3G6;7r} z%#5B*4Nn{$k|rn^dQ6Z|@!%0sVdE5PX6bZnI-=m{G(n-(03tpKSsbWbQ{YVh9A}o# z%wbCz6g?Yes5tltDX4J@OlIluWGXqZP}^boT&068b7eTy)+irrah=3q2vn5FVdQ&MBb@09LQGcmMzZ literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..0419ecc5227c71173779d8ea2ba8c9d6f21afb12 GIT binary patch literal 312 zcmV-80muG{P)4KySp)c`9iwFgknMgeRs)B;U81EtL@%W))k{>_m%lRJv$z)`(Z`t<;= z`a)d>QOcdjU8sC0jnM};mX8=4C?j`ME>kyhC#4*?J~fhF!?{{sQ9Dq|wd5Yu)I4mI zA*wg7qw72iI^iXl~Soxa_9*z0bS0M5ep>%0000< KMNUMnLSTYMf_~`$ literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..3af97b1df73d790974b9eb916b4a9fcf2b64eea8 GIT binary patch literal 312 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY0wn)GsXhauJY5_^Dj45hYh*i=AmR4V@oK5- zn>69zSqpAwH4BTKkuUfrw6B#c>c!6xxJ>IN6{k0&Z} z@*OymY0by&S-@~_(h-IYYCwUjjeR}3o(1#XupFOgb9}Omy6d9^i+NXy;^sZ}oF}YM z*{2@q*N|e#IO!>iN0+d|T~&35Pcr_W=GdGpw$VIq_&fEU^?{En=Pxc{I6j$a&O}28 zqu1vQ9g1GhQ{eRLd2-Fxsp$1P&v}#8CHvGX{pO`u&U<26Iq7MOhfQ5E%gaX&w!x6g z!eg-7$@;wW=?|x;CWQTE=Y11eA$1<8j(ZuSPQx|*PSeA0t!&RT-IIRhf6(>PtM|Iz zOBme+kaO=oshN#z9>u0b5El@w7lyLcv!T+v% zHhb=-AGKfbQ#^5A+vYw?rY)+q+kPy3d8wh>QgX&q^LeFg;y$sD%7UBjyA?e?zc#9@ z`0t^db)O##d~M&&tkWm+_pqYj@n14W&hSajo%?B(dzrU*46+3 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..412081ebf31f5a3010a22f3b5ed66b1ea078e3d4 GIT binary patch literal 544 zcmeAS@N?(olHy`uVBq!ia0vp^6F``S8Ays|{O<-*S^+*Gu0Z-faiDGYDSe=7zLFrn zUle9HZz_cU|?XJ?&;zfQo;E4nxda`pn%)M%tN17< zShufUtnr&A`?m?ll^8XOCvZ%ORAEXz$8*XiZ+Zh;r{mWy$C<{BuSA<3taEtoIlE;y zQ|dnU?cPeF8vX};<6X2a+>Wi7#BSK>=<-QHp;BOi4Tpyu%aW-~jG4I(4pDCZw5Jz`mRNwR=v$>wk21zf{!z7VpKQTCK}N>G zs94RHO=#Db(!f0rM4{h4!*-Mj;@7XztT2VMtr9#Gp*c}REzZ~m+gS3~42=3Mx7AqQdr*qmFh ziy7q|8s-c9QvQ}Nav@!q;jiiZvqCc@)eVA6t$AfKY=X)PR+O)4;@>8t`_1r9>7H}( z1rx0w8E4B4<0_=e{Y}7jHd?}7#OE}x;TbZFuuK}=;s_L;Px={(B~C-43lo@H9ja{ zx%wdBE@Lp)3iTTf!eI_K)OKdwd1>9%Wmz@p=M(w6ORtv3UN61-O08*keUIu|{@VxE z?W-4S{AS7iZNhOSMvdYL98)4ym{QO2oU+NA-oV!B__fP%rg7sd(WVFM9G-j5ZrRP0 zx{rOkx00xa|3Tk)7p)7oV=E@H8+JOnd{R)T6qsPc;UUMeWGWM5X0C%n)LY3G6;7r} z%#5B*4Nn{$k|rn^dQ6Z|@!%0sVdE5PX6bZnI-=m{G(n-(03tpKSsbWbQ{YVh9A}o# z%wbCz6g?Yes5tltDX4J@OlIluWGXqZP}^boT&068b7eTy)+irrah=3q2vn5FVdQ&MBb@09LQGcmMzZ literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..6d1fb36e46ed7c4113bc5483b4cc0983a465298c GIT binary patch literal 738 zcmeAS@N?(olHy`uVBq!ia0vp^2SAvE1xWt5x}=AJfoY|ui(^OyFpI(7Rq_Gb$d}n6YDSQgDijNCfLV=)$P?Q7OKhoD=f6--%X$) z_wT)^X?pr=xq8K_*Y)e~&sA9Q|9q3~zcBfErF*}yJwM-ME%SG8Tvn_qFYH1W zI>}WDXfcc`uR&Wg8hs41THy+ z0-sCrd$gW^^`3BD-!X1K%TMVBp(pu`-VXH&jDKIUX#d*GS$Cxi*?@+BHB7(WpEqVCX}-LqL%kj2-=8dBo&zoSN8;EkADsBFq#>7ef&GMry6KAc z&M*B{3Zkv%ACNajQ7**ttita4=Ho$|^0{5)&n)`y2K3EEmM`sa1BEJlr~W%IgXtG@ zv(<~~2X;vPQZ?Ar^Dl(SSXkAAd!mB=;WsgdcfXu|fL*5MgnQEgnez@zzf?acn*S>1 z4Eu7Neep$mpwDi?+_;~`U-^qUf45N0TK@C<^iF@}x8&B_x?XB>Lc>450-hJ=RWkk_ zc2n8{ipl>oSk~6pHC?k;JGk@fd9Q+f&Hrkeir;2w?6%^+bf0tDYxzkFzWRG!`1M>p z<8QLioT&eQfEv7-f^EZ$Z4R!#X0LMaVeVfnX$mOxfqz=x*4F=$g#{paPgg&ebxsLQ E0C$g1)&Kwi literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..3af97b1df73d790974b9eb916b4a9fcf2b64eea8 GIT binary patch literal 312 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY0wn)GsXhauJY5_^Dj45hYh*i=AmR4V@oK5- zn>69zSqpAwH4BTKkuUfrw6B#c>c!6xxJ>IN6{k0&Z} z@*OymY0by&S-@~_(h-IYYCwUjjeR}3o(1#XupFOgb9}Omy6d9^i+NXy;^sZ}oF}YM z*{2@q*N|e#IO!>iN0+d|T~&35Pcr_W=GdGpw$VIq_&fEU^?{En=Pxc{I6j$a&O}28 zqu1vQ9g1GhQ{eRLd2-Fxsp$1P&v}#8CHvGX{pO`u&U<26Iq7MOhfQ5E%gaX&w!x=0V z+WnV3FS*t-|LtBfxh(f<|D+Jk*RQ=VZH#Q%w)DNa&DHfSxzc}|x$Ga#tSiizuwZXj zIOo2)jq&ygSN7C3wYC4+>~_DR@=(GH|9$)B8)S04U>D-~Thp|DzW;}>pMfgv93MOX zUD>!#@lng~AMu;k!o?3>Kd=7d*H4z$0yF$COfTm-uYTv#uX|5b*5}9a*P@v4AcnEV z=}(7o{bELtxczG>-w)|?)~BzMS-$gv%lauXuO=spvPEyasC4-5fr`28eSd_7`)hut zhso_aE17QRXy5klH|PENTk?!`BDW_#)ZABe>{9$fzyCkCdP9PPVFzle9HZz_cU|?XJ?&;zfQo;E4nxda`pn%)M%tN17< zShufUtnr&A`?m?ll^8XOCvZ%ORAEXz$8*XiZ+Zh;r{mWy$C<{BuSA<3taEtoIlE;y zQ|dnU?cPeF8vX};<6X2a+>Wi7#BSK>=<-QHp;BOi4Tpyu%aW-~jG4I(4pDCk9?SJ&!c(1SYEVUv?E^`?X_f#MRKwS8UH}4|N>UWC%Ary{`V9#dpDT z`9g*>-=F*6mVQpA-Rj+u~yywz>#%vr8TP&#OzWpTICSk#A=BsMoM+_K4eIA8%!_aO_ER-M z`h>nChmApF_N70K`A;kAI3N8~?RY+2p^iDA>4$Csi0koudcx$h`Um~ZKK>Cp^4VM9 zyuPE&eik5S#{cGa=6s#`@_)MII6ek{ungbNGWq9cZRTIkryuxJ$@IA$Xq&1*{geZL z0+~L$gE%wJ%xANR(48LNR8hCT?T=(u9p|U*OI1(&H&k}C02|8k$y)&EsD%FK+UxD6 z3EApCXpa1NSW3tR*=C?K9vFsx-n~p=-hEeDpi_3NI|d{+o%tigQm*DDz@g-*(6+lc z_FirsXH8?X{-=ow8$5!V9Duxuvw?vm{rR}~;+gve8-c!kHeF#>d{aRkXGLZBzuJE) zpLTQXv$Z>L;_v76|1KN9pir;K@q}Ms${$^h zXJ$XS58hF$SJe3F?|lB@kvl-S7xVWEn4E~`_yyEh`$@bh`u?BR1@8Zx3Lc-{(D!iW z{k_{v3Z9&~{9(PVg#ak|2|UT)dw@ff2ayy(ERJYbvq`7iqrZSSp00i_>zopr0J@}4 APXGV_ literal 0 HcmV?d00001 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 @@ + + +