mirror of
https://github.com/M66B/FairEmail.git
synced 2025-01-01 04:35:57 +00:00
Bringing back XOAuth2
This commit is contained in:
parent
df2005dfc1
commit
27d7eddddc
20 changed files with 486 additions and 196 deletions
2
FAQ.md
2
FAQ.md
|
@ -251,6 +251,8 @@ The following Android permissions are needed:
|
||||||
* Optional: *read your contacts* (READ_CONTACTS): to autocomplete addresses and to show photos
|
* Optional: *read your contacts* (READ_CONTACTS): to autocomplete addresses and to show photos
|
||||||
* Optional: *read the contents of your SD card* (READ_EXTERNAL_STORAGE): to accept files from other, outdated apps, see also [this FAQ](#user-content-faq49)
|
* Optional: *read the contents of your SD card* (READ_EXTERNAL_STORAGE): to accept files from other, outdated apps, see also [this FAQ](#user-content-faq49)
|
||||||
* Optional: *use fingerprint hardware* (USE_FINGERPRINT) and use *biometric hardware* (USE_BIOMETRIC): to use biometric authentication
|
* Optional: *use fingerprint hardware* (USE_FINGERPRINT) and use *biometric hardware* (USE_BIOMETRIC): to use biometric authentication
|
||||||
|
* Optional: *find accounts on the device* (GET_ACCOUNTS): to use [OAuth](https://en.wikipedia.org/wiki/OAuth) instead of passwords
|
||||||
|
* Android 5.1 Lollipop and before: *use accounts on the device* (USE_CREDENTIALS): needed to select accounts (not used/needed on later Android versions)
|
||||||
|
|
||||||
The following permissions are needed to show the count of unread messages as a badge (see also [this FAQ](#user-content-faq106)):
|
The following permissions are needed to show the count of unread messages as a badge (see also [this FAQ](#user-content-faq106)):
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,12 @@
|
||||||
<uses-permission android:name="com.android.vending.BILLING" />
|
<uses-permission android:name="com.android.vending.BILLING" />
|
||||||
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
|
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
|
||||||
|
|
||||||
|
<!-- OAuth -->
|
||||||
|
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
|
||||||
|
<uses-permission
|
||||||
|
android:name="android.permission.USE_CREDENTIALS"
|
||||||
|
android:maxSdkVersion="22" />
|
||||||
|
|
||||||
<!-- https://developer.android.com/guide/topics/manifest/uses-feature-element#features-reference -->
|
<!-- https://developer.android.com/guide/topics/manifest/uses-feature-element#features-reference -->
|
||||||
<uses-feature
|
<uses-feature
|
||||||
android:name="android.software.app_widgets"
|
android:name="android.software.app_widgets"
|
||||||
|
|
|
@ -113,6 +113,7 @@ public class ActivitySetup extends ActivityBase implements FragmentManager.OnBac
|
||||||
static final int REQUEST_SOUND = 2;
|
static final int REQUEST_SOUND = 2;
|
||||||
static final int REQUEST_EXPORT = 3;
|
static final int REQUEST_EXPORT = 3;
|
||||||
static final int REQUEST_IMPORT = 4;
|
static final int REQUEST_IMPORT = 4;
|
||||||
|
static final int REQUEST_CHOOSE_ACCOUNT = 5;
|
||||||
|
|
||||||
static final String ACTION_QUICK_SETUP = BuildConfig.APPLICATION_ID + ".ACTION_QUICK_SETUP";
|
static final String ACTION_QUICK_SETUP = BuildConfig.APPLICATION_ID + ".ACTION_QUICK_SETUP";
|
||||||
static final String ACTION_VIEW_ACCOUNTS = BuildConfig.APPLICATION_ID + ".ACTION_VIEW_ACCOUNTS";
|
static final String ACTION_VIEW_ACCOUNTS = BuildConfig.APPLICATION_ID + ".ACTION_VIEW_ACCOUNTS";
|
||||||
|
|
|
@ -83,7 +83,6 @@ public class AdapterAccount extends RecyclerView.Adapter<AdapterAccount.ViewHold
|
||||||
private ImageView ivState;
|
private ImageView ivState;
|
||||||
private TextView tvHost;
|
private TextView tvHost;
|
||||||
private TextView tvLast;
|
private TextView tvLast;
|
||||||
private TextView tvAuthorize;
|
|
||||||
private TextView tvIdentity;
|
private TextView tvIdentity;
|
||||||
private TextView tvDrafts;
|
private TextView tvDrafts;
|
||||||
private TextView tvWarning;
|
private TextView tvWarning;
|
||||||
|
@ -106,7 +105,6 @@ public class AdapterAccount extends RecyclerView.Adapter<AdapterAccount.ViewHold
|
||||||
ivState = itemView.findViewById(R.id.ivState);
|
ivState = itemView.findViewById(R.id.ivState);
|
||||||
tvHost = itemView.findViewById(R.id.tvHost);
|
tvHost = itemView.findViewById(R.id.tvHost);
|
||||||
tvLast = itemView.findViewById(R.id.tvLast);
|
tvLast = itemView.findViewById(R.id.tvLast);
|
||||||
tvAuthorize = itemView.findViewById(R.id.tvAuthorize);
|
|
||||||
tvIdentity = itemView.findViewById(R.id.tvIdentity);
|
tvIdentity = itemView.findViewById(R.id.tvIdentity);
|
||||||
tvDrafts = itemView.findViewById(R.id.tvDrafts);
|
tvDrafts = itemView.findViewById(R.id.tvDrafts);
|
||||||
tvWarning = itemView.findViewById(R.id.tvWarning);
|
tvWarning = itemView.findViewById(R.id.tvWarning);
|
||||||
|
@ -165,7 +163,6 @@ public class AdapterAccount extends RecyclerView.Adapter<AdapterAccount.ViewHold
|
||||||
tvLast.setText(context.getString(R.string.title_last_connected,
|
tvLast.setText(context.getString(R.string.title_last_connected,
|
||||||
account.last_connected == null ? "-" : DTF.format(account.last_connected)));
|
account.last_connected == null ? "-" : DTF.format(account.last_connected)));
|
||||||
|
|
||||||
tvAuthorize.setVisibility(account.auth_type == ConnectionHelper.AUTH_TYPE_PASSWORD ? View.GONE : View.VISIBLE);
|
|
||||||
tvIdentity.setVisibility(account.identities > 0 || !settings ? View.GONE : View.VISIBLE);
|
tvIdentity.setVisibility(account.identities > 0 || !settings ? View.GONE : View.VISIBLE);
|
||||||
tvDrafts.setVisibility(account.drafts || !settings ? View.GONE : View.VISIBLE);
|
tvDrafts.setVisibility(account.drafts || !settings ? View.GONE : View.VISIBLE);
|
||||||
|
|
||||||
|
|
|
@ -72,7 +72,6 @@ public class AdapterIdentity extends RecyclerView.Adapter<AdapterIdentity.ViewHo
|
||||||
private ImageView ivState;
|
private ImageView ivState;
|
||||||
private TextView tvAccount;
|
private TextView tvAccount;
|
||||||
private TextView tvLast;
|
private TextView tvLast;
|
||||||
private TextView tvAuthorize;
|
|
||||||
private TextView tvError;
|
private TextView tvError;
|
||||||
|
|
||||||
private TwoStateOwner powner = new TwoStateOwner(owner, "IdentityPopup");
|
private TwoStateOwner powner = new TwoStateOwner(owner, "IdentityPopup");
|
||||||
|
@ -90,7 +89,6 @@ public class AdapterIdentity extends RecyclerView.Adapter<AdapterIdentity.ViewHo
|
||||||
ivState = itemView.findViewById(R.id.ivState);
|
ivState = itemView.findViewById(R.id.ivState);
|
||||||
tvAccount = itemView.findViewById(R.id.tvAccount);
|
tvAccount = itemView.findViewById(R.id.tvAccount);
|
||||||
tvLast = itemView.findViewById(R.id.tvLast);
|
tvLast = itemView.findViewById(R.id.tvLast);
|
||||||
tvAuthorize = itemView.findViewById(R.id.tvAuthorize);
|
|
||||||
tvError = itemView.findViewById(R.id.tvError);
|
tvError = itemView.findViewById(R.id.tvError);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -128,8 +126,6 @@ public class AdapterIdentity extends RecyclerView.Adapter<AdapterIdentity.ViewHo
|
||||||
tvLast.setText(context.getString(R.string.title_last_connected,
|
tvLast.setText(context.getString(R.string.title_last_connected,
|
||||||
identity.last_connected == null ? "-" : DTF.format(identity.last_connected)));
|
identity.last_connected == null ? "-" : DTF.format(identity.last_connected)));
|
||||||
|
|
||||||
tvAuthorize.setVisibility(identity.auth_type == ConnectionHelper.AUTH_TYPE_PASSWORD ? View.GONE : View.VISIBLE);
|
|
||||||
|
|
||||||
tvError.setText(identity.error);
|
tvError.setText(identity.error);
|
||||||
tvError.setVisibility(identity.error == null ? View.GONE : View.VISIBLE);
|
tvError.setVisibility(identity.error == null ? View.GONE : View.VISIBLE);
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,9 +33,6 @@ public class ConnectionHelper {
|
||||||
// https://dns.watch/
|
// https://dns.watch/
|
||||||
private static final String DEFAULT_DNS = "84.200.69.80";
|
private static final String DEFAULT_DNS = "84.200.69.80";
|
||||||
|
|
||||||
static final int AUTH_TYPE_PASSWORD = 1;
|
|
||||||
static final int AUTH_TYPE_GMAIL = 2;
|
|
||||||
|
|
||||||
// Roam like at home
|
// Roam like at home
|
||||||
// https://en.wikipedia.org/wiki/European_Union_roaming_regulations
|
// https://en.wikipedia.org/wiki/European_Union_roaming_regulations
|
||||||
private static final List<String> RLAH_COUNTRY_CODES = Collections.unmodifiableList(Arrays.asList(
|
private static final List<String> RLAH_COUNTRY_CODES = Collections.unmodifiableList(Arrays.asList(
|
||||||
|
|
|
@ -105,6 +105,9 @@ public interface DaoAccount {
|
||||||
@Update
|
@Update
|
||||||
void updateAccount(EntityAccount account);
|
void updateAccount(EntityAccount account);
|
||||||
|
|
||||||
|
@Query("UPDATE account SET password = :password WHERE password = :old")
|
||||||
|
int updateAccountPassword(String old, String password);
|
||||||
|
|
||||||
@Query("UPDATE account SET separator = :separator WHERE id = :id")
|
@Query("UPDATE account SET separator = :separator WHERE id = :id")
|
||||||
int setFolderSeparator(long id, Character separator);
|
int setFolderSeparator(long id, Character separator);
|
||||||
|
|
||||||
|
@ -117,9 +120,6 @@ public interface DaoAccount {
|
||||||
@Query("UPDATE account SET last_connected = :last_connected WHERE id = :id")
|
@Query("UPDATE account SET last_connected = :last_connected WHERE id = :id")
|
||||||
int setAccountConnected(long id, long last_connected);
|
int setAccountConnected(long id, long last_connected);
|
||||||
|
|
||||||
@Query("UPDATE account SET password = :password WHERE id = :id")
|
|
||||||
int setAccountPassword(long id, String password);
|
|
||||||
|
|
||||||
@Query("UPDATE account SET `order` = :order WHERE id = :id")
|
@Query("UPDATE account SET `order` = :order WHERE id = :id")
|
||||||
int setAccountOrder(long id, Integer order);
|
int setAccountOrder(long id, Integer order);
|
||||||
|
|
||||||
|
@ -129,9 +129,6 @@ public interface DaoAccount {
|
||||||
@Query("UPDATE account SET error = :error WHERE id = :id")
|
@Query("UPDATE account SET error = :error WHERE id = :id")
|
||||||
int setAccountError(long id, String error);
|
int setAccountError(long id, String error);
|
||||||
|
|
||||||
@Query("UPDATE account SET poll_interval = :poll_interval WHERE id = :id")
|
|
||||||
int setAccountPollInterval(long id, int poll_interval);
|
|
||||||
|
|
||||||
@Query("UPDATE account SET `primary` = 0")
|
@Query("UPDATE account SET `primary` = 0")
|
||||||
void resetPrimary();
|
void resetPrimary();
|
||||||
|
|
||||||
|
|
|
@ -67,6 +67,9 @@ public interface DaoIdentity {
|
||||||
@Update
|
@Update
|
||||||
void updateIdentity(EntityIdentity identity);
|
void updateIdentity(EntityIdentity identity);
|
||||||
|
|
||||||
|
@Query("UPDATE identity SET password = :password WHERE password = :old")
|
||||||
|
int updateIdentityPassword(String old, String password);
|
||||||
|
|
||||||
@Query("UPDATE identity SET synchronize = :synchronize WHERE id = :id")
|
@Query("UPDATE identity SET synchronize = :synchronize WHERE id = :id")
|
||||||
int setIdentitySynchronize(long id, boolean synchronize);
|
int setIdentitySynchronize(long id, boolean synchronize);
|
||||||
|
|
||||||
|
|
|
@ -54,8 +54,6 @@ public class EntityAccount extends EntityOrder implements Serializable {
|
||||||
@PrimaryKey(autoGenerate = true)
|
@PrimaryKey(autoGenerate = true)
|
||||||
public Long id;
|
public Long id;
|
||||||
|
|
||||||
@NonNull
|
|
||||||
public Integer auth_type;
|
|
||||||
@NonNull
|
@NonNull
|
||||||
public Boolean pop = false; // obsolete
|
public Boolean pop = false; // obsolete
|
||||||
@NonNull
|
@NonNull
|
||||||
|
@ -63,10 +61,12 @@ public class EntityAccount extends EntityOrder implements Serializable {
|
||||||
@NonNull
|
@NonNull
|
||||||
public Boolean starttls;
|
public Boolean starttls;
|
||||||
@NonNull
|
@NonNull
|
||||||
public Boolean insecure;
|
public Boolean insecure = false;
|
||||||
@NonNull
|
@NonNull
|
||||||
public Integer port;
|
public Integer port;
|
||||||
@NonNull
|
@NonNull
|
||||||
|
public Integer auth_type;
|
||||||
|
@NonNull
|
||||||
public String user;
|
public String user;
|
||||||
@NonNull
|
@NonNull
|
||||||
public String password;
|
public String password;
|
||||||
|
@ -83,14 +83,14 @@ public class EntityAccount extends EntityOrder implements Serializable {
|
||||||
@NonNull
|
@NonNull
|
||||||
public Boolean primary;
|
public Boolean primary;
|
||||||
@NonNull
|
@NonNull
|
||||||
public Boolean notify;
|
public Boolean notify = false;
|
||||||
@NonNull
|
@NonNull
|
||||||
public Boolean browse = true;
|
public Boolean browse = true;
|
||||||
public Character separator;
|
public Character separator;
|
||||||
public Long swipe_left;
|
public Long swipe_left;
|
||||||
public Long swipe_right;
|
public Long swipe_right;
|
||||||
@NonNull
|
@NonNull
|
||||||
public Integer poll_interval; // keep-alive interval
|
public Integer poll_interval = DEFAULT_KEEP_ALIVE_INTERVAL; // keep-alive interval
|
||||||
@NonNull
|
@NonNull
|
||||||
public Boolean partial_fetch = true;
|
public Boolean partial_fetch = true;
|
||||||
public String prefix; // namespace, obsolete
|
public String prefix; // namespace, obsolete
|
||||||
|
@ -146,11 +146,11 @@ public class EntityAccount extends EntityOrder implements Serializable {
|
||||||
JSONObject json = new JSONObject();
|
JSONObject json = new JSONObject();
|
||||||
json.put("id", id);
|
json.put("id", id);
|
||||||
json.put("order", order);
|
json.put("order", order);
|
||||||
json.put("auth_type", auth_type);
|
|
||||||
json.put("host", host);
|
json.put("host", host);
|
||||||
json.put("starttls", starttls);
|
json.put("starttls", starttls);
|
||||||
json.put("insecure", insecure);
|
json.put("insecure", insecure);
|
||||||
json.put("port", port);
|
json.put("port", port);
|
||||||
|
json.put("auth_type", auth_type);
|
||||||
json.put("user", user);
|
json.put("user", user);
|
||||||
json.put("password", password);
|
json.put("password", password);
|
||||||
json.put("realm", realm);
|
json.put("realm", realm);
|
||||||
|
@ -184,11 +184,11 @@ public class EntityAccount extends EntityOrder implements Serializable {
|
||||||
if (json.has("order"))
|
if (json.has("order"))
|
||||||
account.order = json.getInt("order");
|
account.order = json.getInt("order");
|
||||||
|
|
||||||
account.auth_type = json.getInt("auth_type");
|
|
||||||
account.host = json.getString("host");
|
account.host = json.getString("host");
|
||||||
account.starttls = (json.has("starttls") && json.getBoolean("starttls"));
|
account.starttls = (json.has("starttls") && json.getBoolean("starttls"));
|
||||||
account.insecure = (json.has("insecure") && json.getBoolean("insecure"));
|
account.insecure = (json.has("insecure") && json.getBoolean("insecure"));
|
||||||
account.port = json.getInt("port");
|
account.port = json.getInt("port");
|
||||||
|
account.auth_type = json.getInt("auth_type");
|
||||||
account.user = json.getString("user");
|
account.user = json.getString("user");
|
||||||
account.password = json.getString("password");
|
account.password = json.getString("password");
|
||||||
if (json.has("realm"))
|
if (json.has("realm"))
|
||||||
|
@ -227,11 +227,11 @@ public class EntityAccount extends EntityOrder implements Serializable {
|
||||||
public boolean equals(Object obj) {
|
public boolean equals(Object obj) {
|
||||||
if (obj instanceof EntityAccount) {
|
if (obj instanceof EntityAccount) {
|
||||||
EntityAccount other = (EntityAccount) obj;
|
EntityAccount other = (EntityAccount) obj;
|
||||||
return (this.auth_type.equals(other.auth_type) &&
|
return (this.host.equals(other.host) &&
|
||||||
this.host.equals(other.host) &&
|
|
||||||
this.starttls == other.starttls &&
|
this.starttls == other.starttls &&
|
||||||
this.insecure == other.insecure &&
|
this.insecure == other.insecure &&
|
||||||
this.port.equals(other.port) &&
|
this.port.equals(other.port) &&
|
||||||
|
this.auth_type.equals(other.auth_type) &&
|
||||||
this.user.equals(other.user) &&
|
this.user.equals(other.user) &&
|
||||||
this.password.equals(other.password) &&
|
this.password.equals(other.password) &&
|
||||||
Objects.equals(this.realm, other.realm) &&
|
Objects.equals(this.realm, other.realm) &&
|
||||||
|
|
|
@ -57,16 +57,16 @@ public class EntityIdentity {
|
||||||
public Integer color;
|
public Integer color;
|
||||||
public String signature;
|
public String signature;
|
||||||
@NonNull
|
@NonNull
|
||||||
public Integer auth_type;
|
|
||||||
@NonNull
|
|
||||||
public String host; // SMTP
|
public String host; // SMTP
|
||||||
@NonNull
|
@NonNull
|
||||||
public Boolean starttls;
|
public Boolean starttls;
|
||||||
@NonNull
|
@NonNull
|
||||||
public Boolean insecure;
|
public Boolean insecure = false;
|
||||||
@NonNull
|
@NonNull
|
||||||
public Integer port;
|
public Integer port;
|
||||||
@NonNull
|
@NonNull
|
||||||
|
public Integer auth_type;
|
||||||
|
@NonNull
|
||||||
public String user;
|
public String user;
|
||||||
@NonNull
|
@NonNull
|
||||||
public String password;
|
public String password;
|
||||||
|
@ -113,11 +113,11 @@ public class EntityIdentity {
|
||||||
json.put("signature", signature);
|
json.put("signature", signature);
|
||||||
// not account
|
// not account
|
||||||
|
|
||||||
json.put("auth_type", auth_type);
|
|
||||||
json.put("host", host);
|
json.put("host", host);
|
||||||
json.put("starttls", starttls);
|
json.put("starttls", starttls);
|
||||||
json.put("insecure", insecure);
|
json.put("insecure", insecure);
|
||||||
json.put("port", port);
|
json.put("port", port);
|
||||||
|
json.put("auth_type", auth_type);
|
||||||
json.put("user", user);
|
json.put("user", user);
|
||||||
json.put("password", password);
|
json.put("password", password);
|
||||||
json.put("realm", realm);
|
json.put("realm", realm);
|
||||||
|
@ -151,11 +151,11 @@ public class EntityIdentity {
|
||||||
if (json.has("signature") && !json.isNull("signature"))
|
if (json.has("signature") && !json.isNull("signature"))
|
||||||
identity.signature = json.getString("signature");
|
identity.signature = json.getString("signature");
|
||||||
|
|
||||||
identity.auth_type = json.getInt("auth_type");
|
|
||||||
identity.host = json.getString("host");
|
identity.host = json.getString("host");
|
||||||
identity.starttls = json.getBoolean("starttls");
|
identity.starttls = json.getBoolean("starttls");
|
||||||
identity.insecure = (json.has("insecure") && json.getBoolean("insecure"));
|
identity.insecure = (json.has("insecure") && json.getBoolean("insecure"));
|
||||||
identity.port = json.getInt("port");
|
identity.port = json.getInt("port");
|
||||||
|
identity.auth_type = json.getInt("auth_type");
|
||||||
identity.user = json.getString("user");
|
identity.user = json.getString("user");
|
||||||
identity.password = json.getString("password");
|
identity.password = json.getString("password");
|
||||||
if (json.has("realm") && !json.isNull("realm"))
|
if (json.has("realm") && !json.isNull("realm"))
|
||||||
|
@ -196,11 +196,11 @@ public class EntityIdentity {
|
||||||
Objects.equals(this.display, other.display) &&
|
Objects.equals(this.display, other.display) &&
|
||||||
Objects.equals(this.color, other.color) &&
|
Objects.equals(this.color, other.color) &&
|
||||||
Objects.equals(this.signature, other.signature) &&
|
Objects.equals(this.signature, other.signature) &&
|
||||||
this.auth_type.equals(other.auth_type) &&
|
|
||||||
this.host.equals(other.host) &&
|
this.host.equals(other.host) &&
|
||||||
this.starttls.equals(other.starttls) &&
|
this.starttls.equals(other.starttls) &&
|
||||||
this.insecure.equals(other.insecure) &&
|
this.insecure.equals(other.insecure) &&
|
||||||
this.port.equals(other.port) &&
|
this.port.equals(other.port) &&
|
||||||
|
this.auth_type.equals(other.auth_type) &&
|
||||||
this.user.equals(other.user) &&
|
this.user.equals(other.user) &&
|
||||||
this.password.equals(other.password) &&
|
this.password.equals(other.password) &&
|
||||||
Objects.equals(this.realm, other.realm) &&
|
Objects.equals(this.realm, other.realm) &&
|
||||||
|
|
|
@ -142,6 +142,7 @@ public class FragmentAccount extends FragmentBase {
|
||||||
|
|
||||||
private long id = -1;
|
private long id = -1;
|
||||||
private long copy = -1;
|
private long copy = -1;
|
||||||
|
private int auth = MailService.AUTH_TYPE_PASSWORD;
|
||||||
private boolean saving = false;
|
private boolean saving = false;
|
||||||
private int color = Color.TRANSPARENT;
|
private int color = Color.TRANSPARENT;
|
||||||
|
|
||||||
|
@ -516,6 +517,7 @@ public class FragmentAccount extends FragmentBase {
|
||||||
args.putBoolean("starttls", rgEncryption.getCheckedRadioButtonId() == R.id.radio_starttls);
|
args.putBoolean("starttls", rgEncryption.getCheckedRadioButtonId() == R.id.radio_starttls);
|
||||||
args.putBoolean("insecure", cbInsecure.isChecked());
|
args.putBoolean("insecure", cbInsecure.isChecked());
|
||||||
args.putString("port", etPort.getText().toString());
|
args.putString("port", etPort.getText().toString());
|
||||||
|
args.putInt("auth", auth);
|
||||||
args.putString("user", etUser.getText().toString());
|
args.putString("user", etUser.getText().toString());
|
||||||
args.putString("password", tilPassword.getEditText().getText().toString());
|
args.putString("password", tilPassword.getEditText().getText().toString());
|
||||||
args.putString("realm", etRealm.getText().toString());
|
args.putString("realm", etRealm.getText().toString());
|
||||||
|
@ -551,6 +553,7 @@ public class FragmentAccount extends FragmentBase {
|
||||||
boolean starttls = args.getBoolean("starttls");
|
boolean starttls = args.getBoolean("starttls");
|
||||||
boolean insecure = args.getBoolean("insecure");
|
boolean insecure = args.getBoolean("insecure");
|
||||||
String port = args.getString("port");
|
String port = args.getString("port");
|
||||||
|
int auth = args.getInt("auth");
|
||||||
String user = args.getString("user");
|
String user = args.getString("user");
|
||||||
String password = args.getString("password");
|
String password = args.getString("password");
|
||||||
String realm = args.getString("realm");
|
String realm = args.getString("realm");
|
||||||
|
@ -581,7 +584,7 @@ public class FragmentAccount extends FragmentBase {
|
||||||
// Check IMAP server / get folders
|
// Check IMAP server / get folders
|
||||||
String protocol = "imap" + (starttls ? "" : "s");
|
String protocol = "imap" + (starttls ? "" : "s");
|
||||||
try (MailService iservice = new MailService(context, protocol, realm, insecure, true)) {
|
try (MailService iservice = new MailService(context, protocol, realm, insecure, true)) {
|
||||||
iservice.connect(host, Integer.parseInt(port), user, password);
|
iservice.connect(host, Integer.parseInt(port), auth, user, password);
|
||||||
|
|
||||||
result.idle = iservice.getStore().hasCapability("IDLE");
|
result.idle = iservice.getStore().hasCapability("IDLE");
|
||||||
|
|
||||||
|
@ -709,6 +712,7 @@ public class FragmentAccount extends FragmentBase {
|
||||||
args.putBoolean("starttls", rgEncryption.getCheckedRadioButtonId() == R.id.radio_starttls);
|
args.putBoolean("starttls", rgEncryption.getCheckedRadioButtonId() == R.id.radio_starttls);
|
||||||
args.putBoolean("insecure", cbInsecure.isChecked());
|
args.putBoolean("insecure", cbInsecure.isChecked());
|
||||||
args.putString("port", etPort.getText().toString());
|
args.putString("port", etPort.getText().toString());
|
||||||
|
args.putInt("auth", auth);
|
||||||
args.putString("user", etUser.getText().toString());
|
args.putString("user", etUser.getText().toString());
|
||||||
args.putString("password", tilPassword.getEditText().getText().toString());
|
args.putString("password", tilPassword.getEditText().getText().toString());
|
||||||
args.putString("realm", etRealm.getText().toString());
|
args.putString("realm", etRealm.getText().toString());
|
||||||
|
@ -762,6 +766,7 @@ public class FragmentAccount extends FragmentBase {
|
||||||
boolean starttls = args.getBoolean("starttls");
|
boolean starttls = args.getBoolean("starttls");
|
||||||
boolean insecure = args.getBoolean("insecure");
|
boolean insecure = args.getBoolean("insecure");
|
||||||
String port = args.getString("port");
|
String port = args.getString("port");
|
||||||
|
int auth = args.getInt("auth");
|
||||||
String user = args.getString("user").trim();
|
String user = args.getString("user").trim();
|
||||||
String password = args.getString("password");
|
String password = args.getString("password");
|
||||||
String realm = args.getString("realm");
|
String realm = args.getString("realm");
|
||||||
|
@ -829,6 +834,8 @@ public class FragmentAccount extends FragmentBase {
|
||||||
return true;
|
return true;
|
||||||
if (!Objects.equals(account.port, Integer.parseInt(port)))
|
if (!Objects.equals(account.port, Integer.parseInt(port)))
|
||||||
return true;
|
return true;
|
||||||
|
if (account.auth_type != auth)
|
||||||
|
return true;
|
||||||
if (!Objects.equals(account.user, user))
|
if (!Objects.equals(account.user, user))
|
||||||
return true;
|
return true;
|
||||||
if (!Objects.equals(account.password, password))
|
if (!Objects.equals(account.password, password))
|
||||||
|
@ -907,7 +914,7 @@ public class FragmentAccount extends FragmentBase {
|
||||||
if (check) {
|
if (check) {
|
||||||
String protocol = "imap" + (starttls ? "" : "s");
|
String protocol = "imap" + (starttls ? "" : "s");
|
||||||
try (MailService iservice = new MailService(context, protocol, realm, insecure, true)) {
|
try (MailService iservice = new MailService(context, protocol, realm, insecure, true)) {
|
||||||
iservice.connect(host, Integer.parseInt(port), user, password);
|
iservice.connect(host, Integer.parseInt(port), auth, user, password);
|
||||||
|
|
||||||
for (Folder ifolder : iservice.getStore().getDefaultFolder().list("*")) {
|
for (Folder ifolder : iservice.getStore().getDefaultFolder().list("*")) {
|
||||||
// Check folder attributes
|
// Check folder attributes
|
||||||
|
@ -948,13 +955,15 @@ public class FragmentAccount extends FragmentBase {
|
||||||
if (account == null)
|
if (account == null)
|
||||||
account = new EntityAccount();
|
account = new EntityAccount();
|
||||||
|
|
||||||
account.auth_type = ConnectionHelper.AUTH_TYPE_PASSWORD;
|
|
||||||
account.host = host;
|
account.host = host;
|
||||||
account.starttls = starttls;
|
account.starttls = starttls;
|
||||||
account.insecure = insecure;
|
account.insecure = insecure;
|
||||||
account.port = Integer.parseInt(port);
|
account.port = Integer.parseInt(port);
|
||||||
account.user = user;
|
account.auth_type = auth;
|
||||||
account.password = password;
|
if (auth == MailService.AUTH_TYPE_PASSWORD) {
|
||||||
|
account.user = user;
|
||||||
|
account.password = password;
|
||||||
|
}
|
||||||
account.realm = realm;
|
account.realm = realm;
|
||||||
|
|
||||||
account.name = name;
|
account.name = name;
|
||||||
|
@ -1139,6 +1148,7 @@ public class FragmentAccount extends FragmentBase {
|
||||||
outState.putInt("fair:provider", spProvider.getSelectedItemPosition());
|
outState.putInt("fair:provider", spProvider.getSelectedItemPosition());
|
||||||
outState.putString("fair:password", tilPassword.getEditText().getText().toString());
|
outState.putString("fair:password", tilPassword.getEditText().getText().toString());
|
||||||
outState.putInt("fair:advanced", grpAdvanced.getVisibility());
|
outState.putInt("fair:advanced", grpAdvanced.getVisibility());
|
||||||
|
outState.putInt("fair:auth", auth);
|
||||||
outState.putInt("fair:color", color);
|
outState.putInt("fair:color", color);
|
||||||
super.onSaveInstanceState(outState);
|
super.onSaveInstanceState(outState);
|
||||||
}
|
}
|
||||||
|
@ -1210,6 +1220,7 @@ public class FragmentAccount extends FragmentBase {
|
||||||
etInterval.setText(account == null ? "" : Long.toString(account.poll_interval));
|
etInterval.setText(account == null ? "" : Long.toString(account.poll_interval));
|
||||||
cbPartialFetch.setChecked(account == null ? true : account.partial_fetch);
|
cbPartialFetch.setChecked(account == null ? true : account.partial_fetch);
|
||||||
|
|
||||||
|
auth = (account == null ? MailService.AUTH_TYPE_PASSWORD : account.auth_type);
|
||||||
color = (account == null || account.color == null ? Color.TRANSPARENT : account.color);
|
color = (account == null || account.color == null ? Color.TRANSPARENT : account.color);
|
||||||
|
|
||||||
new SimpleTask<EntityAccount>() {
|
new SimpleTask<EntityAccount>() {
|
||||||
|
@ -1236,11 +1247,17 @@ public class FragmentAccount extends FragmentBase {
|
||||||
|
|
||||||
tilPassword.getEditText().setText(savedInstanceState.getString("fair:password"));
|
tilPassword.getEditText().setText(savedInstanceState.getString("fair:password"));
|
||||||
grpAdvanced.setVisibility(savedInstanceState.getInt("fair:advanced"));
|
grpAdvanced.setVisibility(savedInstanceState.getInt("fair:advanced"));
|
||||||
|
auth = savedInstanceState.getInt("fair:auth");
|
||||||
color = savedInstanceState.getInt("fair:color");
|
color = savedInstanceState.getInt("fair:color");
|
||||||
}
|
}
|
||||||
|
|
||||||
Helper.setViewsEnabled(view, true);
|
Helper.setViewsEnabled(view, true);
|
||||||
|
|
||||||
|
if (auth != MailService.AUTH_TYPE_PASSWORD) {
|
||||||
|
etUser.setEnabled(false);
|
||||||
|
tilPassword.setEnabled(false);
|
||||||
|
}
|
||||||
|
|
||||||
setColor(color);
|
setColor(color);
|
||||||
cbPrimary.setEnabled(cbSynchronize.isChecked());
|
cbPrimary.setEnabled(cbSynchronize.isChecked());
|
||||||
|
|
||||||
|
|
|
@ -130,6 +130,7 @@ public class FragmentIdentity extends FragmentBase {
|
||||||
|
|
||||||
private long id = -1;
|
private long id = -1;
|
||||||
private long copy = -1;
|
private long copy = -1;
|
||||||
|
private int auth = MailService.AUTH_TYPE_PASSWORD;
|
||||||
private boolean saving = false;
|
private boolean saving = false;
|
||||||
private int color = Color.TRANSPARENT;
|
private int color = Color.TRANSPARENT;
|
||||||
|
|
||||||
|
@ -537,6 +538,7 @@ public class FragmentIdentity extends FragmentBase {
|
||||||
args.putBoolean("starttls", rgEncryption.getCheckedRadioButtonId() == R.id.radio_starttls);
|
args.putBoolean("starttls", rgEncryption.getCheckedRadioButtonId() == R.id.radio_starttls);
|
||||||
args.putBoolean("insecure", cbInsecure.isChecked());
|
args.putBoolean("insecure", cbInsecure.isChecked());
|
||||||
args.putString("port", etPort.getText().toString());
|
args.putString("port", etPort.getText().toString());
|
||||||
|
args.putInt("auth", auth);
|
||||||
args.putString("user", etUser.getText().toString());
|
args.putString("user", etUser.getText().toString());
|
||||||
args.putString("password", tilPassword.getEditText().getText().toString());
|
args.putString("password", tilPassword.getEditText().getText().toString());
|
||||||
args.putString("realm", etRealm.getText().toString());
|
args.putString("realm", etRealm.getText().toString());
|
||||||
|
@ -584,6 +586,7 @@ public class FragmentIdentity extends FragmentBase {
|
||||||
boolean starttls = args.getBoolean("starttls");
|
boolean starttls = args.getBoolean("starttls");
|
||||||
boolean insecure = args.getBoolean("insecure");
|
boolean insecure = args.getBoolean("insecure");
|
||||||
String port = args.getString("port");
|
String port = args.getString("port");
|
||||||
|
int auth = args.getInt("auth");
|
||||||
String user = args.getString("user").trim();
|
String user = args.getString("user").trim();
|
||||||
String password = args.getString("password");
|
String password = args.getString("password");
|
||||||
String realm = args.getString("realm");
|
String realm = args.getString("realm");
|
||||||
|
@ -680,6 +683,8 @@ public class FragmentIdentity extends FragmentBase {
|
||||||
return true;
|
return true;
|
||||||
if (!Objects.equals(identity.port, Integer.parseInt(port)))
|
if (!Objects.equals(identity.port, Integer.parseInt(port)))
|
||||||
return true;
|
return true;
|
||||||
|
if (identity.auth_type != auth)
|
||||||
|
return true;
|
||||||
if (!Objects.equals(identity.user, user))
|
if (!Objects.equals(identity.user, user))
|
||||||
return true;
|
return true;
|
||||||
if (!Objects.equals(identity.password, password))
|
if (!Objects.equals(identity.password, password))
|
||||||
|
@ -731,7 +736,7 @@ public class FragmentIdentity extends FragmentBase {
|
||||||
String protocol = (starttls ? "smtp" : "smtps");
|
String protocol = (starttls ? "smtp" : "smtps");
|
||||||
try (MailService iservice = new MailService(context, protocol, realm, insecure, true)) {
|
try (MailService iservice = new MailService(context, protocol, realm, insecure, true)) {
|
||||||
iservice.setUseIp(use_ip);
|
iservice.setUseIp(use_ip);
|
||||||
iservice.connect(host, Integer.parseInt(port), user, password);
|
iservice.connect(host, Integer.parseInt(port), auth, user, password);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -748,13 +753,15 @@ public class FragmentIdentity extends FragmentBase {
|
||||||
identity.color = color;
|
identity.color = color;
|
||||||
identity.signature = signature;
|
identity.signature = signature;
|
||||||
|
|
||||||
identity.auth_type = ConnectionHelper.AUTH_TYPE_PASSWORD;
|
|
||||||
identity.host = host;
|
identity.host = host;
|
||||||
identity.starttls = starttls;
|
identity.starttls = starttls;
|
||||||
identity.insecure = insecure;
|
identity.insecure = insecure;
|
||||||
identity.port = Integer.parseInt(port);
|
identity.port = Integer.parseInt(port);
|
||||||
identity.user = user;
|
identity.auth_type = auth;
|
||||||
identity.password = password;
|
if (auth == MailService.AUTH_TYPE_PASSWORD) {
|
||||||
|
identity.user = user;
|
||||||
|
identity.password = password;
|
||||||
|
}
|
||||||
identity.realm = realm;
|
identity.realm = realm;
|
||||||
identity.use_ip = use_ip;
|
identity.use_ip = use_ip;
|
||||||
identity.synchronize = synchronize;
|
identity.synchronize = synchronize;
|
||||||
|
@ -853,6 +860,7 @@ public class FragmentIdentity extends FragmentBase {
|
||||||
outState.putInt("fair:provider", spProvider.getSelectedItemPosition());
|
outState.putInt("fair:provider", spProvider.getSelectedItemPosition());
|
||||||
outState.putString("fair:password", tilPassword.getEditText().getText().toString());
|
outState.putString("fair:password", tilPassword.getEditText().getText().toString());
|
||||||
outState.putInt("fair:advanced", grpAdvanced.getVisibility());
|
outState.putInt("fair:advanced", grpAdvanced.getVisibility());
|
||||||
|
outState.putInt("fair:auth", auth);
|
||||||
outState.putInt("fair:color", color);
|
outState.putInt("fair:color", color);
|
||||||
outState.putString("fair:html", (String) etSignature.getTag());
|
outState.putString("fair:html", (String) etSignature.getTag());
|
||||||
super.onSaveInstanceState(outState);
|
super.onSaveInstanceState(outState);
|
||||||
|
@ -902,6 +910,7 @@ public class FragmentIdentity extends FragmentBase {
|
||||||
cbDeliveryReceipt.setChecked(identity == null ? false : identity.delivery_receipt);
|
cbDeliveryReceipt.setChecked(identity == null ? false : identity.delivery_receipt);
|
||||||
cbReadReceipt.setChecked(identity == null ? false : identity.read_receipt);
|
cbReadReceipt.setChecked(identity == null ? false : identity.read_receipt);
|
||||||
|
|
||||||
|
auth = (identity == null ? MailService.AUTH_TYPE_PASSWORD : identity.auth_type);
|
||||||
color = (identity == null || identity.color == null ? Color.TRANSPARENT : identity.color);
|
color = (identity == null || identity.color == null ? Color.TRANSPARENT : identity.color);
|
||||||
|
|
||||||
if (identity == null || copy > 0)
|
if (identity == null || copy > 0)
|
||||||
|
@ -924,12 +933,18 @@ public class FragmentIdentity extends FragmentBase {
|
||||||
} else {
|
} else {
|
||||||
tilPassword.getEditText().setText(savedInstanceState.getString("fair:password"));
|
tilPassword.getEditText().setText(savedInstanceState.getString("fair:password"));
|
||||||
grpAdvanced.setVisibility(savedInstanceState.getInt("fair:advanced"));
|
grpAdvanced.setVisibility(savedInstanceState.getInt("fair:advanced"));
|
||||||
|
auth = savedInstanceState.getInt("fair:auth");
|
||||||
color = savedInstanceState.getInt("fair:color");
|
color = savedInstanceState.getInt("fair:color");
|
||||||
etSignature.setTag(savedInstanceState.getString("fair:html"));
|
etSignature.setTag(savedInstanceState.getString("fair:html"));
|
||||||
}
|
}
|
||||||
|
|
||||||
Helper.setViewsEnabled(view, true);
|
Helper.setViewsEnabled(view, true);
|
||||||
|
|
||||||
|
if (auth != MailService.AUTH_TYPE_PASSWORD) {
|
||||||
|
etUser.setEnabled(false);
|
||||||
|
tilPassword.setEnabled(false);
|
||||||
|
}
|
||||||
|
|
||||||
setColor(color);
|
setColor(color);
|
||||||
|
|
||||||
cbPrimary.setEnabled(cbSynchronize.isChecked());
|
cbPrimary.setEnabled(cbSynchronize.isChecked());
|
||||||
|
@ -949,7 +964,6 @@ public class FragmentIdentity extends FragmentBase {
|
||||||
|
|
||||||
EntityAccount unselected = new EntityAccount();
|
EntityAccount unselected = new EntityAccount();
|
||||||
unselected.id = -1L;
|
unselected.id = -1L;
|
||||||
unselected.auth_type = ConnectionHelper.AUTH_TYPE_PASSWORD;
|
|
||||||
unselected.name = getString(R.string.title_select);
|
unselected.name = getString(R.string.title_select);
|
||||||
unselected.primary = false;
|
unselected.primary = false;
|
||||||
accounts.add(0, unselected);
|
accounts.add(0, unselected);
|
||||||
|
|
|
@ -49,15 +49,12 @@ import androidx.appcompat.app.AlertDialog;
|
||||||
import androidx.constraintlayout.widget.Group;
|
import androidx.constraintlayout.widget.Group;
|
||||||
|
|
||||||
import com.google.android.material.textfield.TextInputLayout;
|
import com.google.android.material.textfield.TextInputLayout;
|
||||||
import com.sun.mail.imap.IMAPFolder;
|
|
||||||
|
|
||||||
import java.net.UnknownHostException;
|
import java.net.UnknownHostException;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import javax.mail.AuthenticationFailedException;
|
import javax.mail.AuthenticationFailedException;
|
||||||
import javax.mail.Folder;
|
|
||||||
|
|
||||||
import static android.app.Activity.RESULT_OK;
|
import static android.app.Activity.RESULT_OK;
|
||||||
|
|
||||||
|
@ -256,79 +253,32 @@ public class FragmentQuickSetup extends FragmentBase {
|
||||||
String user = (provider.user == EmailProvider.UserType.EMAIL ? email : dparts[0]);
|
String user = (provider.user == EmailProvider.UserType.EMAIL ? email : dparts[0]);
|
||||||
Log.i("User type=" + provider.user + " name=" + user);
|
Log.i("User type=" + provider.user + " name=" + user);
|
||||||
|
|
||||||
List<EntityFolder> folders = new ArrayList<>();
|
List<EntityFolder> folders;
|
||||||
long now = new Date().getTime();
|
|
||||||
|
|
||||||
{
|
String aprotocol = provider.imap.starttls ? "imap" : "imaps";
|
||||||
String protocol = provider.imap.starttls ? "imap" : "imaps";
|
try (MailService iservice = new MailService(context, aprotocol, null, false, true)) {
|
||||||
try (MailService iservice = new MailService(context, protocol, null, false, true)) {
|
try {
|
||||||
try {
|
iservice.connect(provider.imap.host, provider.imap.port, MailService.AUTH_TYPE_PASSWORD, user, password);
|
||||||
iservice.connect(provider.imap.host, provider.imap.port, user, password);
|
} catch (AuthenticationFailedException ex) {
|
||||||
} catch (AuthenticationFailedException ex) {
|
if (user.contains("@")) {
|
||||||
if (user.contains("@")) {
|
Log.w(ex);
|
||||||
Log.w(ex);
|
user = dparts[0];
|
||||||
user = dparts[0];
|
Log.i("Retry with user=" + user);
|
||||||
Log.i("Retry with user=" + user);
|
iservice.connect(provider.imap.host, provider.imap.port, MailService.AUTH_TYPE_PASSWORD, user, password);
|
||||||
iservice.connect(provider.imap.host, provider.imap.port, user, password);
|
} else
|
||||||
} else
|
throw ex;
|
||||||
throw ex;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<EntityFolder> guesses = new ArrayList<>();
|
|
||||||
|
|
||||||
for (Folder ifolder : iservice.getStore().getDefaultFolder().list("*")) {
|
|
||||||
String fullName = ifolder.getFullName();
|
|
||||||
String[] attrs = ((IMAPFolder) ifolder).getAttributes();
|
|
||||||
String type = EntityFolder.getType(attrs, fullName, true);
|
|
||||||
Log.i(fullName + " attrs=" + TextUtils.join(" ", attrs) + " type=" + type);
|
|
||||||
|
|
||||||
if (type != null) {
|
|
||||||
EntityFolder folder = new EntityFolder(fullName, type);
|
|
||||||
folders.add(folder);
|
|
||||||
|
|
||||||
if (EntityFolder.USER.equals(type)) {
|
|
||||||
String guess = EntityFolder.guessType(fullName);
|
|
||||||
if (guess != null)
|
|
||||||
guesses.add(folder);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (EntityFolder guess : guesses) {
|
|
||||||
boolean has = false;
|
|
||||||
String gtype = EntityFolder.guessType(guess.name);
|
|
||||||
for (EntityFolder folder : folders)
|
|
||||||
if (folder.type.equals(gtype)) {
|
|
||||||
has = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (!has) {
|
|
||||||
guess.type = gtype;
|
|
||||||
Log.i(guess.name + " guessed type=" + gtype);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean inbox = false;
|
|
||||||
boolean drafts = false;
|
|
||||||
for (EntityFolder folder : folders)
|
|
||||||
if (EntityFolder.INBOX.equals(folder.type))
|
|
||||||
inbox = true;
|
|
||||||
else if (EntityFolder.DRAFTS.equals(folder.type))
|
|
||||||
drafts = true;
|
|
||||||
|
|
||||||
Log.i("Quick inbox=" + inbox + " drafts=" + drafts);
|
|
||||||
|
|
||||||
if (!inbox || !drafts)
|
|
||||||
throw new IllegalArgumentException(
|
|
||||||
context.getString(R.string.title_setup_no_settings, dparts[1]));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
folders = iservice.getFolders();
|
||||||
|
|
||||||
|
if (folders == null)
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
context.getString(R.string.title_setup_no_settings, dparts[1]));
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
String iprotocol = provider.smtp.starttls ? "smtp" : "smtps";
|
||||||
String protocol = provider.smtp.starttls ? "smtp" : "smtps";
|
try (MailService iservice = new MailService(context, iprotocol, null, false, true)) {
|
||||||
try (MailService iservice = new MailService(context, protocol, null, false, true)) {
|
iservice.connect(provider.smtp.host, provider.smtp.port, MailService.AUTH_TYPE_PASSWORD, user, password);
|
||||||
iservice.connect(provider.smtp.host, provider.smtp.port, user, password);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (check)
|
if (check)
|
||||||
|
@ -337,31 +287,26 @@ public class FragmentQuickSetup extends FragmentBase {
|
||||||
DB db = DB.getInstance(context);
|
DB db = DB.getInstance(context);
|
||||||
try {
|
try {
|
||||||
db.beginTransaction();
|
db.beginTransaction();
|
||||||
|
|
||||||
EntityAccount primary = db.account().getPrimaryAccount();
|
EntityAccount primary = db.account().getPrimaryAccount();
|
||||||
|
|
||||||
// Create account
|
// Create account
|
||||||
EntityAccount account = new EntityAccount();
|
EntityAccount account = new EntityAccount();
|
||||||
|
|
||||||
account.auth_type = ConnectionHelper.AUTH_TYPE_PASSWORD;
|
|
||||||
account.host = provider.imap.host;
|
account.host = provider.imap.host;
|
||||||
account.starttls = provider.imap.starttls;
|
account.starttls = provider.imap.starttls;
|
||||||
account.insecure = false;
|
|
||||||
account.port = provider.imap.port;
|
account.port = provider.imap.port;
|
||||||
|
account.auth_type = MailService.AUTH_TYPE_PASSWORD;
|
||||||
account.user = user;
|
account.user = user;
|
||||||
account.password = password;
|
account.password = password;
|
||||||
|
|
||||||
account.name = provider.name;
|
account.name = provider.name;
|
||||||
account.color = null;
|
|
||||||
|
|
||||||
account.synchronize = true;
|
account.synchronize = true;
|
||||||
account.primary = (primary == null);
|
account.primary = (primary == null);
|
||||||
account.notify = false;
|
|
||||||
account.browse = true;
|
|
||||||
account.poll_interval = EntityAccount.DEFAULT_KEEP_ALIVE_INTERVAL;
|
|
||||||
|
|
||||||
account.created = now;
|
account.created = new Date().getTime();
|
||||||
account.error = null;
|
account.last_connected = account.created;
|
||||||
account.last_connected = now;
|
|
||||||
|
|
||||||
account.id = db.account().insertAccount(account);
|
account.id = db.account().insertAccount(account);
|
||||||
EntityLog.log(context, "Quick added account=" + account.name);
|
EntityLog.log(context, "Quick added account=" + account.name);
|
||||||
|
@ -388,27 +333,15 @@ public class FragmentQuickSetup extends FragmentBase {
|
||||||
identity.email = email;
|
identity.email = email;
|
||||||
identity.account = account.id;
|
identity.account = account.id;
|
||||||
|
|
||||||
identity.display = null;
|
|
||||||
identity.color = null;
|
|
||||||
|
|
||||||
identity.signature = null;
|
|
||||||
|
|
||||||
identity.auth_type = ConnectionHelper.AUTH_TYPE_PASSWORD;
|
|
||||||
identity.host = provider.smtp.host;
|
identity.host = provider.smtp.host;
|
||||||
identity.starttls = provider.smtp.starttls;
|
identity.starttls = provider.smtp.starttls;
|
||||||
identity.insecure = false;
|
|
||||||
identity.port = provider.smtp.port;
|
identity.port = provider.smtp.port;
|
||||||
|
identity.auth_type = MailService.AUTH_TYPE_PASSWORD;
|
||||||
identity.user = user;
|
identity.user = user;
|
||||||
identity.password = password;
|
identity.password = password;
|
||||||
identity.synchronize = true;
|
identity.synchronize = true;
|
||||||
identity.primary = true;
|
identity.primary = true;
|
||||||
|
|
||||||
identity.replyto = null;
|
|
||||||
identity.bcc = null;
|
|
||||||
identity.delivery_receipt = false;
|
|
||||||
identity.read_receipt = false;
|
|
||||||
identity.error = null;
|
|
||||||
|
|
||||||
identity.id = db.identity().insertIdentity(identity);
|
identity.id = db.identity().insertIdentity(identity);
|
||||||
EntityLog.log(context, "Quick added identity=" + identity.name + " email=" + identity.email);
|
EntityLog.log(context, "Quick added identity=" + identity.name + " email=" + identity.email);
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,12 @@ package eu.faircode.email;
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import android.Manifest;
|
import android.Manifest;
|
||||||
|
import android.accounts.Account;
|
||||||
|
import android.accounts.AccountManager;
|
||||||
|
import android.accounts.AccountManagerCallback;
|
||||||
|
import android.accounts.AccountManagerFuture;
|
||||||
|
import android.accounts.AccountsException;
|
||||||
|
import android.app.Activity;
|
||||||
import android.app.Dialog;
|
import android.app.Dialog;
|
||||||
import android.content.ComponentName;
|
import android.content.ComponentName;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
@ -27,12 +33,14 @@ import android.content.DialogInterface;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
|
import android.database.Cursor;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.net.ConnectivityManager;
|
import android.net.ConnectivityManager;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.PowerManager;
|
import android.os.PowerManager;
|
||||||
|
import android.provider.ContactsContract;
|
||||||
import android.provider.Settings;
|
import android.provider.Settings;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
@ -45,13 +53,20 @@ import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.appcompat.app.AlertDialog;
|
import androidx.appcompat.app.AlertDialog;
|
||||||
import androidx.constraintlayout.widget.Group;
|
import androidx.constraintlayout.widget.Group;
|
||||||
import androidx.core.content.ContextCompat;
|
import androidx.lifecycle.Lifecycle;
|
||||||
import androidx.lifecycle.Observer;
|
import androidx.lifecycle.Observer;
|
||||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||||
import androidx.preference.PreferenceManager;
|
import androidx.preference.PreferenceManager;
|
||||||
|
|
||||||
|
import com.google.android.material.snackbar.Snackbar;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import static android.accounts.AccountManager.newChooseAccountIntent;
|
||||||
|
|
||||||
public class FragmentSetup extends FragmentBase {
|
public class FragmentSetup extends FragmentBase {
|
||||||
private ViewGroup view;
|
private ViewGroup view;
|
||||||
|
|
||||||
|
@ -60,6 +75,7 @@ public class FragmentSetup extends FragmentBase {
|
||||||
|
|
||||||
private Button btnHelp;
|
private Button btnHelp;
|
||||||
private Button btnQuick;
|
private Button btnQuick;
|
||||||
|
private Button btnGmail;
|
||||||
|
|
||||||
private TextView tvAccountDone;
|
private TextView tvAccountDone;
|
||||||
private Button btnAccount;
|
private Button btnAccount;
|
||||||
|
@ -87,10 +103,6 @@ public class FragmentSetup extends FragmentBase {
|
||||||
private int colorWarning;
|
private int colorWarning;
|
||||||
private Drawable check;
|
private Drawable check;
|
||||||
|
|
||||||
private static final String[] permissions = new String[]{
|
|
||||||
Manifest.permission.READ_CONTACTS
|
|
||||||
};
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Nullable
|
@Nullable
|
||||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||||
|
@ -108,6 +120,7 @@ public class FragmentSetup extends FragmentBase {
|
||||||
|
|
||||||
btnHelp = view.findViewById(R.id.btnHelp);
|
btnHelp = view.findViewById(R.id.btnHelp);
|
||||||
btnQuick = view.findViewById(R.id.btnQuick);
|
btnQuick = view.findViewById(R.id.btnQuick);
|
||||||
|
btnGmail = view.findViewById(R.id.btnGmail);
|
||||||
|
|
||||||
tvAccountDone = view.findViewById(R.id.tvAccountDone);
|
tvAccountDone = view.findViewById(R.id.tvAccountDone);
|
||||||
btnAccount = view.findViewById(R.id.btnAccount);
|
btnAccount = view.findViewById(R.id.btnAccount);
|
||||||
|
@ -166,6 +179,28 @@ public class FragmentSetup extends FragmentBase {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
btnGmail.setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
List<String> permissions = new ArrayList<>();
|
||||||
|
permissions.add(Manifest.permission.READ_CONTACTS); // profile
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O)
|
||||||
|
permissions.add(Manifest.permission.GET_ACCOUNTS);
|
||||||
|
|
||||||
|
boolean granted = true;
|
||||||
|
for (String permission : permissions)
|
||||||
|
if (!hasPermission(permission)) {
|
||||||
|
granted = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (granted)
|
||||||
|
selectAccount();
|
||||||
|
else
|
||||||
|
requestPermissions(permissions.toArray(new String[0]), ActivitySetup.REQUEST_CHOOSE_ACCOUNT);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
btnAccount.setOnClickListener(new View.OnClickListener() {
|
btnAccount.setOnClickListener(new View.OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View view) {
|
public void onClick(View view) {
|
||||||
|
@ -186,7 +221,8 @@ public class FragmentSetup extends FragmentBase {
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View view) {
|
public void onClick(View view) {
|
||||||
btnPermissions.setEnabled(false);
|
btnPermissions.setEnabled(false);
|
||||||
requestPermissions(permissions, ActivitySetup.REQUEST_PERMISSION);
|
String permission = Manifest.permission.READ_CONTACTS;
|
||||||
|
requestPermissions(new String[]{permission}, ActivitySetup.REQUEST_PERMISSION);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -226,6 +262,8 @@ public class FragmentSetup extends FragmentBase {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Initialize
|
// Initialize
|
||||||
|
btnGmail.setVisibility(Helper.hasValidFingerprint(getContext()) ? View.VISIBLE : View.GONE);
|
||||||
|
|
||||||
tvAccountDone.setText(null);
|
tvAccountDone.setText(null);
|
||||||
tvAccountDone.setCompoundDrawables(null, null, null, null);
|
tvAccountDone.setCompoundDrawables(null, null, null, null);
|
||||||
tvNoPrimaryDrafts.setVisibility(View.GONE);
|
tvNoPrimaryDrafts.setVisibility(View.GONE);
|
||||||
|
@ -248,11 +286,7 @@ public class FragmentSetup extends FragmentBase {
|
||||||
grpWelcome.setVisibility(welcome ? View.VISIBLE : View.GONE);
|
grpWelcome.setVisibility(welcome ? View.VISIBLE : View.GONE);
|
||||||
grpDataSaver.setVisibility(View.GONE);
|
grpDataSaver.setVisibility(View.GONE);
|
||||||
|
|
||||||
int[] grantResults = new int[permissions.length];
|
setContactsPermission(hasPermission(Manifest.permission.READ_CONTACTS));
|
||||||
for (int i = 0; i < permissions.length; i++)
|
|
||||||
grantResults[i] = ContextCompat.checkSelfPermission(getActivity(), permissions[i]);
|
|
||||||
|
|
||||||
checkPermissions(permissions, grantResults, true);
|
|
||||||
|
|
||||||
// Create outbox
|
// Create outbox
|
||||||
new SimpleTask<Void>() {
|
new SimpleTask<Void>() {
|
||||||
|
@ -392,25 +426,227 @@ public class FragmentSetup extends FragmentBase {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
||||||
if (requestCode == ActivitySetup.REQUEST_PERMISSION)
|
boolean granted = true;
|
||||||
checkPermissions(permissions, grantResults, false);
|
for (int i = 0; i < permissions.length; i++)
|
||||||
|
if (grantResults[i] == PackageManager.PERMISSION_GRANTED) {
|
||||||
|
if (Manifest.permission.READ_CONTACTS.equals(permissions[i]))
|
||||||
|
setContactsPermission(true);
|
||||||
|
} else
|
||||||
|
granted = false;
|
||||||
|
|
||||||
|
if (requestCode == ActivitySetup.REQUEST_CHOOSE_ACCOUNT)
|
||||||
|
if (granted)
|
||||||
|
selectAccount();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkPermissions(String[] permissions, @NonNull int[] grantResults, boolean init) {
|
private void setContactsPermission(boolean granted) {
|
||||||
boolean has = (grantResults.length > 0);
|
if (granted)
|
||||||
for (int result : grantResults)
|
|
||||||
if (result != PackageManager.PERMISSION_GRANTED) {
|
|
||||||
has = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (has)
|
|
||||||
ContactInfo.init(getContext());
|
ContactInfo.init(getContext());
|
||||||
|
|
||||||
tvPermissionsDone.setText(has ? R.string.title_setup_done : R.string.title_setup_to_do);
|
tvPermissionsDone.setText(granted ? R.string.title_setup_done : R.string.title_setup_to_do);
|
||||||
tvPermissionsDone.setTextColor(has ? textColorPrimary : colorWarning);
|
tvPermissionsDone.setTextColor(granted ? textColorPrimary : colorWarning);
|
||||||
tvPermissionsDone.setCompoundDrawablesWithIntrinsicBounds(has ? check : null, null, null, null);
|
tvPermissionsDone.setCompoundDrawablesWithIntrinsicBounds(granted ? check : null, null, null, null);
|
||||||
btnPermissions.setEnabled(!has);
|
btnPermissions.setEnabled(!granted);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void selectAccount() {
|
||||||
|
Log.i("Select account");
|
||||||
|
startActivityForResult(
|
||||||
|
Helper.getChooser(getContext(), newChooseAccountIntent(
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
new String[]{"com.google"},
|
||||||
|
false,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null)),
|
||||||
|
ActivitySetup.REQUEST_CHOOSE_ACCOUNT);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||||
|
switch (requestCode) {
|
||||||
|
case ActivitySetup.REQUEST_CHOOSE_ACCOUNT:
|
||||||
|
if (resultCode == Activity.RESULT_OK && data != null)
|
||||||
|
onAccountSelected(data);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onAccountSelected(Intent data) {
|
||||||
|
Log.i("Selected " + data);
|
||||||
|
Log.logExtras(data);
|
||||||
|
|
||||||
|
String name = data.getStringExtra(AccountManager.KEY_ACCOUNT_NAME);
|
||||||
|
String type = data.getStringExtra(AccountManager.KEY_ACCOUNT_TYPE);
|
||||||
|
|
||||||
|
AccountManager am = AccountManager.get(getContext());
|
||||||
|
Account[] accounts = am.getAccountsByType(type);
|
||||||
|
Log.i("Accounts=" + accounts.length);
|
||||||
|
for (final Account account : accounts)
|
||||||
|
if (name.equals(account.name)) {
|
||||||
|
Snackbar.make(view, R.string.title_authorizing, Snackbar.LENGTH_LONG).show();
|
||||||
|
|
||||||
|
am.getAuthToken(
|
||||||
|
account,
|
||||||
|
MailService.getAuthTokenType(type),
|
||||||
|
new Bundle(),
|
||||||
|
getActivity(),
|
||||||
|
new AccountManagerCallback<Bundle>() {
|
||||||
|
@Override
|
||||||
|
public void run(AccountManagerFuture<Bundle> future) {
|
||||||
|
try {
|
||||||
|
Bundle bundle = future.getResult();
|
||||||
|
String token = bundle.getString(AccountManager.KEY_AUTHTOKEN);
|
||||||
|
Log.i("Got token=" + token);
|
||||||
|
onAuthorized(name, token);
|
||||||
|
} catch (Throwable ex) {
|
||||||
|
if (ex instanceof AccountsException || ex instanceof IOException) {
|
||||||
|
if (getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.RESUMED))
|
||||||
|
Snackbar.make(view, Helper.formatThrowable(ex), Snackbar.LENGTH_LONG).show();
|
||||||
|
} else {
|
||||||
|
Log.e(ex);
|
||||||
|
Helper.unexpectedError(getFragmentManager(), ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
null);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onAuthorized(String user, String password) {
|
||||||
|
Bundle args = new Bundle();
|
||||||
|
args.putString("user", user);
|
||||||
|
args.putString("password", password);
|
||||||
|
|
||||||
|
new SimpleTask<Void>() {
|
||||||
|
@Override
|
||||||
|
protected Void onExecute(Context context, Bundle args) throws Throwable {
|
||||||
|
String user = args.getString("user");
|
||||||
|
String password = args.getString("password");
|
||||||
|
|
||||||
|
if (!user.contains("@"))
|
||||||
|
throw new IllegalArgumentException(user);
|
||||||
|
|
||||||
|
String domain = user.split("@")[1];
|
||||||
|
EmailProvider provider = EmailProvider.fromDomain(context, domain, EmailProvider.Discover.ALL);
|
||||||
|
if (provider == null)
|
||||||
|
throw new IllegalArgumentException(user);
|
||||||
|
|
||||||
|
List<EntityFolder> folders;
|
||||||
|
|
||||||
|
String aprotocol = provider.imap.starttls ? "imap" : "imaps";
|
||||||
|
try (MailService iservice = new MailService(context, aprotocol, null, false, true)) {
|
||||||
|
iservice.connect(provider.imap.host, provider.imap.port, MailService.AUTH_TYPE_GMAIL, user, password);
|
||||||
|
|
||||||
|
folders = iservice.getFolders();
|
||||||
|
|
||||||
|
if (folders == null)
|
||||||
|
throw new IllegalArgumentException(domain);
|
||||||
|
}
|
||||||
|
|
||||||
|
String iprotocol = provider.smtp.starttls ? "smtp" : "smtps";
|
||||||
|
try (MailService iservice = new MailService(context, iprotocol, null, false, true)) {
|
||||||
|
iservice.connect(provider.smtp.host, provider.smtp.port, MailService.AUTH_TYPE_GMAIL, user, password);
|
||||||
|
}
|
||||||
|
|
||||||
|
DB db = DB.getInstance(context);
|
||||||
|
try {
|
||||||
|
db.beginTransaction();
|
||||||
|
|
||||||
|
EntityAccount primary = db.account().getPrimaryAccount();
|
||||||
|
|
||||||
|
// Create account
|
||||||
|
EntityAccount account = new EntityAccount();
|
||||||
|
|
||||||
|
account.host = provider.imap.host;
|
||||||
|
account.starttls = provider.imap.starttls;
|
||||||
|
account.port = provider.imap.port;
|
||||||
|
account.auth_type = MailService.AUTH_TYPE_GMAIL;
|
||||||
|
account.user = user;
|
||||||
|
account.password = password;
|
||||||
|
|
||||||
|
account.name = provider.name;
|
||||||
|
|
||||||
|
account.synchronize = true;
|
||||||
|
account.primary = (primary == null);
|
||||||
|
|
||||||
|
account.created = new Date().getTime();
|
||||||
|
account.last_connected = account.created;
|
||||||
|
|
||||||
|
account.id = db.account().insertAccount(account);
|
||||||
|
EntityLog.log(context, "Gmail account=" + account.name);
|
||||||
|
|
||||||
|
// Create folders
|
||||||
|
for (EntityFolder folder : folders) {
|
||||||
|
folder.account = account.id;
|
||||||
|
folder.id = db.folder().insertFolder(folder);
|
||||||
|
EntityLog.log(context, "Gmail folder=" + folder.name + " type=" + folder.type);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set swipe left/right folder
|
||||||
|
for (EntityFolder folder : folders)
|
||||||
|
if (EntityFolder.TRASH.equals(folder.type))
|
||||||
|
account.swipe_left = folder.id;
|
||||||
|
else if (EntityFolder.ARCHIVE.equals(folder.type))
|
||||||
|
account.swipe_right = folder.id;
|
||||||
|
|
||||||
|
db.account().updateAccount(account);
|
||||||
|
|
||||||
|
String name = user.split("@")[0];
|
||||||
|
try (Cursor cursor = context.getContentResolver().query(
|
||||||
|
ContactsContract.Profile.CONTENT_URI,
|
||||||
|
new String[]{ContactsContract.Profile.DISPLAY_NAME}, null, null, null)) {
|
||||||
|
if (cursor != null && cursor.moveToFirst()) {
|
||||||
|
int colDisplay = cursor.getColumnIndex(ContactsContract.Profile.DISPLAY_NAME);
|
||||||
|
name = cursor.getString(colDisplay);
|
||||||
|
}
|
||||||
|
} catch (Throwable ex) {
|
||||||
|
Log.e(ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create identity
|
||||||
|
EntityIdentity identity = new EntityIdentity();
|
||||||
|
identity.name = name;
|
||||||
|
identity.email = user;
|
||||||
|
identity.account = account.id;
|
||||||
|
|
||||||
|
identity.host = provider.smtp.host;
|
||||||
|
identity.starttls = provider.smtp.starttls;
|
||||||
|
identity.port = provider.smtp.port;
|
||||||
|
identity.auth_type = MailService.AUTH_TYPE_GMAIL;
|
||||||
|
identity.user = user;
|
||||||
|
identity.password = password;
|
||||||
|
identity.synchronize = true;
|
||||||
|
identity.primary = true;
|
||||||
|
|
||||||
|
identity.id = db.identity().insertIdentity(identity);
|
||||||
|
EntityLog.log(context, "Gmail identity=" + identity.name + " email=" + identity.email);
|
||||||
|
|
||||||
|
db.setTransactionSuccessful();
|
||||||
|
} finally {
|
||||||
|
db.endTransaction();
|
||||||
|
}
|
||||||
|
|
||||||
|
ServiceSynchronize.reload(getContext(), "Gmail");
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onExecuted(Bundle args, Void data) {
|
||||||
|
FragmentQuickSetup.FragmentDialogDone fragment = new FragmentQuickSetup.FragmentDialogDone();
|
||||||
|
fragment.show(getFragmentManager(), "gmail:done");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onException(Bundle args, Throwable ex) {
|
||||||
|
Helper.unexpectedError(getFragmentManager(), ex);
|
||||||
|
}
|
||||||
|
}.execute(this, args, "setup:gmail");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class FragmentDialogDoze extends FragmentDialogBase {
|
public static class FragmentDialogDoze extends FragmentDialogBase {
|
||||||
|
|
|
@ -1,7 +1,11 @@
|
||||||
package eu.faircode.email;
|
package eu.faircode.email;
|
||||||
|
|
||||||
|
import android.accounts.Account;
|
||||||
|
import android.accounts.AccountManager;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
|
||||||
|
import com.sun.mail.imap.IMAPFolder;
|
||||||
import com.sun.mail.imap.IMAPStore;
|
import com.sun.mail.imap.IMAPStore;
|
||||||
import com.sun.mail.smtp.SMTPTransport;
|
import com.sun.mail.smtp.SMTPTransport;
|
||||||
import com.sun.mail.util.MailConnectException;
|
import com.sun.mail.util.MailConnectException;
|
||||||
|
@ -10,13 +14,17 @@ import java.net.Inet4Address;
|
||||||
import java.net.Inet6Address;
|
import java.net.Inet6Address;
|
||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
import java.net.UnknownHostException;
|
import java.net.UnknownHostException;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
|
|
||||||
|
import javax.mail.AuthenticationFailedException;
|
||||||
|
import javax.mail.Folder;
|
||||||
import javax.mail.MessagingException;
|
import javax.mail.MessagingException;
|
||||||
import javax.mail.NoSuchProviderException;
|
import javax.mail.NoSuchProviderException;
|
||||||
import javax.mail.Service;
|
import javax.mail.Service;
|
||||||
|
@ -33,6 +41,9 @@ public class MailService implements AutoCloseable {
|
||||||
|
|
||||||
private ExecutorService executor = Executors.newCachedThreadPool(Helper.backgroundThreadFactory);
|
private ExecutorService executor = Executors.newCachedThreadPool(Helper.backgroundThreadFactory);
|
||||||
|
|
||||||
|
static final int AUTH_TYPE_PASSWORD = 1;
|
||||||
|
static final int AUTH_TYPE_GMAIL = 2;
|
||||||
|
|
||||||
private final static int CONNECT_TIMEOUT = 20 * 1000; // milliseconds
|
private final static int CONNECT_TIMEOUT = 20 * 1000; // milliseconds
|
||||||
private final static int WRITE_TIMEOUT = 60 * 1000; // milliseconds
|
private final static int WRITE_TIMEOUT = 60 * 1000; // milliseconds
|
||||||
private final static int READ_TIMEOUT = 60 * 1000; // milliseconds
|
private final static int READ_TIMEOUT = 60 * 1000; // milliseconds
|
||||||
|
@ -127,18 +138,56 @@ public class MailService implements AutoCloseable {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void connect(EntityAccount account) throws MessagingException {
|
public void connect(EntityAccount account) throws MessagingException {
|
||||||
connect(account.host, account.port, account.user, account.password);
|
connect(account.host, account.port, account.auth_type, account.user, account.password);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void connect(EntityIdentity identity) throws MessagingException {
|
public void connect(EntityIdentity identity) throws MessagingException {
|
||||||
connect(identity.host, identity.port, identity.user, identity.password);
|
connect(identity.host, identity.port, identity.auth_type, identity.user, identity.password);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void connect(String host, int port, String user, String password) throws MessagingException {
|
public void connect(String host, int port, int auth, String user, String password) throws MessagingException {
|
||||||
try {
|
try {
|
||||||
|
if (auth == AUTH_TYPE_GMAIL)
|
||||||
|
properties.put("mail." + protocol + ".auth.mechanisms", "XOAUTH2");
|
||||||
|
|
||||||
//if (BuildConfig.DEBUG)
|
//if (BuildConfig.DEBUG)
|
||||||
// throw new MailConnectException(new SocketConnectException("Debug", new Exception(), host, port, 0));
|
// throw new MailConnectException(new SocketConnectException("Debug", new Exception(), host, port, 0));
|
||||||
|
|
||||||
_connect(context, host, port, user, password);
|
_connect(context, host, port, user, password);
|
||||||
|
} catch (AuthenticationFailedException ex) {
|
||||||
|
// Refresh token
|
||||||
|
if (auth == AUTH_TYPE_GMAIL)
|
||||||
|
try {
|
||||||
|
String type = "com.google";
|
||||||
|
AccountManager am = AccountManager.get(context);
|
||||||
|
Account[] accounts = am.getAccountsByType(type);
|
||||||
|
for (Account account : accounts)
|
||||||
|
if (user.equals(account.name)) {
|
||||||
|
Log.i("Refreshing token user=" + user);
|
||||||
|
am.invalidateAuthToken(type, password);
|
||||||
|
String refreshed = am.blockingGetAuthToken(account, getAuthTokenType(type), true);
|
||||||
|
if (refreshed == null)
|
||||||
|
throw new IllegalStateException("no token");
|
||||||
|
|
||||||
|
int count = 0;
|
||||||
|
DB db = DB.getInstance(context);
|
||||||
|
if ("imap".equals(protocol) || "imaps".equals(protocol))
|
||||||
|
count = db.account().updateAccountPassword(password, refreshed);
|
||||||
|
else if ("smtp".equals(protocol) || "smtps".equals(protocol))
|
||||||
|
count = db.identity().updateIdentityPassword(password, refreshed);
|
||||||
|
|
||||||
|
if (count != 1)
|
||||||
|
throw new IllegalStateException(protocol + "=" + count);
|
||||||
|
|
||||||
|
_connect(context, host, port, user, refreshed);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} catch (Throwable ex1) {
|
||||||
|
Log.e(ex1);
|
||||||
|
throw ex;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
throw ex;
|
||||||
} catch (MailConnectException ex) {
|
} catch (MailConnectException ex) {
|
||||||
try {
|
try {
|
||||||
// Some devices resolve IPv6 addresses while not having IPv6 connectivity
|
// Some devices resolve IPv6 addresses while not having IPv6 connectivity
|
||||||
|
@ -213,6 +262,63 @@ public class MailService implements AutoCloseable {
|
||||||
throw new NoSuchProviderException(protocol);
|
throw new NoSuchProviderException(protocol);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static String getAuthTokenType(String type) {
|
||||||
|
// https://developers.google.com/gmail/imap/xoauth2-protocol
|
||||||
|
if ("com.google".equals(type))
|
||||||
|
return "oauth2:https://mail.google.com/";
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<EntityFolder> getFolders() throws MessagingException {
|
||||||
|
List<EntityFolder> folders = new ArrayList<>();
|
||||||
|
List<EntityFolder> guesses = new ArrayList<>();
|
||||||
|
|
||||||
|
for (Folder ifolder : getStore().getDefaultFolder().list("*")) {
|
||||||
|
String fullName = ifolder.getFullName();
|
||||||
|
String[] attrs = ((IMAPFolder) ifolder).getAttributes();
|
||||||
|
String type = EntityFolder.getType(attrs, fullName, true);
|
||||||
|
Log.i(fullName + " attrs=" + TextUtils.join(" ", attrs) + " type=" + type);
|
||||||
|
|
||||||
|
if (type != null) {
|
||||||
|
EntityFolder folder = new EntityFolder(fullName, type);
|
||||||
|
folders.add(folder);
|
||||||
|
|
||||||
|
if (EntityFolder.USER.equals(type)) {
|
||||||
|
String guess = EntityFolder.guessType(fullName);
|
||||||
|
if (guess != null)
|
||||||
|
guesses.add(folder);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (EntityFolder guess : guesses) {
|
||||||
|
boolean has = false;
|
||||||
|
String gtype = EntityFolder.guessType(guess.name);
|
||||||
|
for (EntityFolder folder : folders)
|
||||||
|
if (folder.type.equals(gtype)) {
|
||||||
|
has = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (!has) {
|
||||||
|
guess.type = gtype;
|
||||||
|
Log.i(guess.name + " guessed type=" + gtype);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean inbox = false;
|
||||||
|
boolean drafts = false;
|
||||||
|
for (EntityFolder folder : folders)
|
||||||
|
if (EntityFolder.INBOX.equals(folder.type))
|
||||||
|
inbox = true;
|
||||||
|
else if (EntityFolder.DRAFTS.equals(folder.type))
|
||||||
|
drafts = true;
|
||||||
|
|
||||||
|
if (!inbox || !drafts)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return folders;
|
||||||
|
}
|
||||||
|
|
||||||
IMAPStore getStore() {
|
IMAPStore getStore() {
|
||||||
return (IMAPStore) iservice;
|
return (IMAPStore) iservice;
|
||||||
}
|
}
|
||||||
|
|
|
@ -105,6 +105,17 @@
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@id/tvQuick" />
|
app:layout_constraintTop_toBottomOf="@id/tvQuick" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btnGmail"
|
||||||
|
style="?android:attr/buttonStyleSmall"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:minWidth="0dp"
|
||||||
|
android:minHeight="0dp"
|
||||||
|
android:text="@string/title_setup_gmail"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/btnQuick" />
|
||||||
|
|
||||||
<!-- account -->
|
<!-- account -->
|
||||||
|
|
||||||
<View
|
<View
|
||||||
|
@ -114,7 +125,7 @@
|
||||||
android:layout_marginTop="12dp"
|
android:layout_marginTop="12dp"
|
||||||
android:background="?attr/colorSeparator"
|
android:background="?attr/colorSeparator"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@id/btnQuick" />
|
app:layout_constraintTop_toBottomOf="@id/btnGmail" />
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/one"
|
android:id="@+id/one"
|
||||||
|
|
|
@ -134,20 +134,6 @@
|
||||||
app:layout_constraintStart_toEndOf="@+id/ivState"
|
app:layout_constraintStart_toEndOf="@+id/ivState"
|
||||||
app:layout_constraintTop_toBottomOf="@id/tvHost" />
|
app:layout_constraintTop_toBottomOf="@id/tvHost" />
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/tvAuthorize"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginStart="6dp"
|
|
||||||
android:layout_marginEnd="6dp"
|
|
||||||
android:text="@string/title_authorization_required"
|
|
||||||
android:textAppearance="@style/TextAppearance.AppCompat.Small"
|
|
||||||
android:textColor="?attr/colorWarning"
|
|
||||||
android:textIsSelectable="true"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toEndOf="@id/vwColor"
|
|
||||||
app:layout_constraintTop_toBottomOf="@id/tvLast" />
|
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/tvIdentity"
|
android:id="@+id/tvIdentity"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
|
@ -160,7 +146,7 @@
|
||||||
android:textIsSelectable="true"
|
android:textIsSelectable="true"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toEndOf="@id/vwColor"
|
app:layout_constraintStart_toEndOf="@id/vwColor"
|
||||||
app:layout_constraintTop_toBottomOf="@id/tvAuthorize" />
|
app:layout_constraintTop_toBottomOf="@id/tvLast" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/tvDrafts"
|
android:id="@+id/tvDrafts"
|
||||||
|
|
|
@ -136,20 +136,6 @@
|
||||||
app:layout_constraintStart_toEndOf="@+id/ivState"
|
app:layout_constraintStart_toEndOf="@+id/ivState"
|
||||||
app:layout_constraintTop_toBottomOf="@id/tvHost" />
|
app:layout_constraintTop_toBottomOf="@id/tvHost" />
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/tvAuthorize"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginStart="6dp"
|
|
||||||
android:layout_marginEnd="6dp"
|
|
||||||
android:text="@string/title_authorization_required"
|
|
||||||
android:textAppearance="@style/TextAppearance.AppCompat.Small"
|
|
||||||
android:textColor="?attr/colorWarning"
|
|
||||||
android:textIsSelectable="true"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toEndOf="@id/vwColor"
|
|
||||||
app:layout_constraintTop_toBottomOf="@id/tvLast" />
|
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/tvError"
|
android:id="@+id/tvError"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
|
@ -162,7 +148,7 @@
|
||||||
android:textIsSelectable="true"
|
android:textIsSelectable="true"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toEndOf="@id/vwColor"
|
app:layout_constraintStart_toEndOf="@id/vwColor"
|
||||||
app:layout_constraintTop_toBottomOf="@id/tvAuthorize" />
|
app:layout_constraintTop_toBottomOf="@id/tvLast" />
|
||||||
|
|
||||||
<View
|
<View
|
||||||
android:id="@+id/marginBottom"
|
android:id="@+id/marginBottom"
|
||||||
|
|
|
@ -135,6 +135,8 @@
|
||||||
<string name="title_setup_quick_smtp">SMTP server to send messages</string>
|
<string name="title_setup_quick_smtp">SMTP server to send messages</string>
|
||||||
<string name="title_setup_go">Go</string>
|
<string name="title_setup_go">Go</string>
|
||||||
<string name="title_setup_wizard">Wizard</string>
|
<string name="title_setup_wizard">Wizard</string>
|
||||||
|
<string name="title_setup_gmail" translatable="false">Gmail</string>
|
||||||
|
<string name="title_authorizing">Authorizing …</string>
|
||||||
<string name="title_setup_instructions">Setup instructions</string>
|
<string name="title_setup_instructions">Setup instructions</string>
|
||||||
<string name="title_setup_no_settings">No settings found for domain \'%1$s\'</string>
|
<string name="title_setup_no_settings">No settings found for domain \'%1$s\'</string>
|
||||||
<string name="title_setup_quick_success">An account and an identity have successfully been added</string>
|
<string name="title_setup_quick_success">An account and an identity have successfully been added</string>
|
||||||
|
@ -406,7 +408,6 @@
|
||||||
<string name="title_no_idle">This provider does not support push messages. This will delay reception of new messages and increase battery usage.</string>
|
<string name="title_no_idle">This provider does not support push messages. This will delay reception of new messages and increase battery usage.</string>
|
||||||
<string name="title_no_utf8">This provider does not support UTF-8</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_no_sync">Synchronization errors since %1$s</string>
|
||||||
<string name="title_authorization_required">Authorization required</string>
|
|
||||||
<string name="title_identity_required">An identity is required to send messages</string>
|
<string name="title_identity_required">An identity is required to send messages</string>
|
||||||
<string name="title_drafts_required">A drafts folder is required to send messages</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>
|
<string name="title_account_delete">Delete this account permanently?</string>
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
<providers>
|
<providers>
|
||||||
<provider
|
<provider
|
||||||
name="Gmail"
|
name="Gmail"
|
||||||
|
domain="gmail.com"
|
||||||
link="https://github.com/M66B/FairEmail/blob/master/FAQ.md#user-content-faq6"
|
link="https://github.com/M66B/FairEmail/blob/master/FAQ.md#user-content-faq6"
|
||||||
order="1"
|
order="1"
|
||||||
type="com.google">
|
type="com.google">
|
||||||
|
|
Loading…
Reference in a new issue