Compare commits

...

41 Commits
1.74 ... 1.78

Author SHA1 Message Date
M66B
70fbc5aa6a 1.78 release 2018-10-07 15:49:34 +00:00
M66B
095a885d30 Fixed checking folder connection 2018-10-07 15:44:05 +00:00
M66B
38df2f6d24 Fixed null errors 2018-10-07 15:43:55 +00:00
M66B
3a53d69b31 Added FAQ 2018-10-07 12:58:09 +00:00
M66B
215fbbb74e Small improvement 2018-10-07 12:31:29 +00:00
M66B
b867e93291 Prevent crash 2018-10-07 12:21:55 +00:00
M66B
e496278128 Updated FAQ 2018-10-07 11:14:41 +00:00
M66B
e7292f0fc9 1.77 release 2018-10-07 10:55:15 +00:00
M66B
f4a25a359d Crowdin sync 2018-10-07 10:52:46 +00:00
M66B
ee4339a971 Simplify store keep alive 2018-10-07 10:19:38 +00:00
M66B
0bf88fcf9e Small fix/improvement 2018-10-07 09:49:22 +00:00
M66B
11d355c3d5 Refactoring 2018-10-07 08:45:38 +00:00
M66B
ed881a42ad Refactoring 2018-10-07 08:08:01 +00:00
M66B
1fe8784b1f Small improvements/fixes 2018-10-07 06:53:09 +00:00
M66B
5fe713180d Make folder serializable 2018-10-06 21:49:46 +00:00
M66B
c0fa6acc33 Fixed folder compare 2018-10-06 21:42:20 +00:00
M66B
09b530dd9d Fixed folder export/import 2018-10-06 21:36:11 +00:00
M66B
3f0aa1b59a Fixed empty reply message body 2018-10-06 21:31:12 +00:00
M66B
a4949c6473 Fixed search on server for messages already stored locally 2018-10-06 21:23:06 +00:00
M66B
64493bb573 Search on to address 2018-10-06 21:06:39 +00:00
M66B
6bb0d05478 Show badges for new messages notification only 2018-10-06 20:58:32 +00:00
M66B
d3fc9aadcc Prevent crash when storage access framework not installed 2018-10-06 20:55:47 +00:00
M66B
d7c4064f0b Workaround bug in Sony Android version 2018-10-06 20:55:06 +00:00
M66B
7a47f40fe1 Prevent crash 2018-10-06 20:30:13 +00:00
M66B
a2ab19206c Fixed commas in email addresses 2018-10-01 16:59:03 +00:00
M66B
faf36edef0 Updated FAQ 2018-10-01 15:41:04 +00:00
M66B
34884e7e85 Auto download attachments on unmetered connection 2018-10-01 13:05:14 +00:00
M66B
aa138cabc0 1.76 release 2018-10-01 09:22:31 +00:00
M66B
87817b9412 Crowdin sync 2018-10-01 09:16:04 +00:00
M66B
11e9333f14 Added option to set poll interval per folder 2018-10-01 09:14:21 +00:00
M66B
703d8af6e8 Fixed displaying stars in thread view 2018-10-01 08:32:58 +00:00
M66B
0c2b035970 1.75 release 2018-10-01 05:47:19 +00:00
M66B
ad218d7b49 Crowdin sync 2018-10-01 05:44:51 +00:00
M66B
a0fc3c4058 Report Play store crashes too 2018-09-30 15:43:16 +00:00
M66B
695bb5bc8a Enable log/debug info in release builds again 2018-09-30 15:33:57 +00:00
M66B
6786358082 Fixed potential crash 2018-09-30 15:23:17 +00:00
M66B
f93e4ca813 Fixed out of memory on viewing large original messages 2018-09-30 15:20:30 +00:00
M66B
4fb2e7dbc2 Removed limit of 25 folders to synchronize 2018-09-30 15:02:29 +00:00
M66B
84eeadd2f7 Poll user folders 2018-09-30 14:55:47 +00:00
M66B
e41cc0cccf Fixed race condition 2018-09-30 13:49:01 +00:00
M66B
4d5d202ed7 Updated legend 2018-09-30 11:11:30 +00:00
142 changed files with 1672 additions and 451 deletions

Binary file not shown.

20
FAQ.md
View File

