future : futures)
try {
future.get();
} catch (Throwable ex) {
Log.w(ex);
}
// @page WordSection1 {size:612.0pt 792.0pt; margin:70.85pt 70.85pt 70.85pt 70.85pt;}
// div.WordSection1 {page:WordSection1;}
//
for (Element element : document.body().select("div[class]")) {
String clazz = element.attr("class");
if (clazz.startsWith("WordSection"))
element.removeClass(clazz);
}
if (print_html_header) {
Element header = document.createElement("p");
if (message.from != null && message.from.length > 0) {
Element span = document.createElement("span");
Element strong = document.createElement("strong");
strong.text(context.getString(R.string.title_from));
span.appendChild(strong);
span.appendText(" " + MessageHelper.formatAddresses(message.from));
span.appendElement("br");
header.appendChild(span);
}
if (message.to != null && message.to.length > 0) {
Element span = document.createElement("span");
Element strong = document.createElement("strong");
strong.text(context.getString(R.string.title_to));
span.appendChild(strong);
span.appendText(" " + MessageHelper.formatAddresses(message.to));
span.appendElement("br");
header.appendChild(span);
}
if (message.cc != null && message.cc.length > 0) {
Element span = document.createElement("span");
Element strong = document.createElement("strong");
strong.text(context.getString(R.string.title_cc));
span.appendChild(strong);
span.appendText(" " + MessageHelper.formatAddresses(message.cc));
span.appendElement("br");
header.appendChild(span);
}
if (message.received != null) {
DateFormat DTF = Helper.getDateTimeInstance(context, SimpleDateFormat.LONG, SimpleDateFormat.LONG);
Element span = document.createElement("span");
Element strong = document.createElement("strong");
strong.text(context.getString(R.string.title_received));
span.appendChild(strong);
span.appendText(" " + DTF.format(message.received));
span.appendElement("br");
header.appendChild(span);
}
for (EntityAttachment attachment : attachments)
if (attachment.isAttachment()) {
Element span = document.createElement("span");
Element strong = document.createElement("strong");
strong.text(context.getString(R.string.title_attachment));
span.appendChild(strong);
if (!TextUtils.isEmpty(attachment.name))
span.appendText(" " + attachment.name);
if (attachment.size != null)
span.appendText(" " + Helper.humanReadableByteCount(attachment.size));
span.appendElement("br");
header.appendChild(span);
}
if (!TextUtils.isEmpty(message.subject)) {
Element span = document.createElement("span");
span.appendText(message.subject);
span.appendElement("br");
header.appendChild(span);
}
if (headers && message.headers != null) {
header.appendElement("hr");
Element pre = document.createElement("pre");
pre.text(message.headers);
header.appendChild(pre);
}
header.appendElement("hr").appendElement("br");
document.body().prependChild(header);
}
return new String[]{message.subject, document.body().html()};
}
@Override
protected void onExecuted(Bundle args, final String[] data) {
if (data == null) {
Log.w("Print no data");
return;
}
ActivityBase activity = (ActivityBase) getActivity();
if (activity == null) {
Log.w("Print no activity");
return;
}
final Context context = activity.getOriginalContext();
boolean print_html_images = args.getBoolean("print_html_images");
// https://developer.android.com/training/printing/html-docs.html
printWebView = new WebView(context);
WebSettings settings = printWebView.getSettings();
settings.setUserAgentString(WebViewEx.getUserAgent(context, printWebView));
settings.setLoadsImagesAutomatically(print_html_images);
settings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
settings.setAllowFileAccess(true);
printWebView.setWebViewClient(new WebViewClient() {
public boolean shouldOverrideUrlLoading(WebView view, String url) {
return false;
}
@Override
public void onPageFinished(WebView view, String url) {
Log.i("Print page finished");
try {
if (printWebView == null) {
Log.w("Print no view");
return;
}
PrintManager printManager = (PrintManager) context.getSystemService(Context.PRINT_SERVICE);
String jobName = getString(R.string.app_name);
if (!TextUtils.isEmpty(data[0]))
jobName += " - " + data[0];
Log.i("Print queue job=" + jobName);
PrintDocumentAdapter adapter = printWebView.createPrintDocumentAdapter(jobName);
PrintJob job = printManager.print(jobName, adapter, new PrintAttributes.Builder().build());
EntityLog.log(context, "Print queued job=" + job.getInfo());
} catch (Throwable ex) {
try {
Log.unexpectedError(getParentFragmentManager(), ex, !(ex instanceof ActivityNotFoundException));
} catch (IllegalStateException exex) {
ToastEx.makeText(context, Log.formatThrowable(ex), Toast.LENGTH_LONG).show();
}
} finally {
printWebView = null;
}
}
});
Log.i("Print load data");
printWebView.loadDataWithBaseURL("about:blank", data[1], "text/html", StandardCharsets.UTF_8.name(), null);
}
@Override
protected void onException(Bundle args, Throwable ex) {
Log.unexpectedError(getParentFragmentManager(), ex);
}
}.execute(this, args, "message:print");
}
private void onEmptyFolder(Bundle args) {
new SimpleTask() {
@Override
protected Void onExecute(Context context, Bundle args) {
long aid = args.getLong("account");
String type = args.getString("type");
EntityLog.log(context, "Empty account=" + account + " type=" + type);
DB db = DB.getInstance(context);
try {
db.beginTransaction();
List accounts;
if (account < 0)
accounts = db.account().getSynchronizingAccounts(null);
else {
EntityAccount account = db.account().getAccount(aid);
if (account == null)
return null;
accounts = Arrays.asList(account);
}
for (EntityAccount account : accounts) {
EntityFolder folder = db.folder().getFolderByType(account.id, type);
if (folder == null)
continue;
EntityLog.log(context,
"Empty account=" + account.name + " folder=" + folder.name + " count=" + folder.total);
List ids = db.message().getMessageByFolder(folder.id);
for (Long id : ids) {
EntityMessage message = db.message().getMessage(id);
if (message == null)
continue;
if (message.uid != null || account.protocol == EntityAccount.TYPE_POP)
db.message().setMessageUiHide(message.id, true);
}
EntityOperation.queue(context, folder, EntityOperation.PURGE);
}
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
ServiceSynchronize.eval(context, "purge");
return null;
}
@Override
protected void onException(Bundle args, Throwable ex) {
Log.unexpectedError(getParentFragmentManager(), ex);
}
}.execute(this, args, "folder:delete");
}
private void onBoundaryRetry() {
ViewModelMessages model = new ViewModelProvider(getActivity()).get(ViewModelMessages.class);
model.retry(viewType);
}
private void onPickContact(Uri contactUri) {
String email = kv.get("email");
// This requires contacts permission
ContentResolver resolver = getContext().getContentResolver();
Uri lookupUri = ContactsContract.Contacts.getLookupUri(resolver, contactUri);
Intent edit = new Intent();
edit.putExtra(ContactsContract.Intents.Insert.EMAIL, email);
edit.setAction(Intent.ACTION_EDIT);
edit.setDataAndTypeAndNormalize(lookupUri, ContactsContract.Contacts.CONTENT_ITEM_TYPE);
startActivity(edit);
}
static void search(
final Context context, final LifecycleOwner owner, final FragmentManager manager,
long account, long folder, boolean server, BoundaryCallbackMessages.SearchCriteria criteria) {
if (owner.getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.STARTED))
manager.popBackStack("search", FragmentManager.POP_BACK_STACK_INCLUSIVE);
Bundle args = new Bundle();
args.putLong("account", account);
args.putLong("folder", folder);
args.putBoolean("server", server);
args.putSerializable("criteria", criteria);
FragmentMessages fragment = new FragmentMessages();
fragment.setArguments(args);
FragmentTransaction fragmentTransaction = manager.beginTransaction();
fragmentTransaction.replace(R.id.content_frame, fragment).addToBackStack("search");
fragmentTransaction.commit();
}
private static class ActionData {
private boolean delete; // Selects action
private boolean forever; // Selects icon
private boolean trashable;
private boolean snoozable;
private boolean archivable;
}
private class ReplyData {
List identities;
List answers;
}
private class MoreResult {
boolean seen;
boolean unseen;
boolean visible;
boolean hidden;
boolean flagged;
boolean unflagged;
Integer importance;
Boolean hasInbox;
Boolean hasArchive;
Boolean hasTrash;
Boolean hasJunk;
Boolean isInbox;
Boolean isArchive;
Boolean isTrash;
Boolean isJunk;
Boolean isDrafts;
boolean hasImap;
boolean hasPop;
Boolean leave_deleted;
boolean read_only;
List folders;
Map accounts;
EntityAccount copyto;
}
public static class MessageTarget implements Parcelable {
long id;
boolean found;
Account sourceAccount;
Folder sourceFolder;
Account targetAccount;
Folder targetFolder;
boolean copy;
boolean block;
MessageTarget(Context context, EntityMessage message,
EntityAccount sourceAccount, EntityFolder sourceFolder,
EntityAccount targetAccount, EntityFolder targetFolder) {
this.id = message.id;
this.found = message.ui_found;
this.sourceAccount = new Account(sourceAccount);
this.sourceFolder = new Folder(context, sourceFolder);
this.targetAccount = new Account(targetAccount);
this.targetFolder = new Folder(context, targetFolder);
}
MessageTarget setCopy(boolean copy) {
this.copy = copy;
return this;
}
MessageTarget setBlock(boolean block) {
this.block = block;
return this;
}
boolean isAcross() {
return (sourceAccount.id != targetAccount.id);
}
protected MessageTarget(Parcel in) {
id = in.readLong();
found = (in.readInt() != 0);
sourceAccount = (Account) in.readSerializable();
sourceFolder = (Folder) in.readSerializable();
targetAccount = (Account) in.readSerializable();
targetFolder = (Folder) in.readSerializable();
copy = (in.readInt() != 0);
block = (in.readInt() != 0);
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeLong(id);
dest.writeInt(found ? 1 : 0);
dest.writeSerializable(sourceAccount);
dest.writeSerializable(sourceFolder);
dest.writeSerializable(targetAccount);
dest.writeSerializable(targetFolder);
dest.writeInt(copy ? 1 : 0);
dest.writeInt(block ? 1 : 0);
}
@Override
public int describeContents() {
return 0;
}
public static final Creator CREATOR = new Creator() {
@Override
public MessageTarget createFromParcel(Parcel in) {
return new MessageTarget(in);
}
@Override
public MessageTarget[] newArray(int size) {
return new MessageTarget[size];
}
};
static class Account implements Serializable {
long id;
String name;
Account(EntityAccount account) {
this.id = account.id;
this.name = account.name;
}
}
static class Folder implements Serializable {
long id;
String type;
String name;
String display;
Integer color;
Folder(Context context, EntityFolder folder) {
this.id = folder.id;
this.type = folder.type;
this.name = folder.name;
this.display = folder.getDisplayName(context);
this.color = folder.color;
}
}
}
public static class FragmentDialogAskSpam extends FragmentDialogBase {
@NonNull
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
Bundle args = getArguments();
int count = args.getInt("count");
final Context context = getContext();
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
boolean block_sender = prefs.getBoolean("block_sender", true);
String text = getResources().getQuantityString(R.plurals.title_ask_spam, count, count);
View dview = LayoutInflater.from(context).inflate(R.layout.dialog_ask_spam, null);
TextView tvMessage = dview.findViewById(R.id.tvMessage);
CheckBox cbBlockSender = dview.findViewById(R.id.cbBlockSender);
tvMessage.setText(text);
cbBlockSender.setChecked(block_sender);
return new AlertDialog.Builder(context)
.setView(dview)
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
boolean block = cbBlockSender.isChecked();
prefs.edit().putBoolean("block_sender", block).apply();
getArguments().putBoolean("block", block);
sendResult(Activity.RESULT_OK);
}
})
.setNegativeButton(android.R.string.cancel, null)
.create();
}
}
public static class FragmentDialogReporting extends FragmentDialogBase {
@NonNull
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
View dview = LayoutInflater.from(getContext()).inflate(R.layout.dialog_error_reporting, null);
Button btnInfo = dview.findViewById(R.id.btnInfo);
btnInfo.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Helper.viewFAQ(v.getContext(), 104);
}
});
return new AlertDialog.Builder(getContext())
.setView(dview)
.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getContext());
prefs.edit().putBoolean("crash_reports", true).apply();
Log.setCrashReporting(true);
}
})
.setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getContext());
prefs.edit().putBoolean("crash_reports_asked", true).apply();
}
})
.create();
}
}
public static class FragmentDialogReview extends FragmentDialogBase {
@NonNull
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
View dview = LayoutInflater.from(getContext()).inflate(R.layout.dialog_review, null);
TextView tvHelp = dview.findViewById(R.id.tvHelp);
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getContext());
Dialog dialog = new AlertDialog.Builder(getContext())
.setView(dview)
.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
prefs.edit().putBoolean("review_asked", true).apply();
startActivity(Helper.getIntentRate(getContext()));
}
})
.setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
prefs.edit().putBoolean("review_asked", true).apply();
}
})
.setNeutralButton(R.string.title_later, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
prefs.edit().putLong("review_later", new Date().getTime()).apply();
}
})
.create();
tvHelp.setPaintFlags(tvHelp.getPaintFlags() | Paint.UNDERLINE_TEXT_FLAG);
tvHelp.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
dismiss();
prefs.edit().putLong("review_later", new Date().getTime()).apply();
startActivity(Helper.getIntentIssue(v.getContext()));
}
});
return dialog;
}
@Override
public void onCancel(@NonNull DialogInterface dialog) {
super.onCancel(dialog);
try {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getContext());
prefs.edit().putBoolean("review_asked", true).apply();
} catch (Throwable ex) {
Log.e(ex);
}
}
}
public static class FragmentDialogBoundaryError extends FragmentDialogBase {
@NonNull
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
String error = getArguments().getString("error");
View dview = LayoutInflater.from(getContext()).inflate(R.layout.dialog_boundary_error, null);
TextView tvError = dview.findViewById(R.id.tvError);
tvError.setText(error);
return new AlertDialog.Builder(getContext())
.setView(dview)
.setPositiveButton(R.string.title_boundary_retry, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
sendResult(Activity.RESULT_OK);
}
})
.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
sendResult(Activity.RESULT_CANCELED);
}
})
.create();
}
}
public static class FragmentMoveAsk extends FragmentDialogBase {
@NonNull
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
String notagain = getArguments().getString("notagain");
ArrayList result = getArguments().getParcelableArrayList("result");
final Context context = getContext();
View dview = LayoutInflater.from(context).inflate(R.layout.dialog_ask_move, null);
TextView tvMessages = dview.findViewById(R.id.tvMessages);
TextView tvSourceFolders = dview.findViewById(R.id.tvSourceFolders);
TextView tvTargetFolders = dview.findViewById(R.id.tvTargetFolders);
CheckBox cbNotAgain = dview.findViewById(R.id.cbNotAgain);
TextView tvJunkLearn = dview.findViewById(R.id.tvJunkLearn);
String question = context.getResources()
.getQuantityString(R.plurals.title_moving_messages,
result.size(), result.size());
tvMessages.setText(question);
tvSourceFolders.setText(getNames(result, false));
tvTargetFolders.setText(getNames(result, true));
List sources = new ArrayList<>();
List targets = new ArrayList<>();
Integer sourceColor = null;
Integer targetColor = null;
boolean junk = false;
for (MessageTarget t : result) {
if (!sources.contains(t.sourceFolder.type))
sources.add(t.sourceFolder.type);
if (!targets.contains(t.targetFolder.type))
targets.add(t.targetFolder.type);
if (sourceColor == null)
sourceColor = t.sourceFolder.color;
if (targetColor == null)
targetColor = t.targetFolder.color;
if (!junk &&
(EntityFolder.JUNK.equals(t.sourceFolder.type) ||
EntityFolder.JUNK.equals(t.targetFolder.type)))
junk = true;
}
Drawable source = null;
if (sources.size() == 1) {
source = context.getDrawable(EntityFolder.getIcon(sources.get(0)));
if (source != null)
source.setBounds(0, 0, source.getIntrinsicWidth(), source.getIntrinsicHeight());
if (sourceColor == null)
sourceColor = EntityFolder.getDefaultColor(sources.get(0), context);
} else {
source = context.getDrawable(R.drawable.twotone_folders_24);
source.setBounds(0, 0, source.getIntrinsicWidth(), source.getIntrinsicHeight());
sourceColor = null;
}
Drawable target = null;
if (targets.size() == 1) {
target = context.getDrawable(EntityFolder.getIcon(targets.get(0)));
if (target != null)
target.setBounds(0, 0, target.getIntrinsicWidth(), target.getIntrinsicHeight());
if (targetColor == null)
targetColor = EntityFolder.getDefaultColor(targets.get(0), context);
} else
targetColor = null;
tvSourceFolders.setCompoundDrawablesRelative(source, null, null, null);
tvTargetFolders.setCompoundDrawablesRelative(target, null, null, null);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (sourceColor != null)
tvSourceFolders.setCompoundDrawableTintList(ColorStateList.valueOf(sourceColor));
if (targetColor != null)
tvTargetFolders.setCompoundDrawableTintList(ColorStateList.valueOf(targetColor));
}
if (notagain != null)
cbNotAgain.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(buttonView.getContext());
prefs.edit().putBoolean(notagain, isChecked).apply();
}
});
tvJunkLearn.setVisibility(junk ? View.VISIBLE : View.GONE);
return new AlertDialog.Builder(context)
.setView(dview)
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
sendResult(Activity.RESULT_OK);
}
})
.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
sendResult(Activity.RESULT_CANCELED);
}
})
.create();
}
}
public static class FragmentDialogSaveSearch extends FragmentDialogBase {
private ViewButtonColor btnColor;
@NonNull
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
final Bundle args = getArguments();
BoundaryCallbackMessages.SearchCriteria criteria =
(BoundaryCallbackMessages.SearchCriteria) args.getSerializable("criteria");
if (criteria == null)
criteria = new BoundaryCallbackMessages.SearchCriteria();
final Context context = getContext();
View dview = LayoutInflater.from(context).inflate(R.layout.dialog_save_search, null);
EditText etName = dview.findViewById(R.id.etName);
btnColor = dview.findViewById(R.id.btnColor);
btnColor.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Helper.hideKeyboard(etName);
Bundle args = new Bundle();
args.putInt("color", btnColor.getColor());
args.putString("title", getString(R.string.title_color));
args.putBoolean("reset", true);
FragmentDialogColor fragment = new FragmentDialogColor();
fragment.setArguments(args);
fragment.setTargetFragment(FragmentDialogSaveSearch.this, 1234);
fragment.show(getParentFragmentManager(), "search:color");
}
});
etName.setText(criteria.getTitle(context));
btnColor.setColor(Color.TRANSPARENT);
return new AlertDialog.Builder(context)
.setView(dview)
.setPositiveButton(R.string.title_save, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
args.putString("name", etName.getText().toString());
args.putInt("color", btnColor.getColor());
sendResult(Activity.RESULT_OK);
}
})
.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
sendResult(Activity.RESULT_CANCELED);
}
})
.create();
}
@Override
public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
try {
if (resultCode == RESULT_OK && data != null) {
Bundle args = data.getBundleExtra("args");
int color = args.getInt("color");
btnColor.setColor(color);
}
} catch (Throwable ex) {
Log.e(ex);
}
}
}
}