mirror of
https://github.com/M66B/FairEmail.git
synced 2024-12-29 19:25:34 +00:00
Added UI service
Unfinished work
This commit is contained in:
parent
a736035bc9
commit
861cf0eb4a
12 changed files with 1876 additions and 1781 deletions
|
@ -164,6 +164,8 @@
|
|||
|
||||
<service android:name=".ServiceSend" />
|
||||
|
||||
<service android:name=".ServiceUI" />
|
||||
|
||||
<service android:name=".ServiceExternal">
|
||||
<intent-filter>
|
||||
<action android:name="${applicationId}.ENABLE" />
|
||||
|
|
|
@ -292,10 +292,6 @@ public class AdapterFolder extends RecyclerView.Adapter<AdapterFolder.ViewHolder
|
|||
long aid = args.getLong("account");
|
||||
long fid = args.getLong("folder");
|
||||
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
if (!prefs.getBoolean("enabled", true))
|
||||
throw new IllegalStateException(context.getString(R.string.title_sync_disabled));
|
||||
|
||||
ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||
NetworkInfo ni = cm.getActiveNetworkInfo();
|
||||
boolean internet = (ni != null && ni.isConnected());
|
||||
|
@ -314,7 +310,7 @@ public class AdapterFolder extends RecyclerView.Adapter<AdapterFolder.ViewHolder
|
|||
if (account.ondemand) {
|
||||
if (internet) {
|
||||
now = true;
|
||||
ServiceSynchronize.sync(context, fid);
|
||||
ServiceUI.sync(context, fid);
|
||||
} else
|
||||
throw new IllegalArgumentException(context.getString(R.string.title_no_internet));
|
||||
} else {
|
||||
|
@ -341,18 +337,7 @@ public class AdapterFolder extends RecyclerView.Adapter<AdapterFolder.ViewHolder
|
|||
protected void onException(Bundle args, Throwable ex) {
|
||||
if (ex instanceof IllegalArgumentException)
|
||||
Snackbar.make(itemView, ex.getMessage(), Snackbar.LENGTH_LONG).show();
|
||||
else if (ex instanceof IllegalStateException) {
|
||||
Snackbar snackbar = Snackbar.make(itemView, ex.getMessage(), Snackbar.LENGTH_LONG);
|
||||
snackbar.setAction(R.string.title_enable, new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
prefs.edit().putBoolean("enabled", true).apply();
|
||||
ServiceSynchronize.reload(context, "refresh/disabled");
|
||||
}
|
||||
});
|
||||
snackbar.show();
|
||||
} else
|
||||
else
|
||||
Helper.unexpectedError(context, owner, ex);
|
||||
}
|
||||
}.execute(context, owner, args, "folder:sync");
|
||||
|
|
1530
app/src/main/java/eu/faircode/email/Core.java
Normal file
1530
app/src/main/java/eu/faircode/email/Core.java
Normal file
File diff suppressed because it is too large
Load diff
|
@ -175,7 +175,7 @@ public class EntityMessage implements Serializable {
|
|||
static void snooze(Context context, long id, Long wakeup) {
|
||||
Intent snoozed = new Intent(context, ServiceSynchronize.class);
|
||||
snoozed.setAction("snooze:" + id);
|
||||
PendingIntent pi = PendingIntent.getService(context, ServiceSynchronize.PI_SNOOZED, snoozed, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
PendingIntent pi = PendingIntent.getService(context, ServiceUI.PI_SNOOZED, snoozed, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
|
||||
AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
|
||||
if (wakeup == null) {
|
||||
|
|
|
@ -2078,10 +2078,6 @@ public class FragmentCompose extends FragmentBase {
|
|||
if (!attachment.available)
|
||||
throw new IllegalArgumentException(context.getString(R.string.title_attachments_missing));
|
||||
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
if (!prefs.getBoolean("enabled", true))
|
||||
throw new IllegalStateException(context.getString(R.string.title_sync_disabled));
|
||||
|
||||
// Delete draft (cannot move to outbox)
|
||||
EntityOperation.queue(context, db, draft, EntityOperation.DELETE);
|
||||
|
||||
|
@ -2160,18 +2156,7 @@ public class FragmentCompose extends FragmentBase {
|
|||
finish();
|
||||
else if (ex instanceof IllegalArgumentException || ex instanceof AddressException)
|
||||
Snackbar.make(view, ex.getMessage(), Snackbar.LENGTH_LONG).show();
|
||||
else if (ex instanceof IllegalStateException) {
|
||||
Snackbar snackbar = Snackbar.make(view, ex.getMessage(), Snackbar.LENGTH_LONG);
|
||||
snackbar.setAction(R.string.title_enable, new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getContext());
|
||||
prefs.edit().putBoolean("enabled", true).apply();
|
||||
ServiceSynchronize.reload(getContext(), "compose/disabled");
|
||||
}
|
||||
});
|
||||
snackbar.show();
|
||||
} else
|
||||
else
|
||||
Helper.unexpectedError(getContext(), getViewLifecycleOwner(), ex);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -492,10 +492,6 @@ public class FragmentMessages extends FragmentBase {
|
|||
protected Boolean onExecute(Context context, Bundle args) {
|
||||
long fid = args.getLong("folder");
|
||||
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
if (!prefs.getBoolean("enabled", true))
|
||||
throw new IllegalStateException(context.getString(R.string.title_sync_disabled));
|
||||
|
||||
ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||
NetworkInfo ni = cm.getActiveNetworkInfo();
|
||||
boolean internet = (ni != null && ni.isConnected());
|
||||
|
@ -527,7 +523,7 @@ public class FragmentMessages extends FragmentBase {
|
|||
if (account.ondemand) {
|
||||
if (internet) {
|
||||
now = true;
|
||||
ServiceSynchronize.sync(context, folder.id);
|
||||
ServiceUI.sync(context, folder.id);
|
||||
} else
|
||||
nointernet = true;
|
||||
} else {
|
||||
|
@ -561,18 +557,7 @@ public class FragmentMessages extends FragmentBase {
|
|||
|
||||
if (ex instanceof IllegalArgumentException)
|
||||
Snackbar.make(view, ex.getMessage(), Snackbar.LENGTH_LONG).show();
|
||||
else if (ex instanceof IllegalStateException) {
|
||||
Snackbar snackbar = Snackbar.make(view, ex.getMessage(), Snackbar.LENGTH_LONG);
|
||||
snackbar.setAction(R.string.title_enable, new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getContext());
|
||||
prefs.edit().putBoolean("enabled", true).apply();
|
||||
ServiceSynchronize.reload(getContext(), "refresh/disabled");
|
||||
}
|
||||
});
|
||||
snackbar.show();
|
||||
} else
|
||||
else
|
||||
Helper.unexpectedError(getContext(), getViewLifecycleOwner(), ex);
|
||||
}
|
||||
}.execute(FragmentMessages.this, args, "messages:refresh");
|
||||
|
|
|
@ -997,38 +997,4 @@ public class Helper {
|
|||
static String sanitizeFilename(String name) {
|
||||
return (name == null ? null : name.replaceAll("[^a-zA-Z0-9\\.\\-]", "_"));
|
||||
}
|
||||
|
||||
static NotificationCompat.Builder getNotificationError(Context context, String title, Throwable ex) {
|
||||
return getNotificationError(context, "error", title, ex, true);
|
||||
}
|
||||
|
||||
static NotificationCompat.Builder getNotificationError(Context context, String channel, String title, Throwable ex, boolean debug) {
|
||||
// Build pending intent
|
||||
Intent intent = new Intent(context, ActivitySetup.class);
|
||||
if (debug)
|
||||
intent.setAction("error");
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
PendingIntent pi = PendingIntent.getActivity(
|
||||
context, ActivitySetup.REQUEST_ERROR, intent, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
|
||||
// Build notification
|
||||
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, channel);
|
||||
|
||||
builder
|
||||
.setSmallIcon(R.drawable.baseline_warning_white_24)
|
||||
.setContentTitle(context.getString(R.string.title_notification_failed, title))
|
||||
.setContentText(Helper.formatThrowable(ex))
|
||||
.setContentIntent(pi)
|
||||
.setAutoCancel(false)
|
||||
.setShowWhen(true)
|
||||
.setPriority(Notification.PRIORITY_MAX)
|
||||
.setOnlyAlertOnce(true)
|
||||
.setCategory(Notification.CATEGORY_ERROR)
|
||||
.setVisibility(NotificationCompat.VISIBILITY_SECRET);
|
||||
|
||||
builder.setStyle(new NotificationCompat.BigTextStyle()
|
||||
.bigText(Helper.formatThrowable(ex, false, "\n")));
|
||||
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -374,11 +374,11 @@ public class ServiceSend extends LifecycleService {
|
|||
|
||||
long now = new Date().getTime();
|
||||
long delayed = now - message.last_attempt;
|
||||
if (delayed > IDENTITY_ERROR_AFTER * 60 * 1000L) {
|
||||
if (delayed > IDENTITY_ERROR_AFTER * 60 * 1000L || ex instanceof SendFailedException) {
|
||||
Log.i("Reporting send error after=" + delayed);
|
||||
NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
nm.notify("send", message.identity.intValue(),
|
||||
Helper.getNotificationError(this, ident.name, ex).build());
|
||||
Core.getNotificationError(this, ident.name, ex).build());
|
||||
}
|
||||
|
||||
throw ex;
|
||||
|
|
File diff suppressed because it is too large
Load diff
294
app/src/main/java/eu/faircode/email/ServiceUI.java
Normal file
294
app/src/main/java/eu/faircode/email/ServiceUI.java
Normal file
|
@ -0,0 +1,294 @@
|
|||
package eu.faircode.email;
|
||||
|
||||
import android.app.IntentService;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.net.Uri;
|
||||
import android.preference.PreferenceManager;
|
||||
|
||||
import com.sun.mail.imap.IMAPFolder;
|
||||
|
||||
import java.util.Properties;
|
||||
|
||||
import javax.mail.Folder;
|
||||
import javax.mail.MessagingException;
|
||||
import javax.mail.Session;
|
||||
import javax.mail.Store;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
public class ServiceUI extends IntentService {
|
||||
static final int PI_WHY = 1;
|
||||
static final int PI_SUMMARY = 2;
|
||||
static final int PI_CLEAR = 3;
|
||||
static final int PI_SEEN = 4;
|
||||
static final int PI_ARCHIVE = 5;
|
||||
static final int PI_TRASH = 6;
|
||||
static final int PI_IGNORED = 7;
|
||||
static final int PI_SNOOZED = 8;
|
||||
|
||||
public ServiceUI() {
|
||||
this(ServiceUI.class.getName());
|
||||
}
|
||||
|
||||
public ServiceUI(String name) {
|
||||
super(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onHandleIntent(@Nullable Intent intent) {
|
||||
if (intent == null)
|
||||
return;
|
||||
|
||||
// TODO: wakelock?
|
||||
|
||||
String action = intent.getAction();
|
||||
if (action == null)
|
||||
return;
|
||||
|
||||
String[] parts = action.split(":");
|
||||
long id = (parts.length > 1 ? Long.parseLong(parts[1]) : -1);
|
||||
|
||||
switch (parts[0]) {
|
||||
case "why":
|
||||
onWhy();
|
||||
break;
|
||||
|
||||
case "summary":
|
||||
onSummary();
|
||||
break;
|
||||
|
||||
case "clear":
|
||||
onClear();
|
||||
break;
|
||||
|
||||
case "seen":
|
||||
onSeen(id);
|
||||
break;
|
||||
|
||||
case "archive":
|
||||
onArchive(id);
|
||||
break;
|
||||
|
||||
case "trash":
|
||||
onTrash(id);
|
||||
break;
|
||||
|
||||
case "ignore":
|
||||
onIgnore(id);
|
||||
break;
|
||||
|
||||
case "snooze":
|
||||
onSnooze(id);
|
||||
break;
|
||||
|
||||
case "synchronize":
|
||||
// AlarmManager.RTC_WAKEUP
|
||||
onSyncOndemand(id);
|
||||
break;
|
||||
|
||||
default:
|
||||
Log.w("Unknown action: " + parts[0]);
|
||||
}
|
||||
}
|
||||
|
||||
private void onWhy() {
|
||||
Intent why = new Intent(Intent.ACTION_VIEW);
|
||||
why.setData(Uri.parse(Helper.FAQ_URI + "#user-content-faq2"));
|
||||
why.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
|
||||
PackageManager pm = getPackageManager();
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
if (prefs.getBoolean("why", false) || why.resolveActivity(pm) == null) {
|
||||
Intent view = new Intent(this, ActivityView.class);
|
||||
view.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
startActivity(view);
|
||||
} else {
|
||||
prefs.edit().putBoolean("why", true).apply();
|
||||
startActivity(why);
|
||||
}
|
||||
}
|
||||
|
||||
private void onSummary() {
|
||||
DB.getInstance(this).message().ignoreAll();
|
||||
|
||||
Intent view = new Intent(this, ActivityView.class);
|
||||
view.setAction("unified");
|
||||
view.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
startActivity(view);
|
||||
}
|
||||
|
||||
private void onClear() {
|
||||
DB.getInstance(this).message().ignoreAll();
|
||||
}
|
||||
|
||||
private void onSeen(long id) {
|
||||
DB db = DB.getInstance(this);
|
||||
try {
|
||||
db.beginTransaction();
|
||||
|
||||
EntityMessage message = db.message().getMessage(id);
|
||||
if (message != null)
|
||||
EntityOperation.queue(this, db, message, EntityOperation.SEEN, true);
|
||||
|
||||
db.setTransactionSuccessful();
|
||||
} finally {
|
||||
db.endTransaction();
|
||||
}
|
||||
}
|
||||
|
||||
private void onArchive(long id) {
|
||||
DB db = DB.getInstance(this);
|
||||
try {
|
||||
db.beginTransaction();
|
||||
|
||||
EntityMessage message = db.message().getMessage(id);
|
||||
if (message != null) {
|
||||
EntityFolder archive = db.folder().getFolderByType(message.account, EntityFolder.ARCHIVE);
|
||||
if (archive == null)
|
||||
archive = db.folder().getFolderByType(message.account, EntityFolder.TRASH);
|
||||
if (archive != null)
|
||||
EntityOperation.queue(this, db, message, EntityOperation.MOVE, archive.id);
|
||||
}
|
||||
|
||||
db.setTransactionSuccessful();
|
||||
} finally {
|
||||
db.endTransaction();
|
||||
}
|
||||
}
|
||||
|
||||
private void onTrash(long id) {
|
||||
DB db = DB.getInstance(this);
|
||||
try {
|
||||
db.beginTransaction();
|
||||
|
||||
EntityMessage message = db.message().getMessage(id);
|
||||
if (message != null) {
|
||||
EntityFolder trash = db.folder().getFolderByType(message.account, EntityFolder.TRASH);
|
||||
if (trash != null)
|
||||
EntityOperation.queue(this, db, message, EntityOperation.MOVE, trash.id);
|
||||
}
|
||||
|
||||
db.setTransactionSuccessful();
|
||||
} finally {
|
||||
db.endTransaction();
|
||||
}
|
||||
}
|
||||
|
||||
private void onIgnore(long id) {
|
||||
DB db = DB.getInstance(this);
|
||||
try {
|
||||
db.beginTransaction();
|
||||
|
||||
EntityMessage message = db.message().getMessage(id);
|
||||
if (message != null)
|
||||
db.message().setMessageUiIgnored(message.id, true);
|
||||
|
||||
db.setTransactionSuccessful();
|
||||
} finally {
|
||||
db.endTransaction();
|
||||
}
|
||||
}
|
||||
|
||||
private void onSnooze(long id) {
|
||||
DB db = DB.getInstance(this);
|
||||
try {
|
||||
db.beginTransaction();
|
||||
|
||||
EntityMessage message = db.message().getMessage(id);
|
||||
if (message != null) {
|
||||
db.message().setMessageSnoozed(message.id, null);
|
||||
|
||||
EntityFolder folder = db.folder().getFolder(message.folder);
|
||||
if (EntityFolder.OUTBOX.equals(folder.type)) {
|
||||
Log.i("Delayed send id=" + message.id);
|
||||
EntityOperation.queue(
|
||||
this, db, message, EntityOperation.SEND);
|
||||
} else {
|
||||
EntityOperation.queue(
|
||||
this, db, message, EntityOperation.SEEN, false);
|
||||
db.message().setMessageUiIgnored(message.id, false);
|
||||
}
|
||||
}
|
||||
|
||||
db.setTransactionSuccessful();
|
||||
} finally {
|
||||
db.endTransaction();
|
||||
}
|
||||
}
|
||||
|
||||
private void onSyncOndemand(long fid) {
|
||||
Log.i("Synchronize on demand folder=" + fid);
|
||||
|
||||
DB db = DB.getInstance(this);
|
||||
EntityFolder folder = null;
|
||||
EntityAccount account = null;
|
||||
|
||||
Store istore = null;
|
||||
try {
|
||||
folder = db.folder().getFolder(fid);
|
||||
account = db.account().getAccount(folder.account);
|
||||
|
||||
// Create session
|
||||
Properties props = MessageHelper.getSessionProperties(account.auth_type, account.realm, account.insecure);
|
||||
final Session isession = Session.getInstance(props, null);
|
||||
isession.setDebug(true);
|
||||
|
||||
// Connect account
|
||||
Log.i(account.name + " connecting");
|
||||
db.account().setAccountState(account.id, "connecting");
|
||||
istore = isession.getStore(account.getProtocol());
|
||||
Helper.connect(this, istore, account);
|
||||
db.account().setAccountState(account.id, "connected");
|
||||
Log.i(account.name + " connected");
|
||||
|
||||
// Synchronize folders
|
||||
Core.synchronizeFolders(this, account, istore, new Core.State());
|
||||
|
||||
// Connect folder
|
||||
Log.i(folder.name + " connecting");
|
||||
db.folder().setFolderState(folder.id, "connecting");
|
||||
Folder ifolder = istore.getFolder(folder.name);
|
||||
ifolder.open(Folder.READ_WRITE);
|
||||
db.folder().setFolderState(folder.id, "connected");
|
||||
db.folder().setFolderError(folder.id, null);
|
||||
Log.i(folder.name + " connected");
|
||||
|
||||
// Process operations
|
||||
Core.processOperations(this, account, folder, isession, istore, ifolder, new Core.State());
|
||||
|
||||
// Synchronize messages
|
||||
Core.synchronizeMessages(this, account, folder, (IMAPFolder) ifolder, folder.getSyncArgs(), new Core.State());
|
||||
|
||||
} catch (Throwable ex) {
|
||||
Log.w(ex);
|
||||
Core.reportError(this, account, folder, ex);
|
||||
db.account().setAccountError(account.id, Helper.formatThrowable(ex));
|
||||
db.folder().setFolderError(folder.id, Helper.formatThrowable(ex, false));
|
||||
} finally {
|
||||
if (istore != null) {
|
||||
Log.i(account.name + " closing");
|
||||
db.account().setAccountState(account.id, "closing");
|
||||
db.folder().setFolderState(folder.id, "closing");
|
||||
|
||||
try {
|
||||
istore.close();
|
||||
} catch (MessagingException ex) {
|
||||
Log.e(ex);
|
||||
}
|
||||
|
||||
db.account().setAccountState(account.id, null);
|
||||
db.folder().setFolderState(folder.id, null);
|
||||
Log.i(account.name + " closed");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void sync(Context context, long folder) {
|
||||
context.startService(
|
||||
new Intent(context, ServiceUI.class)
|
||||
.setAction("synchronize:" + folder));
|
||||
}
|
||||
}
|
|
@ -306,7 +306,7 @@ public class ViewModelBrowse extends ViewModel {
|
|||
Log.i("Boundary sync uid=" + uid);
|
||||
EntityMessage message = db.message().getMessageByUid(folder.id, uid);
|
||||
if (message == null) {
|
||||
message = ServiceSynchronize.synchronizeMessage(state.context,
|
||||
message = Core.synchronizeMessage(state.context,
|
||||
folder, state.ifolder, (IMAPMessage) isub[j],
|
||||
true,
|
||||
new ArrayList<EntityRule>());
|
||||
|
|
|
@ -256,7 +256,6 @@
|
|||
<string name="title_no_uidplus">This provider does not support UIDPLUS</string>
|
||||
<string name="title_no_utf8">This provider does not support UTF-8</string>
|
||||
<string name="title_no_sync">Synchronization errors since %1$s</string>
|
||||
<string name="title_sync_disabled">Synchronization is disabled</string>
|
||||
<string name="title_sync_delayed">Synchronization will be performed on the next account connection</string>
|
||||
<string name="title_drafts_required">A drafts folder is required to send messages</string>
|
||||
<string name="title_account_delete">Delete this account permanently?</string>
|
||||
|
|
Loading…
Reference in a new issue