mirror of
https://github.com/M66B/FairEmail.git
synced 2024-12-29 11:15:51 +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 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: *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)):
|
||||
|
||||
|
|
|
@ -12,6 +12,12 @@
|
|||
<uses-permission android:name="com.android.vending.BILLING" />
|
||||
<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 -->
|
||||
<uses-feature
|
||||
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_EXPORT = 3;
|
||||
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_VIEW_ACCOUNTS = BuildConfig.APPLICATION_ID + ".ACTION_VIEW_ACCOUNTS";
|
||||
|
|
|
@ -83,7 +83,6 @@ public class AdapterAccount extends RecyclerView.Adapter<AdapterAccount.ViewHold
|
|||
private ImageView ivState;
|
||||
private TextView tvHost;
|
||||
private TextView tvLast;
|
||||
private TextView tvAuthorize;
|
||||
private TextView tvIdentity;
|
||||
private TextView tvDrafts;
|
||||
private TextView tvWarning;
|
||||
|
@ -106,7 +105,6 @@ public class AdapterAccount extends RecyclerView.Adapter<AdapterAccount.ViewHold
|
|||
ivState = itemView.findViewById(R.id.ivState);
|
||||
tvHost = itemView.findViewById(R.id.tvHost);
|
||||
tvLast = itemView.findViewById(R.id.tvLast);
|
||||
tvAuthorize = itemView.findViewById(R.id.tvAuthorize);
|
||||
tvIdentity = itemView.findViewById(R.id.tvIdentity);
|
||||
tvDrafts = itemView.findViewById(R.id.tvDrafts);
|
||||
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,
|
||||
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);
|
||||
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 TextView tvAccount;
|
||||
private TextView tvLast;
|
||||
private TextView tvAuthorize;
|
||||
private TextView tvError;
|
||||
|
||||
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);
|
||||
tvAccount = itemView.findViewById(R.id.tvAccount);
|
||||
tvLast = itemView.findViewById(R.id.tvLast);
|
||||
tvAuthorize = itemView.findViewById(R.id.tvAuthorize);
|
||||
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,
|
||||
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.setVisibility(identity.error == null ? View.GONE : View.VISIBLE);
|
||||
}
|
||||
|
|
|
@ -33,9 +33,6 @@ public class ConnectionHelper {
|
|||
// https://dns.watch/
|
||||
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
|
||||
// https://en.wikipedia.org/wiki/European_Union_roaming_regulations
|
||||
private static final List<String> RLAH_COUNTRY_CODES = Collections.unmodifiableList(Arrays.asList(
|
||||
|
|
|
@ -105,6 +105,9 @@ public interface DaoAccount {
|
|||
@Update
|
||||
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")
|
||||
int setFolderSeparator(long id, Character separator);
|
||||
|
||||
|
@ -117,9 +120,6 @@ public interface DaoAccount {
|
|||
@Query("UPDATE account SET last_connected = :last_connected WHERE id = :id")
|
||||
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")
|
||||
int setAccountOrder(long id, Integer order);
|
||||
|
||||
|
@ -129,9 +129,6 @@ public interface DaoAccount {
|
|||
@Query("UPDATE account SET error = :error WHERE id = :id")
|
||||
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")
|
||||
void resetPrimary();
|
||||
|
||||
|
|
|
@ -67,6 +67,9 @@ public interface DaoIdentity {
|
|||
@Update
|
||||
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")
|
||||
int setIdentitySynchronize(long id, boolean synchronize);
|
||||
|
||||
|
|
|
@ -54,8 +54,6 @@ public class EntityAccount extends EntityOrder implements Serializable {
|
|||
@PrimaryKey(autoGenerate = true)
|
||||
public Long id;
|
||||
|
||||
@NonNull
|
||||
public Integer auth_type;
|
||||
@NonNull
|
||||
public Boolean pop = false; // obsolete
|
||||
@NonNull
|
||||
|
@ -63,10 +61,12 @@ public class EntityAccount extends EntityOrder implements Serializable {
|
|||
@NonNull
|
||||
public Boolean starttls;
|
||||
@NonNull
|
||||
public Boolean insecure;
|
||||
public Boolean insecure = false;
|
||||
@NonNull
|
||||
public Integer port;
|
||||
@NonNull
|
||||
public Integer auth_type;
|
||||
@NonNull
|
||||
public String user;
|
||||
@NonNull
|
||||
public String password;
|
||||
|
@ -83,14 +83,14 @@ public class EntityAccount extends EntityOrder implements Serializable {
|
|||
@NonNull
|
||||
public Boolean primary;
|
||||
@NonNull
|
||||
public Boolean notify;
|
||||
public Boolean notify = false;
|
||||
@NonNull
|
||||
public Boolean browse = true;
|
||||
public Character separator;
|
||||
public Long swipe_left;
|
||||
public Long swipe_right;
|
||||
@NonNull
|
||||
public Integer poll_interval; // keep-alive interval
|
||||
public Integer poll_interval = DEFAULT_KEEP_ALIVE_INTERVAL; // keep-alive interval
|
||||
@NonNull
|
||||
public Boolean partial_fetch = true;
|
||||
public String prefix; // namespace, obsolete
|
||||
|
@ -146,11 +146,11 @@ public class EntityAccount extends EntityOrder implements Serializable {
|
|||
JSONObject json = new JSONObject();
|
||||
json.put("id", id);
|
||||
json.put("order", order);
|
||||
json.put("auth_type", auth_type);
|
||||
json.put("host", host);
|
||||
json.put("starttls", starttls);
|
||||
json.put("insecure", insecure);
|
||||
json.put("port", port);
|
||||
json.put("auth_type", auth_type);
|
||||
json.put("user", user);
|
||||
json.put("password", password);
|
||||
json.put("realm", realm);
|
||||
|
@ -184,11 +184,11 @@ public class EntityAccount extends EntityOrder implements Serializable {
|
|||
if (json.has("order"))
|
||||
account.order = json.getInt("order");
|
||||
|
||||
account.auth_type = json.getInt("auth_type");
|
||||
account.host = json.getString("host");
|
||||
account.starttls = (json.has("starttls") && json.getBoolean("starttls"));
|
||||
account.insecure = (json.has("insecure") && json.getBoolean("insecure"));
|
||||
account.port = json.getInt("port");
|
||||
account.auth_type = json.getInt("auth_type");
|
||||
account.user = json.getString("user");
|
||||
account.password = json.getString("password");
|
||||
if (json.has("realm"))
|
||||
|
@ -227,11 +227,11 @@ public class EntityAccount extends EntityOrder implements Serializable {
|
|||
public boolean equals(Object obj) {
|
||||
if (obj instanceof EntityAccount) {
|
||||
EntityAccount other = (EntityAccount) obj;
|
||||
return (this.auth_type.equals(other.auth_type) &&
|
||||
this.host.equals(other.host) &&
|
||||
return (this.host.equals(other.host) &&
|
||||
this.starttls == other.starttls &&
|
||||
this.insecure == other.insecure &&
|
||||
this.port.equals(other.port) &&
|
||||
this.auth_type.equals(other.auth_type) &&
|
||||
this.user.equals(other.user) &&
|
||||
this.password.equals(other.password) &&
|
||||
Objects.equals(this.realm, other.realm) &&
|
||||
|
|
|
@ -57,16 +57,16 @@ public class EntityIdentity {
|
|||
public Integer color;
|
||||
public String signature;
|
||||
@NonNull
|
||||
public Integer auth_type;
|
||||
@NonNull
|
||||
public String host; // SMTP
|
||||
@NonNull
|
||||
public Boolean starttls;
|
||||
@NonNull
|
||||
public Boolean insecure;
|
||||
public Boolean insecure = false;
|
||||
@NonNull
|
||||
public Integer port;
|
||||
@NonNull
|
||||
public Integer auth_type;
|
||||
@NonNull
|
||||
public String user;
|
||||
@NonNull
|
||||
public String password;
|
||||
|
@ -113,11 +113,11 @@ public class EntityIdentity {
|
|||
json.put("signature", signature);
|
||||
// not account
|
||||
|
||||
json.put("auth_type", auth_type);
|
||||
json.put("host", host);
|
||||
json.put("starttls", starttls);
|
||||
json.put("insecure", insecure);
|
||||
json.put("port", port);
|
||||
json.put("auth_type", auth_type);
|
||||
json.put("user", user);
|
||||
json.put("password", password);
|
||||
json.put("realm", realm);
|
||||
|
@ -151,11 +151,11 @@ public class EntityIdentity {
|
|||
if (json.has("signature") && !json.isNull("signature"))
|
||||
identity.signature = json.getString("signature");
|
||||
|
||||
identity.auth_type = json.getInt("auth_type");
|
||||
identity.host = json.getString("host");
|
||||
identity.starttls = json.getBoolean("starttls");
|
||||
identity.insecure = (json.has("insecure") && json.getBoolean("insecure"));
|
||||
identity.port = json.getInt("port");
|
||||
identity.auth_type = json.getInt("auth_type");
|
||||
identity.user = json.getString("user");
|
||||
identity.password = json.getString("password");
|
||||
if (json.has("realm") && !json.isNull("realm"))
|
||||
|
@ -196,11 +196,11 @@ public class EntityIdentity {
|
|||
Objects.equals(this.display, other.display) &&
|
||||
Objects.equals(this.color, other.color) &&
|
||||
Objects.equals(this.signature, other.signature) &&
|
||||
this.auth_type.equals(other.auth_type) &&
|
||||
this.host.equals(other.host) &&
|
||||
this.starttls.equals(other.starttls) &&
|
||||
this.insecure.equals(other.insecure) &&
|
||||
this.port.equals(other.port) &&
|
||||
this.auth_type.equals(other.auth_type) &&
|
||||
this.user.equals(other.user) &&
|
||||
this.password.equals(other.password) &&
|
||||
Objects.equals(this.realm, other.realm) &&
|
||||
|
|
|
@ -142,6 +142,7 @@ public class FragmentAccount extends FragmentBase {
|
|||
|
||||
private long id = -1;
|
||||
private long copy = -1;
|
||||
private int auth = MailService.AUTH_TYPE_PASSWORD;
|
||||
private boolean saving = false;
|
||||
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("insecure", cbInsecure.isChecked());
|
||||
args.putString("port", etPort.getText().toString());
|
||||
args.putInt("auth", auth);
|
||||
args.putString("user", etUser.getText().toString());
|
||||
args.putString("password", tilPassword.getEditText().getText().toString());
|
||||
args.putString("realm", etRealm.getText().toString());
|
||||
|
@ -551,6 +553,7 @@ public class FragmentAccount extends FragmentBase {
|
|||
boolean starttls = args.getBoolean("starttls");
|
||||
boolean insecure = args.getBoolean("insecure");
|
||||
String port = args.getString("port");
|
||||
int auth = args.getInt("auth");
|
||||
String user = args.getString("user");
|
||||
String password = args.getString("password");
|
||||
String realm = args.getString("realm");
|
||||
|
@ -581,7 +584,7 @@ public class FragmentAccount extends FragmentBase {
|
|||
// Check IMAP server / get folders
|
||||
String protocol = "imap" + (starttls ? "" : "s");
|
||||
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");
|
||||
|
||||
|
@ -709,6 +712,7 @@ public class FragmentAccount extends FragmentBase {
|
|||
args.putBoolean("starttls", rgEncryption.getCheckedRadioButtonId() == R.id.radio_starttls);
|
||||
args.putBoolean("insecure", cbInsecure.isChecked());
|
||||
args.putString("port", etPort.getText().toString());
|
||||
args.putInt("auth", auth);
|
||||
args.putString("user", etUser.getText().toString());
|
||||
args.putString("password", tilPassword.getEditText().getText().toString());
|
||||
args.putString("realm", etRealm.getText().toString());
|
||||
|
@ -762,6 +766,7 @@ public class FragmentAccount extends FragmentBase {
|
|||
boolean starttls = args.getBoolean("starttls");
|
||||
boolean insecure = args.getBoolean("insecure");
|
||||
String port = args.getString("port");
|
||||
int auth = args.getInt("auth");
|
||||
String user = args.getString("user").trim();
|
||||
String password = args.getString("password");
|
||||
String realm = args.getString("realm");
|
||||
|
@ -829,6 +834,8 @@ public class FragmentAccount extends FragmentBase {
|
|||
return true;
|
||||
if (!Objects.equals(account.port, Integer.parseInt(port)))
|
||||
return true;
|
||||
if (account.auth_type != auth)
|
||||
return true;
|
||||
if (!Objects.equals(account.user, user))
|
||||
return true;
|
||||
if (!Objects.equals(account.password, password))
|
||||
|
@ -907,7 +914,7 @@ public class FragmentAccount extends FragmentBase {
|
|||
if (check) {
|
||||
String protocol = "imap" + (starttls ? "" : "s");
|
||||
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("*")) {
|
||||
// Check folder attributes
|
||||
|
@ -948,13 +955,15 @@ public class FragmentAccount extends FragmentBase {
|
|||
if (account == null)
|
||||
account = new EntityAccount();
|
||||
|
||||
account.auth_type = ConnectionHelper.AUTH_TYPE_PASSWORD;
|
||||
account.host = host;
|
||||
account.starttls = starttls;
|
||||
account.insecure = insecure;
|
||||
account.port = Integer.parseInt(port);
|
||||
account.user = user;
|
||||
account.password = password;
|
||||
account.auth_type = auth;
|
||||
if (auth == MailService.AUTH_TYPE_PASSWORD) {
|
||||
account.user = user;
|
||||
account.password = password;
|
||||
}
|
||||
account.realm = realm;
|
||||
|
||||
account.name = name;
|
||||
|
@ -1139,6 +1148,7 @@ public class FragmentAccount extends FragmentBase {
|
|||
outState.putInt("fair:provider", spProvider.getSelectedItemPosition());
|
||||
outState.putString("fair:password", tilPassword.getEditText().getText().toString());
|
||||
outState.putInt("fair:advanced", grpAdvanced.getVisibility());
|
||||
outState.putInt("fair:auth", auth);
|
||||
outState.putInt("fair:color", color);
|
||||
super.onSaveInstanceState(outState);
|
||||
}
|
||||
|
@ -1210,6 +1220,7 @@ public class FragmentAccount extends FragmentBase {
|
|||
etInterval.setText(account == null ? "" : Long.toString(account.poll_interval));
|
||||
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);
|
||||
|
||||
new SimpleTask<EntityAccount>() {
|
||||
|
@ -1236,11 +1247,17 @@ public class FragmentAccount extends FragmentBase {
|
|||
|
||||
tilPassword.getEditText().setText(savedInstanceState.getString("fair:password"));
|
||||
grpAdvanced.setVisibility(savedInstanceState.getInt("fair:advanced"));
|
||||
auth = savedInstanceState.getInt("fair:auth");
|
||||
color = savedInstanceState.getInt("fair:color");
|
||||
}
|
||||
|
||||
Helper.setViewsEnabled(view, true);
|
||||
|
||||
if (auth != MailService.AUTH_TYPE_PASSWORD) {
|
||||
etUser.setEnabled(false);
|
||||
tilPassword.setEnabled(false);
|
||||
}
|
||||
|
||||
setColor(color);
|
||||
cbPrimary.setEnabled(cbSynchronize.isChecked());
|
||||
|
||||
|
|
|
@ -130,6 +130,7 @@ public class FragmentIdentity extends FragmentBase {
|
|||
|
||||
private long id = -1;
|
||||
private long copy = -1;
|
||||
private int auth = MailService.AUTH_TYPE_PASSWORD;
|
||||
private boolean saving = false;
|
||||
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("insecure", cbInsecure.isChecked());
|
||||
args.putString("port", etPort.getText().toString());
|
||||
args.putInt("auth", auth);
|
||||
args.putString("user", etUser.getText().toString());
|
||||
args.putString("password", tilPassword.getEditText().getText().toString());
|
||||
args.putString("realm", etRealm.getText().toString());
|
||||
|
@ -584,6 +586,7 @@ public class FragmentIdentity extends FragmentBase {
|
|||
boolean starttls = args.getBoolean("starttls");
|
||||
boolean insecure = args.getBoolean("insecure");
|
||||
String port = args.getString("port");
|
||||
int auth = args.getInt("auth");
|
||||
String user = args.getString("user").trim();
|
||||
String password = args.getString("password");
|
||||
String realm = args.getString("realm");
|
||||
|
@ -680,6 +683,8 @@ public class FragmentIdentity extends FragmentBase {
|
|||
return true;
|
||||
if (!Objects.equals(identity.port, Integer.parseInt(port)))
|
||||
return true;
|
||||
if (identity.auth_type != auth)
|
||||
return true;
|
||||
if (!Objects.equals(identity.user, user))
|
||||
return true;
|
||||
if (!Objects.equals(identity.password, password))
|
||||
|
@ -731,7 +736,7 @@ public class FragmentIdentity extends FragmentBase {
|
|||
String protocol = (starttls ? "smtp" : "smtps");
|
||||
try (MailService iservice = new MailService(context, protocol, realm, insecure, true)) {
|
||||
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.signature = signature;
|
||||
|
||||
identity.auth_type = ConnectionHelper.AUTH_TYPE_PASSWORD;
|
||||
identity.host = host;
|
||||
identity.starttls = starttls;
|
||||
identity.insecure = insecure;
|
||||
identity.port = Integer.parseInt(port);
|
||||
identity.user = user;
|
||||
identity.password = password;
|
||||
identity.auth_type = auth;
|
||||
if (auth == MailService.AUTH_TYPE_PASSWORD) {
|
||||
identity.user = user;
|
||||
identity.password = password;
|
||||
}
|
||||
identity.realm = realm;
|
||||
identity.use_ip = use_ip;
|
||||
identity.synchronize = synchronize;
|
||||
|
@ -853,6 +860,7 @@ public class FragmentIdentity extends FragmentBase {
|
|||
outState.putInt("fair:provider", spProvider.getSelectedItemPosition());
|
||||
outState.putString("fair:password", tilPassword.getEditText().getText().toString());
|
||||
outState.putInt("fair:advanced", grpAdvanced.getVisibility());
|
||||
outState.putInt("fair:auth", auth);
|
||||
outState.putInt("fair:color", color);
|
||||
outState.putString("fair:html", (String) etSignature.getTag());
|
||||
super.onSaveInstanceState(outState);
|
||||
|
@ -902,6 +910,7 @@ public class FragmentIdentity extends FragmentBase {
|
|||
cbDeliveryReceipt.setChecked(identity == null ? false : identity.delivery_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);
|
||||
|
||||
if (identity == null || copy > 0)
|
||||
|
@ -924,12 +933,18 @@ public class FragmentIdentity extends FragmentBase {
|
|||
} else {
|
||||
tilPassword.getEditText().setText(savedInstanceState.getString("fair:password"));
|
||||
grpAdvanced.setVisibility(savedInstanceState.getInt("fair:advanced"));
|
||||
auth = savedInstanceState.getInt("fair:auth");
|
||||
color = savedInstanceState.getInt("fair:color");
|
||||
etSignature.setTag(savedInstanceState.getString("fair:html"));
|
||||
}
|
||||
|
||||
Helper.setViewsEnabled(view, true);
|
||||
|
||||
if (auth != MailService.AUTH_TYPE_PASSWORD) {
|
||||
etUser.setEnabled(false);
|
||||
tilPassword.setEnabled(false);
|
||||
}
|
||||
|
||||
setColor(color);
|
||||
|
||||
cbPrimary.setEnabled(cbSynchronize.isChecked());
|
||||
|
@ -949,7 +964,6 @@ public class FragmentIdentity extends FragmentBase {
|
|||
|
||||
EntityAccount unselected = new EntityAccount();
|
||||
unselected.id = -1L;
|
||||
unselected.auth_type = ConnectionHelper.AUTH_TYPE_PASSWORD;
|
||||
unselected.name = getString(R.string.title_select);
|
||||
unselected.primary = false;
|
||||
accounts.add(0, unselected);
|
||||
|
|
|
@ -49,15 +49,12 @@ import androidx.appcompat.app.AlertDialog;
|
|||
import androidx.constraintlayout.widget.Group;
|
||||
|
||||
import com.google.android.material.textfield.TextInputLayout;
|
||||
import com.sun.mail.imap.IMAPFolder;
|
||||
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import javax.mail.AuthenticationFailedException;
|
||||
import javax.mail.Folder;
|
||||
|
||||
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]);
|
||||
Log.i("User type=" + provider.user + " name=" + user);
|
||||
|
||||
List<EntityFolder> folders = new ArrayList<>();
|
||||
long now = new Date().getTime();
|
||||
List<EntityFolder> folders;
|
||||
|
||||
{
|
||||
String protocol = provider.imap.starttls ? "imap" : "imaps";
|
||||
try (MailService iservice = new MailService(context, protocol, null, false, true)) {
|
||||
try {
|
||||
iservice.connect(provider.imap.host, provider.imap.port, user, password);
|
||||
} catch (AuthenticationFailedException ex) {
|
||||
if (user.contains("@")) {
|
||||
Log.w(ex);
|
||||
user = dparts[0];
|
||||
Log.i("Retry with user=" + user);
|
||||
iservice.connect(provider.imap.host, provider.imap.port, user, password);
|
||||
} else
|
||||
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]));
|
||||
String aprotocol = provider.imap.starttls ? "imap" : "imaps";
|
||||
try (MailService iservice = new MailService(context, aprotocol, null, false, true)) {
|
||||
try {
|
||||
iservice.connect(provider.imap.host, provider.imap.port, MailService.AUTH_TYPE_PASSWORD, user, password);
|
||||
} catch (AuthenticationFailedException ex) {
|
||||
if (user.contains("@")) {
|
||||
Log.w(ex);
|
||||
user = dparts[0];
|
||||
Log.i("Retry with user=" + user);
|
||||
iservice.connect(provider.imap.host, provider.imap.port, MailService.AUTH_TYPE_PASSWORD, user, password);
|
||||
} else
|
||||
throw ex;
|
||||
}
|
||||
|
||||
folders = iservice.getFolders();
|
||||
|
||||
if (folders == null)
|
||||
throw new IllegalArgumentException(
|
||||
context.getString(R.string.title_setup_no_settings, dparts[1]));
|
||||
}
|
||||
|
||||
{
|
||||
String protocol = provider.smtp.starttls ? "smtp" : "smtps";
|
||||
try (MailService iservice = new MailService(context, protocol, null, false, true)) {
|
||||
iservice.connect(provider.smtp.host, provider.smtp.port, user, password);
|
||||
}
|
||||
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_PASSWORD, user, password);
|
||||
}
|
||||
|
||||
if (check)
|
||||
|
@ -337,31 +287,26 @@ public class FragmentQuickSetup extends FragmentBase {
|
|||
DB db = DB.getInstance(context);
|
||||
try {
|
||||
db.beginTransaction();
|
||||
|
||||
EntityAccount primary = db.account().getPrimaryAccount();
|
||||
|
||||
// Create account
|
||||
EntityAccount account = new EntityAccount();
|
||||
|
||||
account.auth_type = ConnectionHelper.AUTH_TYPE_PASSWORD;
|
||||
account.host = provider.imap.host;
|
||||
account.starttls = provider.imap.starttls;
|
||||
account.insecure = false;
|
||||
account.port = provider.imap.port;
|
||||
account.auth_type = MailService.AUTH_TYPE_PASSWORD;
|
||||
account.user = user;
|
||||
account.password = password;
|
||||
|
||||
account.name = provider.name;
|
||||
account.color = null;
|
||||
|
||||
account.synchronize = true;
|
||||
account.primary = (primary == null);
|
||||
account.notify = false;
|
||||
account.browse = true;
|
||||
account.poll_interval = EntityAccount.DEFAULT_KEEP_ALIVE_INTERVAL;
|
||||
|
||||
account.created = now;
|
||||
account.error = null;
|
||||
account.last_connected = now;
|
||||
account.created = new Date().getTime();
|
||||
account.last_connected = account.created;
|
||||
|
||||
account.id = db.account().insertAccount(account);
|
||||
EntityLog.log(context, "Quick added account=" + account.name);
|
||||
|
@ -388,27 +333,15 @@ public class FragmentQuickSetup extends FragmentBase {
|
|||
identity.email = email;
|
||||
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.starttls = provider.smtp.starttls;
|
||||
identity.insecure = false;
|
||||
identity.port = provider.smtp.port;
|
||||
identity.auth_type = MailService.AUTH_TYPE_PASSWORD;
|
||||
identity.user = user;
|
||||
identity.password = password;
|
||||
identity.synchronize = 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);
|
||||
EntityLog.log(context, "Quick added identity=" + identity.name + " email=" + identity.email);
|
||||
|
||||
|
|
|
@ -20,6 +20,12 @@ package eu.faircode.email;
|
|||
*/
|
||||
|
||||
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.content.ComponentName;
|
||||
import android.content.Context;
|
||||
|
@ -27,12 +33,14 @@ import android.content.DialogInterface;
|
|||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.database.Cursor;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.PowerManager;
|
||||
import android.provider.ContactsContract;
|
||||
import android.provider.Settings;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
|
@ -45,13 +53,20 @@ import androidx.annotation.NonNull;
|
|||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.constraintlayout.widget.Group;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.lifecycle.Lifecycle;
|
||||
import androidx.lifecycle.Observer;
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||
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 static android.accounts.AccountManager.newChooseAccountIntent;
|
||||
|
||||
public class FragmentSetup extends FragmentBase {
|
||||
private ViewGroup view;
|
||||
|
||||
|
@ -60,6 +75,7 @@ public class FragmentSetup extends FragmentBase {
|
|||
|
||||
private Button btnHelp;
|
||||
private Button btnQuick;
|
||||
private Button btnGmail;
|
||||
|
||||
private TextView tvAccountDone;
|
||||
private Button btnAccount;
|
||||
|
@ -87,10 +103,6 @@ public class FragmentSetup extends FragmentBase {
|
|||
private int colorWarning;
|
||||
private Drawable check;
|
||||
|
||||
private static final String[] permissions = new String[]{
|
||||
Manifest.permission.READ_CONTACTS
|
||||
};
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
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);
|
||||
btnQuick = view.findViewById(R.id.btnQuick);
|
||||
btnGmail = view.findViewById(R.id.btnGmail);
|
||||
|
||||
tvAccountDone = view.findViewById(R.id.tvAccountDone);
|
||||
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() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
|
@ -186,7 +221,8 @@ public class FragmentSetup extends FragmentBase {
|
|||
@Override
|
||||
public void onClick(View view) {
|
||||
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
|
||||
btnGmail.setVisibility(Helper.hasValidFingerprint(getContext()) ? View.VISIBLE : View.GONE);
|
||||
|
||||
tvAccountDone.setText(null);
|
||||
tvAccountDone.setCompoundDrawables(null, null, null, null);
|
||||
tvNoPrimaryDrafts.setVisibility(View.GONE);
|
||||
|
@ -248,11 +286,7 @@ public class FragmentSetup extends FragmentBase {
|
|||
grpWelcome.setVisibility(welcome ? View.VISIBLE : View.GONE);
|
||||
grpDataSaver.setVisibility(View.GONE);
|
||||
|
||||
int[] grantResults = new int[permissions.length];
|
||||
for (int i = 0; i < permissions.length; i++)
|
||||
grantResults[i] = ContextCompat.checkSelfPermission(getActivity(), permissions[i]);
|
||||
|
||||
checkPermissions(permissions, grantResults, true);
|
||||
setContactsPermission(hasPermission(Manifest.permission.READ_CONTACTS));
|
||||
|
||||
// Create outbox
|
||||
new SimpleTask<Void>() {
|
||||
|
@ -392,25 +426,227 @@ public class FragmentSetup extends FragmentBase {
|
|||
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
||||
if (requestCode == ActivitySetup.REQUEST_PERMISSION)
|
||||
checkPermissions(permissions, grantResults, false);
|
||||
boolean granted = true;
|
||||
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) {
|
||||
boolean has = (grantResults.length > 0);
|
||||
for (int result : grantResults)
|
||||
if (result != PackageManager.PERMISSION_GRANTED) {
|
||||
has = false;
|
||||
break;
|
||||
}
|
||||
|
||||
if (has)
|
||||
private void setContactsPermission(boolean granted) {
|
||||
if (granted)
|
||||
ContactInfo.init(getContext());
|
||||
|
||||
tvPermissionsDone.setText(has ? R.string.title_setup_done : R.string.title_setup_to_do);
|
||||
tvPermissionsDone.setTextColor(has ? textColorPrimary : colorWarning);
|
||||
tvPermissionsDone.setCompoundDrawablesWithIntrinsicBounds(has ? check : null, null, null, null);
|
||||
btnPermissions.setEnabled(!has);
|
||||
tvPermissionsDone.setText(granted ? R.string.title_setup_done : R.string.title_setup_to_do);
|
||||
tvPermissionsDone.setTextColor(granted ? textColorPrimary : colorWarning);
|
||||
tvPermissionsDone.setCompoundDrawablesWithIntrinsicBounds(granted ? check : null, null, null, null);
|
||||
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 {
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
package eu.faircode.email;
|
||||
|
||||
import android.accounts.Account;
|
||||
import android.accounts.AccountManager;
|
||||
import android.content.Context;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.sun.mail.imap.IMAPFolder;
|
||||
import com.sun.mail.imap.IMAPStore;
|
||||
import com.sun.mail.smtp.SMTPTransport;
|
||||
import com.sun.mail.util.MailConnectException;
|
||||
|
@ -10,13 +14,17 @@ import java.net.Inet4Address;
|
|||
import java.net.Inet6Address;
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import javax.mail.AuthenticationFailedException;
|
||||
import javax.mail.Folder;
|
||||
import javax.mail.MessagingException;
|
||||
import javax.mail.NoSuchProviderException;
|
||||
import javax.mail.Service;
|
||||
|
@ -33,6 +41,9 @@ public class MailService implements AutoCloseable {
|
|||
|
||||
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 WRITE_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 {
|
||||
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 {
|
||||
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 {
|
||||
if (auth == AUTH_TYPE_GMAIL)
|
||||
properties.put("mail." + protocol + ".auth.mechanisms", "XOAUTH2");
|
||||
|
||||
//if (BuildConfig.DEBUG)
|
||||
// throw new MailConnectException(new SocketConnectException("Debug", new Exception(), host, port, 0));
|
||||
|
||||
_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) {
|
||||
try {
|
||||
// Some devices resolve IPv6 addresses while not having IPv6 connectivity
|
||||
|
@ -213,6 +262,63 @@ public class MailService implements AutoCloseable {
|
|||
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() {
|
||||
return (IMAPStore) iservice;
|
||||
}
|
||||
|
|
|
@ -105,6 +105,17 @@
|
|||
app:layout_constraintEnd_toEndOf="parent"
|
||||
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 -->
|
||||
|
||||
<View
|
||||
|
@ -114,7 +125,7 @@
|
|||
android:layout_marginTop="12dp"
|
||||
android:background="?attr/colorSeparator"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/btnQuick" />
|
||||
app:layout_constraintTop_toBottomOf="@id/btnGmail" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/one"
|
||||
|
|
|
@ -134,20 +134,6 @@
|
|||
app:layout_constraintStart_toEndOf="@+id/ivState"
|
||||
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
|
||||
android:id="@+id/tvIdentity"
|
||||
android:layout_width="0dp"
|
||||
|
@ -160,7 +146,7 @@
|
|||
android:textIsSelectable="true"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/vwColor"
|
||||
app:layout_constraintTop_toBottomOf="@id/tvAuthorize" />
|
||||
app:layout_constraintTop_toBottomOf="@id/tvLast" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvDrafts"
|
||||
|
|
|
@ -136,20 +136,6 @@
|
|||
app:layout_constraintStart_toEndOf="@+id/ivState"
|
||||
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
|
||||
android:id="@+id/tvError"
|
||||
android:layout_width="0dp"
|
||||
|
@ -162,7 +148,7 @@
|
|||
android:textIsSelectable="true"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/vwColor"
|
||||
app:layout_constraintTop_toBottomOf="@id/tvAuthorize" />
|
||||
app:layout_constraintTop_toBottomOf="@id/tvLast" />
|
||||
|
||||
<View
|
||||
android:id="@+id/marginBottom"
|
||||
|
|
|
@ -135,6 +135,8 @@
|
|||
<string name="title_setup_quick_smtp">SMTP server to send messages</string>
|
||||
<string name="title_setup_go">Go</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_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>
|
||||
|
@ -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_utf8">This provider does not support UTF-8</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_drafts_required">A drafts folder is required to send messages</string>
|
||||
<string name="title_account_delete">Delete this account permanently?</string>
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
<providers>
|
||||
<provider
|
||||
name="Gmail"
|
||||
domain="gmail.com"
|
||||
link="https://github.com/M66B/FairEmail/blob/master/FAQ.md#user-content-faq6"
|
||||
order="1"
|
||||
type="com.google">
|
||||
|
|
Loading…
Reference in a new issue