Simple email application for Android. Original source code: https://framagit.org/dystopia-project/simple-email
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

465 lines
20 KiB

6 years ago
6 years ago
6 years ago
  1. package eu.faircode.email;
  2. /*
  3. This file is part of FairEmail.
  4. FairEmail is free software: you can redistribute it and/or modify
  5. it under the terms of the GNU General Public License as published by
  6. the Free Software Foundation, either version 3 of the License, or
  7. (at your option) any later version.
  8. NetGuard is distributed in the hope that it will be useful,
  9. but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. GNU General Public License for more details.
  12. You should have received a copy of the GNU General Public License
  13. along with NetGuard. If not, see <http://www.gnu.org/licenses/>.
  14. Copyright 2018 by Marcel Bokhorst (M66B)
  15. */
  16. import android.content.Context;
  17. import android.content.DialogInterface;
  18. import android.os.Bundle;
  19. import android.text.Editable;
  20. import android.text.TextUtils;
  21. import android.text.TextWatcher;
  22. import android.view.LayoutInflater;
  23. import android.view.View;
  24. import android.view.ViewGroup;
  25. import android.widget.AdapterView;
  26. import android.widget.ArrayAdapter;
  27. import android.widget.Button;
  28. import android.widget.CheckBox;
  29. import android.widget.CompoundButton;
  30. import android.widget.EditText;
  31. import android.widget.ImageButton;
  32. import android.widget.ProgressBar;
  33. import android.widget.Spinner;
  34. import android.widget.Toast;
  35. import com.google.android.material.textfield.TextInputLayout;
  36. import java.util.ArrayList;
  37. import java.util.List;
  38. import java.util.Properties;
  39. import javax.mail.Session;
  40. import javax.mail.Transport;
  41. import androidx.annotation.NonNull;
  42. import androidx.annotation.Nullable;
  43. import androidx.appcompat.app.AlertDialog;
  44. import androidx.lifecycle.Observer;
  45. public class FragmentIdentity extends FragmentEx {
  46. private ViewGroup view;
  47. private EditText etName;
  48. private EditText etEmail;
  49. private EditText etReplyTo;
  50. private Spinner spProvider;
  51. private Spinner spAccount;
  52. private EditText etHost;
  53. private CheckBox cbStartTls;
  54. private EditText etPort;
  55. private EditText etUser;
  56. private TextInputLayout tilPassword;
  57. private CheckBox cbSynchronize;
  58. private CheckBox cbPrimary;
  59. private Button btnSave;
  60. private ProgressBar pbSave;
  61. private ImageButton ibDelete;
  62. private ProgressBar pbWait;
  63. @Override
  64. @Nullable
  65. public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
  66. setSubtitle(R.string.title_edit_identity);
  67. view = (ViewGroup) inflater.inflate(R.layout.fragment_identity, container, false);
  68. // Get arguments
  69. Bundle args = getArguments();
  70. final long id = (args == null ? -1 : args.getLong("id", -1));
  71. // Get controls
  72. etName = view.findViewById(R.id.etName);
  73. etEmail = view.findViewById(R.id.etEmail);
  74. etReplyTo = view.findViewById(R.id.etReplyTo);
  75. spProvider = view.findViewById(R.id.spProvider);
  76. spAccount = view.findViewById(R.id.spAccount);
  77. etHost = view.findViewById(R.id.etHost);
  78. cbStartTls = view.findViewById(R.id.cbStartTls);
  79. etPort = view.findViewById(R.id.etPort);
  80. etUser = view.findViewById(R.id.etUser);
  81. tilPassword = view.findViewById(R.id.tilPassword);
  82. cbSynchronize = view.findViewById(R.id.cbSynchronize);
  83. cbPrimary = view.findViewById(R.id.cbPrimary);
  84. btnSave = view.findViewById(R.id.btnSave);
  85. pbSave = view.findViewById(R.id.pbSave);
  86. ibDelete = view.findViewById(R.id.ibDelete);
  87. pbWait = view.findViewById(R.id.pbWait);
  88. // Wire controls
  89. etEmail.addTextChangedListener(new TextWatcher() {
  90. @Override
  91. public void beforeTextChanged(CharSequence s, int start, int count, int after) {
  92. }
  93. @Override
  94. public void onTextChanged(CharSequence s, int start, int before, int count) {
  95. }
  96. @Override
  97. public void afterTextChanged(Editable s) {
  98. etUser.setText(s.toString());
  99. }
  100. });
  101. spAccount.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
  102. @Override
  103. public void onItemSelected(AdapterView<?> adapterView, View view, int position, long id) {
  104. Integer tag = (Integer) adapterView.getTag();
  105. if (tag != null && tag.equals(position))
  106. return;
  107. adapterView.setTag(position);
  108. EntityAccount account = (EntityAccount) adapterView.getAdapter().getItem(position);
  109. for (int pos = 1; pos < spProvider.getAdapter().getCount(); pos++) {
  110. Provider provider = (Provider) spProvider.getItemAtPosition(pos);
  111. if (provider.imap_host.equals(account.host) && provider.imap_port == account.port) {
  112. spProvider.setSelection(pos);
  113. break;
  114. }
  115. }
  116. if (position > 0 && TextUtils.isEmpty(etUser.getText()))
  117. etUser.setText(account.user);
  118. if (position > 0 && TextUtils.isEmpty(tilPassword.getEditText().getText())) {
  119. tilPassword.getEditText().setText(account.password);
  120. tilPassword.setPasswordVisibilityToggleEnabled(false);
  121. }
  122. }
  123. @Override
  124. public void onNothingSelected(AdapterView<?> adapterView) {
  125. }
  126. });
  127. spProvider.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
  128. @Override
  129. public void onItemSelected(AdapterView<?> adapterView, View view, int position, long id) {
  130. Integer tag = (Integer) adapterView.getTag();
  131. if (tag != null && tag.equals(position))
  132. return;
  133. adapterView.setTag(position);
  134. Provider provider = (Provider) adapterView.getSelectedItem();
  135. if (provider.smtp_port != 0) {
  136. etHost.setText(provider.smtp_host);
  137. etPort.setText(Integer.toString(provider.smtp_port));
  138. cbStartTls.setChecked(provider.starttls);
  139. }
  140. }
  141. @Override
  142. public void onNothingSelected(AdapterView<?> adapterView) {
  143. }
  144. });
  145. cbStartTls.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
  146. @Override
  147. public void onCheckedChanged(CompoundButton compoundButton, boolean checked) {
  148. etPort.setHint(checked ? "587" : "465");
  149. }
  150. });
  151. cbSynchronize.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
  152. @Override
  153. public void onCheckedChanged(CompoundButton compoundButton, boolean checked) {
  154. cbPrimary.setEnabled(checked);
  155. }
  156. });
  157. btnSave.setOnClickListener(new View.OnClickListener() {
  158. @Override
  159. public void onClick(View v) {
  160. Helper.setViewsEnabled(view, false);
  161. btnSave.setEnabled(false);
  162. pbSave.setVisibility(View.VISIBLE);
  163. EntityAccount account = (EntityAccount) spAccount.getSelectedItem();
  164. Bundle args = new Bundle();
  165. args.putLong("id", id);
  166. args.putString("name", etName.getText().toString());
  167. args.putString("email", etEmail.getText().toString());
  168. args.putString("replyto", etReplyTo.getText().toString());
  169. args.putLong("account", account == null ? -1 : account.id);
  170. args.putString("host", etHost.getText().toString());
  171. args.putBoolean("starttls", cbStartTls.isChecked());
  172. args.putString("port", etPort.getText().toString());
  173. args.putString("user", etUser.getText().toString());
  174. args.putString("password", tilPassword.getEditText().getText().toString());
  175. args.putBoolean("synchronize", cbSynchronize.isChecked());
  176. args.putBoolean("primary", cbPrimary.isChecked());
  177. new SimpleTask<Void>() {
  178. @Override
  179. protected Void onLoad(Context context, Bundle args) throws Throwable {
  180. long id = args.getLong("id");
  181. String name = args.getString("name");
  182. String email = args.getString("email");
  183. String replyto = args.getString("replyto");
  184. long account = args.getLong("account");
  185. String host = args.getString("host");
  186. boolean starttls = args.getBoolean("starttls");
  187. String port = args.getString("port");
  188. String user = args.getString("user");
  189. String password = args.getString("password");
  190. boolean synchronize = args.getBoolean("synchronize");
  191. if (TextUtils.isEmpty(name))
  192. throw new IllegalArgumentException(getContext().getString(R.string.title_no_name));
  193. if (TextUtils.isEmpty(email))
  194. throw new IllegalArgumentException(getContext().getString(R.string.title_no_email));
  195. if (account < 0)
  196. throw new IllegalArgumentException(getContext().getString(R.string.title_no_account));
  197. if (TextUtils.isEmpty(host))
  198. throw new IllegalArgumentException(getContext().getString(R.string.title_no_host));
  199. if (TextUtils.isEmpty(port))
  200. throw new IllegalArgumentException(getContext().getString(R.string.title_no_port));
  201. if (TextUtils.isEmpty(user))
  202. throw new IllegalArgumentException(getContext().getString(R.string.title_no_user));
  203. if (TextUtils.isEmpty(password))
  204. throw new IllegalArgumentException(getContext().getString(R.string.title_no_password));
  205. if (TextUtils.isEmpty(replyto))
  206. replyto = null;
  207. // Check SMTP server
  208. if (synchronize) {
  209. Properties props = MessageHelper.getSessionProperties();
  210. Session isession = Session.getInstance(props, null);
  211. Transport itransport = isession.getTransport(starttls ? "smtp" : "smtps");
  212. try {
  213. itransport.connect(host, Integer.parseInt(port), user, password);
  214. } finally {
  215. itransport.close();
  216. }
  217. }
  218. DB db = DB.getInstance(getContext());
  219. try {
  220. db.beginTransaction();
  221. EntityIdentity identity = db.identity().getIdentity(id);
  222. boolean update = (identity != null);
  223. if (identity == null)
  224. identity = new EntityIdentity();
  225. identity.name = name;
  226. identity.email = email;
  227. identity.replyto = replyto;
  228. identity.account = account;
  229. identity.host = host;
  230. identity.port = Integer.parseInt(port);
  231. identity.starttls = starttls;
  232. identity.user = user;
  233. identity.password = password;
  234. identity.synchronize = synchronize;
  235. identity.primary = (identity.synchronize && args.getBoolean("primary"));
  236. if (!identity.synchronize)
  237. identity.error = null;
  238. if (identity.primary)
  239. db.identity().resetPrimary();
  240. if (update)
  241. db.identity().updateIdentity(identity);
  242. else
  243. identity.id = db.identity().insertIdentity(identity);
  244. db.setTransactionSuccessful();
  245. } finally {
  246. db.endTransaction();
  247. }
  248. ServiceSynchronize.reload(getContext(), "save identity");
  249. return null;
  250. }
  251. @Override
  252. protected void onLoaded(Bundle args, Void data) {
  253. getFragmentManager().popBackStack();
  254. }
  255. @Override
  256. protected void onException(Bundle args, Throwable ex) {
  257. Helper.setViewsEnabled(view, true);
  258. btnSave.setEnabled(true);
  259. pbSave.setVisibility(View.GONE);
  260. Toast.makeText(getContext(), Helper.formatThrowable(ex), Toast.LENGTH_LONG).show();
  261. }
  262. }.load(FragmentIdentity.this, args);
  263. }
  264. });
  265. ibDelete.setOnClickListener(new View.OnClickListener() {
  266. @Override
  267. public void onClick(View v) {
  268. AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
  269. builder
  270. .setMessage(R.string.title_identity_delete)
  271. .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
  272. @Override
  273. public void onClick(DialogInterface dialog, int which) {
  274. Helper.setViewsEnabled(view, false);
  275. btnSave.setEnabled(false);
  276. pbWait.setVisibility(View.VISIBLE);
  277. Bundle args = new Bundle();
  278. args.putLong("id", id);
  279. new SimpleTask<Void>() {
  280. @Override
  281. protected Void onLoad(Context context, Bundle args) {
  282. long id = args.getLong("id");
  283. DB.getInstance(context).identity().deleteIdentity(id);
  284. ServiceSynchronize.reload(getContext(), "delete identity");
  285. return null;
  286. }
  287. @Override
  288. protected void onLoaded(Bundle args, Void data) {
  289. getFragmentManager().popBackStack();
  290. }
  291. @Override
  292. protected void onException(Bundle args, Throwable ex) {
  293. Toast.makeText(getContext(), ex.toString(), Toast.LENGTH_LONG).show();
  294. // TODO: recover from error
  295. }
  296. }.load(FragmentIdentity.this, args);
  297. }
  298. })
  299. .setNegativeButton(android.R.string.cancel, null).show();
  300. }
  301. });
  302. // Initialize
  303. Helper.setViewsEnabled(view, false);
  304. tilPassword.setPasswordVisibilityToggleEnabled(id < 0);
  305. btnSave.setEnabled(false);
  306. pbSave.setVisibility(View.GONE);
  307. ibDelete.setVisibility(View.GONE);
  308. return view;
  309. }
  310. @Override
  311. public void onSaveInstanceState(Bundle outState) {
  312. super.onSaveInstanceState(outState);
  313. outState.putInt("account", spAccount.getSelectedItemPosition());
  314. outState.putInt("provider", spProvider.getSelectedItemPosition());
  315. outState.putString("password", tilPassword.getEditText().getText().toString());
  316. }
  317. @Override
  318. public void onActivityCreated(@Nullable final Bundle savedInstanceState) {
  319. super.onActivityCreated(savedInstanceState);
  320. // Get arguments
  321. Bundle args = getArguments();
  322. long id = (args == null ? -1 : args.getLong("id", -1));
  323. final DB db = DB.getInstance(getContext());
  324. // Observe identity
  325. db.identity().liveIdentity(id).observe(getViewLifecycleOwner(), new Observer<EntityIdentity>() {
  326. boolean once = false;
  327. @Override
  328. public void onChanged(@Nullable final EntityIdentity identity) {
  329. if (savedInstanceState == null) {
  330. if (once)
  331. return;
  332. once = true;
  333. etName.setText(identity == null ? null : identity.name);
  334. etEmail.setText(identity == null ? null : identity.email);
  335. etReplyTo.setText(identity == null ? null : identity.replyto);
  336. etHost.setText(identity == null ? null : identity.host);
  337. cbStartTls.setChecked(identity == null ? false : identity.starttls);
  338. etPort.setText(identity == null ? null : Long.toString(identity.port));
  339. etUser.setText(identity == null ? null : identity.user);
  340. tilPassword.getEditText().setText(identity == null ? null : identity.password);
  341. cbSynchronize.setChecked(identity == null ? true : identity.synchronize);
  342. cbPrimary.setChecked(identity == null ? true : identity.primary);
  343. etName.requestFocus();
  344. } else
  345. tilPassword.getEditText().setText(savedInstanceState.getString("password"));
  346. Helper.setViewsEnabled(view, true);
  347. cbPrimary.setEnabled(cbSynchronize.isChecked());
  348. // Consider previous save/delete as cancelled
  349. ibDelete.setVisibility(identity == null ? View.GONE : View.VISIBLE);
  350. btnSave.setEnabled(true);
  351. pbWait.setVisibility(View.GONE);
  352. db.account().liveAccounts().removeObservers(getViewLifecycleOwner());
  353. db.account().liveAccounts().observe(getViewLifecycleOwner(), new Observer<List<EntityAccount>>() {
  354. @Override
  355. public void onChanged(List<EntityAccount> accounts) {
  356. if (accounts == null)
  357. accounts = new ArrayList<>();
  358. EntityAccount unselected = new EntityAccount();
  359. unselected.id = -1L;
  360. unselected.name = "";
  361. unselected.primary = false;
  362. accounts.add(0, unselected);
  363. ArrayAdapter<EntityAccount> aa = new ArrayAdapter<>(getContext(), R.layout.spinner_item, accounts);
  364. aa.setDropDownViewResource(R.layout.spinner_dropdown_item);
  365. spAccount.setAdapter(aa);
  366. // Get providers
  367. List<Provider> providers = Provider.loadProfiles(getContext());
  368. providers.add(0, new Provider(getString(R.string.title_custom)));
  369. ArrayAdapter<Provider> adapterProfile = new ArrayAdapter<>(getContext(), R.layout.spinner_item, providers);
  370. adapterProfile.setDropDownViewResource(R.layout.spinner_dropdown_item);
  371. spProvider.setAdapter(adapterProfile);
  372. if (savedInstanceState == null) {
  373. for (int pos = 0; pos < accounts.size(); pos++)
  374. if (accounts.get(pos).id == (identity == null ? -1 : identity.account)) {
  375. spAccount.setSelection(pos);
  376. break;
  377. }
  378. } else {
  379. int provider = savedInstanceState.getInt("provider");
  380. spProvider.setTag(provider);
  381. spProvider.setSelection(provider);
  382. int account = savedInstanceState.getInt("account");
  383. spAccount.setTag(account);
  384. spAccount.setSelection(account);
  385. }
  386. }
  387. });
  388. }
  389. });
  390. }
  391. }