|
|
@ -120,7 +120,6 @@ public class ServiceSynchronize extends LifecycleService { |
|
|
|
private ServiceManager serviceManager = new ServiceManager(); |
|
|
|
|
|
|
|
private static final int NOTIFICATION_SYNCHRONIZE = 1; |
|
|
|
private static final int NOTIFICATION_UNSEEN = 2; |
|
|
|
|
|
|
|
private static final int CONNECT_BACKOFF_START = 8; // seconds |
|
|
|
private static final int CONNECT_BACKOFF_MAX = 1024; // seconds (1024 sec ~ 17 min) |
|
|
@ -130,6 +129,10 @@ public class ServiceSynchronize extends LifecycleService { |
|
|
|
private static final int MESSAGE_AUTO_DOWNLOAD_SIZE = 32 * 1024; // bytes |
|
|
|
private static final int ATTACHMENT_AUTO_DOWNLOAD_SIZE = 32 * 1024; // bytes |
|
|
|
|
|
|
|
static final int PI_UNSEEN = 1; |
|
|
|
static final int PI_SEEN = 2; |
|
|
|
static final int PI_TRASH = 3; |
|
|
|
|
|
|
|
static final String ACTION_SYNCHRONIZE_FOLDER = BuildConfig.APPLICATION_ID + ".SYNCHRONIZE_FOLDER"; |
|
|
|
static final String ACTION_PROCESS_OPERATIONS = BuildConfig.APPLICATION_ID + ".PROCESS_OPERATIONS"; |
|
|
|
|
|
|
@ -159,20 +162,40 @@ public class ServiceSynchronize extends LifecycleService { |
|
|
|
}); |
|
|
|
|
|
|
|
db.message().liveUnseenUnified().observe(this, new Observer<List<EntityMessage>>() { |
|
|
|
private int prev_unseen = -1; |
|
|
|
private List<Integer> notifying = new ArrayList<>(); |
|
|
|
|
|
|
|
@Override |
|
|
|
public void onChanged(List<EntityMessage> messages) { |
|
|
|
NotificationManager nm = getSystemService(NotificationManager.class); |
|
|
|
if (messages.size() > 0) { |
|
|
|
if (messages.size() > prev_unseen) { |
|
|
|
nm.cancel(NOTIFICATION_UNSEEN); |
|
|
|
nm.notify(NOTIFICATION_UNSEEN, getNotificationUnseen(messages).build()); |
|
|
|
List<Notification> notifications = getNotificationUnseen(messages); |
|
|
|
|
|
|
|
List<Integer> all = new ArrayList<>(); |
|
|
|
List<Integer> added = new ArrayList<>(); |
|
|
|
List<Integer> removed = new ArrayList<>(notifying); |
|
|
|
for (Notification notification : notifications) { |
|
|
|
Integer id = (int) notification.extras.getLong("id", 0); |
|
|
|
if (id > 0) { |
|
|
|
all.add(id); |
|
|
|
if (removed.contains(id)) |
|
|
|
removed.remove(id); |
|
|
|
else |
|
|
|
added.add(id); |
|
|
|
} |
|
|
|
} else |
|
|
|
nm.cancel(NOTIFICATION_UNSEEN); |
|
|
|
} |
|
|
|
|
|
|
|
if (notifications.size() == 0) |
|
|
|
nm.cancel("unseen", 0); |
|
|
|
|
|
|
|
prev_unseen = messages.size(); |
|
|
|
for (Integer id : removed) |
|
|
|
nm.cancel("unseen", id); |
|
|
|
|
|
|
|
for (Notification notification : notifications) { |
|
|
|
Integer id = (int) notification.extras.getLong("id", 0); |
|
|
|
if ((id == 0 && added.size() + removed.size() > 0) || added.contains(id)) |
|
|
|
nm.notify("unseen", id, notification); |
|
|
|
} |
|
|
|
|
|
|
|
notifying = all; |
|
|
|
} |
|
|
|
}); |
|
|
|
} |
|
|
@ -199,10 +222,11 @@ public class ServiceSynchronize extends LifecycleService { |
|
|
|
Log.i(Helper.TAG, "Service command intent=" + intent); |
|
|
|
super.onStartCommand(intent, flags, startId); |
|
|
|
|
|
|
|
if (intent != null) |
|
|
|
if ("reload".equals(intent.getAction())) |
|
|
|
if (intent != null) { |
|
|
|
String action = intent.getAction(); |
|
|
|
if ("reload".equals(action)) |
|
|
|
serviceManager.restart(); |
|
|
|
else if ("unseen".equals(intent.getAction())) { |
|
|
|
else if ("until".equals(action)) { |
|
|
|
Bundle args = new Bundle(); |
|
|
|
args.putLong("time", new Date().getTime()); |
|
|
|
|
|
|
@ -232,19 +256,31 @@ public class ServiceSynchronize extends LifecycleService { |
|
|
|
} |
|
|
|
}.load(this, args); |
|
|
|
|
|
|
|
} else if ("seen".equals(intent.getAction())) { |
|
|
|
} else if (action != null && |
|
|
|
(action.startsWith("seen:") || action.startsWith("trash:"))) { |
|
|
|
Bundle args = new Bundle(); |
|
|
|
args.putLong("id", Long.parseLong(action.split(":")[1])); |
|
|
|
args.putString("action", action.split(":")[0]); |
|
|
|
|
|
|
|
new SimpleTask<Void>() { |
|
|
|
@Override |
|
|
|
protected Void onLoad(Context context, Bundle args) { |
|
|
|
long id = args.getLong("id"); |
|
|
|
String action = args.getString("action"); |
|
|
|
|
|
|
|
DB db = DB.getInstance(context); |
|
|
|
try { |
|
|
|
db.beginTransaction(); |
|
|
|
|
|
|
|
for (EntityMessage message : db.message().getUnseenUnifiedMessages()) { |
|
|
|
EntityMessage message = db.message().getMessage(id); |
|
|
|
if ("seen".equals(action)) { |
|
|
|
db.message().setMessageUiSeen(message.id, true); |
|
|
|
EntityOperation.queue(db, message, EntityOperation.SEEN, true); |
|
|
|
} else if ("trash".equals(action)) { |
|
|
|
db.message().setMessageUiHide(message.id, true); |
|
|
|
EntityFolder trash = db.folder().getFolderByType(message.account, EntityFolder.TRASH); |
|
|
|
if (trash != null) |
|
|
|
EntityOperation.queue(db, message, EntityOperation.MOVE, trash.id); |
|
|
|
} |
|
|
|
|
|
|
|
db.setTransactionSuccessful(); |
|
|
@ -263,6 +299,7 @@ public class ServiceSynchronize extends LifecycleService { |
|
|
|
} |
|
|
|
}.load(this, args); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
return START_STICKY; |
|
|
|
} |
|
|
@ -301,49 +338,53 @@ public class ServiceSynchronize extends LifecycleService { |
|
|
|
return builder; |
|
|
|
} |
|
|
|
|
|
|
|
private Notification.Builder getNotificationUnseen(List<EntityMessage> messages) { |
|
|
|
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); |
|
|
|
private List<Notification> getNotificationUnseen(List<EntityMessage> messages) { |
|
|
|
// https://developer.android.com/training/notify-user/group |
|
|
|
List<Notification> notifications = new ArrayList<>(); |
|
|
|
|
|
|
|
// Build pending intent |
|
|
|
Intent intent = new Intent(this, ActivityView.class); |
|
|
|
intent.setAction("unseen"); |
|
|
|
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); |
|
|
|
PendingIntent pi = PendingIntent.getActivity( |
|
|
|
this, ActivityView.REQUEST_UNSEEN, intent, PendingIntent.FLAG_UPDATE_CURRENT); |
|
|
|
if (messages.size() == 0) |
|
|
|
return notifications; |
|
|
|
|
|
|
|
Intent delete = new Intent(this, ServiceSynchronize.class); |
|
|
|
delete.setAction("unseen"); |
|
|
|
PendingIntent pid = PendingIntent.getService(this, 1, delete, PendingIntent.FLAG_UPDATE_CURRENT); |
|
|
|
|
|
|
|
Intent seen = new Intent(this, ServiceSynchronize.class); |
|
|
|
seen.setAction("seen"); |
|
|
|
PendingIntent pis = PendingIntent.getService(this, 2, seen, PendingIntent.FLAG_UPDATE_CURRENT); |
|
|
|
boolean pro = Helper.isPro(this); |
|
|
|
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); |
|
|
|
|
|
|
|
Notification.Action.Builder actionBuilder = new Notification.Action.Builder( |
|
|
|
Icon.createWithResource(this, R.drawable.baseline_mail_outline_24), |
|
|
|
getString(R.string.title_seen), |
|
|
|
pis); |
|
|
|
// Build pending intent |
|
|
|
Intent view = new Intent(this, ActivityView.class); |
|
|
|
view.setAction("notification"); |
|
|
|
view.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); |
|
|
|
PendingIntent piView = PendingIntent.getActivity( |
|
|
|
this, ActivityView.REQUEST_UNSEEN, view, PendingIntent.FLAG_UPDATE_CURRENT); |
|
|
|
|
|
|
|
Uri uri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION); |
|
|
|
Intent until = new Intent(this, ServiceSynchronize.class); |
|
|
|
until.setAction("until"); |
|
|
|
PendingIntent piUntil = PendingIntent.getService( |
|
|
|
this, PI_UNSEEN, until, PendingIntent.FLAG_UPDATE_CURRENT); |
|
|
|
|
|
|
|
// Build notification |
|
|
|
Notification.Builder builder; |
|
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) |
|
|
|
builder = new Notification.Builder(this, "notification"); |
|
|
|
else |
|
|
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) |
|
|
|
builder = new Notification.Builder(this); |
|
|
|
else |
|
|
|
builder = new Notification.Builder(this, "notification"); |
|
|
|
|
|
|
|
builder |
|
|
|
.setSmallIcon(R.drawable.baseline_mail_24) |
|
|
|
.setContentTitle(getResources().getQuantityString(R.plurals.title_notification_unseen, messages.size(), messages.size())) |
|
|
|
.setContentIntent(pi) |
|
|
|
.setSound(uri) |
|
|
|
.setContentText("") |
|
|
|
.setContentIntent(piView) |
|
|
|
.setNumber(messages.size()) |
|
|
|
.setShowWhen(false) |
|
|
|
.setPriority(Notification.PRIORITY_DEFAULT) |
|
|
|
.setCategory(Notification.CATEGORY_STATUS) |
|
|
|
.setVisibility(Notification.VISIBILITY_PUBLIC) |
|
|
|
.setDeleteIntent(pid) |
|
|
|
.addAction(actionBuilder.build()); |
|
|
|
.setVisibility(Notification.VISIBILITY_PRIVATE) |
|
|
|
.setDeleteIntent(piUntil) |
|
|
|
.setGroup(BuildConfig.APPLICATION_ID) |
|
|
|
.setGroupSummary(true); |
|
|
|
|
|
|
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) |
|
|
|
builder.setSound(null); |
|
|
|
else |
|
|
|
builder.setGroupAlertBehavior(Notification.GROUP_ALERT_CHILDREN); |
|
|
|
|
|
|
|
if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.O && |
|
|
|
prefs.getBoolean("light", false)) { |
|
|
@ -351,7 +392,7 @@ public class ServiceSynchronize extends LifecycleService { |
|
|
|
builder.setLights(0xff00ff00, 1000, 1000); |
|
|
|
} |
|
|
|
|
|
|
|
if (Helper.isPro(this)) { |
|
|
|
if (pro) { |
|
|
|
DateFormat df = SimpleDateFormat.getDateTimeInstance(SimpleDateFormat.SHORT, SimpleDateFormat.SHORT); |
|
|
|
StringBuilder sb = new StringBuilder(); |
|
|
|
for (EntityMessage message : messages) { |
|
|
@ -365,7 +406,64 @@ public class ServiceSynchronize extends LifecycleService { |
|
|
|
builder.setStyle(new Notification.BigTextStyle().bigText(Html.fromHtml(sb.toString()))); |
|
|
|
} |
|
|
|
|
|
|
|
return builder; |
|
|
|
notifications.add(builder.build()); |
|
|
|
|
|
|
|
Uri uri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION); |
|
|
|
|
|
|
|
for (EntityMessage message : messages) { |
|
|
|
Bundle args = new Bundle(); |
|
|
|
args.putLong("id", message.id); |
|
|
|
|
|
|
|
Intent seen = new Intent(this, ServiceSynchronize.class); |
|
|
|
seen.setAction("seen:" + message.id); |
|
|
|
PendingIntent piSeen = PendingIntent.getService(this, PI_SEEN, seen, PendingIntent.FLAG_UPDATE_CURRENT); |
|
|
|
|
|
|
|
Intent trash = new Intent(this, ServiceSynchronize.class); |
|
|
|
trash.setAction("trash:" + message.id); |
|
|
|
PendingIntent piTrash = PendingIntent.getService(this, PI_TRASH, trash, PendingIntent.FLAG_UPDATE_CURRENT); |
|
|
|
|
|
|
|
Notification.Action.Builder actionSeen = new Notification.Action.Builder( |
|
|
|
Icon.createWithResource(this, R.drawable.baseline_visibility_24), |
|
|
|
getString(R.string.title_seen), |
|
|
|
piSeen); |
|
|
|
|
|
|
|
Notification.Action.Builder actionTrash = new Notification.Action.Builder( |
|
|
|
Icon.createWithResource(this, R.drawable.baseline_delete_24), |
|
|
|
getString(R.string.title_trash), |
|
|
|
piTrash); |
|
|
|
|
|
|
|
Notification.Builder mbuilder; |
|
|
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) |
|
|
|
mbuilder = new Notification.Builder(this); |
|
|
|
else |
|
|
|
mbuilder = new Notification.Builder(this, "notification"); |
|
|
|
|
|
|
|
mbuilder |
|
|
|
.addExtras(args) |
|
|
|
.setSmallIcon(R.drawable.baseline_mail_24) |
|
|
|
.setContentTitle(MessageHelper.getFormattedAddresses(message.from, true)) |
|
|
|
.setContentIntent(piView) |
|
|
|
.setSound(uri) |
|
|
|
.setWhen(message.sent == null ? message.received : message.sent) |
|
|
|
.setPriority(Notification.PRIORITY_DEFAULT) |
|
|
|
.setCategory(Notification.CATEGORY_STATUS) |
|
|
|
.setVisibility(Notification.VISIBILITY_PRIVATE) |
|
|
|
.setGroup(BuildConfig.APPLICATION_ID) |
|
|
|
.setGroupSummary(false) |
|
|
|
.addAction(actionSeen.build()) |
|
|
|
.addAction(actionTrash.build()); |
|
|
|
|
|
|
|
if (pro) |
|
|
|
if (!TextUtils.isEmpty(message.subject)) |
|
|
|
mbuilder.setContentText(message.subject); |
|
|
|
|
|
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) |
|
|
|
mbuilder.setGroupAlertBehavior(Notification.GROUP_ALERT_CHILDREN); |
|
|
|
|
|
|
|
notifications.add(mbuilder.build()); |
|
|
|
} |
|
|
|
|
|
|
|
return notifications; |
|
|
|
} |
|
|
|
|
|
|
|
private Notification.Builder getNotificationError(String action, Throwable ex) { |
|
|
@ -1399,7 +1497,7 @@ public class ServiceSynchronize extends LifecycleService { |
|
|
|
Log.i(Helper.TAG, folder.name + " add=" + imessages.length); |
|
|
|
for (int i = imessages.length - 1; i >= 0; i -= SYNC_BATCH_SIZE) { |
|
|
|
int from = Math.max(0, i - SYNC_BATCH_SIZE + 1); |
|
|
|
Log.i(Helper.TAG, folder.name + " update " + from + " .. " + i); |
|
|
|
//Log.i(Helper.TAG, folder.name + " update " + from + " .. " + i); |
|
|
|
|
|
|
|
Message[] isub = Arrays.copyOfRange(imessages, from, i + 1); |
|
|
|
|
|
|
@ -1411,8 +1509,10 @@ public class ServiceSynchronize extends LifecycleService { |
|
|
|
if (message == null) |
|
|
|
full.add(imessage); |
|
|
|
} |
|
|
|
Log.i(Helper.TAG, folder.name + " fetch headers=" + full.size()); |
|
|
|
long headers = SystemClock.elapsedRealtime(); |
|
|
|
ifolder.fetch(full.toArray(new Message[0]), fp); |
|
|
|
Log.i(Helper.TAG, folder.name + " fetched headers=" + full.size() + |
|
|
|
" " + (SystemClock.elapsedRealtime() - fetch) + " ms"); |
|
|
|
|
|
|
|
for (int j = isub.length - 1; j >= 0; j--) |
|
|
|
try { |
|
|
@ -1439,14 +1539,14 @@ public class ServiceSynchronize extends LifecycleService { |
|
|
|
Log.i(Helper.TAG, folder.name + " download=" + imessages.length); |
|
|
|
for (int i = imessages.length - 1; i >= 0; i -= DOWNLOAD_BATCH_SIZE) { |
|
|
|
int from = Math.max(0, i - DOWNLOAD_BATCH_SIZE + 1); |
|
|
|
Log.i(Helper.TAG, folder.name + " download " + from + " .. " + i); |
|
|
|
//Log.i(Helper.TAG, folder.name + " download " + from + " .. " + i); |
|
|
|
|
|
|
|
Message[] isub = Arrays.copyOfRange(imessages, from, i + 1); |
|
|
|
// Fetch on demand |
|
|
|
|
|
|
|
for (int j = isub.length - 1; j >= 0; j--) |
|
|
|
try { |
|
|
|
Log.i(Helper.TAG, folder.name + " download index=" + (from + j) + " id=" + ids[from + j]); |
|
|
|
//Log.i(Helper.TAG, folder.name + " download index=" + (from + j) + " id=" + ids[from + j]); |
|
|
|
if (ids[from + j] != null) |
|
|
|
downloadMessage(this, folder, ifolder, (IMAPMessage) isub[j], ids[from + j]); |
|
|
|
} catch (FolderClosedException ex) { |
|
|
|