@@ -24,6 +24,8 @@ to prevent Android from killing the service that takes care of receiving and sen
Most, if not all, other email apps don't show a notification with the "side effect" that new email is often not or late being reported.
Background: this is because of the introduction of [doze mode](https://developer.android.com/training/monitoring-device-state/doze-standby) in Android 6 Marshmallow.
<a name="FAQ3"></a>
**(3) What are operations and why are they pending?**
@@ -99,8 +101,12 @@ So, unless your provider can enable this extension, you cannot use FairEmail for
**(11) Why is STARTTLS for IMAP not supported?**
STARTTLS starts with a not encrypted connection and is therefore not secure.
All known IMAP servers support IMAP with STARTTLS, so there is no need to support STARTTLS for IMAP.
If you encounter an IMAP server that requires STARTTLS, please [create an issue](https://github.com/M66B/open-source-email/issues/new).
All well known IMAP servers support IMAP with a plain SSL connection, so there is no need to support STARTTLS for IMAP.
If you encounter an IMAP server that requires STARTTLS, please let me know.
For more background information, please see [this article](https://www.eff.org/nl/deeplinks/2018/06/announcing-starttls-everywhere-securing-hop-hop-email-delivery).
tl;dr; "*Additionally, even if you configure STARTTLS perfectly and use a valid certificate, theres still no guarantee your communication will be encrypted.*"
<a name="FAQ13"></a>
**(13) How does progressive search work?**
@@ -227,6 +233,16 @@ Browse messages on the server will fetch messages from the email server in real
when you reach the end of the list of synchronized messages, even when the folder is set to not synchronize.
You can disable this feature under *Setup* > *Advanced options* > *Browse messages on the server*.
<a name="FAQ25"></a>
**(25) Why can't I select an image, attachment or a file to export/import?**
If a menu item to select a file is disabled (dimmed),
likely the [storage access framework](https://developer.android.com/guide/topics/providers/document-provider),
a standard Android component, is not present,
for example because your custom ROM does not include it or because it was removed.
FairEmail does not request storage permissions, so this framework is required to select files and folders.
No app, except maybe file managers, targetting Android 4.4 KitKat or later should ask for storage permissions because it would allow access to *all* files.
<br>
If you have another question, want to request a feature or report a bug, you can use [this forum](https://forum.xda-developers.com/android/apps-games/source-email-t3824168).

View File

@@ -6,8 +6,8 @@ android {
applicationId "eu.faircode.email"
minSdkVersion 23
targetSdkVersion 28
versionCode 74
versionName "1.74"
versionCode 78
versionName "1.78"
archivesBaseName = "FairEmail-v$versionName"
javaCompileOptions {

File diff suppressed because it is too large Load Diff

View File

@@ -125,6 +125,7 @@ public class AdapterMessage extends PagedListAdapter<TupleMessageEx, AdapterMess
}
private void clear() {
vwColor.setBackgroundColor(Color.TRANSPARENT);
ivFlagged.setVisibility(View.GONE);
ivAvatar.setVisibility(View.GONE);
tvFrom.setText(null);
@@ -158,7 +159,10 @@ public class AdapterMessage extends PagedListAdapter<TupleMessageEx, AdapterMess
vwColor.setBackgroundColor(message.accountColor == null ? Color.TRANSPARENT : message.accountColor);
vwColor.setVisibility(viewType == ViewType.UNIFIED ? View.VISIBLE : View.GONE);
ivFlagged.setVisibility(message.count - message.unflagged > 0 ? View.VISIBLE : View.GONE);
if (viewType == ViewType.THREAD)
ivFlagged.setVisibility(message.unflagged == 1 ? View.GONE : View.VISIBLE);
else
ivFlagged.setVisibility(message.count - message.unflagged > 0 ? View.VISIBLE : View.GONE);
if (EntityFolder.DRAFTS.equals(message.folderType) ||
EntityFolder.OUTBOX.equals(message.folderType) ||

View File

@@ -67,6 +67,7 @@ public class ApplicationEx extends Application {
getString(R.string.channel_service),
NotificationManager.IMPORTANCE_MIN);
service.setSound(null, Notification.AUDIO_ATTRIBUTES_DEFAULT);
service.setShowBadge(false);
nm.createNotificationChannel(service);
NotificationChannel notification = new NotificationChannel(
@@ -79,13 +80,14 @@ public class ApplicationEx extends Application {
"error",
getString(R.string.channel_error),
NotificationManager.IMPORTANCE_HIGH);
error.setShowBadge(false);
nm.createNotificationChannel(error);
}
}
public boolean ownFault(Throwable ex) {
if (!Helper.isPlayStoreInstall(this))
return true;
//if (!Helper.isPlayStoreInstall(this))
// return true;
if (ex instanceof OutOfMemoryError)
return false;

View File

@@ -43,6 +43,7 @@ import javax.mail.UIDFolder;
import javax.mail.search.BodyTerm;
import javax.mail.search.FromStringTerm;
import javax.mail.search.OrTerm;
import javax.mail.search.RecipientStringTerm;
import javax.mail.search.SubjectTerm;
import androidx.lifecycle.GenericLifecycleObserver;
@@ -90,7 +91,6 @@ public class BoundaryCallbackMessages extends PagedList.BoundaryCallback<TupleMe
@Override
public void run() {
Log.i(Helper.TAG, "Boundary close");
DB.getInstance(context).message().deleteFoundMessages();
try {
if (istore != null)
istore.close();
@@ -167,10 +167,16 @@ public class BoundaryCallbackMessages extends PagedList.BoundaryCallback<TupleMe
else
imessages = ifolder.search(
new OrTerm(
new FromStringTerm(search),
new OrTerm(
new FromStringTerm(search),
new RecipientStringTerm(Message.RecipientType.TO, search)
),
new OrTerm(
new SubjectTerm(search),
new BodyTerm(search))));
new BodyTerm(search)
)
)
);
Log.i(Helper.TAG, "Boundary found messages=" + imessages.length);
index = imessages.length - 1;
@@ -200,11 +206,13 @@ public class BoundaryCallbackMessages extends PagedList.BoundaryCallback<TupleMe
try {
long uid = ifolder.getUID(isub[j]);
Log.i(Helper.TAG, "Boundary sync uid=" + uid);
if (db.message().getMessageByUid(fid, uid) == null) {
EntityMessage message = db.message().getMessageByUid(fid, uid);
if (message == null) {
ServiceSynchronize.synchronizeMessage(context, folder, ifolder, (IMAPMessage) isub[j], search != null);
count++;
loaded++;
}
} else
db.message().setMessageFound(message.id, true);
} catch (MessageRemovedException ex) {
Log.w(Helper.TAG, "Boundary " + ex + "\n" + Log.getStackTraceString(ex));
} catch (FolderClosedException ex) {

View File

@@ -45,7 +45,7 @@ import androidx.sqlite.db.SupportSQLiteDatabase;
// https://developer.android.com/topic/libraries/architecture/room.html
@Database(
version = 19,
version = 20,
entities = {
EntityIdentity.class,
EntityAccount.class,
@@ -250,6 +250,13 @@ public abstract class DB extends RoomDatabase {
db.execSQL("ALTER TABLE `folder` ADD COLUMN `hide` INTEGER NOT NULL DEFAULT 0");
}
})
.addMigrations(new Migration(19, 20) {
@Override
public void migrate(SupportSQLiteDatabase db) {
Log.i(Helper.TAG, "DB migration from version " + startVersion + " to " + endVersion);
db.execSQL("ALTER TABLE `folder` ADD COLUMN `poll_interval` INTEGER");
}
})
.build();
}

View File

@@ -90,10 +90,6 @@ public interface DaoFolder {
" WHERE account = :account AND type = :type")
EntityFolder getFolderByType(long account, String type);
@Query("SELECT COUNT(folder.id) FROM folder" +
" WHERE account = :account AND `synchronize`")
int getFolderSyncCount(long account);
// For debug/crash info
@Query("SELECT folder.* FROM folder" +
" JOIN account ON account.id = folder.account" +
@@ -128,8 +124,9 @@ public interface DaoFolder {
", synchronize = :synchronize" +
", unified = :unified" +
", `after` = :after" +
", `poll_interval` = :poll_interval" +
" WHERE id = :id")
int setFolderProperties(long id, String name, String display, boolean hide, boolean synchronize, boolean unified, int after);
int setFolderProperties(long id, String name, String display, boolean hide, boolean synchronize, boolean unified, int after, Integer poll_interval);
@Query("UPDATE folder SET name = :name WHERE account = :account AND name = :old")
int renameFolder(long account, String old, String name);

View File

@@ -241,7 +241,4 @@ public interface DaoMessage {
@Query("DELETE FROM message WHERE folder = :folder AND received < :received AND NOT uid IS NULL")
int deleteMessagesBefore(long folder, long received);
@Query("DELETE FROM message WHERE ui_found")
int deleteFoundMessages();
}

View File

@@ -49,14 +49,14 @@ public class EntityAccount {
@NonNull
public Integer auth_type;
@NonNull
public Boolean primary;
@NonNull
public Boolean synchronize;
@NonNull
public Boolean primary;
public Integer color;
@NonNull
public Boolean store_sent; // obsolete
@NonNull
public Integer poll_interval; // NOOP interval
public Integer poll_interval; // keep-alive interval
public Long seen_until;
public String state;
public String error;
@@ -66,12 +66,15 @@ public class EntityAccount {
if (obj instanceof EntityAccount) {
EntityAccount other = (EntityAccount) obj;
return ((this.name == null ? other.name == null : this.name.equals(other.name)) &&
(this.signature == null ? other.signature == null : this.signature.equals(other.signature)) &&
this.host.equals(other.host) &&
this.port.equals(other.port) &&
this.user.equals(other.user) &&
this.password.equals(other.password) &&
this.primary.equals(other.primary) &&
this.auth_type.equals(other.auth_type) &&
this.synchronize.equals(other.synchronize) &&
this.primary.equals(other.primary) &&
(this.color == null ? other.color == null : this.color.equals(other.color)) &&
this.poll_interval.equals(other.poll_interval) &&
(this.seen_until == null ? other.seen_until == null : this.seen_until.equals(other.seen_until)) &&
(this.state == null ? other.state == null : this.state.equals(other.state)) &&
@@ -89,8 +92,8 @@ public class EntityAccount {
json.put("user", user);
json.put("password", "");
json.put("auth_type", auth_type);
json.put("primary", primary);
json.put("synchronize", false);
json.put("primary", primary);
if (color != null)
json.put("color", color);
json.put("poll_interval", poll_interval);
@@ -108,8 +111,8 @@ public class EntityAccount {
account.user = json.getString("user");
account.password = json.getString("password");
account.auth_type = json.getInt("auth_type");
account.primary = json.getBoolean("primary");
account.synchronize = json.getBoolean("synchronize");
account.primary = json.getBoolean("primary");
if (json.has("color"))
account.color = json.getInt("color");
account.poll_interval = json.getInt("poll_interval");

View File

@@ -19,12 +19,10 @@ package eu.faircode.email;
Copyright 2018 by Marcel Bokhorst (M66B)
*/
import android.os.Parcel;
import android.os.Parcelable;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.Serializable;
import java.util.Arrays;
import java.util.List;
@@ -50,7 +48,7 @@ import static androidx.room.ForeignKey.CASCADE;
}
)
public class EntityFolder implements Parcelable {
public class EntityFolder implements Serializable {
static final String TABLE_NAME = "folder";
@PrimaryKey(autoGenerate = true)
@@ -62,6 +60,7 @@ public class EntityFolder implements Parcelable {
public String type;
@NonNull
public Boolean synchronize;
public Integer poll_interval;
@NonNull
public Integer after; // days
public String display;
@@ -136,7 +135,7 @@ public class EntityFolder implements Parcelable {
this.name.equals(other.name) &&
this.type.equals(other.type) &&
this.synchronize.equals(other.synchronize) &&
this.after.equals(other.after) &&
(this.poll_interval == null ? other.poll_interval == null : this.poll_interval.equals(other.poll_interval)) && this.after.equals(other.after) &&
(this.display == null ? other.display == null : this.display.equals(other.display)) &&
this.hide == other.hide &&
this.unified == other.unified &&
@@ -151,81 +150,16 @@ public class EntityFolder implements Parcelable {
return name;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel parcel, int i) {
if (id == null) {
parcel.writeByte((byte) 0);
} else {
parcel.writeByte((byte) 1);
parcel.writeLong(id);
}
if (account == null) {
parcel.writeByte((byte) 0);
} else {
parcel.writeByte((byte) 1);
parcel.writeLong(account);
}
parcel.writeString(name);
parcel.writeString(type);
parcel.writeByte((byte) (synchronize == null ? 0 : synchronize ? 1 : 2));
if (after == null) {
parcel.writeByte((byte) 0);
} else {
parcel.writeByte((byte) 1);
parcel.writeInt(after);
}
parcel.writeString(state);
parcel.writeString(error);
}
protected EntityFolder(Parcel in) {
if (in.readByte() == 0) {
id = null;
} else {
id = in.readLong();
}
if (in.readByte() == 0) {
account = null;
} else {
account = in.readLong();
}
name = in.readString();
type = in.readString();
byte tmpSynchronize = in.readByte();
synchronize = tmpSynchronize == 0 ? null : tmpSynchronize == 1;
if (in.readByte() == 0) {
after = null;
} else {
after = in.readInt();
}
state = in.readString();
error = in.readString();
}
public static final Creator<EntityFolder> CREATOR = new Creator<EntityFolder>() {
@Override
public EntityFolder createFromParcel(Parcel in) {
return new EntityFolder(in);
}
@Override
public EntityFolder[] newArray(int size) {
return new EntityFolder[size];
}
};
public JSONObject toJSON() throws JSONException {
JSONObject json = new JSONObject();
json.put("name", name);
json.put("type", type);
json.put("unified", unified);
json.put("synchronize", synchronize);
json.put("poll_interval", poll_interval);
json.put("after", after);
json.put("display", display);
json.put("hide", hide);
json.put("unified", unified);
return json;
}
@@ -233,9 +167,15 @@ public class EntityFolder implements Parcelable {
EntityFolder folder = new EntityFolder();
folder.name = json.getString("name");
folder.type = json.getString("type");
folder.unified = json.getBoolean("unified");
folder.synchronize = json.getBoolean("synchronize");
if (json.has("poll_interval"))
folder.poll_interval = json.getInt("poll_interval");
folder.after = json.getInt("after");
if (json.has("display"))
folder.display = json.getString("display");
if (json.has("hide"))
folder.hide = json.getBoolean("hide");
folder.unified = json.getBoolean("unified");
return folder;
}
}

View File

@@ -23,7 +23,6 @@ import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
@@ -253,9 +252,9 @@ public class FragmentAbout extends FragmentEx {
}
});
boolean debug = PreferenceManager.getDefaultSharedPreferences(getContext()).getBoolean("debug", false);
btnLog.setVisibility(debug || BuildConfig.DEBUG ? View.VISIBLE : View.GONE);
btnDebugInfo.setVisibility(debug || BuildConfig.DEBUG ? View.VISIBLE : View.GONE);
//boolean debug = PreferenceManager.getDefaultSharedPreferences(getContext()).getBoolean("debug", false);
//btnLog.setVisibility(debug || BuildConfig.DEBUG ? View.VISIBLE : View.GONE);
//btnDebugInfo.setVisibility(debug || BuildConfig.DEBUG ? View.VISIBLE : View.GONE);
return view;
}

View File

@@ -102,6 +102,7 @@ public class FragmentAccount extends FragmentEx {
private TextInputLayout tilPassword;
private Button btnAdvanced;
private TextView tvName;
private EditText etName;
private Button btnColor;
@@ -109,11 +110,12 @@ public class FragmentAccount extends FragmentEx {
private ImageView ibColorDefault;
private EditText etSignature;
private ImageButton ibPro;
private CheckBox cbSynchronize;
private CheckBox cbPrimary;
private EditText etInterval;
private Button btnCheck;
private Button btnCheck;
private ProgressBar pbCheck;
private TextView tvIdle;
@@ -169,6 +171,7 @@ public class FragmentAccount extends FragmentEx {
tilPassword = view.findViewById(R.id.tilPassword);
btnAdvanced = view.findViewById(R.id.btnAdvanced);
etName = view.findViewById(R.id.etName);
tvName = view.findViewById(R.id.tvName);
btnColor = view.findViewById(R.id.btnColor);
@@ -576,18 +579,19 @@ public class FragmentAccount extends FragmentEx {
args.putString("password", tilPassword.getEditText().getText().toString());
args.putInt("auth_type", authorized == null ? Helper.AUTH_TYPE_PASSWORD : provider.getAuthType());
args.putBoolean("synchronize", cbSynchronize.isChecked());
args.putString("name", etName.getText().toString());
args.putInt("color", color);
args.putString("signature", Html.toHtml(etSignature.getText()));
args.putBoolean("synchronize", cbSynchronize.isChecked());
args.putBoolean("primary", cbPrimary.isChecked());
args.putString("interval", etInterval.getText().toString());
args.putParcelable("drafts", drafts);
args.putParcelable("sent", sent);
args.putParcelable("all", all);
args.putParcelable("trash", trash);
args.putParcelable("junk", junk);
args.putSerializable("drafts", drafts);
args.putSerializable("sent", sent);
args.putSerializable("all", all);
args.putSerializable("trash", trash);
args.putSerializable("junk", junk);
new SimpleTask<Void>() {
@Override
@@ -601,15 +605,16 @@ public class FragmentAccount extends FragmentEx {
String name = args.getString("name");
int color = args.getInt("color");
String signature = args.getString("signature");
boolean synchronize = args.getBoolean("synchronize");
boolean primary = args.getBoolean("primary");
String interval = args.getString("interval");
EntityFolder drafts = args.getParcelable("drafts");
EntityFolder sent = args.getParcelable("sent");
EntityFolder all = args.getParcelable("all");
EntityFolder trash = args.getParcelable("trash");
EntityFolder junk = args.getParcelable("junk");
EntityFolder drafts = (EntityFolder) args.getSerializable("drafts");
EntityFolder sent = (EntityFolder) args.getSerializable("sent");
EntityFolder all = (EntityFolder) args.getSerializable("all");
EntityFolder trash = (EntityFolder) args.getSerializable("trash");
EntityFolder junk = (EntityFolder) args.getSerializable("junk");
if (TextUtils.isEmpty(host))
throw new Throwable(getContext().getString(R.string.title_no_host));
@@ -660,19 +665,23 @@ public class FragmentAccount extends FragmentEx {
boolean update = (account != null);
if (account == null)
account = new EntityAccount();
account.name = name;
account.color = color;
account.signature = signature;
account.host = host;
account.port = Integer.parseInt(port);
account.user = user;
account.password = password;
account.auth_type = auth_type;
account.name = name;
account.color = color;
account.signature = signature;
account.synchronize = synchronize;
account.primary = (account.synchronize && primary);
account.store_sent = false;
account.poll_interval = Integer.parseInt(interval);
account.store_sent = false;
if (!synchronize)
account.error = null;
@@ -1024,7 +1033,7 @@ public class FragmentAccount extends FragmentEx {
tilPassword.getEditText().setText(token);
} catch (Throwable ex) {
Log.e(Helper.TAG, ex + "\n" + Log.getStackTraceString(ex));
Helper.unexpectedError(getContext(), ex);
snackbar.setText(Helper.formatThrowable(ex));
} finally {
snackbar.dismiss();
}

View File

@@ -322,7 +322,16 @@ public class FragmentCompose extends FragmentEx {
public CharSequence convertToString(Cursor cursor) {
int colName = cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME);
int colEmail = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Email.DATA);
return cursor.getString(colName) + " <" + cursor.getString(colEmail) + ">";
String name = cursor.getString(colName);
String email = cursor.getString(colEmail);
StringBuilder sb = new StringBuilder();
if (name == null)
sb.append(email);
else {
sb.append(name.replace(",", "")).append(" ");
sb.append("<").append(email).append(">");
}
return sb.toString();
}
});
}
@@ -413,6 +422,10 @@ public class FragmentCompose extends FragmentEx {
menu.findItem(R.id.menu_attachment).setVisible(!free && working >= 0);
menu.findItem(R.id.menu_attachment).setEnabled(etBody.isEnabled());
menu.findItem(R.id.menu_addresses).setVisible(!free && working >= 0);
PackageManager pm = getContext().getPackageManager();
menu.findItem(R.id.menu_image).setEnabled(getImageIntent().resolveActivity(pm) != null);
menu.findItem(R.id.menu_attachment).setEnabled(getAttachmentIntent().resolveActivity(pm) != null);
}
@Override
@@ -479,17 +492,11 @@ public class FragmentCompose extends FragmentEx {
}
private void onMenuImage() {
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("image/*");
startActivityForResult(intent, ActivityCompose.REQUEST_IMAGE);
startActivityForResult(getImageIntent(), ActivityCompose.REQUEST_IMAGE);
}
private void onMenuAttachment() {
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("*/*");
startActivityForResult(intent, ActivityCompose.REQUEST_ATTACHMENT);
startActivityForResult(getAttachmentIntent(), ActivityCompose.REQUEST_ATTACHMENT);
}
private void onMenuAddresses() {
@@ -513,23 +520,18 @@ public class FragmentCompose extends FragmentEx {
}
}
private void handleExit() {
if (getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.RESUMED))
new DialogBuilderLifecycle(getContext(), getViewLifecycleOwner())
.setMessage(R.string.title_ask_delete)
.setPositiveButton(R.string.title_yes, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
onAction(R.id.action_delete);
}
})
.setNegativeButton(R.string.title_no, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
finish();
}
})
.show();
private Intent getImageIntent() {
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("image/*");
return intent;
}
private Intent getAttachmentIntent() {
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("*/*");
return intent;
}
private void handlePickContact(int requestCode, Intent data) {
@@ -559,9 +561,7 @@ public class FragmentCompose extends FragmentEx {
InternetAddress address = new InternetAddress(email, name);
StringBuilder sb = new StringBuilder(text);
if (sb.length() > 0)
sb.append(", ");
sb.append(address.toString());
sb.append(address.toString().replace(",", "")).append(", ");
if (requestCode == ActivityCompose.REQUEST_CONTACT_TO)
etTo.setText(sb.toString());
@@ -622,6 +622,25 @@ public class FragmentCompose extends FragmentEx {
}.load(this, args);
}
private void handleExit() {
if (getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.RESUMED))
new DialogBuilderLifecycle(getContext(), getViewLifecycleOwner())
.setMessage(R.string.title_ask_delete)
.setPositiveButton(R.string.title_yes, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
onAction(R.id.action_delete);
}
})
.setNegativeButton(R.string.title_no, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
finish();
}
})
.show();
}
private void onAction(int action) {
Helper.setViewsEnabled(view, false);
getActivity().invalidateOptionsMenu();
@@ -1285,8 +1304,10 @@ public class FragmentCompose extends FragmentEx {
long id = Long.parseLong(cid[1].replace(BuildConfig.APPLICATION_ID + ".", ""));
File file = EntityAttachment.getFile(getContext(), id);
Drawable d = Drawable.createFromPath(file.getAbsolutePath());
d.setBounds(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight());
return d;
if (d != null) {
d.setBounds(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight());
return d;
}
}
}

View File

@@ -44,6 +44,7 @@ import javax.mail.Session;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.constraintlayout.widget.Group;
import androidx.lifecycle.Observer;
public class FragmentFolder extends FragmentEx {
@@ -54,16 +55,16 @@ public class FragmentFolder extends FragmentEx {
private CheckBox cbSynchronize;
private CheckBox cbUnified;
private EditText etAfter;
private EditText etInterval;
private Button btnSave;
private ImageButton ibDelete;
private ProgressBar pbSave;
private ProgressBar pbWait;
private Group grpInterval;
private long id = -1;
private long account = -1;
private static final int MAX_FOLDER_SYNC = 25;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -88,10 +89,12 @@ public class FragmentFolder extends FragmentEx {
cbSynchronize = view.findViewById(R.id.cbSynchronize);
cbUnified = view.findViewById(R.id.cbUnified);
etAfter = view.findViewById(R.id.etAfter);
etInterval = view.findViewById(R.id.etInterval);
btnSave = view.findViewById(R.id.btnSave);
ibDelete = view.findViewById(R.id.ibDelete);
pbSave = view.findViewById(R.id.pbSave);
pbWait = view.findViewById(R.id.pbWait);
grpInterval = view.findViewById(R.id.grpInterval);
btnSave.setOnClickListener(new View.OnClickListener() {
@Override
@@ -110,6 +113,7 @@ public class FragmentFolder extends FragmentEx {
args.putBoolean("unified", cbUnified.isChecked());
args.putBoolean("synchronize", cbSynchronize.isChecked());
args.putString("after", etAfter.getText().toString());
args.putString("interval", etInterval.getText().toString());
new SimpleTask<Void>() {
@Override
@@ -122,10 +126,12 @@ public class FragmentFolder extends FragmentEx {
boolean unified = args.getBoolean("unified");
boolean synchronize = args.getBoolean("synchronize");
String after = args.getString("after");
String interval = args.getString("interval");
if (TextUtils.isEmpty(display) || display.equals(name))
display = null;
int days = (TextUtils.isEmpty(after) ? EntityFolder.DEFAULT_USER_SYNC : Integer.parseInt(after));
Integer poll_interval = (TextUtils.isEmpty(interval) ? null : Integer.parseInt(interval));
IMAPStore istore = null;
DB db = DB.getInstance(getContext());
@@ -159,6 +165,7 @@ public class FragmentFolder extends FragmentEx {
create.unified = unified;
create.synchronize = synchronize;
create.after = days;
create.poll_interval = poll_interval;
db.folder().insertFolder(create);
} else {
Log.i(Helper.TAG, "Renaming folder=" + name);
@@ -173,7 +180,7 @@ public class FragmentFolder extends FragmentEx {
if (folder != null) {
Log.i(Helper.TAG, "Updating folder=" + name);
db.folder().setFolderProperties(id, name, display, hide, synchronize, unified, days);
db.folder().setFolderProperties(id, name, display, hide, synchronize, unified, days, poll_interval);
if (!synchronize)
db.folder().setFolderError(id, null);
}
@@ -296,6 +303,7 @@ public class FragmentFolder extends FragmentEx {
ibDelete.setVisibility(View.GONE);
pbSave.setVisibility(View.GONE);
pbWait.setVisibility(View.VISIBLE);
grpInterval.setVisibility(View.GONE);
return view;
}
@@ -320,34 +328,70 @@ public class FragmentFolder extends FragmentEx {
etDisplay.setHint(folder == null ? null : folder.name);
cbHide.setChecked(folder == null ? false : folder.hide);
cbUnified.setChecked(folder == null ? false : folder.unified);
cbSynchronize.setChecked(folder == null || folder.synchronize);
etAfter.setText(Integer.toString(folder == null ? EntityFolder.DEFAULT_USER_SYNC : folder.after));
etInterval.setText(folder == null || folder.poll_interval == null ? null : Integer.toString(folder.poll_interval));
}
// Consider previous save as cancelled
pbWait.setVisibility(View.GONE);
Helper.setViewsEnabled(view, true);
etRename.setEnabled(folder == null || EntityFolder.USER.equals(folder.type));
cbSynchronize.setEnabled(false);
btnSave.setEnabled(true);
ibDelete.setVisibility(folder == null || !EntityFolder.USER.equals(folder.type) ? View.GONE : View.VISIBLE);
Bundle args = new Bundle();
args.putLong("account", folder == null ? account : folder.account);
new SimpleTask<Integer>() {
@Override
protected Integer onLoad(Context context, Bundle args) {
long account = args.getLong("account");
return DB.getInstance(context).folder().getFolderSyncCount(account);
}
@Override
protected void onLoaded(Bundle args, Integer count) {
cbSynchronize.setChecked((folder == null || folder.synchronize) && count < MAX_FOLDER_SYNC);
cbSynchronize.setEnabled(count < MAX_FOLDER_SYNC);
}
}.load(FragmentFolder.this, args);
}
});
Bundle args = new Bundle();
args.putLong("id", id);
args.putLong("account", account);
new SimpleTask<Boolean>() {
@Override
protected Boolean onLoad(Context context, Bundle args) throws Throwable {
long fid = args.getLong("id");
long aid = args.getLong("account");
IMAPStore istore = null;
DB db = DB.getInstance(getContext());
try {
db.beginTransaction();
EntityAccount account;
if (fid < 0)
account = db.account().getAccount(aid);
else {
EntityFolder folder = db.folder().getFolder(fid);
account = db.account().getAccount(folder.account);
}
db.setTransactionSuccessful();
Properties props = MessageHelper.getSessionProperties(account.auth_type);
Session isession = Session.getInstance(props, null);
istore = (IMAPStore) isession.getStore("imaps");
istore.connect(account.host, account.port, account.user, account.password);
return istore.hasCapability("IDLE");
} finally {
db.endTransaction();
if (istore != null)
istore.close();
}
}
@Override
protected void onLoaded(Bundle args, Boolean capIdle) {
grpInterval.setVisibility(capIdle ? View.GONE : View.VISIBLE);
}
@Override
protected void onException(Bundle args, Throwable ex) {
grpInterval.setVisibility(View.VISIBLE);
if (BuildConfig.DEBUG)
Helper.unexpectedError(getContext(), ex);
}
}.load(this, args);
}
}

View File

@@ -796,26 +796,16 @@ public class FragmentMessage extends FragmentEx {
}
private void onMenuShowHtml() {
new SimpleTask<String>() {
@Override
protected String onLoad(Context context, Bundle args) throws Throwable {
return message.read(context);
}
Bundle args = new Bundle();
args.putLong("id", message.id);
args.putString("from", MessageHelper.getFormattedAddresses(message.from, true));
@Override
protected void onLoaded(Bundle a, String html) {
Bundle args = new Bundle();
args.putString("html", html);
args.putString("from", MessageHelper.getFormattedAddresses(message.from, true));
FragmentWebView fragment = new FragmentWebView();
fragment.setArguments(args);
FragmentWebView fragment = new FragmentWebView();
fragment.setArguments(args);
FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
fragmentTransaction.replace(R.id.content_frame, fragment).addToBackStack("webview");
fragmentTransaction.commit();
}
}.load(this, new Bundle());
FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
fragmentTransaction.replace(R.id.content_frame, fragment).addToBackStack("webview");
fragmentTransaction.commit();
}
private void onMenuUnseen() {

View File

@@ -182,7 +182,7 @@ public class FragmentMessages extends FragmentEx {
return 0;
TupleMessageEx message = ((AdapterMessage) rvMessage.getAdapter()).getCurrentList().get(pos);
if (message.threaded || EntityFolder.OUTBOX.equals(message.folderType))
if (message == null || message.threaded || EntityFolder.OUTBOX.equals(message.folderType))
return 0;
return makeMovementFlags(0, ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT);
@@ -200,6 +200,9 @@ public class FragmentMessages extends FragmentEx {
return;
TupleMessageEx message = ((AdapterMessage) rvMessage.getAdapter()).getCurrentList().get(pos);
if (message == null)
return;
boolean inbox = (EntityFolder.ARCHIVE.equals(message.folderType) || EntityFolder.TRASH.equals(message.folderType));
View itemView = viewHolder.itemView;
@@ -237,6 +240,8 @@ public class FragmentMessages extends FragmentEx {
return;
TupleMessageEx message = ((AdapterMessage) rvMessage.getAdapter()).getCurrentList().get(pos);
if (message == null)
return;
Log.i(Helper.TAG, "Swiped dir=" + direction + " message=" + message.id);
Bundle args = new Bundle();

View File

@@ -19,6 +19,7 @@ package eu.faircode.email;
Copyright 2018 by Marcel Bokhorst (M66B)
*/
import android.content.Context;
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.Bundle;
@@ -82,11 +83,26 @@ public class FragmentWebView extends FragmentEx {
String url = args.getString("url");
webview.loadUrl(url);
setSubtitle(url);
} else if (args.containsKey("html")) {
String html = args.getString("html");
String from = args.getString("from");
webview.loadDataWithBaseURL("email://", html, "text/html", "UTF-8", null);
setSubtitle(from);
} else if (args.containsKey("id")) {
new SimpleTask<String>() {
@Override
protected String onLoad(Context context, Bundle args) throws Throwable {
long id = args.getLong("id");
return EntityMessage.read(context, id);
}
@Override
protected void onLoaded(Bundle args, String html) {
String from = args.getString("from");
webview.loadDataWithBaseURL("email://", html, "text/html", "UTF-8", null);
setSubtitle(from);
}
@Override
protected void onException(Bundle args, Throwable ex) {
Helper.unexpectedError(getContext(), ex);
}
}.load(this, args);
}
((ActivityBase) getActivity()).addBackPressedListener(new ActivityBase.IBackPressedListener() {

View File

@@ -57,7 +57,6 @@ import java.util.concurrent.ThreadFactory;
import javax.mail.Address;
import javax.mail.AuthenticationFailedException;
import javax.mail.FolderClosedException;
import javax.mail.MessagingException;
import javax.mail.internet.InternetAddress;
@@ -142,11 +141,6 @@ public class Helper {
}
static String formatThrowable(Throwable ex) {
if (ex instanceof FolderClosedException)
return null;
if (ex instanceof IllegalStateException)
return null;
StringBuilder sb = new StringBuilder();
sb.append(ex.getMessage() == null ? ex.getClass().getName() : ex.getMessage());
Throwable cause = ex.getCause();

View File

@@ -83,7 +83,7 @@ public class MessageHelper {
// https://tools.ietf.org/html/rfc4978
// https://docs.oracle.com/javase/8/docs/api/java/util/zip/Deflater.html
if (false) {
if (true) {
Log.i(Helper.TAG, "IMAP compress enabled");
props.put("mail.imaps.compress.enable", "true");
//props.put("mail.imaps.compress.level", "-1");
@@ -91,6 +91,7 @@ public class MessageHelper {
}
props.put("mail.imaps.fetchsize", Integer.toString(48 * 1024)); // default 16K
props.put("mail.imaps.peek", "true");
// https://javaee.github.io/javamail/docs/api/com/sun/mail/smtp/package-summary.html#properties
props.put("mail.smtps.ssl.checkserveridentity", "true");
@@ -113,9 +114,6 @@ public class MessageHelper {
props.put("mail.smtp.writetimeout", Integer.toString(NETWORK_TIMEOUT)); // one thread overhead
props.put("mail.smtp.timeout", Integer.toString(NETWORK_TIMEOUT));
props.put("mail.imaps.peek", "true");
//props.put("mail.imaps.minidletime", "5000");
props.put("mail.mime.address.strict", "false");
props.put("mail.mime.decodetext.strict", "false");

View File

@@ -50,12 +50,10 @@ import android.text.TextUtils;
import android.util.Log;
import com.sun.mail.iap.ConnectionException;
import com.sun.mail.iap.ProtocolException;
import com.sun.mail.imap.AppendUID;
import com.sun.mail.imap.IMAPFolder;
import com.sun.mail.imap.IMAPMessage;
import com.sun.mail.imap.IMAPStore;
import com.sun.mail.imap.protocol.IMAPProtocol;
import com.sun.mail.util.FolderClosedIOException;
import com.sun.mail.util.MailConnectException;
@@ -129,7 +127,6 @@ public class ServiceSynchronize extends LifecycleService {
private static final int CONNECT_BACKOFF_START = 8; // seconds
private static final int CONNECT_BACKOFF_MAX = 1024; // seconds (1024 sec ~ 17 min)
private static final long STORE_NOOP_INTERVAL = 9 * 60 * 1000L; // milliseconds
private static final int SYNC_BATCH_SIZE = 20;
private static final int DOWNLOAD_BATCH_SIZE = 20;
private static final int MESSAGE_AUTO_DOWNLOAD_SIZE = 32 * 1024; // bytes
@@ -214,7 +211,7 @@ public class ServiceSynchronize extends LifecycleService {
ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
cm.unregisterNetworkCallback(serviceManager);
serviceManager.stop();
serviceManager.onLost(null);
stopForeground(true);
@@ -326,7 +323,7 @@ public class ServiceSynchronize extends LifecycleService {
builder = new Notification.Builder(this);
builder
.setSmallIcon(R.drawable.baseline_compare_arrows_24)
.setSmallIcon(R.drawable.baseline_compare_arrows_white_24)
.setContentTitle(getResources().getQuantityString(R.plurals.title_notification_synchronizing, accounts, accounts))
.setContentIntent(pi)
.setAutoCancel(false)
@@ -375,7 +372,7 @@ public class ServiceSynchronize extends LifecycleService {
builder = new Notification.Builder(this, "notification");
builder
.setSmallIcon(R.drawable.baseline_mail_24)
.setSmallIcon(R.drawable.baseline_email_white_24)
.setContentTitle(getResources().getQuantityString(R.plurals.title_notification_unseen, messages.size(), messages.size()))
.setContentText("")
.setContentIntent(piView)
@@ -559,6 +556,7 @@ public class ServiceSynchronize extends LifecycleService {
// Debug
boolean debug = PreferenceManager.getDefaultSharedPreferences(this).getBoolean("debug", false);
debug = debug || BuildConfig.DEBUG;
System.setProperty("mail.socket.debug", Boolean.toString(debug));
// Create session
@@ -569,7 +567,7 @@ public class ServiceSynchronize extends LifecycleService {
final IMAPStore istore = (IMAPStore) isession.getStore("imaps");
final Map<EntityFolder, IMAPFolder> folders = new HashMap<>();
List<Thread> noops = new ArrayList<>();
List<Thread> pollers = new ArrayList<>();
List<Thread> idlers = new ArrayList<>();
try {
// Listen for store events
@@ -672,7 +670,7 @@ public class ServiceSynchronize extends LifecycleService {
db.folder().setFolderError(folder.id, null);
// Keep folder connection alive
Thread noop = new Thread(new Runnable() {
Thread poller = new Thread(new Runnable() {
@Override
public void run() {
try {
@@ -811,26 +809,15 @@ public class ServiceSynchronize extends LifecycleService {
}
});
Log.i(Helper.TAG, folder.name + " start noop");
while (state.running && ifolder.isOpen()) {
try {
Thread.sleep(account.poll_interval * 60 * 1000L);
if (capIdle) {
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;
}
});
} else
if (!capIdle) {
Log.i(Helper.TAG, folder.name + " start polling");
while (state.running) {
try {
Thread.sleep((folder.poll_interval == null ? 9 : folder.poll_interval) * 60 * 1000L);
synchronizeMessages(account, folder, ifolder, state);
} catch (InterruptedException ex) {
Log.w(Helper.TAG, folder.name + " noop " + ex.toString());
} catch (InterruptedException ex) {
Log.w(Helper.TAG, folder.name + " poll " + ex.toString());
}
}
}
} catch (Throwable ex) {
@@ -843,16 +830,17 @@ public class ServiceSynchronize extends LifecycleService {
state.notifyAll();
}
} finally {
Log.i(Helper.TAG, folder.name + " end noop");
if (!capIdle)
Log.i(Helper.TAG, folder.name + " end polling");
}
}
}, "sync.noop." + folder.id);
noop.start();
noops.add(noop);
}, "sync.poller." + folder.id);
poller.start();
pollers.add(poller);
// Receive folder events
if (capIdle) {
Thread idle = new Thread(new Runnable() {
Thread idler = new Thread(new Runnable() {
@Override
public void run() {
try {
@@ -876,8 +864,8 @@ public class ServiceSynchronize extends LifecycleService {
}
}
}, "sync.idle." + folder.id);
idle.start();
idlers.add(idle);
idler.start();
idlers.add(idler);
}
}
@@ -965,22 +953,22 @@ public class ServiceSynchronize extends LifecycleService {
try {
// Keep store alive
while (state.running && istore.isConnected()) {
Log.i(Helper.TAG, "Checking folders");
for (EntityFolder folder : folders.keySet())
if (!folders.get(folder).isOpen())
throw new FolderClosedException(folders.get(folder));
// Wait for stop or folder error
while (state.running) {
Log.i(Helper.TAG, account.name + " wait");
synchronized (state) {
try {
state.wait(STORE_NOOP_INTERVAL);
state.wait(account.poll_interval * 60 * 1000L);
} catch (InterruptedException ex) {
Log.w(Helper.TAG, account.name + " wait " + ex.toString());
}
}
Log.i(Helper.TAG, account.name + " waited");
if (!istore.isConnected())
throw new StoreClosedException(istore);
for (EntityFolder folder : folders.keySet())
if (!folders.get(folder).isOpen())
throw new FolderClosedException(folders.get(folder));
}
Log.i(Helper.TAG, account.name + " done running=" + state.running);
} finally {
@@ -992,11 +980,18 @@ public class ServiceSynchronize extends LifecycleService {
db.account().setAccountError(account.id, Helper.formatThrowable(ex));
} finally {
// Close store
EntityLog.log(this, account.name + " closing");
db.account().setAccountState(account.id, "closing");
for (EntityFolder folder : folders.keySet())
db.folder().setFolderState(folder.id, "closing");
// Stop pollers
for (Thread poller : pollers) {
poller.interrupt();
join(poller);
}
// Close store
try {
Thread t = new Thread(new Runnable() {
@Override
@@ -1026,16 +1021,10 @@ public class ServiceSynchronize extends LifecycleService {
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);
// Stop idlers
for (Thread idler : idlers) {
idler.interrupt();
join(idler);
}
}
@@ -1330,6 +1319,9 @@ public class ServiceSynchronize extends LifecycleService {
private void doHeaders(EntityFolder folder, IMAPFolder ifolder, EntityMessage message, DB db) throws MessagingException {
Message imessage = ifolder.getMessageByUID(message.uid);
if (imessage == null)
throw new MessageRemovedException();
Enumeration<Header> headers = imessage.getAllHeaders();
StringBuilder sb = new StringBuilder();
while (headers.hasMoreElements()) {
@@ -1534,10 +1526,12 @@ public class ServiceSynchronize extends LifecycleService {
if (message == null)
full.add(imessage);
}
long headers = SystemClock.elapsedRealtime();
ifolder.fetch(full.toArray(new Message[0]), fp);
Log.i(Helper.TAG, folder.name + " fetched headers=" + full.size() +
" " + (SystemClock.elapsedRealtime() - headers) + " ms");
if (full.size() > 0) {
long headers = SystemClock.elapsedRealtime();
ifolder.fetch(full.toArray(new Message[0]), fp);
Log.i(Helper.TAG, folder.name + " fetched headers=" + full.size() +
" " + (SystemClock.elapsedRealtime() - headers) + " ms");
}
for (int j = isub.length - 1; j >= 0; j--)
try {
@@ -1791,7 +1785,7 @@ public class ServiceSynchronize extends LifecycleService {
if (!fetch)
for (EntityAttachment attachment : attachments)
if (!attachment.available)
if (attachment.size != null && attachment.size < ATTACHMENT_AUTO_DOWNLOAD_SIZE) {
if (!metered || (attachment.size != null && attachment.size < ATTACHMENT_AUTO_DOWNLOAD_SIZE)) {
fetch = true;
break;
}
@@ -1821,7 +1815,7 @@ public class ServiceSynchronize extends LifecycleService {
for (int i = 0; i < attachments.size(); i++) {
EntityAttachment attachment = attachments.get(i);
if (!attachment.available)
if (attachment.size != null && attachment.size < ATTACHMENT_AUTO_DOWNLOAD_SIZE) {
if (!metered || (attachment.size != null && attachment.size < ATTACHMENT_AUTO_DOWNLOAD_SIZE)) {
if (iattachments == null)
iattachments = helper.getAttachments();
attachment.part = iattachments.get(i).part;
@@ -1864,7 +1858,7 @@ public class ServiceSynchronize extends LifecycleService {
if (running) {
ConnectivityManager cm = getSystemService(ConnectivityManager.class);
NetworkInfo ani = cm.getActiveNetworkInfo();
NetworkInfo ani = (network == null ? null : cm.getActiveNetworkInfo());
EntityLog.log(ServiceSynchronize.this, "Network active=" + (ani == null ? null : ani.toString()));
if (ani == null || !ani.isConnected()) {
EntityLog.log(ServiceSynchronize.this, "Network disconnected=" + ani);

Binary file not shown.

After

Width:  |  Height:  |  Size: 178 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 178 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 229 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 234 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 185 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 181 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 233 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 236 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 267 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 269 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 373 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 421 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 262 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 268 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 368 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 419 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 144 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 178 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 197 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 181 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 200 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 189 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 203 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 269 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 325 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 185 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 209 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 268 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 322 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 178 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 197 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 234 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 304 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 181 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 200 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 236 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 305 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 269 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 325 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 421 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 533 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 268 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 322 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 419 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 532 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 229 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 234 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 341 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 398 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 233 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 236 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 341 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 398 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 373 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 421 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 587 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 769 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 368 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 419 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 585 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 759 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 234 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 304 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 398 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 484 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 236 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 305 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 398 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 484 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 421 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 533 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 769 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1023 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 419 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 532 B

Some files were not shown because too many files have changed in this diff Show More