mirror of
https://github.com/M66B/FairEmail.git
synced 2026-01-29 19:17:24 +01:00
Compare commits
1475 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dcbf5730ef | ||
|
|
3c6e8685eb | ||
|
|
b970144285 | ||
|
|
95154e2c09 | ||
|
|
47da32aa97 | ||
|
|
e09bd95cc6 | ||
|
|
e94f07bba3 | ||
|
|
083fdb2b3b | ||
|
|
601be58d56 | ||
|
|
a7a2322913 | ||
|
|
3ccdc904c4 | ||
|
|
0a583b7a01 | ||
|
|
8073cf425e | ||
|
|
2f6c0d37d6 | ||
|
|
257776f68a | ||
|
|
626d301eec | ||
|
|
a4918e0e94 | ||
|
|
c7597ec2a8 | ||
|
|
7a49a7dd89 | ||
|
|
9b4493d356 | ||
|
|
23a3c72a69 | ||
|
|
321daabd0e | ||
|
|
361e9f68b3 | ||
|
|
eb405aaded | ||
|
|
ddae17a8d2 | ||
|
|
4e0bd7a925 | ||
|
|
1465fcfdd9 | ||
|
|
796a80078b | ||
|
|
43915d7a0a | ||
|
|
c8ca1a29aa | ||
|
|
ac49c712b3 | ||
|
|
b39ef5ba48 | ||
|
|
37097ecfbb | ||
|
|
520ec2d714 | ||
|
|
34b738f0fa | ||
|
|
6af420cf4e | ||
|
|
6800c38893 | ||
|
|
c2f2afe0f5 | ||
|
|
ac71c03f21 | ||
|
|
e642c378f2 | ||
|
|
d6b9ceb388 | ||
|
|
a20524199b | ||
|
|
4afc7f659b | ||
|
|
228e6b4dc9 | ||
|
|
ef56231855 | ||
|
|
c3b06035b4 | ||
|
|
d8dbe8b7c7 | ||
|
|
c2834f3352 | ||
|
|
68ce09107b | ||
|
|
9e3ea2818f | ||
|
|
0a0dbac11c | ||
|
|
f3c070b76c | ||
|
|
8892c58a79 | ||
|
|
5fb69d269d | ||
|
|
267a845ac9 | ||
|
|
41bb207724 | ||
|
|
508fd8fbba | ||
|
|
060e1e3dfa | ||
|
|
8767aed899 | ||
|
|
3185166d32 | ||
|
|
c54a4bfc25 | ||
|
|
3645a8c684 | ||
|
|
2274409b62 | ||
|
|
bebc65e8de | ||
|
|
b16c07918c | ||
|
|
bfc79ff1c4 | ||
|
|
ad31094cc9 | ||
|
|
6522b711dd | ||
|
|
12f05b9edd | ||
|
|
f60d15a02b | ||
|
|
72b58a5167 | ||
|
|
b68aa65826 | ||
|
|
a2b484d4a5 | ||
|
|
e2c46f978e | ||
|
|
3b14f58d32 | ||
|
|
73e4f2c4f9 | ||
|
|
eeda538546 | ||
|
|
c473db6239 | ||
|
|
fcd8bc6700 | ||
|
|
6b34b64d10 | ||
|
|
2d76845fe0 | ||
|
|
dfed6cd8b0 | ||
|
|
08d4d9e851 | ||
|
|
3cb8ffd0cd | ||
|
|
1053ba3ac1 | ||
|
|
a49f20db83 | ||
|
|
7c039c28cb | ||
|
|
c887268d63 | ||
|
|
1ad850ae89 | ||
|
|
2eb51d2189 | ||
|
|
0ae44923e9 | ||
|
|
f59adf17f5 | ||
|
|
952d93bf17 | ||
|
|
07d0b30816 | ||
|
|
ec5709bdc8 | ||
|
|
a4d212fa6d | ||
|
|
16942e2487 | ||
|
|
6b7b8e50b8 | ||
|
|
f20ebe1183 | ||
|
|
68d03fac18 | ||
|
|
d2d20dd6ee | ||
|
|
8b3edfe1fe | ||
|
|
69fadd295d | ||
|
|
8a5b5ec577 | ||
|
|
d8701a69a3 | ||
|
|
c72e752a43 | ||
|
|
bcb165db59 | ||
|
|
f74c4c460f | ||
|
|
3930428969 | ||
|
|
316ed410a4 | ||
|
|
a0d37c9919 | ||
|
|
e386a405ae | ||
|
|
6f47a2e22c | ||
|
|
2fda4ebd35 | ||
|
|
d89b6f86bc | ||
|
|
b5613e46a6 | ||
|
|
59b66ecd23 | ||
|
|
320e4e882a | ||
|
|
f18402ef14 | ||
|
|
4c7345abb4 | ||
|
|
b0110efea2 | ||
|
|
a1f0418d03 | ||
|
|
59e3555bf7 | ||
|
|
9c3c999ae2 | ||
|
|
f83585a753 | ||
|
|
d98298160d | ||
|
|
0fa78c3e66 | ||
|
|
f0456e1a41 | ||
|
|
b9411b82d6 | ||
|
|
fe2a6c5e25 | ||
|
|
85fa83faf8 | ||
|
|
0606dac914 | ||
|
|
5f71c31357 | ||
|
|
496c1215e9 | ||
|
|
62587b966d | ||
|
|
6934feda1a | ||
|
|
aded278004 | ||
|
|
98593432c4 | ||
|
|
52443b3a7a | ||
|
|
6957a15c0a | ||
|
|
d60ff43ed4 | ||
|
|
279c1eeacf | ||
|
|
9883a82cd7 | ||
|
|
a9a5ca6077 | ||
|
|
f2233cf9e9 | ||
|
|
b1f485c575 | ||
|
|
f8397ea8b7 | ||
|
|
f6a0e908e7 | ||
|
|
0ce5f6bf57 | ||
|
|
3b51d9ce64 | ||
|
|
0affd9e1f3 | ||
|
|
d9dd78e3a7 | ||
|
|
928119b4e9 | ||
|
|
849bfc4c56 | ||
|
|
133a1974d5 | ||
|
|
01a205e7fa | ||
|
|
5bbb1b081a | ||
|
|
1f5e98ca16 | ||
|
|
d99f19f2b6 | ||
|
|
f544f485a8 | ||
|
|
0dcbc24ca5 | ||
|
|
2c4c91f7cf | ||
|
|
954c8db225 | ||
|
|
eaf3ea4a7c | ||
|
|
773b5660ab | ||
|
|
2e626fd5e9 | ||
|
|
5cb43e07ee | ||
|
|
e6da47140d | ||
|
|
a8b0c4481d | ||
|
|
f1b904b75b | ||
|
|
1ed78da2a0 | ||
|
|
c4ebff4c09 | ||
|
|
d768ea3db9 | ||
|
|
22cb48be11 | ||
|
|
1b024566fd | ||
|
|
8c47a34624 | ||
|
|
0d62c69f7c | ||
|
|
c92878d6b9 | ||
|
|
dd9d50ff13 | ||
|
|
4363218b7b | ||
|
|
99412f294f | ||
|
|
af028c3daa | ||
|
|
f1acfd1ac8 | ||
|
|
b7d2406cad | ||
|
|
5a38cf523a | ||
|
|
2d80959e37 | ||
|
|
2956b39cfe | ||
|
|
bfac316768 | ||
|
|
48a7a714ad | ||
|
|
f6f43ef575 | ||
|
|
3687a632f6 | ||
|
|
381e0b2052 | ||
|
|
767b8ea00e | ||
|
|
53cdce5fa9 | ||
|
|
0688d3ec63 | ||
|
|
deab3432b5 | ||
|
|
78e9ed5dd9 | ||
|
|
ed35dddceb | ||
|
|
a18767e9e8 | ||
|
|
9656401794 | ||
|
|
531fcfeb2f | ||
|
|
50697e66f6 | ||
|
|
7db054342d | ||
|
|
4dec43ae27 | ||
|
|
45a9f8cc5f | ||
|
|
a947db12a9 | ||
|
|
c15302c453 | ||
|
|
d488d8a8f5 | ||
|
|
5d9c57db3c | ||
|
|
c60953a9e0 | ||
|
|
ba0d63e5d0 | ||
|
|
c036c686a6 | ||
|
|
ae5cbb3ea5 | ||
|
|
2c63a12e0a | ||
|
|
e263ee43f6 | ||
|
|
ea0b69f46b | ||
|
|
118bf99d34 | ||
|
|
410170d6d6 | ||
|
|
3e88be112d | ||
|
|
c3d41e6a9a | ||
|
|
a317e804c1 | ||
|
|
304794c677 | ||
|
|
8a8ca574cd | ||
|
|
9be0e5c788 | ||
|
|
c55cafa5d0 | ||
|
|
9f1f446210 | ||
|
|
abbd89eece | ||
|
|
55a4fe2549 | ||
|
|
b01c75679b | ||
|
|
d6ba051e85 | ||
|
|
45379870b1 | ||
|
|
7b3d0a106b | ||
|
|
1beea3e88d | ||
|
|
1fcfee1d3f | ||
|
|
ac11efb0a0 | ||
|
|
027e7d8698 | ||
|
|
04dbed7e8d | ||
|
|
55f5faf794 | ||
|
|
98a99417ad | ||
|
|
657ce04251 | ||
|
|
abfa22c348 | ||
|
|
661f86fe67 | ||
|
|
39293c4143 | ||
|
|
b9253671a1 | ||
|
|
834e84273b | ||
|
|
03f7f4271b | ||
|
|
95a5b10e6c | ||
|
|
519e617dc7 | ||
|
|
86c0638fa0 | ||
|
|
17aa81ccaf | ||
|
|
3e58d22dc6 | ||
|
|
f661a7c09b | ||
|
|
4a72f7ff90 | ||
|
|
d7d00556b9 | ||
|
|
f7ed05b40c | ||
|
|
41dfa81fb1 | ||
|
|
9ab39ea3ea | ||
|
|
36bfa673e6 | ||
|
|
b2b48be05c | ||
|
|
1d3cd1d44f | ||
|
|
d02e246189 | ||
|
|
5f5d219b71 | ||
|
|
8fc3813961 | ||
|
|
78c83b1f35 | ||
|
|
0191240a27 | ||
|
|
9d234aa9a4 | ||
|
|
44a8381290 | ||
|
|
df706f36ec | ||
|
|
13882be6d0 | ||
|
|
c102711570 | ||
|
|
0b53989826 | ||
|
|
afbfd20b9b | ||
|
|
40b8086ba6 | ||
|
|
3e80a86726 | ||
|
|
828d86e86f | ||
|
|
2b8ec3d342 | ||
|
|
7618845a39 | ||
|
|
0421ecb5c7 | ||
|
|
c8ed3a4a33 | ||
|
|
55627735bd | ||
|
|
805b5601fd | ||
|
|
04f2c3f367 | ||
|
|
7fd52db273 | ||
|
|
c6a56f8040 | ||
|
|
39e4eebdb8 | ||
|
|
490cb6cc80 | ||
|
|
4c433674e3 | ||
|
|
a5dc0a3081 | ||
|
|
1e30425bc3 | ||
|
|
f5ca9277c3 | ||
|
|
4d3f19c5b3 | ||
|
|
de963c56b7 | ||
|
|
c4e29a9539 | ||
|
|
6d7e034ad4 | ||
|
|
3e080b9706 | ||
|
|
4d2755e17c | ||
|
|
d7377f0f0f | ||
|
|
8ba3d39cff | ||
|
|
680149b63d | ||
|
|
748d959a7d | ||
|
|
92332a7c78 | ||
|
|
58943f1291 | ||
|
|
26f5e0d764 | ||
|
|
10cd919172 | ||
|
|
1c13bc29a9 | ||
|
|
d43f7a170f | ||
|
|
120a837c32 | ||
|
|
ceee4166de | ||
|
|
e046e0f7cf | ||
|
|
e0d35792b2 | ||
|
|
ccb6c259a4 | ||
|
|
906d98a14f | ||
|
|
ac4e904d29 | ||
|
|
9cbda608dc | ||
|
|
efb2107fd1 | ||
|
|
687fc722e5 | ||
|
|
7fcf1da77a | ||
|
|
7c41153bad | ||
|
|
db0b6fcd45 | ||
|
|
7969833e2c | ||
|
|
59e4c797ea | ||
|
|
e0a446a8a9 | ||
|
|
f2c454a744 | ||
|
|
0042c5e691 | ||
|
|
cf785de592 | ||
|
|
40cba0f42b | ||
|
|
d8b398cf27 | ||
|
|
7e94319498 | ||
|
|
44416cfa20 | ||
|
|
861c6da4b9 | ||
|
|
ab50d9db77 | ||
|
|
968c6800d3 | ||
|
|
7fcae32ab2 | ||
|
|
cbd1f119dd | ||
|
|
e65c5c44ec | ||
|
|
24cdaf213a | ||
|
|
3535908520 | ||
|
|
c40beae0fa | ||
|
|
a0c2e41adb | ||
|
|
0be109a816 | ||
|
|
6040b420b8 | ||
|
|
f77b257312 | ||
|
|
3be40ef641 | ||
|
|
752aee0a3b | ||
|
|
989bdb1cb9 | ||
|
|
1ac7f26dae | ||
|
|
13896fc72a | ||
|
|
b306bd1eff | ||
|
|
e02c891db9 | ||
|
|
b6047488d0 | ||
|
|
1f2074a26f | ||
|
|
00400d7ef6 | ||
|
|
6104ecfadb | ||
|
|
f8665c8526 | ||
|
|
9d01825146 | ||
|
|
cab37caac9 | ||
|
|
ca6827c266 | ||
|
|
0a212e93af | ||
|
|
8210434891 | ||
|
|
d2f673dafc | ||
|
|
3704960fd4 | ||
|
|
e5c14ff25d | ||
|
|
a633bd8689 | ||
|
|
c197562c3d | ||
|
|
ccfffe71e0 | ||
|
|
54d54f5d9f | ||
|
|
83c5ec091b | ||
|
|
b5f0729de3 | ||
|
|
102be15f79 | ||
|
|
f7361fc495 | ||
|
|
0d55e5880b | ||
|
|
196354cf8c | ||
|
|
cbb1cd48c1 | ||
|
|
6df557f62b | ||
|
|
54b7c6a3a8 | ||
|
|
fd5f7b3418 | ||
|
|
4808f118bd | ||
|
|
bf608284b4 | ||
|
|
a24d612704 | ||
|
|
5ce960ea61 | ||
|
|
edfaa010c0 | ||
|
|
4c4f4cb979 | ||
|
|
bb33abc52a | ||
|
|
f22c8ce801 | ||
|
|
c35e4a8b31 | ||
|
|
8489ca28d5 | ||
|
|
145dc94e04 | ||
|
|
a70641c9cd | ||
|
|
e139fe0b5c | ||
|
|
c97ea7556f | ||
|
|
0fe0b4857e | ||
|
|
c748836da7 | ||
|
|
2839760d37 | ||
|
|
bebf1e0e04 | ||
|
|
958982481d | ||
|
|
ed92a999f0 | ||
|
|
bd3cc5018d | ||
|
|
0b9d717eb4 | ||
|
|
a1bead9301 | ||
|
|
816ab11ef9 | ||
|
|
7f9b4fc2b5 | ||
|
|
24ac579b1c | ||
|
|
45ddabc9f0 | ||
|
|
b316486119 | ||
|
|
226964e9fc | ||
|
|
bc6555210f | ||
|
|
049f3c342d | ||
|
|
704b5c30b0 | ||
|
|
39c600822f | ||
|
|
4ce99a70d0 | ||
|
|
932d0f6570 | ||
|
|
0635dfdc5d | ||
|
|
a492881e4a | ||
|
|
4fb5c528ca | ||
|
|
ba19d816c5 | ||
|
|
c805749114 | ||
|
|
fd7c286752 | ||
|
|
65c3d4f3ad | ||
|
|
3730027913 | ||
|
|
564ee54baa | ||
|
|
5d3fb91deb | ||
|
|
cb6a239716 | ||
|
|
0d65ab0d25 | ||
|
|
938446435b | ||
|
|
7d8cfeb9ab | ||
|
|
c3e5f8ba0f | ||
|
|
97e9bed623 | ||
|
|
6ca9d9d3b3 | ||
|
|
4977f66c1d | ||
|
|
38f827c023 | ||
|
|
b9f71a875a | ||
|
|
145b6d7efa | ||
|
|
fe0714d10e | ||
|
|
bad6f79ff7 | ||
|
|
3d5d994852 | ||
|
|
adad5602f0 | ||
|
|
29246a0f2b | ||
|
|
3037791282 | ||
|
|
8241d19d09 | ||
|
|
39087ae79c | ||
|
|
cade6c9dc3 | ||
|
|
5e54dae5fa | ||
|
|
76399d131e | ||
|
|
9c13eee4b5 | ||
|
|
12c47070fd | ||
|
|
fa6e5e6d3f | ||
|
|
ded0259acc | ||
|
|
bc73d2f83a | ||
|
|
5b5ec254fb | ||
|
|
174bad50f4 | ||
|
|
1daa8052c1 | ||
|
|
a942cf1b79 | ||
|
|
08573a1a6f | ||
|
|
7677c89af8 | ||
|
|
272e691478 | ||
|
|
1f36e69e04 | ||
|
|
7a97739689 | ||
|
|
fa432a76d7 | ||
|
|
e757446d5e | ||
|
|
7229ca3f83 | ||
|
|
b7a562994d | ||
|
|
9bb8437096 | ||
|
|
ad7b497405 | ||
|
|
952f6132df | ||
|
|
00ef5e056a | ||
|
|
99abbb9a1d | ||
|
|
d4f6653047 | ||
|
|
463c59650c | ||
|
|
e65317bbe7 | ||
|
|
ec59befb82 | ||
|
|
456756efd3 | ||
|
|
9d53cc6c75 | ||
|
|
c474d399e8 | ||
|
|
3da3d14a92 | ||
|
|
c14af37a71 | ||
|
|
a11963938f | ||
|
|
ebfa20b952 | ||
|
|
748f532a94 | ||
|
|
f0922e9152 | ||
|
|
87b9dbd834 | ||
|
|
3a96e3d13e | ||
|
|
450ccde55e | ||
|
|
83ca643da8 | ||
|
|
0e3d79db1d | ||
|
|
67b8756b78 | ||
|
|
96c3ab83a0 | ||
|
|
2c531aa76d | ||
|
|
a662203213 | ||
|
|
e5b24d3cc6 | ||
|
|
21cf9fd9a3 | ||
|
|
1b31abc4fc | ||
|
|
69e375b542 | ||
|
|
a8215cd7bd | ||
|
|
369a476513 | ||
|
|
b8203fe0f0 | ||
|
|
4fb3f3c638 | ||
|
|
368d344f0b | ||
|
|
3bfc29d4c9 | ||
|
|
62059347f6 | ||
|
|
ceed8f0b03 | ||
|
|
684cb896c0 | ||
|
|
2e4d0c5917 | ||
|
|
8de929ff54 | ||
|
|
af391e3308 | ||
|
|
057cf01a1f | ||
|
|
0e159b9f1c | ||
|
|
9c3d886c59 | ||
|
|
5817a280f6 | ||
|
|
6bd3817fc2 | ||
|
|
9c17a9758d | ||
|
|
075d3dc6e9 | ||
|
|
ea14fa769a | ||
|
|
f044559475 | ||
|
|
fe41220b42 | ||
|
|
667e25b7dd | ||
|
|
ae029a1b11 | ||
|
|
8267434832 | ||
|
|
dd60779b29 | ||
|
|
6499a14c7b | ||
|
|
4a5235b4bf | ||
|
|
7c437d2a2d | ||
|
|
c142c44055 | ||
|
|
4b585c3567 | ||
|
|
9361e2131b | ||
|
|
269bd11d87 | ||
|
|
930663ff76 | ||
|
|
40ad0bf068 | ||
|
|
31fc9f73a6 | ||
|
|
cf810659d6 | ||
|
|
0b10c17b0b | ||
|
|
317adf4f02 | ||
|
|
052ea3964b | ||
|
|
041e293ac1 | ||
|
|
d593c230ce | ||
|
|
d4f330e1af | ||
|
|
c248dce45b | ||
|
|
c9b46d6ecb | ||
|
|
631fc07b8d | ||
|
|
ff738caa37 | ||
|
|
7b07850a91 | ||
|
|
6d69005b52 | ||
|
|
d2f25ad652 | ||
|
|
52c16a52ed | ||
|
|
2359276202 | ||
|
|
c9d9061919 | ||
|
|
ff4d6f307f | ||
|
|
fb67b073dc | ||
|
|
b66a34a785 | ||
|
|
f3fff6b6e1 | ||
|
|
426b3e553a | ||
|
|
42f48f4745 | ||
|
|
05705b7641 | ||
|
|
68d204d5b3 | ||
|
|
8108de074e | ||
|
|
a49c64d87c | ||
|
|
f8badc4d06 | ||
|
|
74925669c3 | ||
|
|
2c80c25b8a | ||
|
|
30abe9e607 | ||
|
|
0eba14de0c | ||
|
|
7ad57f431a | ||
|
|
091067073a | ||
|
|
4d5484c936 | ||
|
|
746d5ec921 | ||
|
|
abf06559c7 | ||
|
|
7a1ccd4db0 | ||
|
|
96be1c9bf6 | ||
|
|
27b89de1f5 | ||
|
|
0a1aab9ee3 | ||
|
|
9f0e94107b | ||
|
|
c55b94a8cf | ||
|
|
3c19cad2e3 | ||
|
|
c1a453bc90 | ||
|
|
0aa8b01976 | ||
|
|
5748d298fc | ||
|
|
4e32004ca9 | ||
|
|
79e40ade73 | ||
|
|
d488809a69 | ||
|
|
dcc43a1f1c | ||
|
|
93ea791bad | ||
|
|
0b8655f9a1 | ||
|
|
ec87b383e8 | ||
|
|
0998f408b0 | ||
|
|
902d05c53b | ||
|
|
59bd41281a | ||
|
|
4b5137e0d9 | ||
|
|
5e8a0bb311 | ||
|
|
37cf5fe9c7 | ||
|
|
77eb810766 | ||
|
|
a37037be3e | ||
|
|
081faac153 | ||
|
|
b3c925944d | ||
|
|
fee604659f | ||
|
|
8e6d0adac3 | ||
|
|
24f28b05b5 | ||
|
|
71706dc098 | ||
|
|
f9bdb3c8a2 | ||
|
|
9138720150 | ||
|
|
a8d18b8bf7 | ||
|
|
9e9c3ccff1 | ||
|
|
d9d027195e | ||
|
|
d4d93f63f0 | ||
|
|
f6d730405d | ||
|
|
4ca77df6b9 | ||
|
|
21b66b165a | ||
|
|
ba699002b0 | ||
|
|
f9729cdca4 | ||
|
|
be2f1a2aa7 | ||
|
|
226f729236 | ||
|
|
6926933094 | ||
|
|
d28635d13a | ||
|
|
91ec7305d2 | ||
|
|
599e2564a0 | ||
|
|
31923694be | ||
|
|
c3b7275a48 | ||
|
|
1e7949a982 | ||
|
|
d162aee196 | ||
|
|
777a0bb897 | ||
|
|
d66db8e7c9 | ||
|
|
64b40af9ce | ||
|
|
c842392f67 | ||
|
|
6f268c1b56 | ||
|
|
b35a63dff9 | ||
|
|
5e372c4afa | ||
|
|
8a63e3e269 | ||
|
|
2c7529ab1c | ||
|
|
c758723855 | ||
|
|
7d60483274 | ||
|
|
2e697cfa4d | ||
|
|
15a33ba71a | ||
|
|
df584f637e | ||
|
|
2ea7718c32 | ||
|
|
e17f313956 | ||
|
|
3bb6956bfe | ||
|
|
25088e1771 | ||
|
|
cc639ab44f | ||
|
|
4baa05a11e | ||
|
|
cb5fd98909 | ||
|
|
955c997736 | ||
|
|
0b29f443f9 | ||
|
|
59c2eedb5f | ||
|
|
7e5d4bc715 | ||
|
|
d4742e157f | ||
|
|
0611cd66d1 | ||
|
|
02433e0bc1 | ||
|
|
2efda11920 | ||
|
|
8ef86ee219 | ||
|
|
77e486bf7d | ||
|
|
9b53442dcc | ||
|
|
1dc90c332f | ||
|
|
6a7969b6ce | ||
|
|
51537d9d28 | ||
|
|
e51b7a1fbf | ||
|
|
4c78cc823b | ||
|
|
ce73273ae7 | ||
|
|
485474b415 | ||
|
|
3cbc81cb08 | ||
|
|
091b64f055 | ||
|
|
d9052eaaae | ||
|
|
3512920bbc | ||
|
|
786862e195 | ||
|
|
4bfef11c2e | ||
|
|
2067d89666 | ||
|
|
35f254eade | ||
|
|
7d040e2658 | ||
|
|
d1f2dac002 | ||
|
|
235fc2e82f | ||
|
|
b8dc9faf64 | ||
|
|
291436e4fe | ||
|
|
665827e37e | ||
|
|
f47f039f66 | ||
|
|
4bc057d9bb | ||
|
|
813e74f5b6 | ||
|
|
7619052ada | ||
|
|
f31e7e91ed | ||
|
|
5d8b14f25d | ||
|
|
c504225786 | ||
|
|
95a29a6225 | ||
|
|
484ee8eeab | ||
|
|
32515e01df | ||
|
|
b880bfef96 | ||
|
|
7f867220b0 | ||
|
|
ed0a57f707 | ||
|
|
22a6df4942 | ||
|
|
cff8541d44 | ||
|
|
4074263d58 | ||
|
|
6032fad099 | ||
|
|
3d814f5ff9 | ||
|
|
39a0849872 | ||
|
|
11dc9b40af | ||
|
|
9fada9c0e4 | ||
|
|
61ebab80f0 | ||
|
|
45c6e5678c | ||
|
|
69729b0566 | ||
|
|
4d38a63a0b | ||
|
|
864b9ebcea | ||
|
|
61408dfd02 | ||
|
|
a6f2101352 | ||
|
|
db3b9ac42b | ||
|
|
08ab6e99a1 | ||
|
|
619a6685ea | ||
|
|
4d756593f5 | ||
|
|
f62e1bce82 | ||
|
|
381046d800 | ||
|
|
3280410c0c | ||
|
|
db1ea29fbf | ||
|
|
d7ca03cfbb | ||
|
|
717a1f336b | ||
|
|
4df14940a1 | ||
|
|
b815d622fe | ||
|
|
88b5146245 | ||
|
|
77bd52ace6 | ||
|
|
7d4810ceae | ||
|
|
f39d973b1f | ||
|
|
8fb7b47f10 | ||
|
|
0bee705e4d | ||
|
|
b31df5d1a4 | ||
|
|
a2d9556393 | ||
|
|
91a4f527ba | ||
|
|
f0039c234a | ||
|
|
f31dc74c3d | ||
|
|
f1393e5793 | ||
|
|
5662a25a3c | ||
|
|
74016592ac | ||
|
|
377812ece9 | ||
|
|
e878a9fe2a | ||
|
|
ba4fc168de | ||
|
|
75a26bf68d | ||
|
|
3a34a30cbc | ||
|
|
bd7e879eff | ||
|
|
1e5560dd1c | ||
|
|
9e5ccb28b9 | ||
|
|
beba3b3815 | ||
|
|
697f72a30f | ||
|
|
9f6c1f3ea9 | ||
|
|
e8ced5da45 | ||
|
|
f33047f332 | ||
|
|
38a53f2f49 | ||
|
|
40d55cfd00 | ||
|
|
c127ff6fa4 | ||
|
|
0451dda73c | ||
|
|
f2ddefc150 | ||
|
|
3ff91764d8 | ||
|
|
1cf32ef90e | ||
|
|
6e02eb9b07 | ||
|
|
338523ac0c | ||
|
|
6a6be9f9c3 | ||
|
|
963d36cb4b | ||
|
|
0fbff8cfdf | ||
|
|
18f0e29437 | ||
|
|
28fd55a639 | ||
|
|
9341b166ba | ||
|
|
7630a6edcb | ||
|
|
edde5e8414 | ||
|
|
309001f156 | ||
|
|
a7f455f159 | ||
|
|
d139c72bba | ||
|
|
da839c8857 | ||
|
|
586db9d9ba | ||
|
|
fb31a4ff7c | ||
|
|
2102131d99 | ||
|
|
24ea54f034 | ||
|
|
7c856c7a9e | ||
|
|
46dd099e24 | ||
|
|
dd3d92d61f | ||
|
|
828d936325 | ||
|
|
11a581dff8 | ||
|
|
d4f1d92f9e | ||
|
|
2a03d75639 | ||
|
|
4797737b79 | ||
|
|
4a25cbe269 | ||
|
|
f906271b0c | ||
|
|
178c885183 | ||
|
|
f01235ba01 | ||
|
|
6f78a96682 | ||
|
|
25a19ad0c1 | ||
|
|
b4a8bc1568 | ||
|
|
151c479269 | ||
|
|
20bdcf65b0 | ||
|
|
4a4d003787 | ||
|
|
7a8a34343f | ||
|
|
3b718aad63 | ||
|
|
34b2a0583e | ||
|
|
ddbe0dcae3 | ||
|
|
8ee2e1ec83 | ||
|
|
05f8d03e96 | ||
|
|
0b012fc774 | ||
|
|
435b23a444 | ||
|
|
ce39684810 | ||
|
|
1ef7a69670 | ||
|
|
351e851083 | ||
|
|
2699b34bcf | ||
|
|
4888ee8482 | ||
|
|
5b33d64a51 | ||
|
|
8f1d5cda93 | ||
|
|
86822f4e79 | ||
|
|
cebd727209 | ||
|
|
a087e79c5e | ||
|
|
d9c111beb7 | ||
|
|
ba242ac0b6 | ||
|
|
53a71bd632 | ||
|
|
44fa9c4ce0 | ||
|
|
e8aa4d3f7d | ||
|
|
9325eb98ba | ||
|
|
ce159b1776 | ||
|
|
737eef3cbb | ||
|
|
4b1da12f81 | ||
|
|
5354e4eadd | ||
|
|
22cedd0a84 | ||
|
|
9f5a829206 | ||
|
|
b855358169 | ||
|
|
72db63e979 | ||
|
|
0d5b1e2bac | ||
|
|
784f4bb442 | ||
|
|
19c8fbce7a | ||
|
|
be56a865cb | ||
|
|
976daac18a | ||
|
|
da13999b2e | ||
|
|
879d348c3a | ||
|
|
f3e573f13f | ||
|
|
d98d8e9ad5 | ||
|
|
4f5fa6791e | ||
|
|
8cec626e72 | ||
|
|
77502a5a7b | ||
|
|
0d33077b19 | ||
|
|
0f7f0e1ad3 | ||
|
|
d2b0cba6a6 | ||
|
|
8a0fafa620 | ||
|
|
5e65931e37 | ||
|
|
ec6e63d190 | ||
|
|
29ec1ef5bc | ||
|
|
ea1f85e712 | ||
|
|
a6963da515 | ||
|
|
655f0e3d3b | ||
|
|
1072edaa78 | ||
|
|
003d7c720e | ||
|
|
a1434ca3cf | ||
|
|
eb009db29f | ||
|
|
eb2ecc2003 | ||
|
|
6ed556d476 | ||
|
|
257d27b2ca | ||
|
|
3669b9ef2e | ||
|
|
3fadf58d6a | ||
|
|
8030207323 | ||
|
|
94f38877ba | ||
|
|
7262523e77 | ||
|
|
7a988e3005 | ||
|
|
c94f272714 | ||
|
|
dcce8a89a0 | ||
|
|
a7926d42b1 | ||
|
|
e28119dbd7 | ||
|
|
a3f9cd75ea | ||
|
|
27bce77069 | ||
|
|
1a55487a00 | ||
|
|
1af694cd86 | ||
|
|
fd9bd9c962 | ||
|
|
271927c472 | ||
|
|
f52e556e2a | ||
|
|
31ec9bff63 | ||
|
|
1c3bebdc39 | ||
|
|
3ef9db42ab | ||
|
|
7d93011f3a | ||
|
|
2e429e4f8e | ||
|
|
51606f2763 | ||
|
|
d64143c118 | ||
|
|
2498e5be3b | ||
|
|
893980f6d6 | ||
|
|
7710b223f0 | ||
|
|
59797464ef | ||
|
|
4795f62b45 | ||
|
|
d70cf95aea | ||
|
|
227d092c2a | ||
|
|
0eb9698c30 | ||
|
|
7ecfd7a788 | ||
|
|
851ead228c | ||
|
|
8ba11789b3 | ||
|
|
8abce39883 | ||
|
|
ebf2d1a895 | ||
|
|
898e7bccf3 | ||
|
|
9e6d9903c0 | ||
|
|
fd5eac4cda | ||
|
|
b11f948451 | ||
|
|
44a55a677a | ||
|
|
2925312631 | ||
|
|
9b1514e2f0 | ||
|
|
e981120b96 | ||
|
|
0e3e4ddcf2 | ||
|
|
171eb97e55 | ||
|
|
18f90b24a1 | ||
|
|
041406d0c5 | ||
|
|
286a6a9720 | ||
|
|
4cc1ed3a85 | ||
|
|
4bd5f7387c | ||
|
|
5dc3a6b163 | ||
|
|
bdf9478075 | ||
|
|
02f129ad07 | ||
|
|
7338ebe7cc | ||
|
|
fcd9dbd4e9 | ||
|
|
3cf4261118 | ||
|
|
e4fdccd098 | ||
|
|
9ca617ab44 | ||
|
|
7df58a4a94 | ||
|
|
bbd9c73613 | ||
|
|
6920dc0aaa | ||
|
|
bd12f769c9 | ||
|
|
384d1180cf | ||
|
|
6f67982ce0 | ||
|
|
1774a619e0 | ||
|
|
965dfdb84b | ||
|
|
45c01d39a4 | ||
|
|
83464aafe0 | ||
|
|
dfcbd40dbe | ||
|
|
3288ae1d45 | ||
|
|
693284e5a0 | ||
|
|
455d6f1cd0 | ||
|
|
4115ed4bbc | ||
|
|
21631c17be | ||
|
|
96f820658b | ||
|
|
bf835e2207 | ||
|
|
e0e881944c | ||
|
|
272d1adecb | ||
|
|
5f847a1f49 | ||
|
|
1a5fa5e830 | ||
|
|
78e48cd506 | ||
|
|
ecfbe1df43 | ||
|
|
c1d80804d6 | ||
|
|
02936de391 | ||
|
|
cf03e9f94f | ||
|
|
ebdb255484 | ||
|
|
a165940858 | ||
|
|
1675ccd62e | ||
|
|
e8e3542a2b | ||
|
|
c4a46648a2 | ||
|
|
4d820e84bd | ||
|
|
6c629d9c79 | ||
|
|
857bc5f409 | ||
|
|
5501318b79 | ||
|
|
5932a8672c | ||
|
|
4558e07710 | ||
|
|
80d92b976d | ||
|
|
0abdc55feb | ||
|
|
c9facb64f6 | ||
|
|
1141cf735f | ||
|
|
8c0e81d6df | ||
|
|
da0f528798 | ||
|
|
0ed815fff7 | ||
|
|
f5ca8f746d | ||
|
|
833191f5d1 | ||
|
|
11d8272633 | ||
|
|
dea368dd96 | ||
|
|
b5921b8390 | ||
|
|
0654ed2eb0 | ||
|
|
978843f134 | ||
|
|
fa22664ed3 | ||
|
|
7b2e8f5879 | ||
|
|
fd48898cba | ||
|
|
0de1896e47 | ||
|
|
a8cc0ab5d6 | ||
|
|
66c2941dd6 | ||
|
|
947032f898 | ||
|
|
494162f97d | ||
|
|
6a0c23d012 | ||
|
|
1bce1da06d | ||
|
|
b56d295723 | ||
|
|
7a4c4aa9f4 | ||
|
|
6a81dc4ef0 | ||
|
|
192a522551 | ||
|
|
8e9083b343 | ||
|
|
153e165b8f | ||
|
|
923c4ffd91 | ||
|
|
14dc411826 | ||
|
|
935d2f0e60 | ||
|
|
6fb436155a | ||
|
|
ce39566d60 | ||
|
|
09744379f6 | ||
|
|
9a8b6eb98b | ||
|
|
639c7b3d62 | ||
|
|
24126504a6 | ||
|
|
f0058de11b | ||
|
|
5620e8b2c0 | ||
|
|
80200de9dc | ||
|
|
eb29899862 | ||
|
|
131d585432 | ||
|
|
8b2e83aca8 | ||
|
|
d33d4dfe83 | ||
|
|
4929bf2429 | ||
|
|
087a98359c | ||
|
|
b01391a480 | ||
|
|
fd88eed6f9 | ||
|
|
c55936b806 | ||
|
|
af5b3ba467 | ||
|
|
23cc120c3a | ||
|
|
ac8b92f8c0 | ||
|
|
8762509e80 | ||
|
|
618a0cbfef | ||
|
|
ba9340ab8e | ||
|
|
626b2f3f53 | ||
|
|
3c399d0997 | ||
|
|
1f6cefab1c | ||
|
|
311403e3e0 | ||
|
|
3762513d4f | ||
|
|
270aa6eab6 | ||
|
|
1a606cbf6d | ||
|
|
8bd364b050 | ||
|
|
8c2132fc56 | ||
|
|
03ff3d068a | ||
|
|
6a86625862 | ||
|
|
0eecf0bede | ||
|
|
5f872c51fd | ||
|
|
f324ef4769 | ||
|
|
cbdb296d13 | ||
|
|
aebed56970 | ||
|
|
3345ad6505 | ||
|
|
e4206e74d3 | ||
|
|
b993791358 | ||
|
|
25f2c2e4e5 | ||
|
|
3376a58adf | ||
|
|
36848ded6d | ||
|
|
3314ffaa09 | ||
|
|
f3e5159168 | ||
|
|
08c37785ad | ||
|
|
56a98260f7 | ||
|
|
54d630dd59 | ||
|
|
986d34abe8 | ||
|
|
a510e8013e | ||
|
|
af9fd4c4a0 | ||
|
|
75f05d52b2 | ||
|
|
7150d32872 | ||
|
|
3d7e4e0d75 | ||
|
|
4efae3cdf6 | ||
|
|
a70e408dee | ||
|
|
f141e866a9 | ||
|
|
39cabfd5f0 | ||
|
|
a262cd731b | ||
|
|
5215bfce98 | ||
|
|
7843134b47 | ||
|
|
e4b33ba7d8 | ||
|
|
bccbd1b2f0 | ||
|
|
975098de1f | ||
|
|
3309dd5f7d | ||
|
|
ce9b7e82e2 | ||
|
|
c81389769e | ||
|
|
c95de9e756 | ||
|
|
a60ded83dd | ||
|
|
fec7c0f0f0 | ||
|
|
9dae2b5bd7 | ||
|
|
45bdac39d2 | ||
|
|
2f59257b1c | ||
|
|
d15ccee62f | ||
|
|
3675880c1d | ||
|
|
0b313fa9df | ||
|
|
cf1452e5b9 | ||
|
|
65ce1df6ae | ||
|
|
fe7150674e | ||
|
|
2c1a0155d5 | ||
|
|
0d35a7a22b | ||
|
|
12bc1c02eb | ||
|
|
5cb41067cd | ||
|
|
eddfad1e25 | ||
|
|
4e72ae5a16 | ||
|
|
da530944d4 | ||
|
|
ce0e2ba5f8 | ||
|
|
b9408f26ce | ||
|
|
ccbd430166 | ||
|
|
db0c857e29 | ||
|
|
3749521ffb | ||
|
|
b05653b94b | ||
|
|
878037e83e | ||
|
|
96d1e83c95 | ||
|
|
b40ce24f61 | ||
|
|
852b90ebb7 | ||
|
|
a1f13d8e3a | ||
|
|
4dcbe807df | ||
|
|
d274ea64f6 | ||
|
|
b80f7db3cb | ||
|
|
6e54c67429 | ||
|
|
1f989b2499 | ||
|
|
829d6f38ce | ||
|
|
cb7e42ee77 | ||
|
|
1a602664c4 | ||
|
|
ea2ed1e8cf | ||
|
|
f7e5de7fe3 | ||
|
|
119df372d6 | ||
|
|
e56673332e | ||
|
|
ce25f9bf64 | ||
|
|
64b56a50a3 | ||
|
|
43a8ed9744 | ||
|
|
349f17d79a | ||
|
|
558eec10b6 | ||
|
|
b280e2bfa5 | ||
|
|
231181da81 | ||
|
|
757bebd2e4 | ||
|
|
f9b8afa988 | ||
|
|
c885aa4927 | ||
|
|
35311118ab | ||
|
|
c98683818b | ||
|
|
cdada06344 | ||
|
|
a2f664e979 | ||
|
|
968b441d3b | ||
|
|
7ba878ef98 | ||
|
|
26216a88d5 | ||
|
|
d34833fc8a | ||
|
|
1cb056d059 | ||
|
|
3d6ffadc5e | ||
|
|
61b8df8fa8 | ||
|
|
07106a6ef9 | ||
|
|
1939d28a2d | ||
|
|
42bf645ae6 | ||
|
|
2f635d582d | ||
|
|
ad1e2e4f80 | ||
|
|
025454ae32 | ||
|
|
dd4262039e | ||
|
|
917bac6825 | ||
|
|
d710ddfdaa | ||
|
|
1bad828624 | ||
|
|
0e0386aa49 | ||
|
|
31480d8713 | ||
|
|
2088d3a4ba | ||
|
|
ca23b0dd85 | ||
|
|
34fbdfde30 | ||
|
|
7b0782ae49 | ||
|
|
9f76ae2a26 | ||
|
|
a267d95b2e | ||
|
|
d0c1ee416f | ||
|
|
50c3a7b6a9 | ||
|
|
fb1007e9cf | ||
|
|
f327b7b284 | ||
|
|
56858282b9 | ||
|
|
86c11ad784 | ||
|
|
2dfc92c5ad | ||
|
|
c2b34ddd3a | ||
|
|
a4fd99414b | ||
|
|
c6b977b5e7 | ||
|
|
58d38a1b29 | ||
|
|
e27c7b0c2b | ||
|
|
bffbc59b70 | ||
|
|
bb3e7bbd69 | ||
|
|
a678e9292b | ||
|
|
86a192fa9c | ||
|
|
2496d8f546 | ||
|
|
4c1db52b01 | ||
|
|
d44d0d6230 | ||
|
|
b490e49ee7 | ||
|
|
3e9e56d878 | ||
|
|
69acf59245 | ||
|
|
6839eef7f8 | ||
|
|
6ed8168316 | ||
|
|
48a348fcff | ||
|
|
5d830c2d16 | ||
|
|
1f3e4a20f1 | ||
|
|
6fc86bc2b5 | ||
|
|
2c134685ca | ||
|
|
028ce2c455 | ||
|
|
fc2840ae30 | ||
|
|
5e54bc0c6a | ||
|
|
3c2878c1bf | ||
|
|
5002f8c518 | ||
|
|
a35c9c6b7a | ||
|
|
2e2fe58cc1 | ||
|
|
4471e08341 | ||
|
|
e98c5aea27 | ||
|
|
02e58770e9 | ||
|
|
e7b9eb56b9 | ||
|
|
a59a14a279 | ||
|
|
419e4af5ea | ||
|
|
492c12483a | ||
|
|
58d0fd071c | ||
|
|
f014d820da | ||
|
|
35e24c590e | ||
|
|
28de9b2b27 | ||
|
|
c4d6f7a89f | ||
|
|
b1e2080fa8 | ||
|
|
4bc0776483 | ||
|
|
e6e2c6e5bb | ||
|
|
27cb003558 | ||
|
|
196d204c9b | ||
|
|
ff11c6a9e8 | ||
|
|
a7ea56b5a0 | ||
|
|
c86ad5339a | ||
|
|
3287ce719b | ||
|
|
d169fbd395 | ||
|
|
4072367812 | ||
|
|
805a8cc559 | ||
|
|
0b9fa94bb1 | ||
|
|
d2c39d6404 | ||
|
|
3b273a2d4a | ||
|
|
58a45aff3f | ||
|
|
91f0daface | ||
|
|
119d1fedf3 | ||
|
|
1f07ecc025 | ||
|
|
5a2a330574 | ||
|
|
93465ee5e1 | ||
|
|
396fba1c95 | ||
|
|
e107cee4e5 | ||
|
|
87d8792683 | ||
|
|
13f3181d08 | ||
|
|
4578065562 | ||
|
|
2827e8abeb | ||
|
|
d1ec0dce7e | ||
|
|
44c5455bba | ||
|
|
29728faaa9 | ||
|
|
5ffd56869a | ||
|
|
cf35d058ad | ||
|
|
ddf6e3ce35 | ||
|
|
d56ff5ffde | ||
|
|
9382b98a63 | ||
|
|
ed29b6e29b | ||
|
|
f464c14b81 | ||
|
|
26eb0c1f6d | ||
|
|
794f1840a7 | ||
|
|
16b1552bc2 | ||
|
|
9f5c62f534 | ||
|
|
07e2d839f5 | ||
|
|
5738841280 | ||
|
|
ec1871acf2 | ||
|
|
1b90faeac8 | ||
|
|
beebc2b852 | ||
|
|
35e9614136 | ||
|
|
c069e3051c | ||
|
|
5f41c4eae0 | ||
|
|
452ad0525e | ||
|
|
52f3ec4c95 | ||
|
|
244e0114d8 | ||
|
|
0c3f4d1e39 | ||
|
|
78162151f6 | ||
|
|
631badaace | ||
|
|
12cf106305 | ||
|
|
3639e70450 | ||
|
|
83c37d5b81 | ||
|
|
bac89846a4 | ||
|
|
9a9ebeb79c | ||
|
|
183bb1b7a9 | ||
|
|
de37f8aabf | ||
|
|
1e9f979364 | ||
|
|
f5132de1f4 | ||
|
|
57a9d0ae54 | ||
|
|
46ae47b5c9 | ||
|
|
d70517cdfe | ||
|
|
b4fbf80141 | ||
|
|
845eff88b4 | ||
|
|
01bfae0ddd | ||
|
|
94b3590118 | ||
|
|
3e56a09074 | ||
|
|
df96a02c32 | ||
|
|
b35713c6b1 | ||
|
|
72c7e3df08 | ||
|
|
f77cf6c029 | ||
|
|
d784a0e494 | ||
|
|
badd2879c4 | ||
|
|
8134333444 | ||
|
|
c167024212 | ||
|
|
9d1be95154 | ||
|
|
2784ea389c | ||
|
|
c5a3a91221 | ||
|
|
8815838685 | ||
|
|
9c7399c9e7 | ||
|
|
3205db6798 | ||
|
|
537bf56951 | ||
|
|
3dbbff1fb5 | ||
|
|
70568c0e1c | ||
|
|
45731d619f | ||
|
|
c68ca3fa4c | ||
|
|
fe4e7ebeeb | ||
|
|
fb575b3a53 | ||
|
|
db931c6f6f | ||
|
|
85c79f8c89 | ||
|
|
12f6fb2f37 | ||
|
|
3ac99725fd | ||
|
|
1b21064a77 | ||
|
|
9a9a55b3bf | ||
|
|
bb9a634a07 | ||
|
|
1a08fe8058 | ||
|
|
343d687753 | ||
|
|
581097d4ff | ||
|
|
4ca69a0bd8 | ||
|
|
31679a9560 | ||
|
|
e172a02d48 | ||
|
|
1e85c2ac37 | ||
|
|
c063e40ff8 | ||
|
|
1fc5fc387b | ||
|
|
b7a0987c52 | ||
|
|
a3658a869c | ||
|
|
ffa7a406f0 | ||
|
|
d0d4d15899 | ||
|
|
d5eca31bc3 | ||
|
|
273f377789 | ||
|
|
0fe803156a | ||
|
|
e7581e9827 | ||
|
|
60dd38d822 | ||
|
|
3592bea8a8 | ||
|
|
03cedc80d5 | ||
|
|
5ed2a43633 | ||
|
|
ef9f68d222 | ||
|
|
cc87d33cd7 | ||
|
|
75be113b33 | ||
|
|
a54ca0247b | ||
|
|
d48a87f094 | ||
|
|
194045c92c | ||
|
|
dbb1cc1303 | ||
|
|
d03d87ca4d | ||
|
|
a312f6ce4d | ||
|
|
bb81d3c4c0 | ||
|
|
04a2a12546 | ||
|
|
f439458899 | ||
|
|
3b6e4390da | ||
|
|
31d5010bb2 | ||
|
|
74004c0829 | ||
|
|
401b2d723a | ||
|
|
1845fe7a4d | ||
|
|
5164637b5a | ||
|
|
032d12d731 | ||
|
|
2cbe22c350 | ||
|
|
3819c5dea7 | ||
|
|
31e66fb8d8 | ||
|
|
af2ea12835 | ||
|
|
3a7e3936e0 | ||
|
|
6fa14c93e7 | ||
|
|
0816a5deb1 | ||
|
|
5ba764f010 | ||
|
|
a0301e65cc | ||
|
|
467a77a66d | ||
|
|
0791c8d47b | ||
|
|
c6a3da5a58 | ||
|
|
e5edb6b230 | ||
|
|
696f9a7a82 | ||
|
|
a29d8d97cc | ||
|
|
f3a3ec12fa | ||
|
|
75b5bfce87 | ||
|
|
c1ae5bfa6b | ||
|
|
ea385e9788 | ||
|
|
fd8e202e59 | ||
|
|
125f708286 | ||
|
|
201ed53ac1 | ||
|
|
d681b09e58 | ||
|
|
c57d60bfc1 | ||
|
|
2cb3ab0994 | ||
|
|
30b1330ce2 | ||
|
|
0b96f5aff4 | ||
|
|
ef411bb255 | ||
|
|
65c84c7b46 | ||
|
|
0d7332b614 | ||
|
|
35514effd8 | ||
|
|
6024db8670 | ||
|
|
50eaabd59e | ||
|
|
b17855ab7a | ||
|
|
91084fbb31 | ||
|
|
9d56de35ef | ||
|
|
b4680dcab9 | ||
|
|
116e48acda | ||
|
|
27416553c8 | ||
|
|
2f253dac28 | ||
|
|
110a1cda94 | ||
|
|
ff0b4a2dc4 | ||
|
|
16bea88b20 | ||
|
|
9b4649d3d8 | ||
|
|
2ce181cf2e | ||
|
|
c91b6403c5 | ||
|
|
a263c384c0 | ||
|
|
6b36d47666 | ||
|
|
025bbc219c | ||
|
|
763c4201f2 | ||
|
|
0af96a930f | ||
|
|
a4705aa7b8 | ||
|
|
89c1c8a57c | ||
|
|
62af22cb51 | ||
|
|
11937d07b6 | ||
|
|
1dd91b878d | ||
|
|
66cd64a7ad | ||
|
|
5b704136ee | ||
|
|
b6fd02234b | ||
|
|
bbd6126e79 | ||
|
|
6f87a5d943 | ||
|
|
a63c8ae5e4 | ||
|
|
c8b6d71d36 | ||
|
|
931a4b96ec | ||
|
|
fe615f022a | ||
|
|
0d2bd37772 | ||
|
|
822515623c | ||
|
|
8b4f9246c7 | ||
|
|
5f83e3351a | ||
|
|
74e99f6a3f | ||
|
|
59ce40f9a4 | ||
|
|
f9784e14fd | ||
|
|
4d8a61618e | ||
|
|
b420e83992 | ||
|
|
55c65eca7f | ||
|
|
95b709e757 | ||
|
|
e50abcbcea | ||
|
|
c596293b61 | ||
|
|
d1df1379cd | ||
|
|
2cb958097b | ||
|
|
0b5f544ab7 | ||
|
|
fa562dc391 | ||
|
|
5715f3972a | ||
|
|
299fd56c0c | ||
|
|
d5d373f835 | ||
|
|
dc7879ccc1 | ||
|
|
133d30de56 | ||
|
|
93991a7696 | ||
|
|
3be7a4d1a2 | ||
|
|
378896195e | ||
|
|
376e4ff344 | ||
|
|
709506deb2 | ||
|
|
7f5d9293a4 | ||
|
|
58f5fa5787 | ||
|
|
6e56003ece | ||
|
|
0ed3bbfd20 | ||
|
|
74d24c9825 | ||
|
|
5f8f5cba49 | ||
|
|
0da464588a | ||
|
|
6e581c9e92 | ||
|
|
2178d6d38a | ||
|
|
0f0e1935fc | ||
|
|
ef3a03ca87 | ||
|
|
89c45410da | ||
|
|
13c2961af3 | ||
|
|
e98c71334b | ||
|
|
34e93cdb48 | ||
|
|
dca3447446 | ||
|
|
9dd73d9d46 | ||
|
|
a3316c1aae | ||
|
|
6f8893ebec | ||
|
|
169d1b81d2 | ||
|
|
2137c272dd | ||
|
|
7988a82e33 | ||
|
|
109ab986c8 | ||
|
|
ba9dfc9aea | ||
|
|
583b584935 | ||
|
|
c671c1b9d9 | ||
|
|
35ed2e5187 | ||
|
|
7497acf5ed | ||
|
|
7f82cdbe7d | ||
|
|
a475aac754 | ||
|
|
b85f41fbc7 | ||
|
|
320a196fba | ||
|
|
75812c06d1 | ||
|
|
bc652cc8af | ||
|
|
0544f4252a | ||
|
|
eebadf6309 | ||
|
|
366183d035 | ||
|
|
eba867dd93 | ||
|
|
7103571086 | ||
|
|
b3a65772d1 | ||
|
|
654fb4f4b4 | ||
|
|
7db95e7e2c | ||
|
|
ebeeeab4d4 | ||
|
|
6add12cfb9 | ||
|
|
3a23df76a0 | ||
|
|
8965d69b99 | ||
|
|
84b06ef4e4 | ||
|
|
39fbf1508d | ||
|
|
cf189511e3 | ||
|
|
5f24921230 | ||
|
|
b13a7d62e2 | ||
|
|
6703cb80ac | ||
|
|
a7ce90c2db | ||
|
|
455d7a534d | ||
|
|
320b8c1a26 | ||
|
|
8a7d72e624 | ||
|
|
e88fb35f3b | ||
|
|
960c5ae580 | ||
|
|
9894f0baf5 | ||
|
|
70726f8c91 | ||
|
|
104fa6f8c4 | ||
|
|
6ff3264e63 | ||
|
|
50bcc79a0d | ||
|
|
e5d91ac619 | ||
|
|
6e5aba2535 | ||
|
|
a2365a7ba7 | ||
|
|
0515ae9ea9 | ||
|
|
017f7e2b0a | ||
|
|
1d4b953daa | ||
|
|
c74f32f1e7 | ||
|
|
7c4c9119ef | ||
|
|
5ac4ae818b |
20
ATTRIBUTION.md
Normal file
20
ATTRIBUTION.md
Normal file
@@ -0,0 +1,20 @@
|
||||
## Attribution
|
||||
|
||||
FairEmail uses:
|
||||
|
||||
* [JavaMail](https://projects.eclipse.org/projects/ee4j.javamail). Copyright (c) 1997-2018 Oracle® and/or its affiliates. All rights reserved. [GPLv2+CE license](https://javaee.github.io/javamail/JavaMail-License).
|
||||
* [jsoup](https://jsoup.org/). Copyright © 2009 - 2017 Jonathan Hedley. [MIT license](https://jsoup.org/license).
|
||||
* [Android Support Library](https://developer.android.com/tools/support-library/). Copyright (C) 2011 The Android Open Source Project. [Apache license 2.0](https://android.googlesource.com/platform/frameworks/support/+/master/LICENSE.txt).
|
||||
* [Android Architecture Components](https://developer.android.com/topic/libraries/architecture/). Copyright 2018 The Android Open Source Project, Inc. [Apache license 2.0](https://github.com/googlesamples/android-architecture-components/blob/master/LICENSE).
|
||||
* [colorpicker](https://android.googlesource.com/platform/frameworks/opt/colorpicker). Copyright (C) 2013 The Android Open Source Project. [Apache license 2.0](https://android.googlesource.com/platform/frameworks/opt/colorpicker/+/master/src/com/android/colorpicker/ColorPickerDialog.java).
|
||||
* [dnsjava](http://www.xbill.org/dnsjava/). Copyright (c) 1998-2011, Brian Wellington. [BSD License](https://sourceforge.net/p/dnsjava/code/HEAD/tree/trunk/LICENSE).
|
||||
* [OpenPGP API library](https://github.com/open-keychain/openpgp-api). Copyright (C) 2014-2015 Dominik Schürmann. [Apache License 2.0](https://github.com/open-keychain/openpgp-api/blob/master/LICENSE).
|
||||
* [Android SQLite support library](https://github.com/requery/sqlite-android). Copyright (C) 2017 requery.io. [Apache License 2.0](https://github.com/requery/sqlite-android/blob/master/LICENSE).
|
||||
* [App shortcut icon generator](https://romannurik.github.io/AndroidAssetStudio/icons-app-shortcut.html). Copyright ???. [Apache License 2.0](https://github.com/romannurik/AndroidAssetStudio/blob/master/LICENSE).
|
||||
* [Mozilla ISPDB](https://developer.mozilla.org/en-US/docs/Mozilla/Thunderbird/Autoconfiguration#ISPDB). *Free to use for any client.*
|
||||
* [ShortcutBadger](https://github.com/leolin310148/ShortcutBadger). Copyright 2014 Leo Lin. [Apache license 2.0](https://github.com/leolin310148/ShortcutBadger/blob/master/LICENSE).
|
||||
* [Bugsnag exception reporter for Android](https://github.com/bugsnag/bugsnag-android). Copyright (c) 2012 Bugsnag. [MIT License](https://github.com/bugsnag/bugsnag-android/blob/master/LICENSE.txt).
|
||||
* [biweekly](https://github.com/mangstadt/biweekly). Copyright (c) 2013-2018, Michael Angstadt. [BSD 2-Clause](https://github.com/mangstadt/biweekly/blob/master/LICENSE).
|
||||
* [PhotoView](https://github.com/chrisbanes/PhotoView). Copyright 2018 Chris Banes. [Apache License 2.0](https://github.com/chrisbanes/PhotoView/blob/master/LICENSE).
|
||||
* [ReLinker](https://github.com/KeepSafe/ReLinker). Copyright 2015 - 2016 KeepSafe Software, Inc. [Apache License 2.0](https://github.com/KeepSafe/ReLinker/blob/master/LICENSE).
|
||||
* [Markwon](https://github.com/noties/Markwon). Copyright 2019 Dimitry Ivanov. [Apache License 2.0](https://github.com/noties/Markwon/blob/master/LICENSE).
|
||||
518
FAQ.md
518
FAQ.md
@@ -23,13 +23,15 @@ For authorizing:
|
||||
|
||||
## Known problems
|
||||
|
||||
* ~~A [bug in Android](https://issuetracker.google.com/issues/78495471) lets FairEmail occasionally crash on long pressing or swiping.~~
|
||||
* ~~A [bug in Android 5.1 and 6](https://issuetracker.google.com/issues/37054851) causes apps to sometimes show a wrong time format. Toggling the Android setting *Use 24-hour format* might temporarily solve the issue.~~
|
||||
* ~~A [bug in Google Drive](https://issuetracker.google.com/issues/126362828) causes files exported to Google Drive to be empty.~~
|
||||
* "*... Couldn't read row ...*" causes sometimes a crash. This could be caused by a bug in the [Room Persistence Library](https://developer.android.com/topic/libraries/architecture/room) but more likely indicates a corrupt database.
|
||||
* A [bug in Android](https://issuetracker.google.com/issues/119872129) "*... Bad notification posted ...*" lets FairEmail crash on some devices after updating FairEmail and tapping on a notification.
|
||||
* A [bug in Android](https://issuetracker.google.com/issues/62427912) "*... ActivityRecord not found for ...*" sometimes causes a crash after updating FairEmail.
|
||||
* Encryption with [YubiKey](https://www.yubico.com/) results into an infinite loop. FairEmail follows the latest version of the [OpenKeychain API](https://github.com/open-keychain/openpgp-api), so this is likely being caused by an external bug.
|
||||
* ~~A [bug in Android 5.1 and 6](https://issuetracker.google.com/issues/37054851) causes apps to sometimes show a wrong time format. Toggling the Android setting *Use 24-hour format* might temporarily solve the issue. A workaround was added.~~
|
||||
* ~~A [bug in Google Drive](https://issuetracker.google.com/issues/126362828) causes files exported to Google Drive to be empty. Google has fixed this.~~
|
||||
* ~~Encryption with [YubiKey](https://www.yubico.com/) results into an infinite loop. FairEmail follows the latest version of the [OpenKeychain API](https://github.com/open-keychain/openpgp-api), so this is likely being caused by an external bug. This seems not be happening anymore.~~
|
||||
* ~~A [bug in AndroidX](https://issuetracker.google.com/issues/78495471) lets FairEmail occasionally crash on long pressing or swiping. Google has fixed this.~~
|
||||
* ~~A [bug in AndroidX ROOM](https://issuetracker.google.com/issues/138441698) causes sometimes a crash with "*... Exception while computing database live data ... Couldn't read row ...*". A workaround was added.~~
|
||||
* A [bug in Android](https://issuetracker.google.com/issues/119872129) lets FairEmail crash with "*... Bad notification posted ...*" on some devices once after updating FairEmail and tapping on a notification.
|
||||
* A [bug in Android](https://issuetracker.google.com/issues/62427912) sometimes causes a crash with "*... ActivityRecord not found for ...*" after updating FairEmail. Reinstalling ([source](https://stackoverflow.com/questions/46309428/android-activitythread-reportsizeconfigurations-causes-app-to-freeze-with-black)) might fix the problem.
|
||||
* A bug in Nova Launcher or Android 5.x lets FairEmail crash with a *java.lang.StackOverflowError* when Nova Launcher has access to the accessibility service.
|
||||
* The folder selector sometimes shows no folders for yet unknown reasons.
|
||||
|
||||
## Planned features
|
||||
|
||||
@@ -40,13 +42,19 @@ For authorizing:
|
||||
* ~~Notification settings per folder~~
|
||||
* ~~Select local images for signatures~~ (this will not be added because it requires image file management and because images are not shown by default in most email clients anyway)
|
||||
* ~~Show messages matched by a rule~~
|
||||
* ~~[ManageSieve](https://tools.ietf.org/html/rfc5804)~~ (there are no maintained Java libraries with a suitable license and without dependencies and besides that, FairEmail has its own filter rules)
|
||||
* ~~Search for messages with/without attachments~~ (this cannot be added because IMAP doesn't support searching for attachments)
|
||||
* ~~Search for a folder~~ (filtering a hierarchical folder list is problematic)
|
||||
* ~~Search suggestions~~
|
||||
* ~~[Autocrypt Setup Message](https://autocrypt.org/autocrypt-spec-1.0.0.pdf) (section 4.4)~~ (IMO it is not a good idea to let an email client handle sensitive encryption keys for an exceptional use case while OpenKeychain can export keys too)
|
||||
* ~~Generic unified folders~~
|
||||
* ~~New message notification schedules per account~~ (implemented by added a time condition to rules, so messages can be snoozed in selected periods)
|
||||
|
||||
Anything on this list is in random order and *might* be added in the near future.
|
||||
|
||||
|
||||
## Frequently requested features
|
||||
|
||||
* *Widget to read messages*: widgets can have limited user interaction only, so a widget to read conversations would not be very convenient. Moreover, it would be not very useful to duplicate functions which are already available in the app.
|
||||
* *Design*: the design is based on many discussions and if you like you can discuss about it [in this forum](https://forum.xda-developers.com/android/apps-games/source-email-t3824168) too. See below for the design goals.
|
||||
* *ActiveSync*: using the Exchange ActiveSync protocol requires [a license](https://en.wikipedia.org/wiki/Exchange_ActiveSync#Licensing), so this cannot be added.
|
||||
|
||||
@@ -56,8 +64,8 @@ Fonts, sizes, colors, etc should be material design wherever possible.
|
||||
|
||||
Since FairEmail is meant to be privacy friendly, the following will not be added:
|
||||
|
||||
* Open links without confirmation
|
||||
* Show original messages from unknown senders without confirmation, see also [this FAQ](#user-content-faq35)
|
||||
* Opening links without confirmation
|
||||
* Showing original messages without confirmation, see also [this FAQ](#user-content-faq35)
|
||||
* Direct file/folder access: for security/privacy reasons (other) apps should use the [Storage Access Framework](https://developer.android.com/guide/topics/providers/document-provider), see also [this FAQ](#user-content-faq49)
|
||||
|
||||
Confirmation is just one tap, which is just a small price for better privacy.
|
||||
@@ -77,9 +85,9 @@ FairEmail follows all the best practices for an email client as decribed in [thi
|
||||
* [(4) How can I use an invalid security certificate / IMAP STARTTLS / an empty password?](#user-content-faq4)
|
||||
* [(5) How can I customize the message view?](#user-content-faq5)
|
||||
* [(6) How can I login to Gmail / G suite?](#user-content-faq6)
|
||||
* [(7) Why are sent messages not appearing sent folder?](#user-content-faq7)
|
||||
* [(7) Why are sent messages not appearing (directly) in the sent folder?](#user-content-faq7)
|
||||
* [(8) Can I use a Microsoft Exchange account?](#user-content-faq8)
|
||||
* [(9) What are identities?](#user-content-faq9)
|
||||
* [(9) What are identities / how do I add an alias?](#user-content-faq9)
|
||||
* [(11) Why is POP not supported?](#user-content-faq11)
|
||||
* [~~(10) What does 'UIDPLUS not supported' mean?~~](#user-content-faq10)
|
||||
* [(12) How does encryption/decryption work?](#user-content-faq12)
|
||||
@@ -92,8 +100,8 @@ FairEmail follows all the best practices for an email client as decribed in [thi
|
||||
* [(19) Why are the pro features so expensive?](#user-content-faq19)
|
||||
* [(20) Can I get a refund?](#user-content-faq20)
|
||||
* [(21) How do I enable the notification light?](#user-content-faq21)
|
||||
* [(22) What do 'Couldn't connect to host', 'Connection refused', 'Network unreachable', 'Software caused connection abort', 'Connection reset by peer' and 'Read timed out' mean?](#user-content-faq22)
|
||||
* [(23) Why do I get 'Too many simultaneous connections' ?](#user-content-faq23)
|
||||
* [(22) What do 'Couldn't connect to host', 'Connection refused', 'Network unreachable', 'Software caused connection abort', 'Connection reset by peer', 'Read timed out' and 'Broken pipe' mean?](#user-content-faq22)
|
||||
* [(23) Why do I get 'Too many simultaneous connections' or 'Maximum number of connections ... exceeded' ?](#user-content-faq23)
|
||||
* [(24) What is browse messages on the server?](#user-content-faq24)
|
||||
* [(25) Why can't I select/open/save an image, attachment or a file?](#user-content-faq25)
|
||||
* [(26) Can I help to translate FairEmail in my own language?](#user-content-faq26)
|
||||
@@ -124,7 +132,7 @@ FairEmail follows all the best practices for an email client as decribed in [thi
|
||||
* [(51) How are folders sorted?](#user-content-faq51)
|
||||
* [(52) Why does it take some time to reconnect to an account?](#user-content-faq52)
|
||||
* [(53) Can you stick the message action bar to the top/bottom?](#user-content-faq53)
|
||||
* [(54) How do I use a namespace prefix?](#user-content-faq54)
|
||||
* [~~(54) How do I use a namespace prefix?~~](#user-content-faq54)
|
||||
* [(55) How can I mark all messages as read / move or delete all messages?](#user-content-faq55)
|
||||
* [(56) Can you add support for JMAP?](#user-content-faq56)
|
||||
* [(57) Can I use HTML in signatures?](#user-content-faq57)
|
||||
@@ -134,7 +142,7 @@ FairEmail follows all the best practices for an email client as decribed in [thi
|
||||
* [(61) Why are some messages shown dimmed?](#user-content-faq61)
|
||||
* [(62) Which authentication methods are supported?](#user-content-faq62)
|
||||
* [(63) How are images resized for displaying on screens?](#user-content-faq63)
|
||||
* [(64) Can you add custom actions for swipe left/right?](#user-content-faq64)
|
||||
* [~~(64) Can you add custom actions for swipe left/right?~~](#user-content-faq64)
|
||||
* [(65) Why are some attachments shown dimmed?](#user-content-faq65)
|
||||
* [(66) Is FairEmail available in the Google Play Family Library?](#user-content-faq66)
|
||||
* [(67) How can I snooze conversations?](#user-content-faq67)
|
||||
@@ -156,7 +164,7 @@ FairEmail follows all the best practices for an email client as decribed in [thi
|
||||
* [(83) What does 'User is authenticated but not connected' mean?](#user-content-faq83)
|
||||
* [(84) What are local contacts for?](#user-content-faq84)
|
||||
* [(85) Why is an identity not available?](#user-content-faq85)
|
||||
* [(86) What are 'extra privacy features'?](#user-content-faq86)
|
||||
* [~~(86) What are 'extra privacy features'?~~](#user-content-faq86)
|
||||
* [(87) What does 'invalid credentials' mean?](#user-content-faq87)
|
||||
* [(88) How can I use a Yahoo! account?](#user-content-faq88)
|
||||
* [(89) How can I send plain text only messages?](#user-content-faq89)
|
||||
@@ -178,6 +186,20 @@ FairEmail follows all the best practices for an email client as decribed in [thi
|
||||
* [(105) How does the roam-like-at-home option work?](#user-content-faq105)
|
||||
* [(106) Which launchers can show the number of new messages?](#user-content-faq106)
|
||||
* [(107) How do I used colored stars?](#user-content-faq107)
|
||||
* [(108) Can you add permanently delete messages from any folder?](#user-content-faq108)
|
||||
* [~~(109) Why is 'select account' available in official versions only?~~](#user-content-faq109)
|
||||
* [(110) Why are (some) messages empty and/or attachments corrupted?](#user-content-faq110)
|
||||
* [(111) Can you add OAuth authentication?](#user-content-faq111)
|
||||
* [(112) Which email provider do you recommend?](#user-content-faq112)
|
||||
* [(113) How does biometric authentication work?](#user-content-faq113)
|
||||
* [(114) Can you add an import for the settings of other email apps?](#user-content-faq114)
|
||||
* [(115) Can you add email address chips?](#user-content-faq114)
|
||||
* [(116) How can I show images in messages from trusted senders by default?](#user-content-faq116)
|
||||
* [(117) Can you help me restore my purchase?](#user-content-faq117)
|
||||
* [(118) What does 'Remove tracking parameters' exactly?](#user-content-faq118)
|
||||
* [(119) Can you add colors to the unified inbox widget?](#user-content-faq119)
|
||||
* [(120) Why are new message notifications not removed on opening the app?](#user-content-faq120)
|
||||
* [(121) How are messages grouped into a conversation?](#user-content-faq121)
|
||||
|
||||
[I have another question.](#support)
|
||||
|
||||
@@ -189,13 +211,12 @@ The following Android permissions are needed:
|
||||
* *have full network access* (INTERNET): to send and receive email
|
||||
* *view network connections* (ACCESS_NETWORK_STATE): to monitor internet connectivity changes
|
||||
* *run at startup* (RECEIVE_BOOT_COMPLETED): to start monitoring on device start
|
||||
* *in-app billing* (BILLING): to allow in-app purchases
|
||||
* *foreground service* (FOREGROUND_SERVICE): to run a foreground service on Android 9 Pie and later, see also the next question
|
||||
* *prevent device from sleeping* (WAKE_LOCK): to keep the device awake while synchronizing messages
|
||||
* *in-app billing* (BILLING): to allow in-app purchases
|
||||
* Optional: *read your contacts* (READ_CONTACTS): to autocomplete addresses and to show photos
|
||||
* Optional: *find accounts on the device* (GET_ACCOUNTS): to use [OAuth](https://en.wikipedia.org/wiki/OAuth) instead of passwords
|
||||
* 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)
|
||||
* Android 5.1 Lollipop and before: *use accounts on the device* (USE_CREDENTIALS): needed to select accounts (not used/needed on later Android versions)
|
||||
* Optional: *use fingerprint hardware* (USE_FINGERPRINT) and use *biometric hardware* (USE_BIOMETRIC): to use biometric authentication
|
||||
|
||||
The following permissions are needed to show the count of unread messages as a badge (see also [this FAQ](#user-content-faq106)):
|
||||
|
||||
@@ -272,6 +293,9 @@ Invalid security certificate (*Can't verify identity of server*): you should try
|
||||
because invalid security certificates are insecure and allow [man-in-the-middle attacks](https://en.wikipedia.org/wiki/Man-in-the-middle_attack).
|
||||
If money is an obstacle, you can get free security certificates from [Let’s Encrypt](https://letsencrypt.org).
|
||||
|
||||
Note that older Android versions might not recognize newer certification authorities like Let’s Encrypt causing connections to be considered insecure,
|
||||
see also [here](https://developer.android.com/training/articles/security-ssl).
|
||||
|
||||
IMAP STARTTLS: the EFF [writes](https://www.eff.org/nl/deeplinks/2018/06/announcing-starttls-everywhere-securing-hop-hop-email-delivery):
|
||||
"*Additionally, even if you configure STARTTLS perfectly and use a valid certificate, there’s still no guarantee your communication will be encrypted.*"
|
||||
|
||||
@@ -280,6 +304,8 @@ Empty password: your username is likely easily guessed, so this is very insecure
|
||||
If you still want to use an invalid security certificate, IMAP STARTTLS or an empty password,
|
||||
you'll need to enable insecure connections in the account and/or identity settings.
|
||||
|
||||
Connections without encryption (either SSL or STARTTLS) are not supported because this is very insecure.
|
||||
|
||||
<br />
|
||||
|
||||
<a name="faq5"></a>
|
||||
@@ -290,7 +316,7 @@ In the three dot overflow menu you can enable or disable or select:
|
||||
* *text size*: for three different font sizes
|
||||
* *compact view*: for more condensed message items and a smaller message text font
|
||||
|
||||
In the display section of the advanced options you can enable or disable:
|
||||
In the display section of the settings you can enable or disable:
|
||||
|
||||
* *Unified inbox*: to disable the unified inbox and to list the folders selected for the unified inbox instead
|
||||
* *Group by date*: show date header above messages with the same date
|
||||
@@ -309,7 +335,7 @@ In the display section of the advanced options you can enable or disable:
|
||||
|
||||
Note that messages can be previewed only when the message text was downloaded.
|
||||
Larger message texts are not downloaded by default on metered (generally mobile) networks.
|
||||
You can change this in the advanced options.
|
||||
You can change this in the settings.
|
||||
|
||||
If the list of addresses is long, you can collapse the addresses section with the *less* icon at the top of the addresses section.
|
||||
|
||||
@@ -326,34 +352,58 @@ Unfortunately, it is impossible to make everybody happy and adding lots of setti
|
||||
<a name="faq6"></a>
|
||||
**(6) How can I login to Gmail / G suite?**
|
||||
|
||||
Preferably select Gmail as provider and select an account on your device.
|
||||
To use a Gmail/G suite account,
|
||||
you can either enable access for "less secure apps" and use your account password
|
||||
or enable two factor authentication and use an app specific password.
|
||||
|
||||
If you want/need to use a username/password instead of selecting an account, you'll need to enable access for "less secure" apps,
|
||||
see [here](https://support.google.com/accounts/answer/6010255) for Google's instructions
|
||||
Note that an app specific password is required when two factor authentication is enabled.
|
||||
|
||||
**Enable "Less secure apps"**
|
||||
|
||||
See [here](https://support.google.com/accounts/answer/6010255) about how to enable "less secure apps"
|
||||
or go [directy to the setting](https://www.google.com/settings/security/lesssecureapps).
|
||||
You can solve the error *535-5.7.8 Username and Password not accepted* by enabling "less secure" apps.
|
||||
|
||||
If you use your account username/password, you might get the alert "*Please log in via your web browser*".
|
||||
This security measure can for example be triggered when too many IP addresses were used in a too short time or when you are using a VPN.
|
||||
You can prevent this by using an app specific password.
|
||||
If you use multiple Gmail accounts, make sure you change the "less secure apps" setting of the right account(s).
|
||||
|
||||
To login to Gmail / G suite you'll sometimes need an app specific password, for example when two factor authentication is enabled.
|
||||
See here for instructions: [https://support.google.com/accounts/answer/185833](https://support.google.com/accounts/answer/185833).
|
||||
Be aware that you need to leave the "less secure apps" settings screen by using the back arrow to apply the setting.
|
||||
|
||||
If this doesn't work, see here for more solutions: [https://support.google.com/mail/accounts/answer/78754](https://support.google.com/mail/accounts/answer/78754)
|
||||
When "less secure apps" is not enabled,
|
||||
you'll get the error *Authentication failed - invalid credentials* for accounts (IMAP)
|
||||
and *Username and Password not accepted* for identities (SMTP).
|
||||
|
||||
**App specific password**
|
||||
|
||||
See [here](https://support.google.com/accounts/answer/185833) about how to generate an app specific password.
|
||||
|
||||
You might get the alert "*Please log in via your web browser*".
|
||||
This happens when Google considers the network that connects you to the internet (this could be a VPN) to to be unsafe.
|
||||
This can be prevented by using an app specific password.
|
||||
|
||||
See [here](https://support.google.com/mail/answer/7126229) for Google's instructions
|
||||
and [here](https://support.google.com/mail/accounts/answer/78754) for troubleshooting.
|
||||
|
||||
See [this FAQ](#user-content-faq111) about why OAuth is not being used.
|
||||
|
||||
<br />
|
||||
|
||||
<a name="faq7"></a>
|
||||
**(7) Why are sent messages not appearing sent folder?**
|
||||
**(7) Why are sent messages not appearing (directly) in the sent folder?**
|
||||
|
||||
Sent messages are normally added to the sent folder as soon as your provider adds the messages to the sent folder.
|
||||
Sent messages are normally moved from the outbox to the sent folder as soon as your provider adds sent messages to the sent folder.
|
||||
This requires a sent folder to be selected in the account settings and the sent folder to be set to synchronizing.
|
||||
If this doesn't happen, your provider might not keep track of sent messages or you might be using an SMTP server not related to the provider.
|
||||
In these cases you can enable the advanced identity setting *Store sent messages* to workaround this.
|
||||
|
||||
Note that FairEmail will automatically add sent messages to the sent folder when performing a full synchronize,
|
||||
which happens when reconnecting or if you synchronize manually.
|
||||
Some providers do not keep track of sent messages or the used SMTP server might not be related to the provider.
|
||||
In these cases FairEmail will automatically add sent messages to the sent folder on synchronizing the sent folder, which will happen after a message have been sent.
|
||||
Note that this will result in extra internet traffic.
|
||||
|
||||
~~If this doesn't happen, your provider might not keep track of sent messages or you might be using an SMTP server not related to the provider.~~
|
||||
~~In these cases you can enable the advanced identity setting *Store sent messages* to let FairEmail add sent messages to the sent folder right after sending a message.~~
|
||||
~~Note that enabling this setting might result in duplicate messages if your provider adds sent messages to the sent folder too.~~
|
||||
~~Also beware that enabling this setting will result in extra data usage, especially when when sending messages with large attachments.~~
|
||||
|
||||
~~If sent messages in the outbox are not found in the sent folder on a full synchronize, they will be moved from the outbox to the sent folder too.~~
|
||||
~~A full synchronize happens when reconnecting to the server or when synchronizing periodically or manually.~~
|
||||
~~You'll likely want to enable the advanced setting *Store sent messages* instead to move messages to the sent folder sooner.~~
|
||||
|
||||
<br />
|
||||
|
||||
@@ -361,14 +411,14 @@ which happens when reconnecting or if you synchronize manually.
|
||||
**(8) Can I use a Microsoft Exchange account?**
|
||||
|
||||
You can use a Microsoft Exchange account if it is accessible via IMAP.
|
||||
See here for more information: [https://support.office.com/en-us/article/what-is-a-microsoft-exchange-account-47f000aa-c2bf-48ac-9bc2-83e5c6036793](https://support.office.com/en-us/article/what-is-a-microsoft-exchange-account-47f000aa-c2bf-48ac-9bc2-83e5c6036793)
|
||||
See [here](https://support.office.com/en-us/article/what-is-a-microsoft-exchange-account-47f000aa-c2bf-48ac-9bc2-83e5c6036793) for more information.
|
||||
|
||||
Please see [here](#frequently-requested-features) about ActiveSync support.
|
||||
|
||||
<br />
|
||||
|
||||
<a name="faq9"></a>
|
||||
**(9) What are identities?**
|
||||
**(9) What are identities / how do I add an alias?**
|
||||
|
||||
Identities represent email addresses you are sending *from*.
|
||||
|
||||
@@ -412,6 +462,10 @@ First of all you need to install and configure [OpenKeychain](https://f-droid.or
|
||||
To encrypt and send a message just check the menu *Encrypt* and the message will be encrypted on sending.
|
||||
Similarly, to decrypt a received message, just select the menu *Decrypt* in the expanded message view.
|
||||
|
||||
The first time you send an encrypted message you might be asked for a sign key.
|
||||
FairEmail will automatically store the sign key ID in the selected identity for the next time.
|
||||
If you need to reset the sign key, just save the identity to clear the sign key ID again.
|
||||
|
||||
You can enable *Encrypt by default* in the identity settings, which replaces *Send* by *Encrypt and send*.
|
||||
|
||||
FairEmail will send the [Autocrypt](https://autocrypt.org/) headers for other email clients.
|
||||
@@ -449,7 +503,7 @@ You can start searching for messages on sender, recipient, subject, keyword or m
|
||||
You can also search from any app by select *Search email* in the copy/paste popup menu.
|
||||
|
||||
Messages will be searched on the device first (all accounts, all folders).
|
||||
There will be an action button with a cloud download icon at the bottom to search on the server.
|
||||
There will be an action button with a search again icon at the bottom to search on the server.
|
||||
When the search was started in a specific folder,
|
||||
the same folder will be searched in on the server,
|
||||
else you can select which folder to search in on the server.
|
||||
@@ -471,10 +525,7 @@ Searching messages is a pro feature.
|
||||
To use Outlook or Hotmail with two factor authentication enabled, you need to create an app password.
|
||||
See [here](https://support.microsoft.com/en-us/help/12409/microsoft-account-app-passwords-two-step-verification) for the details.
|
||||
|
||||
Unfortunately, Outlook and Hotmail do not properly support OAuth for IMAP/SMTP connections, so there is no other way.
|
||||
|
||||
Technical background: [MSAL](https://github.com/AzureAD/microsoft-authentication-library-for-android) is supported for business accounts only
|
||||
and OAuth requires embedding a client secret in the app.
|
||||
See [here](https://support.office.com/en-us/article/pop-imap-and-smtp-settings-for-outlook-com-d088b986-291d-42b8-9564-9c414e2aa040) for Microsoft's instructions.
|
||||
|
||||
<br />
|
||||
|
||||
@@ -485,6 +536,9 @@ The message header and message body are fetched separately from the server.
|
||||
The message text of larger messages is not being pre-fetched on metered connections and need to be fetched on opening the message.
|
||||
The message text will keep loading if there is no connection to the account, see also the next question.
|
||||
|
||||
You can check the account and folder list for the account and folder state (see the legend for the meaning of the icons)
|
||||
and the operation list accessible via the main navigation menu for pending operations (see [this FAQ](#user-content-faq3) for the meaning of the operations).
|
||||
|
||||
In the advanced settings you can set the maximum size for automatically downloading of messages on metered connections.
|
||||
|
||||
Mobile connections are almost always metered and some (paid) Wi-Fi hotspots are too.
|
||||
@@ -538,6 +592,7 @@ The right question is "*why are there so many taxes and fees?*":
|
||||
* VAT: 25 % (depending on your country)
|
||||
* Google fee: 30 %
|
||||
* Income tax: 50 %
|
||||
* <sub>Paypal fee: 5-10 % depending on the country/amount</sub>
|
||||
|
||||
So, what is left for the developer is just a fraction of what you pay.
|
||||
|
||||
@@ -546,6 +601,9 @@ Note that only some convenience and advanced features need to be purchased which
|
||||
Also note that most free apps will appear not to be sustainable in the end, whereas FairEmail is properly maintained and supported,
|
||||
and that free apps may have a catch, like sending privacy sensitive information to the internet.
|
||||
|
||||
I have been working on FairEmail almost every day for about a year, so I think the price is more than reasonable.
|
||||
For this reason there won't be discounts either.
|
||||
|
||||
<br />
|
||||
|
||||
<a name="faq20"></a>
|
||||
@@ -569,11 +627,13 @@ Before Android 8 Oreo: there is an advanced option in the setup for this.
|
||||
Android 8 Oreo and later: see [here](https://developer.android.com/training/notify-user/channels) about how to configure notification channels.
|
||||
You can use the button *Manage notifications* in the setup to directly go to the Android notification settings.
|
||||
Note that apps cannot change notification settings, including the notification light setting, on Android 8 Oreo and later anymore.
|
||||
Apps designed and targeting older Android versions might still be able to control the contents of notifications,
|
||||
but such apps cannot be updated anymore and recent Android versions will show a warning such apps are outdated.
|
||||
|
||||
<br />
|
||||
|
||||
<a name="faq22"></a>
|
||||
**(22) What do 'Couldn't connect to host', 'Connection refused', 'Network unreachable', 'Software caused connection abort', 'Connection reset by peer' and 'Read timed out' mean?**
|
||||
**(22) What do 'Couldn't connect to host', 'Connection refused', 'Network unreachable', 'Software caused connection abort', 'Connection reset by peer', 'Read timed out' and 'Broken pipe' mean?**
|
||||
|
||||
The messages *... Couldn't connect to host ...*, *... Connection refused ...* or *... Network unreachable ...*
|
||||
mean that FairEmail was not able to connect to the email server.
|
||||
@@ -582,9 +642,11 @@ The message *... Software caused connection abort ...*
|
||||
means that the email server or something between FairEmail and the email server actively terminated an existing connection.
|
||||
This can for example happen when connectivity was abruptly lost. A typical example is turning on flight mode.
|
||||
|
||||
The message *... Connection reset by peer ...* means that the email server actively terminated an existing connection.
|
||||
The message *... Connection reset by peer ...* or *... Broken pipe ...* means that the email server actively terminated an existing connection.
|
||||
|
||||
The message *... Read timed out ...* means that the email server is not responding anymore or that the internet connction is bad.
|
||||
The message *... Read timed out ...* means that the email server is not responding anymore or that the internet connection is bad.
|
||||
|
||||
See [here](https://linux.die.net/man/3/connect) for what error codes like EHOSTUNREACH and ETIMEDOUT mean.
|
||||
|
||||
Possible causes are:
|
||||
|
||||
@@ -592,6 +654,7 @@ Possible causes are:
|
||||
* The host name or port number is invalid
|
||||
* The are problems with the internet connection
|
||||
* The email server is refusing to accept connections
|
||||
* The email server is refusing to accept a message, for example because it is too large or contains unacceptable links
|
||||
* There are too many connections to the server, see also the next question
|
||||
|
||||
If you are using a VPN, the VPN provider might block the connection because it is too aggressively trying to prevent spam.
|
||||
@@ -602,7 +665,7 @@ This delay will be doubled after each failed attempt to prevent draining the bat
|
||||
<br />
|
||||
|
||||
<a name="faq23"></a>
|
||||
**(23) Why do I get 'Too many simultaneous connections' ?**
|
||||
**(23) Why do I get 'Too many simultaneous connections' or 'Maximum number of connections ... exceeded' ?**
|
||||
|
||||
The message *Too many simultaneous connections* is sent by the email server
|
||||
when there are too many folder connections for the same email account at the same time.
|
||||
@@ -681,14 +744,16 @@ for example to set a specific notification sound or to show notifications on the
|
||||
|
||||
FairEmail has the following notification channels:
|
||||
|
||||
* Service: used for the foreground service notification, see also [this FAQ](#user-content-faq2)
|
||||
* Service: used for the notification of the synchronize service, see also [this FAQ](#user-content-faq2)
|
||||
* Send: used for the notification of the send service
|
||||
* Notifications: used for new message notifications
|
||||
* Warning: used for warning notifications
|
||||
* Error: used for error notifications
|
||||
|
||||
See [here](https://developer.android.com/guide/topics/ui/notifiers/notifications#ManageChannels) for details on notification channels.
|
||||
In short: tap on the notification channel name to access the channel settings.
|
||||
|
||||
On Android before Android 8 Oreo you can set the notification sound in the advanced options.
|
||||
On Android before Android 8 Oreo you can set the notification sound in the settings.
|
||||
|
||||
See [this FAQ](#user-content-faq21) if your device has a notification light.
|
||||
|
||||
@@ -776,11 +841,13 @@ Setting identity colors is a pro feature.
|
||||
Viewing remotely stored images (see also [this FAQ](#user-content-faq27)) might not only tell the sender that you have seen the message,
|
||||
but will also leak your IP address.
|
||||
|
||||
Opening attachments or viewing an original message might execute scripts,
|
||||
Opening attachments or viewing an original message might load remote content and execute scripts,
|
||||
that might not only cause privacy sensitive information to leak, but can also be a security risk.
|
||||
|
||||
Note that your contacts could unknowingly send malicious messages if they got infected with malware.
|
||||
|
||||
FairEmail formats messages again causing messages to look different from the original, but also uncovering phishing links.
|
||||
|
||||
The Gmail app shows images by default by downloading the images through a Google proxy server.
|
||||
Since the images are downloaded from the source server [in real-time](https://blog.filippo.io/how-the-new-gmail-image-proxy-works-and-what-this-means-for-you/),
|
||||
this is even less secure because Google is involved too without providing much benefit.
|
||||
@@ -833,12 +900,6 @@ If you cannot solve the problem with the purchase, you will have to contact Goog
|
||||
<a name="faq39"></a>
|
||||
**(39) How can I reduce the battery usage of FairEmail?**
|
||||
|
||||
First of all, update to [the latest version](https://github.com/M66B/open-source-email/releases/).
|
||||
|
||||
It is inevitable that synchronizing messages will use battery power because it requires network access and accessing the messages database.
|
||||
|
||||
Reconnecting to an email server will use extra battery power, so an unstable internet connection will result in extra battery usage.
|
||||
|
||||
Recent Android versions by default report *app usage* as a percentage in the Android battery settings screen.
|
||||
Confusingly, *app usage* is not the same as *battery usage*.
|
||||
The app usage will be very high because FairEmail is using a foreground service which is considered as constant app usage by Android.
|
||||
@@ -847,10 +908,17 @@ The real battery usage can be seen by using the three dot overflow menu *Show fu
|
||||
As a rule of thumb the battery usage should be below or in any case not be much higher than *Mobile network standby*.
|
||||
If this isn't the case, please let me know.
|
||||
|
||||
It is inevitable that synchronizing messages will use battery power because it requires network access and accessing the messages database.
|
||||
|
||||
Reconnecting to an email server will use extra battery power, so an unstable internet connection will result in extra battery usage.
|
||||
In this case you might want to synchronize periodically, for example each hour, instead of continuously.
|
||||
Note that polling frequently (more than every 30-60 minutes) will likely use more battery power than synchronizing always
|
||||
because connection to the server and comparing the local and remotes messages are expensive operations.
|
||||
|
||||
Most of the battery usage, not considering viewing messages, is due to synchronization (receiving and sending) of messages.
|
||||
So, to reduce the battery usage, set the number of days to synchronize message for to a lower value,
|
||||
especially if there are a lot of recent messages in a folder.
|
||||
Long press a folder name in the folders list to access this setting.
|
||||
Long press a folder name in the folders list and select *Edit properties* to access this setting.
|
||||
|
||||
If you have at least once a day internet connectivity, it is sufficient to synchronize messages just for one day.
|
||||
|
||||
@@ -858,7 +926,7 @@ Note that you can set the number of days to *keep* messages for to a higher numb
|
||||
You could for example initially synchronize messages for a large number of days and after this has been completed
|
||||
reduce the number of days to synchronize messages for, but leave the number of days to keep messages for.
|
||||
|
||||
Starred messages will always be synchronized,
|
||||
In the receive settings you can enable to always synchronize starred messages,
|
||||
which will allow you to keep older messages around while synchronizing messages for a limited number of days.
|
||||
|
||||
Disabling the folder option *Automatically download message texts and attachments*
|
||||
@@ -866,7 +934,24 @@ will result in less network traffic and thus less battery usage.
|
||||
You could disable this option for example for the sent folder and the archive.
|
||||
|
||||
Synchronizing messages at night is mostly not useful, so you can save on battery usage by not synchronizing at night.
|
||||
In the advanced options you can set a schedule for message synchronization (this is a pro feature). See also [this FAQ](#user-content-faq78).
|
||||
In the settings you can select a schedule for message synchronization (this is a pro feature). See also [this FAQ](#user-content-faq78).
|
||||
|
||||
FairEmail will by default synchronize the folder list on each connection.
|
||||
Since folders are mostly not created, renamed and deleted very often, you can save some network and battery usage by disabling this in the receive settings.
|
||||
|
||||
FairEmail will by default check if old messages were deleted from the server on each connection.
|
||||
If you don't mind that old messages that were delete from the server are still visible in FairEmail, you can save some network and battery usage by disabling this in the receive settings.
|
||||
|
||||
Some providers don't follow the IMAP standard and don't keep connections open long enough, forcing FairEmail to reconnect often, causing extra battery usage.
|
||||
You can inspect the *Log* via the main navigation menu to check if there are frequent reconnects.
|
||||
You can workaround this by lowering the keep-alive interval in the advanced account settings to for example 9 or 15 minutes.
|
||||
|
||||
Some providers send every two minutes something like '*Still there*' resulting in network traffic and your device to wake up and causing unnecessary extra battery usage.
|
||||
You can inspect the *Log* via the main navigation menu to check if your provider is doing this.
|
||||
If your provider is using [Dovecot](https://www.dovecot.org/) as IMAP server,
|
||||
you could ask your provider to change the [imap_idle_notify_interval](https://wiki.dovecot.org/Timeouts) setting to a higher value or better yet, to disable this.
|
||||
If your provider is not able or willing to change/disable this, you should consider to switch to periodically instead of continuous synchronization.
|
||||
You can change this in the receive settings.
|
||||
|
||||
If you got the message *This provider does not support push messages* while configuring an account,
|
||||
consider switching to a modern provider which supports push messages (IMAP IDLE) to reduce battery usage.
|
||||
@@ -874,6 +959,8 @@ consider switching to a modern provider which supports push messages (IMAP IDLE)
|
||||
If your device has an [AMOLED](https://en.wikipedia.org/wiki/AMOLED) screen,
|
||||
you can save battery usage while viewing messages by switching to the black theme.
|
||||
|
||||
Finally, make sure you are using [the latest version](https://github.com/M66B/FairEmail/releases/).
|
||||
|
||||
<br />
|
||||
|
||||
<a name="faq40"></a>
|
||||
@@ -881,9 +968,8 @@ you can save battery usage while viewing messages by switching to the black them
|
||||
|
||||
You can reduce the network usage basically in the same way as reducing battery usage, see the previous question for suggestions.
|
||||
|
||||
Additionally, you can set FairEmail to download small messages and attachments on a metered (mobile, paid) connection only
|
||||
or let FairEmail connect via unmetered connections only.
|
||||
These advanced settings are accessible via *Setup* > *Advanced options*.
|
||||
By default FairEmail does not download message texts and attachments larger than 256 KiB when there is a metered (mobile or paid Wi-Fi) internet connection.
|
||||
You can change this in the connection settings.
|
||||
|
||||
<br />
|
||||
|
||||
@@ -1066,17 +1152,17 @@ You can use the *Home* button to quickly go to the top of the message.
|
||||
<br />
|
||||
|
||||
<a name="faq54"></a>
|
||||
**(54) How do I use a namespace prefix?**
|
||||
**~~(54) How do I use a namespace prefix?~~**
|
||||
|
||||
A namespace prefix is used to automatically remove the prefix providers sometimes add to folder names.
|
||||
~~A namespace prefix is used to automatically remove the prefix providers sometimes add to folder names.~~
|
||||
|
||||
For example the Gmail spam folder is called:
|
||||
~~For example the Gmail spam folder is called:~~
|
||||
|
||||
```
|
||||
[Gmail]/Spam
|
||||
```
|
||||
|
||||
By setting the namespace prefix to *[Gmail]* FairEmail will automatically remove *[Gmail]/* from all folder names.
|
||||
~~By setting the namespace prefix to *[Gmail]* FairEmail will automatically remove *[Gmail]/* from all folder names.~~
|
||||
|
||||
<br />
|
||||
|
||||
@@ -1139,11 +1225,11 @@ but even Google's Chrome cannot handle this.
|
||||
<a name="faq60"></a>
|
||||
**(60) Did you know ... ?**
|
||||
|
||||
* Did you know that starred messages are always synchronized/kept?
|
||||
* Did you know that starred messages are by default synchronized/kept? (this can be changed in the receive settings)
|
||||
* Did you know that you can long press the 'write message' icon to go to the drafts folder?
|
||||
* Did you know that you can long press the account name in the navigation menu to go to the inbox of that account?
|
||||
* Did you know there is an advanced option to mark messages read when they are moved and that archiving and trashing is also moving?
|
||||
* Did you know that you can select text (or an email address) in any app on recent Android versions and let FairEmail search for it? You'll need to set a primary account and an archive folder for this to work, so FairEmail knows where to search. There will be 'FairEmail' in the menu with copy, cut, etc.
|
||||
* Did you know there is an advanced option to mark messages read when they are moved? (archiving and trashing is also moving)
|
||||
* Did you know that you can select text (or an email address) in any app on recent Android versions and let FairEmail search for it?
|
||||
* Did you know that FairEmail has a tablet mode? Rotate your device in landscape mode and conversation threads will be opened in a second column if there is enough screen space.
|
||||
* Did you know that you can long press a reply template to create a draft message from the template?
|
||||
* Did you know that you can long press, hold and swipe to select a range of messages?
|
||||
@@ -1151,6 +1237,8 @@ but even Google's Chrome cannot handle this.
|
||||
* Did you know that you can swipe a conversation left or right to go to the next or previous conversation?
|
||||
* Did you know that you can tap on an image to see where it will be downloaded from?
|
||||
* Did you know that you can long press the folder icon in the action bar to select an account?
|
||||
* Did you know that you can long press the star icon in a conversation thread to set a colored star?
|
||||
* Did you know that you can open the navigation drawer by swiping from the left, even when viewing a conversation?
|
||||
|
||||
<br />
|
||||
|
||||
@@ -1177,7 +1265,6 @@ The following authentication methods are supported and used in this order:
|
||||
* LOGIN
|
||||
* PLAIN
|
||||
* NTLM (untested)
|
||||
* XOAUTH2 (used when a Google account is selected)
|
||||
|
||||
SASL authentication methods, like CRAM-MD5, are not supported
|
||||
because [JavaMail for Android](https://javaee.github.io/javamail/Android) does not support SASL authentication.
|
||||
@@ -1200,21 +1287,21 @@ There is an advanced option to disable automatically resizing and to set the tar
|
||||
<br />
|
||||
|
||||
<a name="faq64"></a>
|
||||
**(64) Can you add custom actions for swipe left/right?**
|
||||
**~~(64) Can you add custom actions for swipe left/right?~~**
|
||||
|
||||
The most natural thing to do when swiping a list entry left or right is to remove the entry from the list.
|
||||
The most natural action in the context of an email app is moving the message out of the folder to another folder.
|
||||
You can select the folder to move to in the account settings.
|
||||
~~The most natural thing to do when swiping a list entry left or right is to remove the entry from the list.~~
|
||||
~~The most natural action in the context of an email app is moving the message out of the folder to another folder.~~
|
||||
~~You can select the folder to move to in the account settings.~~
|
||||
|
||||
Other actions, like marking messages read and snoozing messages are available via multiple selection.
|
||||
You can long press a message to start multiple selection. See also [this question](#user-content-faq55).
|
||||
~~Other actions, like marking messages read and snoozing messages are available via multiple selection.~~
|
||||
~~You can long press a message to start multiple selection. See also [this question](#user-content-faq55).~~
|
||||
|
||||
Swiping left or right to mark a message read or unread is unnatural because the message first goes away and later comes back in a different shape.
|
||||
Note that there is an advanced option to mark messages automatically read on moving,
|
||||
which is in most cases a perfect replacement for the sequence mark read and move to some folder.
|
||||
You can also mark messages read from new message notifications.
|
||||
~~Swiping left or right to mark a message read or unread is unnatural because the message first goes away and later comes back in a different shape.~~
|
||||
~~Note that there is an advanced option to mark messages automatically read on moving,~~
|
||||
~~which is in most cases a perfect replacement for the sequence mark read and move to some folder.~~
|
||||
~~You can also mark messages read from new message notifications.~~
|
||||
|
||||
If you want to read a message later, you can hide it until a specific time by using the *snooze* menu.
|
||||
~~If you want to read a message later, you can hide it until a specific time by using the *snooze* menu.~~
|
||||
|
||||
<br />
|
||||
|
||||
@@ -1245,6 +1332,8 @@ Select the time the conversation(s) should snooze and confirm by tapping OK.
|
||||
The conversations will be hidden for the selected time and shown again afterwards.
|
||||
You will receive a new message notification as reminder.
|
||||
|
||||
It is also possible to snooze messages with [a rule](#user-content-faq71).
|
||||
|
||||
You can show snoozed messages by using the *Snoozed* item in the three dot overflow menu.
|
||||
|
||||
You can tap on the small snooze icon to see until when a conversation is snoozed.
|
||||
@@ -1271,7 +1360,7 @@ By selecting a zero snooze duration you can cancel snoozing.
|
||||
|
||||
The message list is automatically scrolled up when navigating from a new message notification or after a manual refresh.
|
||||
Always automatically scrolling up on arrival of new messages would interfere with your own scrolling,
|
||||
but if you like you can enable this in the advanced options.
|
||||
but if you like you can enable this in the settings.
|
||||
|
||||
<br />
|
||||
|
||||
@@ -1283,11 +1372,14 @@ When navigation to a conversation one message will be expanded if:
|
||||
* There is just one message in the conversation
|
||||
* There is exactly one unread message in the conversation
|
||||
|
||||
There is one exception: the message body text was not downloaded yet
|
||||
and the message body text is too large to download automatically on a metered connection.
|
||||
There is one exception: the message was not downloaded yet
|
||||
and the message is too large to download automatically on a metered (mobile) connection.
|
||||
You can set or disable the maximum message size on the 'connection' settings tab.
|
||||
|
||||
Duplicate (archived) messages, trashed messages and draft messages are not counted.
|
||||
|
||||
Messages will automatically be marked read on expanding.
|
||||
|
||||
<br />
|
||||
|
||||
<a name="faq71"></a>
|
||||
@@ -1302,6 +1394,8 @@ You can disable a rule and you can stop processing other rules after a rule has
|
||||
|
||||
All the conditions of a rule need to be true for a filter rule to be executed.
|
||||
Conditions are optional, but there needs to be at least one condition.
|
||||
You can use multiple rules, possibly with a *stop processing*, for an *or* condition.
|
||||
|
||||
Matching is not case sensitive, unless you use [regular expressions](https://en.wikipedia.org/wiki/Regular_expression).
|
||||
|
||||
In the *more* message menu there is an item to create a rule for a received message with the most common conditions filled in.
|
||||
@@ -1310,7 +1404,10 @@ You can select one of these actions to apply to matching messages:
|
||||
|
||||
* Mark as read
|
||||
* Mark as unread
|
||||
* Snooze
|
||||
* Add star
|
||||
* Move
|
||||
* Copy
|
||||
* Reply template
|
||||
* Automation
|
||||
|
||||
@@ -1400,10 +1497,10 @@ so there is little room for performance improvements.
|
||||
<a name="faq78"></a>
|
||||
**(78) How do I use schedules?**
|
||||
|
||||
In the advanced options you can enable scheduling and set the time to turn synchronizing automatically on and off.
|
||||
In the settingss you can enable scheduling and set the time to turn synchronizing automatically on and off.
|
||||
|
||||
An end time equal to or earlier than the start time is considered to be 24 hours later.
|
||||
|
||||
|
||||
Turning FairEmail on or off, for example by using [a quick settings tile](#user-content-faq30), will not turn scheduling off.
|
||||
This means that the next schedule event will still turn FairEmail on or off.
|
||||
|
||||
@@ -1416,6 +1513,13 @@ You can also automate turning synchronization on and off by sending these comman
|
||||
|
||||
Sending these commands will automatically turn scheduling off.
|
||||
|
||||
It is also possible to just enable/disable one account, for example the account with the name *Gmail*:
|
||||
|
||||
```
|
||||
(adb shell) am startservice -a eu.faircode.email.ENABLE --es account Gmail
|
||||
(adb shell) am startservice -a eu.faircode.email.DISABLE --es account Gmail
|
||||
```
|
||||
|
||||
You can automatically send commands with for example [Tasker](https://tasker.joaoapps.com/userguide/en/intents.html):
|
||||
|
||||
```
|
||||
@@ -1425,9 +1529,23 @@ Action: eu.faircode.email.ENABLE
|
||||
Target: Service
|
||||
```
|
||||
|
||||
To enable/disable an account with the name *Gmail*:
|
||||
|
||||
```
|
||||
Extras: account:Gmail
|
||||
```
|
||||
|
||||
Account names are case sensitive.
|
||||
|
||||
Automation can be used for more advanced schedules,
|
||||
like for example multiple synchronization periods per day or different synchronization periods for different days.
|
||||
|
||||
It is possible to install FairEmail in multiple user profiles, for example a personal and a work profile, and to configure FairEmail differently in each profile,
|
||||
which is another possibility to have different synchronization schedules and to synchronize a different set of accounts.
|
||||
|
||||
It is also possible to create [rules](#user-content-faq71) with a time condition and to snooze messages until the end time of the time condition.
|
||||
This way it is possible to snooze business related messages until the start of the business hours.
|
||||
|
||||
Scheduling is a pro feature.
|
||||
|
||||
<br />
|
||||
@@ -1446,6 +1564,8 @@ The synchronization process will also be started to execute [operations](#user-c
|
||||
for example to mark a message read, move a message or store a draft.
|
||||
This is to keep the local and remote message store synchronized.
|
||||
|
||||
If you want to synchronize some or all folders of an account manually, just disable synchronization for the folders (but not of the account).
|
||||
|
||||
<br />
|
||||
|
||||
<a name="faq80"></a>
|
||||
@@ -1519,13 +1639,12 @@ FairEmail will try to select the best identity based on the *to* address of the
|
||||
<br />
|
||||
|
||||
<a name="faq86"></a>
|
||||
**(86) What are 'extra privacy features'?**
|
||||
**~~(86) What are 'extra privacy features'?~~**
|
||||
|
||||
The advanced option *extra privacy features* enables:
|
||||
~~The advanced option *extra privacy features* enables:~~
|
||||
|
||||
* Looking up the owner of the IP address of a link
|
||||
* Detection and removal of [tracking images](#user-content-faq82)
|
||||
* Removal of [Urchin Tracking Module (UTM) parameters](https://en.wikipedia.org/wiki/UTM_parameters) from links
|
||||
* ~~Looking up the owner of the IP address of a link~~
|
||||
* ~~Detection and removal of [tracking images](#user-content-faq82)~~
|
||||
|
||||
<br />
|
||||
|
||||
@@ -1548,7 +1667,8 @@ You will likely need to save the associated identity again as well.
|
||||
For the correct settings, see [here](https://help.yahoo.com/kb/SLN4075.html).
|
||||
|
||||
You might need to enable "*less secure sign in*" for "*outdated*" apps,
|
||||
see [here](https://help.yahoo.com/kb/grant-temporary-access-outdated-apps-account-settings-sln27791.html).
|
||||
see [here](https://help.yahoo.com/kb/grant-temporary-access-outdated-apps-account-settings-sln27791.html) for more information.
|
||||
You can directly access this setting [here](https://login.yahoo.com/account/security#less-secure-apps).
|
||||
|
||||
Note that FairEmail is using the standard [IMAP protocol](https://en.wikipedia.org/wiki/Internet_Message_Access_Protocol), which is really not outdated.
|
||||
|
||||
@@ -1590,9 +1710,16 @@ This is why texts with dots are sometimes incorrectly recognized as links, which
|
||||
Spam filtering, verification of the [DKIM](https://en.wikipedia.org/wiki/DomainKeys_Identified_Mail) signature
|
||||
and [SPF](https://en.wikipedia.org/wiki/Sender_Policy_Framework) authorization is a task of email servers, not of an email client.
|
||||
|
||||
However, FairEmail can show a small vertical warning stripe at the end of the message header
|
||||
if DKIM, SPF or [DMARC](https://en.wikipedia.org/wiki/DMARC) authentication failed on the receiving server.
|
||||
You can enable this in the advanced options.
|
||||
However, FairEmail will show a small red warning flag
|
||||
when DKIM, SPF or [DMARC](https://en.wikipedia.org/wiki/DMARC) authentication failed on the receiving server.
|
||||
You can enable/disable [authentication verification](https://en.wikipedia.org/wiki/Email_authentication) in the behavior settings.
|
||||
|
||||
FairEmail can show a warning flag too when the domain name of the (reply) email address of the sender does not define an MX record pointing to an email server.
|
||||
This can be enabled in the receive settings. Be aware that this will slow down synchronization of messages significantly.
|
||||
|
||||
If legitimate messages are failing authentication, you should notify the sender because this will result in a high risk of messages ending up in the spam folder.
|
||||
Moreover, without proper authentication there is a risk the sender will be impersonated.
|
||||
The sender might use [this tool](https://www.mail-tester.com/) to check authentication and other things.
|
||||
|
||||
<br />
|
||||
|
||||
@@ -1620,7 +1747,7 @@ instead the Storage Access Framework, available and recommended since Android 4.
|
||||
If an app is listed depends on if the app implements a [document provider](https://developer.android.com/guide/topics/providers/document-provider).
|
||||
|
||||
Android Q will make it harder and maybe even impossible to directly access files,
|
||||
see [here](https://developer.android.com/preview/privacy/scoped-storage) for more details.
|
||||
see [here](https://developer.android.com/preview/privacy/scoped-storage) and [here](https://www.xda-developers.com/android-q-storage-access-framework-scoped-storage/) for more details.
|
||||
|
||||
<br />
|
||||
|
||||
@@ -1642,8 +1769,6 @@ About each four hours FairEmail runs a cleanup job that:
|
||||
* Removes old local contacts
|
||||
* Removes old log entries
|
||||
|
||||
You can see when the last cleanup was performed at the bottom of the advanced options.
|
||||
|
||||
Note that the cleanup job will only run when the synchronize service is active.
|
||||
|
||||
<br />
|
||||
@@ -1703,7 +1828,7 @@ The dot is meant as an aid when swiping left/right to go to the previous/next co
|
||||
<a name="faq102"></a>
|
||||
**(102) How can I enable auto rotation of images?**
|
||||
|
||||
Images will automatically be rotated when automatic resizing of images is enabled in the advanced options (enabled by default).
|
||||
Images will automatically be rotated when automatic resizing of images is enabled in the settings (enabled by default).
|
||||
However, automatic rotating depends on the [Exif](https://en.wikipedia.org/wiki/Exif) information to be present and to be correct,
|
||||
which is not always the case. Particularly not when taking a photo with a camara app from FairEmail.
|
||||
|
||||
@@ -1727,7 +1852,7 @@ Unfortunately and surprisingly, most recording apps do not seem to support this
|
||||
|
||||
* Error reports will help improve FairEmail
|
||||
* Error reporting is optional and opt-in
|
||||
* Error reporting can be enabled/disabled in the advanced options, section miscellaneous
|
||||
* Error reporting can be enabled/disabled in the settings, section miscellaneous
|
||||
* Error reports will automatically be sent anonymously to [Bugsnag](https://www.bugsnag.com/)
|
||||
* Bugsnag for Android is [open source](https://github.com/bugsnag/bugsnag-android)
|
||||
* See [here](https://docs.bugsnag.com/platforms/android/automatically-captured-data/) about what data will be sent in case of errors
|
||||
@@ -1760,8 +1885,8 @@ Note that this needs to be enabled in the advance options (default enabled).
|
||||
<a name="faq107"></a>
|
||||
**(107) How do I use colored stars?**
|
||||
|
||||
You can set a colored star via the *more* message menu, via multiple selection (started by long pressing a message)
|
||||
or automatically by using [rules](#user-content-faq71).
|
||||
You can set a colored star via the *more* message menu, via multiple selection (started by long pressing a message),
|
||||
by long pressing a star in a conversation or automatically by using [rules](#user-content-faq71).
|
||||
|
||||
You need to know that colored stars are not supported by the IMAP protocol and can therefore not be synchronized to an email server.
|
||||
This means that colored stars will not be visible in other email clients and will be lost on downloading messages again.
|
||||
@@ -1772,9 +1897,192 @@ However, not all servers support IMAP keywords and besides that there are no sta
|
||||
|
||||
<br />
|
||||
|
||||
<a name="faq108"></a>
|
||||
**(108) Can you add permanently delete messages from any folder?**
|
||||
|
||||
When you delete messages from a folder the messages will be moved to the trash folder, so you have a chance to restore the messages.
|
||||
You can permanently delete messages from the trash folder.
|
||||
Permanently delete messages from other folders would defeat the purpose of the trash folder, so this will not be added.
|
||||
|
||||
<br />
|
||||
|
||||
<a name="faq109"></a>
|
||||
**~~(109) Why is 'select account' available in official versions only?~~**
|
||||
|
||||
~~Using *select account* to select and authorize Google accounts require special permission from Google for security and privacy reasons.~~
|
||||
~~This special permission can only be acquired for apps a developer manages and is responsible for.~~
|
||||
~~Third party builds, like the F-Droid builds, are managed by third parties and are the responsibility of these third parties.~~
|
||||
~~So, only these third parties can acquire the required permission from Google.~~
|
||||
~~Since these third parties do not actually support FairEmail, they are most likely not going to request the required permission.~~
|
||||
|
||||
~~You can solve this in two ways:~~
|
||||
|
||||
* ~~Switch to the official version of FairEmail, see [here](https://github.com/M66B/FairEmail/blob/master/README.md#downloads) for the options~~
|
||||
* ~~Use app specific passwords, see [this FAQ](#user-content-faq6)~~
|
||||
|
||||
~~Using *select account* in third party builds is not possible in recent versions anymore.~~
|
||||
~~In older versions this was possible, but it will now result in the error *UNREGISTERED_ON_API_CONSOLE*.~~
|
||||
|
||||
<br />
|
||||
|
||||
<a name="faq110"></a>
|
||||
**(110) Why are (some) messages empty and/or attachments corrupt?**
|
||||
|
||||
Empty messages and/or corrupt attachments are probably being caused by a bug in the server software.
|
||||
Older Microsoft Exchange software is known to cause this problem.
|
||||
Mostly you can workaround this by disabling *Partial fetch* in the advanced account settings.
|
||||
|
||||
After disabling this setting, you can use the message 'more' (three dots) menu to 'resync' empty messages.
|
||||
Alternatively, you can *Delete local messages* by long pressing the folder(s) in the folder list and synchronize all messages again.
|
||||
|
||||
Disabling *Partial fetch* will result in more memory usage.
|
||||
|
||||
<br />
|
||||
|
||||
<a name="faq111"></a>
|
||||
**(111) Can you add OAuth authentication?**
|
||||
|
||||
(X)OAuth authentication, formerly available as *Select account* for Google accounts, requires creating an online (Google, Microsoft, etc) app,
|
||||
which would make authentication for many people dependent on one (developer) account, which is a bad idea.
|
||||
|
||||
See also [this related article](https://arstechnica.com/gadgets/2019/06/gmails-api-lockdown-will-kill-some-third-party-app-access-starting-july-15/).
|
||||
|
||||
Outlook and Hotmail do not properly support OAuth for IMAP/SMTP connections.
|
||||
[MSAL](https://github.com/AzureAD/microsoft-authentication-library-for-android) is supported for business accounts only
|
||||
and requires embedding a client secret in the app, which is not a good idea for an open source app.
|
||||
|
||||
See also [this FAQ](#user-content-faq6).
|
||||
|
||||
<br />
|
||||
|
||||
<a name="faq112"></a>
|
||||
**(112) Which email provider do you recommend?**
|
||||
|
||||
Which email provider is best for you depends on your wishes/requirements.
|
||||
Please see the websites of [Restore privacy](https://restoreprivacy.com/secure-email/) or [Privacy Tools](https://www.privacytools.io/providers/email/)
|
||||
for a list of privacy friendly email providers with advantages and disadvantages.
|
||||
|
||||
<br />
|
||||
|
||||
<a name="faq113"></a>
|
||||
**(113) How does biometric authentication work?**
|
||||
|
||||
If your device has a biometric sensor, for example a fingerprint sensor, you can enable/disable biometric authentication in the navigation (hamburger) menu of the setup screen.
|
||||
When enabled FairEmail will require biometric authentication after a period of inactivity or after the screen has been turned off while FairEmail was running.
|
||||
Activity is navigation within FairEmail, for example opening a conversation thread.
|
||||
The inactivity period duration can be configured in the miscellaneous settings.
|
||||
When biometric authentication is enabled new message notifications will not show any content and FairEmail won't be visible on the Android recents screen.
|
||||
|
||||
Biometric authentication is meant to prevent others from seeing your messages only.
|
||||
FairEmail relies on device encryption for data encryption, see also [this FAQ](#user-content-faq37).
|
||||
|
||||
Biometric authentication is a pro feature.
|
||||
|
||||
<br />
|
||||
|
||||
<a name="faq114"></a>
|
||||
**(114) Can you add an import for the settings of other email apps?**
|
||||
|
||||
The format of the settings files of most other email apps is not documented, so this is difficult.
|
||||
Sometimes it is possible to reverse engineer the format, but as soon as the settings format changes things will break.
|
||||
Also, settings are often incompatible.
|
||||
For example, FairEmail has unlike most other email apps settings for the number of days to synchronize messages for
|
||||
and for the number of days to keep messages for, mainly to save on battery usage.
|
||||
Moreover, setting up an account/identity with the quick setup is simple, so it is not really worth the effort.
|
||||
|
||||
<br />
|
||||
|
||||
<a name="faq115"></a>
|
||||
**(115) Can you add email address chips?**
|
||||
|
||||
Email address [chips](https://material.io/design/components/chips.html) look nice, but cannot be edited,
|
||||
which is quite inconvenient when you made a typo in an email address.
|
||||
|
||||
Chips are not suitable for showing in a list
|
||||
and since the message header in a list should look similar to the message header of the message view it is not an option to use chips for viewing messages.
|
||||
|
||||
Reverted [commit](https://github.com/M66B/FairEmail/commit/2c80c25b8aa75af2287f471b882ec87d5a5a5015).
|
||||
|
||||
<br />
|
||||
|
||||
<a name="faq116"></a>
|
||||
**(116) How can I show images in messages from trusted senders by default?**
|
||||
|
||||
You can show images in messages from trusted senders by default by enabled the display setting *Automatically show images for known contacts*.
|
||||
|
||||
Contacts in the Android contacts list are considered to be known and trusted,
|
||||
unless the contact is in the group / has the label '*Untrusted*' (case insensitive).
|
||||
|
||||
<br />
|
||||
|
||||
<a name="faq117"></a>
|
||||
**(117) Can you help me restore my purchase?**
|
||||
|
||||
Google manages all purchases, so as developer I have little control over purchases.
|
||||
So, the only thing I can do, is give some advice:
|
||||
|
||||
* Make sure you have an active internet connection
|
||||
* Make sure you are logged in with the right Google account and that there is nothing wrong with your Google account
|
||||
* Open the Play store application and wait at least a minute to give it time to synchronize with the Google servers
|
||||
* Open FairEmail and navigate to the pro features screen to let FairEmail check the purchases
|
||||
|
||||
You can also try to clear the cache of the Play store app via the Android apps settings.
|
||||
|
||||
Note that:
|
||||
|
||||
* Purchases are stored in the Google cloud and cannot get lost
|
||||
* There is no time limit on purchases, so they cannot expire
|
||||
* Google does not expose details (name, e-mail, etc) about buyers to developers
|
||||
* An application like FairEmail cannot select which Google account to use
|
||||
|
||||
<br />
|
||||
|
||||
<a name="faq118"></a>
|
||||
**(118) What does 'Remove tracking parameters' exactly?**
|
||||
|
||||
Checking *Remove tracking parameters* will remove all [UTM parameters](https://en.wikipedia.org/wiki/UTM_parameters) from a link.
|
||||
|
||||
<br />
|
||||
|
||||
<a name="faq119"></a>
|
||||
**(119) Can you add colors to the unified inbox widget?**
|
||||
|
||||
The widget is designed to look good on most home/launcher screens by making it monochrome and by using a half transparent background.
|
||||
This way the widget will nicely blend in, while still being properly readable.
|
||||
|
||||
Adding (account) colors will cause problems with some backgrounds and will cause readability problems, which is why this won't be added.
|
||||
|
||||
<br />
|
||||
|
||||
<a name="faq120"></a>
|
||||
**(120) Why are new message notifications not removed on opening the app?**
|
||||
|
||||
New message notifications will be removed on swiping notifications away or on marking the associated messages read.
|
||||
Opening the app will not remove new message notifications.
|
||||
This gives you a choice to leave new message notifications as a reminder that there are still unread messages.
|
||||
|
||||
On Android 7 Nougat and later new message notifications will be [grouped](https://developer.android.com/training/notify-user/group).
|
||||
Tapping on the summary notification will open the unified inbox.
|
||||
The summary notification can be expanded to view individual new message notifications.
|
||||
Tapping on an individual new message notification will open the conversation the message it is part of.
|
||||
See [this FAQ](#user-content-faq70) about when messages in a conversation will be auto expanded and marked read.
|
||||
|
||||
<br />
|
||||
|
||||
<a name="faq121"></a>
|
||||
**(121) How are messages grouped into a conversation?**
|
||||
|
||||
By default FairEmail groups messages in conversations. This can be turned of in the display settings.
|
||||
|
||||
FairEmail groups messages based on the standard *Message-ID*, *In-Reply-To* and *References* headers.
|
||||
FairEmail does not group on other criteria, like the subject,
|
||||
because this could result in grouping unrelated messages and would be at the expense of increased battery usage.
|
||||
|
||||
<br />
|
||||
|
||||
## Support
|
||||
|
||||
If you have another question, want to request a feature or report a bug, you can use [this forum](https://forum.xda-developers.com/android/apps-games/source-email-t3824168).
|
||||
Registration is free.
|
||||
|
||||
If you are a supporter of the project, you can get limited personal support by using [this form](https://contact.faircode.eu/?product=fairemail%2B).
|
||||
If you are a supporter of the project, you can get limited personal support by using [this form](https://contact.faircode.eu/?product=fairemailsupport).
|
||||
|
||||
@@ -5,12 +5,12 @@
|
||||
# Feature request
|
||||
|
||||
* Did you check if there wasn't a similar feature request?
|
||||
* Did you read [this FAQ](https://github.com/M66B/open-source-email/blob/master/FAQ.md#FAQ15)?
|
||||
* Did you read [this FAQ](https://github.com/M66B/FairEmail/blob/master/FAQ.md#FAQ15)?
|
||||
|
||||
# Bug report
|
||||
|
||||
* Did you check if there wasn't a similar bug report?
|
||||
* Are you using the [latest version](https://github.com/M66B/open-source-email/releases) of the app?
|
||||
* Are you using the [latest version](https://github.com/M66B/FairEmail/releases) of the app?
|
||||
|
||||
## Expected behavior
|
||||
|
||||
|
||||
10
PRIVACY.md
10
PRIVACY.md
@@ -1,7 +1,3 @@
|
||||
# FairEmail
|
||||
|
||||
*Open source, privacy friendly email app*
|
||||
|
||||
## Privacy policy
|
||||
|
||||
FairEmail **does not** collect any information.
|
||||
@@ -9,14 +5,14 @@ FairEmail **does not** collect any information.
|
||||
FairEmail **does not** store data on third party servers.
|
||||
|
||||
FairEmail **does not** require unnecessary permissions.
|
||||
For more information on permissions, see [this FAQ](https://github.com/M66B/open-source-email/blob/master/FAQ.md#user-content-faq1).
|
||||
For more information on permissions, see [this FAQ](https://github.com/M66B/FairEmail/blob/master/FAQ.md#user-content-faq1).
|
||||
|
||||
FairEmail **does not** allow other apps access to messages and attachments without your approval.
|
||||
|
||||
FairEmail **does** follow the recommendations of [this EFF article](https://www.eff.org/deeplinks/2019/01/stop-tracking-my-emails).
|
||||
|
||||
FairEmail is 100 % **open source**, see [the license](https://github.com/M66B/open-source-email/blob/master/LICENSE).
|
||||
FairEmail is 100 % **open source**, see [the license](https://github.com/M66B/FairEmail/blob/master/LICENSE).
|
||||
|
||||
Error reporting is **opt-in**, see [here](https://github.com/M66B/open-source-email/blob/master/FAQ.md#user-content-faq104) for more information.
|
||||
Error reporting is **opt-in**, see [here](https://github.com/M66B/FairEmail/blob/master/FAQ.md#user-content-faq104) for more information.
|
||||
|
||||
Copyright © 2018-2019 Marcel Bokhorst.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Important
|
||||
|
||||
* Did you read the [contributing section](https://github.com/M66B/open-source-email#contributing)?
|
||||
* Do you agree to [the license and the copyright](https://github.com/M66B/open-source-email#license)?
|
||||
* Did you read the [contributing section](https://github.com/M66B/FairEmail#contributing)?
|
||||
* Do you agree to [the license and the copyright](https://github.com/M66B/FairEmail#license)?
|
||||
|
||||
112
README.md
112
README.md
@@ -1,10 +1,20 @@
|
||||
<img src="https://github.com/M66B/FairEmail/raw/master/images/banner_crying6.png" />
|
||||
|
||||
<p align="center">
|
||||
<a href="#downloads">Downloads</a> •
|
||||
<a href="#privacy">Privacy</a> •
|
||||
<a href="#support">Support</a> •
|
||||
<a href="#license">License</a>
|
||||
</p>
|
||||
|
||||
<img align="right" src="https://raw.githubusercontent.com/M66B/FairEmail/master/app/src/main/res/mipmap-hdpi/ic_launcher.png">
|
||||
|
||||
# FairEmail
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
*Open source, privacy friendly email app for Android*
|
||||
|
||||
@@ -26,35 +36,47 @@ This app starts a foreground service with a low priority status bar notification
|
||||
|
||||
## Main features
|
||||
|
||||
* 100 % [open source](https://github.com/M66B/open-source-email/blob/master/LICENSE)
|
||||
* [Privacy friendly](https://github.com/M66B/open-source-email/blob/master/PRIVACY.md)
|
||||
* 100 % [open source](https://github.com/M66B/FairEmail/blob/master/LICENSE)
|
||||
* [Privacy friendly](https://github.com/M66B/FairEmail/blob/master/PRIVACY.md)
|
||||
* Multiple accounts
|
||||
* Multiple email addresses
|
||||
* Unified inbox
|
||||
* Flat [conversation threading](https://en.wikipedia.org/wiki/Conversation_threading)
|
||||
* Two way synchronization
|
||||
* Offline storage and operations
|
||||
* Encryption/decryption ([OpenPGP](https://www.openpgp.org/))
|
||||
* Battery friendly
|
||||
* Low data usage
|
||||
* Small (~6.5 MB)
|
||||
* Material design
|
||||
* Small (< 10 MB)
|
||||
* Material design (including dark theme)
|
||||
* Maintained and supported
|
||||
|
||||
## Privacy features
|
||||
|
||||
* Reformat messages to prevent [phishing](https://en.wikipedia.org/wiki/Phishing)
|
||||
* Confirm showing images to prevent tracking
|
||||
* Confirm opening links to prevent tracking and phishing
|
||||
* Automatically recognize and disable tracking images
|
||||
* Warning if messages could not be [authenticated](https://github.com/M66B/FairEmail/blob/master/FAQ.md#user-content-faq92)
|
||||
|
||||
## Pro features
|
||||
|
||||
All pro features are convenience or advanced features.
|
||||
|
||||
* Account/identity colors
|
||||
* Colored stars ([instructions](https://github.com/M66B/open-source-email/blob/master/FAQ.md#user-content-faq107))
|
||||
* Colored stars ([instructions](https://github.com/M66B/FairEmail/blob/master/FAQ.md#user-content-faq107))
|
||||
* Notification settings (sounds) per account/folder/sender (requires Android 8 Oreo)
|
||||
* Configurable notification actions
|
||||
* Snooze messages ([instructions](https://github.com/M66B/open-source-email/blob/master/FAQ.md#user-content-faq67))
|
||||
* Snooze messages ([instructions](https://github.com/M66B/FairEmail/blob/master/FAQ.md#user-content-faq67))
|
||||
* Send messages after selected time
|
||||
* Synchronization scheduling ([instructions](https://github.com/M66B/open-source-email/blob/master/FAQ.md#user-content-faq78))
|
||||
* Synchronization scheduling ([instructions](https://github.com/M66B/FairEmail/blob/master/FAQ.md#user-content-faq78))
|
||||
* Reply templates
|
||||
* Accept/decline calendar invitations
|
||||
* Filter rules ([instructions](https://github.com/M66B/open-source-email/blob/master/FAQ.md#user-content-faq71))
|
||||
* Search on device or server ([instructions](https://github.com/M66B/open-source-email/blob/master/FAQ.md#user-content-faq13))
|
||||
* Filter rules ([instructions](https://github.com/M66B/FairEmail/blob/master/FAQ.md#user-content-faq71))
|
||||
* Search on device or server ([instructions](https://github.com/M66B/FairEmail/blob/master/FAQ.md#user-content-faq13))
|
||||
* Keyword management
|
||||
* Encryption/decryption ([OpenPGP](https://www.openpgp.org/)) ([instructions](https://github.com/M66B/open-source-email/blob/master/FAQ.md#user-content-faq12))
|
||||
* Biometric authentication ([instructions](https://github.com/M66B/FairEmail/blob/master/FAQ.md#user-content-faq113))
|
||||
* Unified inbox widget
|
||||
* Export settings
|
||||
|
||||
## Simple
|
||||
@@ -71,7 +93,7 @@ This app starts a foreground service with a low priority status bar notification
|
||||
* Confirm opening links, images and attachments
|
||||
* No special permissions required
|
||||
* No advertisements
|
||||
* No analytics and no tracking
|
||||
* No analytics and no tracking ([error reporting](https://github.com/M66B/FairEmail/blob/master/FAQ.md#user-content-faq104) is opt-in)
|
||||
* No [Google backup](https://developer.android.com/guide/topics/data/backup)
|
||||
* FairEmail is an original work, not a fork or a clone
|
||||
|
||||
@@ -86,8 +108,15 @@ Please see [here](https://email.faircode.eu/#screenshots) for screenshots.
|
||||
|
||||
## Downloads
|
||||
|
||||
* [GitHub](https://github.com/M66B/open-source-email/releases)
|
||||
* [Play store](https://play.google.com/apps/testing/eu.faircode.email)
|
||||
* [GitHub](https://github.com/M66B/FairEmail/releases)
|
||||
* [Play store](https://play.google.com/store/apps/details?id=eu.faircode.email)
|
||||
* [Play store](https://play.google.com/apps/testing/eu.faircode.email) (test)
|
||||
|
||||
To download a GitHub release you might need to expand the assets section to download the [APK file](https://en.wikipedia.org/wiki/Android_application_package).
|
||||
|
||||
The GitHub version is being updated more often than the Play store version.
|
||||
The GitHub release will automatically check for updates on GitHub.
|
||||
You can turn this off in the miscellaneous settings.
|
||||
|
||||
Certificate fingerprints:
|
||||
|
||||
@@ -101,18 +130,19 @@ One line command to display certificate fingerprints:
|
||||
|
||||
```unzip -p fairemail.apk META-INF/CERT.RSA | keytool -printcert```
|
||||
|
||||
I do not hand over the signing keys of my apps to Google.
|
||||
|
||||
* [F-Droid](https://f-droid.org/en/packages/eu.faircode.email/) ([last build status](https://f-droid.org/wiki/page/eu.faircode.email/lastbuild))
|
||||
|
||||
Note that F-Droid builds new versions irregularly and you'll need the F-Droid client to get update notifications.
|
||||
To get updates in a timely fashion you are advised to use the GitHub release.
|
||||
The GitHub release will automatically check for updates on GitHub.
|
||||
You can turn this off in the advanced options.
|
||||
|
||||
|
||||
Because F-Droid builds and GitHub releases are signed differently, an F-Droid build needs to be uninstalled first to be able to update to a GitHub release.
|
||||
|
||||
## Privacy
|
||||
|
||||
Please see [here](https://github.com/M66B/open-source-email/blob/master/PRIVACY.md#fairemail) for the privacy policy.
|
||||
Please see [here](https://github.com/M66B/FairEmail/blob/master/PRIVACY.md#fairemail) for the privacy policy.
|
||||
|
||||
## Compatibility
|
||||
|
||||
@@ -122,11 +152,11 @@ because earlier Android versions do not support notification grouping.
|
||||
|
||||
FairEmail will work properly on devices without any Google service installed.
|
||||
|
||||
See [here](https://github.com/M66B/open-source-email/blob/master/FAQ.md#known-problems) for known problems.
|
||||
See [here](https://github.com/M66B/FairEmail/blob/master/FAQ.md#known-problems) for known problems.
|
||||
|
||||
## Support / frequently asked questions
|
||||
## Support
|
||||
|
||||
See [here](https://github.com/M66B/open-source-email/blob/master/FAQ.md) for a list of often asked questions and about how to get support.
|
||||
See [here](https://github.com/M66B/FairEmail/blob/master/FAQ.md) for a list of often asked questions and about how to get support.
|
||||
|
||||
## Contributing
|
||||
|
||||
@@ -152,45 +182,21 @@ Please note that you agree to the license below by contributing, including the c
|
||||
|
||||
## Attribution
|
||||
|
||||
FairEmail uses:
|
||||
|
||||
* [JavaMail](https://projects.eclipse.org/projects/ee4j.javamail). Copyright (c) 1997-2018 Oracle® and/or its affiliates. All rights reserved. [GPLv2+CE license](https://javaee.github.io/javamail/JavaMail-License).
|
||||
* [jsoup](https://jsoup.org/). Copyright © 2009 - 2017 Jonathan Hedley. [MIT license](https://jsoup.org/license).
|
||||
* [Android Support Library](https://developer.android.com/tools/support-library/). Copyright (C) 2011 The Android Open Source Project. [Apache license](https://android.googlesource.com/platform/frameworks/support/+/master/LICENSE.txt).
|
||||
* [Android Architecture Components](https://developer.android.com/topic/libraries/architecture/). Copyright 2018 The Android Open Source Project, Inc. [Apache license](https://github.com/googlesamples/android-architecture-components/blob/master/LICENSE).
|
||||
* [colorpicker](https://android.googlesource.com/platform/frameworks/opt/colorpicker). Copyright (C) 2013 The Android Open Source Project. [Apache license](https://android.googlesource.com/platform/frameworks/opt/colorpicker/+/master/src/com/android/colorpicker/ColorPickerDialog.java).
|
||||
* [dnsjava](http://www.xbill.org/dnsjava/). Copyright (c) 1998-2011, Brian Wellington. [BSD License](https://sourceforge.net/p/dnsjava/code/HEAD/tree/trunk/LICENSE).
|
||||
* [OpenPGP API library](https://github.com/open-keychain/openpgp-api). Copyright (C) 2014-2015 Dominik Schürmann. [Apache License 2.0](https://github.com/open-keychain/openpgp-api/blob/master/LICENSE).
|
||||
* [Android SQLite support library](https://github.com/requery/sqlite-android). Copyright (C) 2017 requery.io. [Apache License 2.0](https://github.com/requery/sqlite-android/blob/master/LICENSE).
|
||||
* [App shortcut icon generator](https://romannurik.github.io/AndroidAssetStudio/icons-app-shortcut.html). Copyright ???. [Apache License 2.0](https://github.com/romannurik/AndroidAssetStudio/blob/master/LICENSE).
|
||||
* [Mozilla ISPDB](https://developer.mozilla.org/en-US/docs/Mozilla/Thunderbird/Autoconfiguration#ISPDB). *Free to use for any client.*
|
||||
* [ShortcutBadger](https://github.com/leolin310148/ShortcutBadger). Copyright 2014 Leo Lin. [Apache license](https://github.com/leolin310148/ShortcutBadger/blob/master/LICENSE).
|
||||
* [PhotoView](https://github.com/chrisbanes/PhotoView). Copyright 2018 Chris Banes. [Apache License](https://github.com/chrisbanes/PhotoView/blob/master/LICENSE).
|
||||
* [Bugsnag exception reporter for Android](https://github.com/bugsnag/bugsnag-android). Copyright (c) 2012 Bugsnag. [MIT License](https://github.com/bugsnag/bugsnag-android/blob/master/LICENSE.txt).
|
||||
* [biweekly](https://github.com/mangstadt/biweekly). Copyright (c) 2013-2018, Michael Angstadt. [BSD 2-Clause](https://github.com/mangstadt/biweekly/blob/master/LICENSE).
|
||||
See [here](https://github.com/M66B/FairEmail/blob/master/ATTRIBUTION.md) for a list of used libraries and associated license information.
|
||||
|
||||
Error reporting is sponsored by:
|
||||
|
||||

|
||||
|
||||
[Bugsnag](https://www.bugsnag.com/) monitors application stability
|
||||
and is used to [help improve FairEmail](https://github.com/M66B/open-source-email/blob/master/FAQ.md#user-content-faq104).
|
||||
and is used to [help improve FairEmail](https://github.com/M66B/FairEmail/blob/master/FAQ.md#user-content-faq104).
|
||||
|
||||
## License
|
||||
|
||||
Copyright © 2018-2019 Marcel Bokhorst. All rights reserved.
|
||||
|
||||
[GNU General Public License version 3](https://www.gnu.org/licenses/gpl.txt)
|
||||
|
||||
Copyright © 2018-2019 Marcel Bokhorst. All rights reserved
|
||||
> FairEmail is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
|
||||
FairEmail is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
FairEmail is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with FairEmail. If not, see [https://www.gnu.org/licenses/](https://www.gnu.org/licenses/).
|
||||
> FairEmail is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
33
SETUP.md
33
SETUP.md
@@ -7,42 +7,44 @@ The quick setup will add an account and an identity in one go for most major pro
|
||||
|
||||
## Requirements
|
||||
|
||||
An internet connection is required to setup accounts and identities.
|
||||
An internet connection is required to set up accounts and identities.
|
||||
|
||||
|
||||
## Quick setup
|
||||
|
||||
Just enter your name, email address and password and tap *Go*.
|
||||
|
||||
If you have a Google account, preferably use *Select account* instead of entering an email address and a password.
|
||||
|
||||
This will work for most major email providers.
|
||||
|
||||
If the quick setup doesn't work, you'll need to setup an account and an identity in another way, see below for instructions.
|
||||
|
||||
|
||||
## Setup account - to receive email
|
||||
## Set up account - to receive email
|
||||
|
||||
To add an account, tap on *Manage accounts* and tap on the orange *add* button at the bottom.
|
||||
To add an account, tap *Manage accounts* and tap the orange *add* button at the bottom.
|
||||
Select a provider from the list, enter the username, which is mostly your email address and enter your password.
|
||||
If you use Gmail, tap *Select account* to fill in the username and password.
|
||||
Tap *Check* to let FairEmail connect to the email server and fetch a list of system folders.
|
||||
After reviewing the system folder selection you can add the account by tapping *Save*.
|
||||
|
||||
If your provider is not in the list of providers, select *Custom*.
|
||||
Enter the domain name, for example *gmail.com* and tap *Get settings*.
|
||||
If your provider supports [auto-discovery](https://tools.ietf.org/html/rfc6186), FairEmail will fill in the host name and port number,
|
||||
else check the setup instructions of your provider for the right IMAP host name and port number.
|
||||
For more about this, please see [here](https://github.com/M66B/open-source-email/blob/master/FAQ.md#authorizing-accounts).
|
||||
If your provider supports [auto-discovery](https://tools.ietf.org/html/rfc6186), FairEmail will fill in the hostname and port number,
|
||||
else check the setup instructions of your provider for the right IMAP hostname, port number and protocol (SSL/TLS or STARTTLS).
|
||||
For more about this, please see [here](https://github.com/M66B/FairEmail/blob/master/FAQ.md#authorizing-accounts).
|
||||
|
||||
|
||||
## Setup identity - to send email
|
||||
## Set up identity - to send email
|
||||
|
||||
Similarly, to add an identity, tap on *Manage identity* and tap on the orange *add* button at the bottom.
|
||||
Similarly, to add an identity, tap *Manage identity* and tap the orange *add* button at the bottom.
|
||||
Enter the name you want to appear in de from address of the emails you send and select a linked account.
|
||||
Tap *Save* to add the identity.
|
||||
|
||||
See [this FAQ](https://github.com/M66B/open-source-email/blob/master/FAQ.md#FAQ9) about using aliases.
|
||||
If the account was configured manually, you likely need to configure the identity manually too.
|
||||
Enter the domain name, for example *gmail.com* and tap *Get settings*.
|
||||
If your provider supports [auto-discovery](https://tools.ietf.org/html/rfc6186), FairEmail will fill in the hostname and port number,
|
||||
else check the setup instructions of your provider for the right SMTP hostname, port number and protocol (SSL/TLS or STARTTLS).
|
||||
|
||||
See [this FAQ](https://github.com/M66B/FairEmail/blob/master/FAQ.md#FAQ9) about using aliases.
|
||||
|
||||
|
||||
## Grant permissions - to access contact information
|
||||
@@ -51,13 +53,14 @@ If you want to lookup email addresses, have contact photos shown, etc, you'll ne
|
||||
Just tap *Grant permissions* and select *Allow*.
|
||||
|
||||
|
||||
## Setup battery optimizations - to continuously receive email
|
||||
## Setup battery optimizations - to continuously receive emails
|
||||
|
||||
On recent Android versions, Android will put apps to sleep when the screen is off for some time to reduce battery usage.
|
||||
If you want to receive new emails without delays, you should disable battery optimizations for FairEmail.
|
||||
Tap *Disable battery optimizations* and follow the instructions.
|
||||
|
||||
|
||||
## Questions
|
||||
## Questions or problems
|
||||
|
||||
If you have a question or problem, please [see here](https://github.com/M66B/open-source-email/blob/master/FAQ.md).
|
||||
If you have a question or problem, please [see here](https://github.com/M66B/FairEmail/blob/master/FAQ.md)
|
||||
or use [this contact form](https://contact.faircode.eu/?product=fairemailsupport) to ask for help (you can use the transaction number "*setup help*").
|
||||
|
||||
164
app/build.gradle
164
app/build.gradle
@@ -7,21 +7,51 @@ def keystoreProperties = new Properties()
|
||||
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
|
||||
|
||||
android {
|
||||
compileSdkVersion 28
|
||||
compileSdkVersion 29
|
||||
|
||||
defaultConfig {
|
||||
applicationId "eu.faircode.email"
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 28
|
||||
versionCode 504
|
||||
versionName "1.504"
|
||||
targetSdkVersion 29
|
||||
versionCode 641
|
||||
versionName "1.641"
|
||||
archivesBaseName = "FairEmail-v$versionName"
|
||||
|
||||
// https://en.wikipedia.org/wiki/List_of_dinosaur_genera
|
||||
// Hypsilophodon, Iguanodon
|
||||
buildConfigField "String", "RELEASE_NAME", "\"Gastonia\""
|
||||
|
||||
javaCompileOptions {
|
||||
annotationProcessorOptions {
|
||||
arguments = ["room.schemaLocation": "$projectDir/schemas".toString()]
|
||||
}
|
||||
}
|
||||
|
||||
// https://developer.android.com/guide/topics/graphics/vector-drawable-resources
|
||||
vectorDrawables.useSupportLibrary = true
|
||||
|
||||
ndk {
|
||||
// Bugsnag
|
||||
abiFilters "armeabi-v7a", "x86", "arm64-v8a", "x86_64"
|
||||
}
|
||||
}
|
||||
|
||||
lintOptions {
|
||||
disable 'MissingTranslation'
|
||||
abortOnError false
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
packagingOptions {
|
||||
exclude 'META-INF/LICENSE.txt'
|
||||
exclude 'META-INF/README.md'
|
||||
exclude 'META-INF/CHANGES'
|
||||
}
|
||||
|
||||
signingConfigs {
|
||||
release {
|
||||
storeFile file(keystoreProperties['storeFile'])
|
||||
@@ -56,14 +86,14 @@ android {
|
||||
buildConfigField "boolean", "PLAY_STORE_RELEASE", "false"
|
||||
buildConfigField "String", "INVITE_URI", "\"https://email.faircode.eu/\""
|
||||
buildConfigField "String", "PRO_FEATURES_URI", "\"https://email.faircode.eu/donate/\""
|
||||
buildConfigField "String", "CHANGELOG", "\"https://github.com/M66B/open-source-email/releases/\""
|
||||
buildConfigField "String", "CHANGELOG", "\"https://github.com/M66B/FairEmail/releases/\""
|
||||
buildConfigField "String", "GITHUB_LATEST_API", "\"https://api.github.com/repos/M66B/open-source-email/releases/latest\""
|
||||
}
|
||||
play_beta {
|
||||
dimension "all"
|
||||
buildConfigField "boolean", "BETA_RELEASE", "true"
|
||||
buildConfigField "boolean", "PLAY_STORE_RELEASE", "true"
|
||||
buildConfigField "String", "INVITE_URI", "\"https://play.google.com/apps/testing/eu.faircode.email\""
|
||||
buildConfigField "String", "INVITE_URI", "\"https://play.google.com/store/apps/details?id=eu.faircode.email\""
|
||||
buildConfigField "String", "PRO_FEATURES_URI", "\"https://email.faircode.eu/#pro\""
|
||||
buildConfigField "String", "CHANGELOG", "\"\""
|
||||
buildConfigField "String", "GITHUB_LATEST_API", "\"\""
|
||||
@@ -79,17 +109,6 @@ android {
|
||||
}
|
||||
}
|
||||
|
||||
lintOptions {
|
||||
disable 'MissingTranslation'
|
||||
abortOnError false
|
||||
}
|
||||
|
||||
packagingOptions {
|
||||
exclude 'META-INF/LICENSE.txt'
|
||||
exclude 'META-INF/README.md'
|
||||
exclude 'META-INF/CHANGES'
|
||||
}
|
||||
|
||||
bugsnag {
|
||||
// https://docs.bugsnag.com/build-integrations/gradle/
|
||||
apiKey "9d2d57476a0614974449a3ec33f2604a"
|
||||
@@ -102,6 +121,34 @@ android {
|
||||
}
|
||||
}
|
||||
|
||||
task copyMarkdown(type: Copy) {
|
||||
from "${rootDir}"
|
||||
into "src/main/assets"
|
||||
include "SETUP.md"
|
||||
include "PRIVACY.md"
|
||||
include "ATTRIBUTION.md"
|
||||
}
|
||||
preBuild.dependsOn copyMarkdown
|
||||
|
||||
task copySetup(type: Copy) {
|
||||
from "src/main/res"
|
||||
include '**/SETUP.md'
|
||||
into "src/main/assets"
|
||||
eachFile { file ->
|
||||
file.relativePath = new RelativePath(true, "SETUP-${file.file.parentFile.name}.md")
|
||||
}
|
||||
includeEmptyDirs = false
|
||||
}
|
||||
preBuild.dependsOn copySetup
|
||||
|
||||
task cleanCrowdin(type: Delete) {
|
||||
delete fileTree("src/main/res").matching {
|
||||
include "**/SETUP.md"
|
||||
}
|
||||
}
|
||||
preBuild.dependsOn cleanCrowdin
|
||||
cleanCrowdin.shouldRunAfter copySetup
|
||||
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
@@ -109,48 +156,52 @@ repositories {
|
||||
maven { url "https://jitpack.io" }
|
||||
}
|
||||
|
||||
configurations.all {
|
||||
// Workaround https://issuetracker.google.com/issues/138441698
|
||||
// Support @69c481c39a17d4e1e44a4eb298bb81c48f226eef
|
||||
exclude group: "androidx.room", module: "room-runtime"
|
||||
// Workaround https://issuetracker.google.com/issues/134685570
|
||||
exclude group: "androidx.lifecycle", module: "lifecycle-livedata"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
|
||||
def appcompat_version = "1.0.2"
|
||||
def recyclerview_version = "1.0.0"
|
||||
def coordinatorlayout_version = "1.0.0"
|
||||
def constraintlayout_version = "1.1.3"
|
||||
def material_version = "1.0.0"
|
||||
def appcompat_version = "1.1.0-rc01"
|
||||
def recyclerview_version = "1.1.0-beta01"
|
||||
def coordinatorlayout_version = "1.1.0-beta01"
|
||||
def constraintlayout_version = "2.0.0-beta2"
|
||||
def material_version = "1.1.0-alpha07"
|
||||
def browser_version = "1.0.0"
|
||||
def lifecycle_version = "2.0.0"
|
||||
def room_version = "2.0.0"
|
||||
def lifecycle_version = "2.1.0-rc01"
|
||||
def room_version = "2.1.0"
|
||||
def paging_version = "2.1.0"
|
||||
def preference_version = "1.0.0"
|
||||
def work_version = "2.1.0-alpha02"
|
||||
def exif_version = "1.0.0"
|
||||
def billingclient_version = "1.2.2"
|
||||
def preference_version = "1.1.0-rc01"
|
||||
def work_version = "2.2.0-rc01"
|
||||
def exif_version = "1.1.0-beta01"
|
||||
def biometric_version = "1.0.0-alpha04"
|
||||
def billingclient_version = "2.0.2"
|
||||
def javamail_version = "1.6.3"
|
||||
def jsoup_version = "1.11.3"
|
||||
def dnsjava_version = "2.1.8"
|
||||
def jsoup_version = "1.12.1"
|
||||
def dnsjava_version = "2.1.9"
|
||||
def openpgp_version = "12.0"
|
||||
def requery_version = "3.28.0"
|
||||
def requery_version = "3.29.0"
|
||||
def badge_version = "1.1.22"
|
||||
def photoview_version = "2.3.0"
|
||||
def bugsnag_version = "4.14.0"
|
||||
def bugsnag_version = "4.17.0"
|
||||
def biweekly_version = "0.6.3"
|
||||
def photoview_version = "2.3.0"
|
||||
def relinker_version = "1.3.1"
|
||||
def markwon_version = "4.0.2"
|
||||
|
||||
// https://developer.android.com/jetpack/androidx/releases/
|
||||
|
||||
// Pin arch/core and sqlite
|
||||
implementation "androidx.core:core:1.0.2"
|
||||
implementation "androidx.arch.core:core-common:2.0.1"
|
||||
implementation "androidx.arch.core:core-runtime:2.0.1"
|
||||
implementation "androidx.sqlite:sqlite:2.0.1"
|
||||
implementation "androidx.sqlite:sqlite-framework:2.0.1"
|
||||
|
||||
// https://mvnrepository.com/artifact/androidx.appcompat/appcompat
|
||||
implementation "androidx.appcompat:appcompat:$appcompat_version"
|
||||
|
||||
// https://mvnrepository.com/artifact/androidx.recyclerview/recyclerview
|
||||
// https://mvnrepository.com/artifact/androidx.recyclerview/recyclerview-selection
|
||||
implementation "androidx.recyclerview:recyclerview:$recyclerview_version"
|
||||
//implementation "androidx.recyclerview:recyclerview-selection:1.1.0-alpha05"
|
||||
//implementation "androidx.recyclerview:recyclerview-selection:1.1.0-alpha06"
|
||||
|
||||
// https://mvnrepository.com/artifact/androidx.coordinatorlayout/coordinatorlayout
|
||||
implementation "androidx.coordinatorlayout:coordinatorlayout:$coordinatorlayout_version"
|
||||
@@ -159,22 +210,27 @@ dependencies {
|
||||
implementation "androidx.constraintlayout:constraintlayout:$constraintlayout_version"
|
||||
|
||||
// https://mvnrepository.com/artifact/com.google.android.material/material
|
||||
// https://github.com/material-components/material-components-android/releases
|
||||
implementation "com.google.android.material:material:$material_version"
|
||||
|
||||
// https://mvnrepository.com/artifact/androidx.browser/browser
|
||||
implementation "androidx.browser:browser:$browser_version"
|
||||
|
||||
// https://mvnrepository.com/artifact/androidx.lifecycle/lifecycle-runtime
|
||||
// https://mvnrepository.com/artifact/androidx.lifecycle/lifecycle-livedata
|
||||
// https://mvnrepository.com/artifact/androidx.lifecycle/lifecycle-livedata-core
|
||||
implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
|
||||
annotationProcessor "androidx.lifecycle:lifecycle-compiler:$lifecycle_version"
|
||||
|
||||
// https://mvnrepository.com/artifact/androidx.room/room-runtime
|
||||
implementation "androidx.room:room-runtime:$room_version"
|
||||
implementation "androidx.room:room-common:$room_version" // because of exclude
|
||||
// https://mvnrepository.com/artifact/androidx.sqlite/sqlite-framework
|
||||
implementation "androidx.sqlite:sqlite-framework:2.0.1" // because of exclude
|
||||
annotationProcessor "androidx.room:room-compiler:$room_version"
|
||||
|
||||
// https://mvnrepository.com/artifact/androidx.paging/paging-runtime
|
||||
implementation "androidx.paging:paging-runtime:$paging_version"
|
||||
|
||||
// https://mvnrepository.com/artifact/androidx.preference/preference
|
||||
implementation "androidx.preference:preference:$preference_version"
|
||||
|
||||
@@ -184,6 +240,10 @@ dependencies {
|
||||
// https://mvnrepository.com/artifact/androidx.exifinterface/exifinterface
|
||||
implementation "androidx.exifinterface:exifinterface:$exif_version"
|
||||
|
||||
// https://mvnrepository.com/artifact/androidx.biometric/biometric
|
||||
// https://developer.android.com/jetpack/androidx/releases/biometric
|
||||
implementation "androidx.biometric:biometric:$biometric_version"
|
||||
|
||||
// https://developer.android.com/google/play/billing/billing_library_releases_notes
|
||||
implementation "com.android.billingclient:billing:$billingclient_version"
|
||||
|
||||
@@ -212,17 +272,29 @@ dependencies {
|
||||
// https://mvnrepository.com/artifact/me.leolin/ShortcutBadger
|
||||
implementation "me.leolin:ShortcutBadger:$badge_version"
|
||||
|
||||
// https://github.com/chrisbanes/PhotoView
|
||||
implementation "com.github.chrisbanes:PhotoView:$photoview_version"
|
||||
|
||||
// https://github.com/bugsnag/bugsnag-android
|
||||
implementation "com.bugsnag:bugsnag-android:$bugsnag_version"
|
||||
implementation("com.bugsnag:bugsnag-android:$bugsnag_version") {
|
||||
exclude group: "com.bugsnag", module: "bugsnag-plugin-android-anr"
|
||||
exclude group: "com.bugsnag", module: "bugsnag-plugin-android-ndk"
|
||||
}
|
||||
|
||||
// https://github.com/mangstadt/biweekly
|
||||
implementation("net.sf.biweekly:biweekly:$biweekly_version") {
|
||||
exclude group: 'com.fasterxml.jackson.core', module: 'jackson-core'
|
||||
}
|
||||
|
||||
// https://github.com/chrisbanes/PhotoView
|
||||
implementation "com.github.chrisbanes:PhotoView:$photoview_version"
|
||||
|
||||
// https://github.com/KeepSafe/ReLinker
|
||||
implementation "com.getkeepsafe.relinker:relinker:$relinker_version"
|
||||
|
||||
// https://square.github.io/leakcanary/getting_started/
|
||||
//debugImplementation "com.squareup.leakcanary:leakcanary-android:2.0-alpha-3"
|
||||
|
||||
// https://github.com/noties/Markwon
|
||||
implementation "io.noties.markwon:core:$markwon_version"
|
||||
|
||||
// git clone https://android.googlesource.com/platform/frameworks/opt/colorpicker
|
||||
implementation project(path: ':colorpicker')
|
||||
}
|
||||
|
||||
3
app/proguard-rules.pro
vendored
3
app/proguard-rules.pro
vendored
@@ -29,9 +29,12 @@
|
||||
-keep class androidx.appcompat.app.AppCompatViewInflater {<init>(...);}
|
||||
-keepclassmembers class * implements android.os.Parcelable {static ** CREATOR;}
|
||||
#android.os.BadParcelableException: Parcelable protocol requires a Parcelable.Creator object called CREATOR on class androidx...
|
||||
-keep class androidx.work.impl.** {*;} #Due to compiling ROOM inline
|
||||
-keepnames class androidx.** {*;}
|
||||
|
||||
#IAB
|
||||
-keep class com.android.billingclient.** {*;}
|
||||
-keepnames class com.android.billingclient.** {*;}
|
||||
-keep class com.android.vending.billing.** {*;}
|
||||
-keepnames class com.android.vending.billing.** {*;}
|
||||
|
||||
|
||||
1781
app/schemas/eu.faircode.email.DB/84.json
Normal file
1781
app/schemas/eu.faircode.email.DB/84.json
Normal file
File diff suppressed because it is too large
Load Diff
1781
app/schemas/eu.faircode.email.DB/85.json
Normal file
1781
app/schemas/eu.faircode.email.DB/85.json
Normal file
File diff suppressed because it is too large
Load Diff
1787
app/schemas/eu.faircode.email.DB/86.json
Normal file
1787
app/schemas/eu.faircode.email.DB/86.json
Normal file
File diff suppressed because it is too large
Load Diff
1782
app/schemas/eu.faircode.email.DB/87.json
Normal file
1782
app/schemas/eu.faircode.email.DB/87.json
Normal file
File diff suppressed because it is too large
Load Diff
1788
app/schemas/eu.faircode.email.DB/88.json
Normal file
1788
app/schemas/eu.faircode.email.DB/88.json
Normal file
File diff suppressed because it is too large
Load Diff
1794
app/schemas/eu.faircode.email.DB/89.json
Normal file
1794
app/schemas/eu.faircode.email.DB/89.json
Normal file
File diff suppressed because it is too large
Load Diff
1800
app/schemas/eu.faircode.email.DB/90.json
Normal file
1800
app/schemas/eu.faircode.email.DB/90.json
Normal file
File diff suppressed because it is too large
Load Diff
1806
app/schemas/eu.faircode.email.DB/91.json
Normal file
1806
app/schemas/eu.faircode.email.DB/91.json
Normal file
File diff suppressed because it is too large
Load Diff
1806
app/schemas/eu.faircode.email.DB/92.json
Normal file
1806
app/schemas/eu.faircode.email.DB/92.json
Normal file
File diff suppressed because it is too large
Load Diff
1812
app/schemas/eu.faircode.email.DB/93.json
Normal file
1812
app/schemas/eu.faircode.email.DB/93.json
Normal file
File diff suppressed because it is too large
Load Diff
1818
app/schemas/eu.faircode.email.DB/94.json
Normal file
1818
app/schemas/eu.faircode.email.DB/94.json
Normal file
File diff suppressed because it is too large
Load Diff
1824
app/schemas/eu.faircode.email.DB/95.json
Normal file
1824
app/schemas/eu.faircode.email.DB/95.json
Normal file
File diff suppressed because it is too large
Load Diff
1830
app/schemas/eu.faircode.email.DB/96.json
Normal file
1830
app/schemas/eu.faircode.email.DB/96.json
Normal file
File diff suppressed because it is too large
Load Diff
1836
app/schemas/eu.faircode.email.DB/97.json
Normal file
1836
app/schemas/eu.faircode.email.DB/97.json
Normal file
File diff suppressed because it is too large
Load Diff
1842
app/schemas/eu.faircode.email.DB/98.json
Normal file
1842
app/schemas/eu.faircode.email.DB/98.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -6,14 +6,11 @@
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||
<uses-permission android:name="android.permission.READ_CONTACTS" />
|
||||
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
|
||||
<uses-permission
|
||||
android:name="android.permission.USE_CREDENTIALS"
|
||||
android:maxSdkVersion="22" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
<uses-permission android:name="com.android.vending.BILLING" />
|
||||
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
|
||||
|
||||
<!-- https://developer.android.com/guide/topics/manifest/uses-feature-element#features-reference -->
|
||||
<uses-feature
|
||||
@@ -25,6 +22,9 @@
|
||||
<uses-feature
|
||||
android:name="android.hardware.camera"
|
||||
android:required="false" />
|
||||
<uses-feature
|
||||
android:name="android.hardware.fingerprint"
|
||||
android:required="false" />
|
||||
|
||||
<application
|
||||
android:name=".ApplicationEx"
|
||||
@@ -33,6 +33,7 @@
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:networkSecurityConfig="@xml/network_security_config"
|
||||
android:requestLegacyExternalStorage="true"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/AppThemeLight">
|
||||
@@ -48,6 +49,14 @@
|
||||
android:name="android.allow_multiple_resumed_activities"
|
||||
android:value="true" />
|
||||
|
||||
<!-- Bugsnag -->
|
||||
<meta-data
|
||||
android:name="com.bugsnag.android.DETECT_ANRS"
|
||||
android:value="false" />
|
||||
<meta-data
|
||||
android:name="com.bugsnag.android.DETECT_NDK_CRASHES"
|
||||
android:value="false" />
|
||||
|
||||
<activity
|
||||
android:name=".ActivityMain"
|
||||
android:excludeFromRecents="true"
|
||||
@@ -77,6 +86,14 @@
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".ActivityWidgetUnified"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".ActivityView"
|
||||
android:exported="false"
|
||||
@@ -183,13 +200,19 @@
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<service android:name=".ServiceSynchronize" />
|
||||
<service
|
||||
android:name=".ServiceSynchronize"
|
||||
android:foregroundServiceType="dataSync" />
|
||||
|
||||
<service android:name=".ServiceSend" />
|
||||
<service
|
||||
android:name=".ServiceSend"
|
||||
android:foregroundServiceType="dataSync" />
|
||||
|
||||
<service android:name=".ServiceUI" />
|
||||
|
||||
<service android:name=".ServiceExternal">
|
||||
<service
|
||||
android:name=".ServiceExternal"
|
||||
android:foregroundServiceType="dataSync">
|
||||
<intent-filter>
|
||||
<action android:name="${applicationId}.ENABLE" />
|
||||
<action android:name="${applicationId}.DISABLE" />
|
||||
@@ -229,7 +252,7 @@
|
||||
|
||||
<receiver
|
||||
android:name=".Widget"
|
||||
android:label="@string/app_name">
|
||||
android:label="@string/tile_unseen">
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
||||
@@ -240,7 +263,24 @@
|
||||
android:resource="@xml/widget" />
|
||||
</receiver>
|
||||
|
||||
<receiver android:name=".ReceiverAutostart">
|
||||
<receiver
|
||||
android:name=".WidgetUnified"
|
||||
android:label="@string/title_folder_unified">
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
||||
</intent-filter>
|
||||
|
||||
<meta-data
|
||||
android:name="android.appwidget.provider"
|
||||
android:resource="@xml/widget_unified" />
|
||||
</receiver>
|
||||
|
||||
<service
|
||||
android:name="WidgetUnifiedService"
|
||||
android:permission="android.permission.BIND_REMOTEVIEWS" />
|
||||
|
||||
<receiver android:name=".ReceiverAutoStart">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||
</intent-filter>
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package androidx.room;
|
||||
|
||||
/**
|
||||
* RPC Callbacks for {@link IMultiInstanceInvalidationService}.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
interface IMultiInstanceInvalidationCallback {
|
||||
|
||||
/**
|
||||
* Called when invalidation is detected in another instance of the same database.
|
||||
*
|
||||
* @param tables List of invalidated table names
|
||||
*/
|
||||
oneway void onInvalidation(in String[] tables);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package androidx.room;
|
||||
|
||||
import androidx.room.IMultiInstanceInvalidationCallback;
|
||||
|
||||
/**
|
||||
* RPC Service that controls interaction about multi-instance invalidation.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
interface IMultiInstanceInvalidationService {
|
||||
|
||||
/**
|
||||
* Registers a new {@link IMultiInstanceInvalidationCallback} as a client of this service.
|
||||
*
|
||||
* @param callback The RPC callback.
|
||||
* @param name The name of the database file as it is passed to {@link RoomDatabase.Builder}.
|
||||
* @return A new client ID. The client needs to hold on to this ID and pass it to the service
|
||||
* for subsequent calls.
|
||||
*/
|
||||
int registerCallback(IMultiInstanceInvalidationCallback callback, String name);
|
||||
|
||||
/**
|
||||
* Unregisters the specified {@link IMultiInstanceInvalidationCallback} from this service.
|
||||
* <p>
|
||||
* Clients might die without explicitly calling this method. In that case, the service should
|
||||
* handle the clean up.
|
||||
*
|
||||
* @param callback The RPC callback.
|
||||
* @param clientId The client ID returned from {@link #registerCallback}.
|
||||
*/
|
||||
void unregisterCallback(IMultiInstanceInvalidationCallback callback, int clientId);
|
||||
|
||||
/**
|
||||
* Broadcasts invalidation of database tables to other clients registered to this service.
|
||||
* <p>
|
||||
* The broadcast is delivered to {@link IMultiInstanceInvalidationCallback#onInvalidation} of
|
||||
* the registered clients. The client calling this method will not receive its own broadcast.
|
||||
* Clients that are associated with a different database file will not be notified.
|
||||
*
|
||||
* @param clientId The client ID returned from {@link #registerCallback}.
|
||||
* @param tables The names of invalidated tables.
|
||||
*/
|
||||
oneway void broadcastInvalidation(int clientId, in String[] tables);
|
||||
|
||||
}
|
||||
20
app/src/main/assets/ATTRIBUTION.md
Normal file
20
app/src/main/assets/ATTRIBUTION.md
Normal file
@@ -0,0 +1,20 @@
|
||||
## Attribution
|
||||
|
||||
FairEmail uses:
|
||||
|
||||
* [JavaMail](https://projects.eclipse.org/projects/ee4j.javamail). Copyright (c) 1997-2018 Oracle® and/or its affiliates. All rights reserved. [GPLv2+CE license](https://javaee.github.io/javamail/JavaMail-License).
|
||||
* [jsoup](https://jsoup.org/). Copyright © 2009 - 2017 Jonathan Hedley. [MIT license](https://jsoup.org/license).
|
||||
* [Android Support Library](https://developer.android.com/tools/support-library/). Copyright (C) 2011 The Android Open Source Project. [Apache license 2.0](https://android.googlesource.com/platform/frameworks/support/+/master/LICENSE.txt).
|
||||
* [Android Architecture Components](https://developer.android.com/topic/libraries/architecture/). Copyright 2018 The Android Open Source Project, Inc. [Apache license 2.0](https://github.com/googlesamples/android-architecture-components/blob/master/LICENSE).
|
||||
* [colorpicker](https://android.googlesource.com/platform/frameworks/opt/colorpicker). Copyright (C) 2013 The Android Open Source Project. [Apache license 2.0](https://android.googlesource.com/platform/frameworks/opt/colorpicker/+/master/src/com/android/colorpicker/ColorPickerDialog.java).
|
||||
* [dnsjava](http://www.xbill.org/dnsjava/). Copyright (c) 1998-2011, Brian Wellington. [BSD License](https://sourceforge.net/p/dnsjava/code/HEAD/tree/trunk/LICENSE).
|
||||
* [OpenPGP API library](https://github.com/open-keychain/openpgp-api). Copyright (C) 2014-2015 Dominik Schürmann. [Apache License 2.0](https://github.com/open-keychain/openpgp-api/blob/master/LICENSE).
|
||||
* [Android SQLite support library](https://github.com/requery/sqlite-android). Copyright (C) 2017 requery.io. [Apache License 2.0](https://github.com/requery/sqlite-android/blob/master/LICENSE).
|
||||
* [App shortcut icon generator](https://romannurik.github.io/AndroidAssetStudio/icons-app-shortcut.html). Copyright ???. [Apache License 2.0](https://github.com/romannurik/AndroidAssetStudio/blob/master/LICENSE).
|
||||
* [Mozilla ISPDB](https://developer.mozilla.org/en-US/docs/Mozilla/Thunderbird/Autoconfiguration#ISPDB). *Free to use for any client.*
|
||||
* [ShortcutBadger](https://github.com/leolin310148/ShortcutBadger). Copyright 2014 Leo Lin. [Apache license 2.0](https://github.com/leolin310148/ShortcutBadger/blob/master/LICENSE).
|
||||
* [Bugsnag exception reporter for Android](https://github.com/bugsnag/bugsnag-android). Copyright (c) 2012 Bugsnag. [MIT License](https://github.com/bugsnag/bugsnag-android/blob/master/LICENSE.txt).
|
||||
* [biweekly](https://github.com/mangstadt/biweekly). Copyright (c) 2013-2018, Michael Angstadt. [BSD 2-Clause](https://github.com/mangstadt/biweekly/blob/master/LICENSE).
|
||||
* [PhotoView](https://github.com/chrisbanes/PhotoView). Copyright 2018 Chris Banes. [Apache License 2.0](https://github.com/chrisbanes/PhotoView/blob/master/LICENSE).
|
||||
* [ReLinker](https://github.com/KeepSafe/ReLinker). Copyright 2015 - 2016 KeepSafe Software, Inc. [Apache License 2.0](https://github.com/KeepSafe/ReLinker/blob/master/LICENSE).
|
||||
* [Markwon](https://github.com/noties/Markwon). Copyright 2019 Dimitry Ivanov. [Apache License 2.0](https://github.com/noties/Markwon/blob/master/LICENSE).
|
||||
18
app/src/main/assets/PRIVACY.md
Normal file
18
app/src/main/assets/PRIVACY.md
Normal file
@@ -0,0 +1,18 @@
|
||||
## Privacy policy
|
||||
|
||||
FairEmail **does not** collect any information.
|
||||
|
||||
FairEmail **does not** store data on third party servers.
|
||||
|
||||
FairEmail **does not** require unnecessary permissions.
|
||||
For more information on permissions, see [this FAQ](https://github.com/M66B/FairEmail/blob/master/FAQ.md#user-content-faq1).
|
||||
|
||||
FairEmail **does not** allow other apps access to messages and attachments without your approval.
|
||||
|
||||
FairEmail **does** follow the recommendations of [this EFF article](https://www.eff.org/deeplinks/2019/01/stop-tracking-my-emails).
|
||||
|
||||
FairEmail is 100 % **open source**, see [the license](https://github.com/M66B/FairEmail/blob/master/LICENSE).
|
||||
|
||||
Error reporting is **opt-in**, see [here](https://github.com/M66B/FairEmail/blob/master/FAQ.md#user-content-faq104) for more information.
|
||||
|
||||
Copyright © 2018-2019 Marcel Bokhorst.
|
||||
41
app/src/main/assets/SETUP-af.md
Normal file
41
app/src/main/assets/SETUP-af.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# Setup help
|
||||
|
||||
Setting up FairEmail is fairly simple. You'll need to add at least one account to receive email and at least one identity if you want to send email. The quick setup will add an account and an identity in one go for most major providers.
|
||||
|
||||
## Requirements
|
||||
|
||||
An internet connection is required to set up accounts and identities.
|
||||
|
||||
## Quick setup
|
||||
|
||||
Just enter your name, email address and password and tap *Go*.
|
||||
|
||||
This will work for most major email providers.
|
||||
|
||||
If the quick setup doesn't work, you'll need to setup an account and an identity in another way, see below for instructions.
|
||||
|
||||
## Set up account - to receive email
|
||||
|
||||
To add an account, tap *Manage accounts* and tap the orange *add* button at the bottom. Select a provider from the list, enter the username, which is mostly your email address and enter your password. Tap *Check* to let FairEmail connect to the email server and fetch a list of system folders. After reviewing the system folder selection you can add the account by tapping *Save*.
|
||||
|
||||
If your provider is not in the list of providers, select *Custom*. Enter the domain name, for example *gmail.com* and tap *Get settings*. If your provider supports [auto-discovery](https://tools.ietf.org/html/rfc6186), FairEmail will fill in the hostname and port number, else check the setup instructions of your provider for the right IMAP hostname, port number and protocol (SSL/TLS or STARTTLS). For more about this, please see [here](https://github.com/M66B/FairEmail/blob/master/FAQ.md#authorizing-accounts).
|
||||
|
||||
## Set up identity - to send email
|
||||
|
||||
Similarly, to add an identity, tap *Manage identity* and tap the orange *add* button at the bottom. Enter the name you want to appear in de from address of the emails you send and select a linked account. Tap *Save* to add the identity.
|
||||
|
||||
If the account was configured manually, you likely need to configure the identity manually too. Enter the domain name, for example *gmail.com* and tap *Get settings*. If your provider supports [auto-discovery](https://tools.ietf.org/html/rfc6186), FairEmail will fill in the hostname and port number, else check the setup instructions of your provider for the right SMTP hostname, port number and protocol (SSL/TLS or STARTTLS).
|
||||
|
||||
See [this FAQ](https://github.com/M66B/FairEmail/blob/master/FAQ.md#FAQ9) about using aliases.
|
||||
|
||||
## Grant permissions - to access contact information
|
||||
|
||||
If you want to lookup email addresses, have contact photos shown, etc, you'll need to grant read contacts permission to FairEmail. Just tap *Grant permissions* and select *Allow*.
|
||||
|
||||
## Setup battery optimizations - to continuously receive emails
|
||||
|
||||
On recent Android versions, Android will put apps to sleep when the screen is off for some time to reduce battery usage. If you want to receive new emails without delays, you should disable battery optimizations for FairEmail. Tap *Disable battery optimizations* and follow the instructions.
|
||||
|
||||
## Questions or problems
|
||||
|
||||
If you have a question or problem, please [see here](https://github.com/M66B/FairEmail/blob/master/FAQ.md) or use [this contact form](https://contact.faircode.eu/?product=fairemailsupport) to ask for help (you can use the transaction number "*setup help*").
|
||||
41
app/src/main/assets/SETUP-ar-BH.md
Normal file
41
app/src/main/assets/SETUP-ar-BH.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# Setup help
|
||||
|
||||
Setting up FairEmail is fairly simple. You'll need to add at least one account to receive email and at least one identity if you want to send email. The quick setup will add an account and an identity in one go for most major providers.
|
||||
|
||||
## Requirements
|
||||
|
||||
An internet connection is required to set up accounts and identities.
|
||||
|
||||
## Quick setup
|
||||
|
||||
Just enter your name, email address and password and tap *Go*.
|
||||
|
||||
This will work for most major email providers.
|
||||
|
||||
If the quick setup doesn't work, you'll need to setup an account and an identity in another way, see below for instructions.
|
||||
|
||||
## Set up account - to receive email
|
||||
|
||||
To add an account, tap *Manage accounts* and tap the orange *add* button at the bottom. Select a provider from the list, enter the username, which is mostly your email address and enter your password. Tap *Check* to let FairEmail connect to the email server and fetch a list of system folders. After reviewing the system folder selection you can add the account by tapping *Save*.
|
||||
|
||||
If your provider is not in the list of providers, select *Custom*. Enter the domain name, for example *gmail.com* and tap *Get settings*. If your provider supports [auto-discovery](https://tools.ietf.org/html/rfc6186), FairEmail will fill in the hostname and port number, else check the setup instructions of your provider for the right IMAP hostname, port number and protocol (SSL/TLS or STARTTLS). For more about this, please see [here](https://github.com/M66B/FairEmail/blob/master/FAQ.md#authorizing-accounts).
|
||||
|
||||
## Set up identity - to send email
|
||||
|
||||
Similarly, to add an identity, tap *Manage identity* and tap the orange *add* button at the bottom. Enter the name you want to appear in de from address of the emails you send and select a linked account. Tap *Save* to add the identity.
|
||||
|
||||
If the account was configured manually, you likely need to configure the identity manually too. Enter the domain name, for example *gmail.com* and tap *Get settings*. If your provider supports [auto-discovery](https://tools.ietf.org/html/rfc6186), FairEmail will fill in the hostname and port number, else check the setup instructions of your provider for the right SMTP hostname, port number and protocol (SSL/TLS or STARTTLS).
|
||||
|
||||
See [this FAQ](https://github.com/M66B/FairEmail/blob/master/FAQ.md#FAQ9) about using aliases.
|
||||
|
||||
## Grant permissions - to access contact information
|
||||
|
||||
If you want to lookup email addresses, have contact photos shown, etc, you'll need to grant read contacts permission to FairEmail. Just tap *Grant permissions* and select *Allow*.
|
||||
|
||||
## Setup battery optimizations - to continuously receive emails
|
||||
|
||||
On recent Android versions, Android will put apps to sleep when the screen is off for some time to reduce battery usage. If you want to receive new emails without delays, you should disable battery optimizations for FairEmail. Tap *Disable battery optimizations* and follow the instructions.
|
||||
|
||||
## Questions or problems
|
||||
|
||||
If you have a question or problem, please [see here](https://github.com/M66B/FairEmail/blob/master/FAQ.md) or use [this contact form](https://contact.faircode.eu/?product=fairemailsupport) to ask for help (you can use the transaction number "*setup help*").
|
||||
41
app/src/main/assets/SETUP-ar-EG.md
Normal file
41
app/src/main/assets/SETUP-ar-EG.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# Setup help
|
||||
|
||||
Setting up FairEmail is fairly simple. You'll need to add at least one account to receive email and at least one identity if you want to send email. The quick setup will add an account and an identity in one go for most major providers.
|
||||
|
||||
## Requirements
|
||||
|
||||
An internet connection is required to set up accounts and identities.
|
||||
|
||||
## Quick setup
|
||||
|
||||
Just enter your name, email address and password and tap *Go*.
|
||||
|
||||
This will work for most major email providers.
|
||||
|
||||
If the quick setup doesn't work, you'll need to setup an account and an identity in another way, see below for instructions.
|
||||
|
||||
## Set up account - to receive email
|
||||
|
||||
To add an account, tap *Manage accounts* and tap the orange *add* button at the bottom. Select a provider from the list, enter the username, which is mostly your email address and enter your password. Tap *Check* to let FairEmail connect to the email server and fetch a list of system folders. After reviewing the system folder selection you can add the account by tapping *Save*.
|
||||
|
||||
If your provider is not in the list of providers, select *Custom*. Enter the domain name, for example *gmail.com* and tap *Get settings*. If your provider supports [auto-discovery](https://tools.ietf.org/html/rfc6186), FairEmail will fill in the hostname and port number, else check the setup instructions of your provider for the right IMAP hostname, port number and protocol (SSL/TLS or STARTTLS). For more about this, please see [here](https://github.com/M66B/FairEmail/blob/master/FAQ.md#authorizing-accounts).
|
||||
|
||||
## Set up identity - to send email
|
||||
|
||||
Similarly, to add an identity, tap *Manage identity* and tap the orange *add* button at the bottom. Enter the name you want to appear in de from address of the emails you send and select a linked account. Tap *Save* to add the identity.
|
||||
|
||||
If the account was configured manually, you likely need to configure the identity manually too. Enter the domain name, for example *gmail.com* and tap *Get settings*. If your provider supports [auto-discovery](https://tools.ietf.org/html/rfc6186), FairEmail will fill in the hostname and port number, else check the setup instructions of your provider for the right SMTP hostname, port number and protocol (SSL/TLS or STARTTLS).
|
||||
|
||||
See [this FAQ](https://github.com/M66B/FairEmail/blob/master/FAQ.md#FAQ9) about using aliases.
|
||||
|
||||
## Grant permissions - to access contact information
|
||||
|
||||
If you want to lookup email addresses, have contact photos shown, etc, you'll need to grant read contacts permission to FairEmail. Just tap *Grant permissions* and select *Allow*.
|
||||
|
||||
## Setup battery optimizations - to continuously receive emails
|
||||
|
||||
On recent Android versions, Android will put apps to sleep when the screen is off for some time to reduce battery usage. If you want to receive new emails without delays, you should disable battery optimizations for FairEmail. Tap *Disable battery optimizations* and follow the instructions.
|
||||
|
||||
## Questions or problems
|
||||
|
||||
If you have a question or problem, please [see here](https://github.com/M66B/FairEmail/blob/master/FAQ.md) or use [this contact form](https://contact.faircode.eu/?product=fairemailsupport) to ask for help (you can use the transaction number "*setup help*").
|
||||
41
app/src/main/assets/SETUP-ar-SA.md
Normal file
41
app/src/main/assets/SETUP-ar-SA.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# Setup help
|
||||
|
||||
Setting up FairEmail is fairly simple. You'll need to add at least one account to receive email and at least one identity if you want to send email. The quick setup will add an account and an identity in one go for most major providers.
|
||||
|
||||
## Requirements
|
||||
|
||||
An internet connection is required to set up accounts and identities.
|
||||
|
||||
## Quick setup
|
||||
|
||||
Just enter your name, email address and password and tap *Go*.
|
||||
|
||||
This will work for most major email providers.
|
||||
|
||||
If the quick setup doesn't work, you'll need to setup an account and an identity in another way, see below for instructions.
|
||||
|
||||
## Set up account - to receive email
|
||||
|
||||
To add an account, tap *Manage accounts* and tap the orange *add* button at the bottom. Select a provider from the list, enter the username, which is mostly your email address and enter your password. Tap *Check* to let FairEmail connect to the email server and fetch a list of system folders. After reviewing the system folder selection you can add the account by tapping *Save*.
|
||||
|
||||
If your provider is not in the list of providers, select *Custom*. Enter the domain name, for example *gmail.com* and tap *Get settings*. If your provider supports [auto-discovery](https://tools.ietf.org/html/rfc6186), FairEmail will fill in the hostname and port number, else check the setup instructions of your provider for the right IMAP hostname, port number and protocol (SSL/TLS or STARTTLS). For more about this, please see [here](https://github.com/M66B/FairEmail/blob/master/FAQ.md#authorizing-accounts).
|
||||
|
||||
## Set up identity - to send email
|
||||
|
||||
Similarly, to add an identity, tap *Manage identity* and tap the orange *add* button at the bottom. Enter the name you want to appear in de from address of the emails you send and select a linked account. Tap *Save* to add the identity.
|
||||
|
||||
If the account was configured manually, you likely need to configure the identity manually too. Enter the domain name, for example *gmail.com* and tap *Get settings*. If your provider supports [auto-discovery](https://tools.ietf.org/html/rfc6186), FairEmail will fill in the hostname and port number, else check the setup instructions of your provider for the right SMTP hostname, port number and protocol (SSL/TLS or STARTTLS).
|
||||
|
||||
See [this FAQ](https://github.com/M66B/FairEmail/blob/master/FAQ.md#FAQ9) about using aliases.
|
||||
|
||||
## Grant permissions - to access contact information
|
||||
|
||||
If you want to lookup email addresses, have contact photos shown, etc, you'll need to grant read contacts permission to FairEmail. Just tap *Grant permissions* and select *Allow*.
|
||||
|
||||
## Setup battery optimizations - to continuously receive emails
|
||||
|
||||
On recent Android versions, Android will put apps to sleep when the screen is off for some time to reduce battery usage. If you want to receive new emails without delays, you should disable battery optimizations for FairEmail. Tap *Disable battery optimizations* and follow the instructions.
|
||||
|
||||
## Questions or problems
|
||||
|
||||
If you have a question or problem, please [see here](https://github.com/M66B/FairEmail/blob/master/FAQ.md) or use [this contact form](https://contact.faircode.eu/?product=fairemailsupport) to ask for help (you can use the transaction number "*setup help*").
|
||||
41
app/src/main/assets/SETUP-ar-YE.md
Normal file
41
app/src/main/assets/SETUP-ar-YE.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# Setup help
|
||||
|
||||
Setting up FairEmail is fairly simple. You'll need to add at least one account to receive email and at least one identity if you want to send email. The quick setup will add an account and an identity in one go for most major providers.
|
||||
|
||||
## Requirements
|
||||
|
||||
An internet connection is required to set up accounts and identities.
|
||||
|
||||
## Quick setup
|
||||
|
||||
Just enter your name, email address and password and tap *Go*.
|
||||
|
||||
This will work for most major email providers.
|
||||
|
||||
If the quick setup doesn't work, you'll need to setup an account and an identity in another way, see below for instructions.
|
||||
|
||||
## Set up account - to receive email
|
||||
|
||||
To add an account, tap *Manage accounts* and tap the orange *add* button at the bottom. Select a provider from the list, enter the username, which is mostly your email address and enter your password. Tap *Check* to let FairEmail connect to the email server and fetch a list of system folders. After reviewing the system folder selection you can add the account by tapping *Save*.
|
||||
|
||||
If your provider is not in the list of providers, select *Custom*. Enter the domain name, for example *gmail.com* and tap *Get settings*. If your provider supports [auto-discovery](https://tools.ietf.org/html/rfc6186), FairEmail will fill in the hostname and port number, else check the setup instructions of your provider for the right IMAP hostname, port number and protocol (SSL/TLS or STARTTLS). For more about this, please see [here](https://github.com/M66B/FairEmail/blob/master/FAQ.md#authorizing-accounts).
|
||||
|
||||
## Set up identity - to send email
|
||||
|
||||
Similarly, to add an identity, tap *Manage identity* and tap the orange *add* button at the bottom. Enter the name you want to appear in de from address of the emails you send and select a linked account. Tap *Save* to add the identity.
|
||||
|
||||
If the account was configured manually, you likely need to configure the identity manually too. Enter the domain name, for example *gmail.com* and tap *Get settings*. If your provider supports [auto-discovery](https://tools.ietf.org/html/rfc6186), FairEmail will fill in the hostname and port number, else check the setup instructions of your provider for the right SMTP hostname, port number and protocol (SSL/TLS or STARTTLS).
|
||||
|
||||
See [this FAQ](https://github.com/M66B/FairEmail/blob/master/FAQ.md#FAQ9) about using aliases.
|
||||
|
||||
## Grant permissions - to access contact information
|
||||
|
||||
If you want to lookup email addresses, have contact photos shown, etc, you'll need to grant read contacts permission to FairEmail. Just tap *Grant permissions* and select *Allow*.
|
||||
|
||||
## Setup battery optimizations - to continuously receive emails
|
||||
|
||||
On recent Android versions, Android will put apps to sleep when the screen is off for some time to reduce battery usage. If you want to receive new emails without delays, you should disable battery optimizations for FairEmail. Tap *Disable battery optimizations* and follow the instructions.
|
||||
|
||||
## Questions or problems
|
||||
|
||||
If you have a question or problem, please [see here](https://github.com/M66B/FairEmail/blob/master/FAQ.md) or use [this contact form](https://contact.faircode.eu/?product=fairemailsupport) to ask for help (you can use the transaction number "*setup help*").
|
||||
41
app/src/main/assets/SETUP-ar.md
Normal file
41
app/src/main/assets/SETUP-ar.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# Setup help
|
||||
|
||||
Setting up FairEmail is fairly simple. You'll need to add at least one account to receive email and at least one identity if you want to send email. The quick setup will add an account and an identity in one go for most major providers.
|
||||
|
||||
## Requirements
|
||||
|
||||
An internet connection is required to set up accounts and identities.
|
||||
|
||||
## Quick setup
|
||||
|
||||
Just enter your name, email address and password and tap *Go*.
|
||||
|
||||
This will work for most major email providers.
|
||||
|
||||
If the quick setup doesn't work, you'll need to setup an account and an identity in another way, see below for instructions.
|
||||
|
||||
## Set up account - to receive email
|
||||
|
||||
To add an account, tap *Manage accounts* and tap the orange *add* button at the bottom. Select a provider from the list, enter the username, which is mostly your email address and enter your password. Tap *Check* to let FairEmail connect to the email server and fetch a list of system folders. After reviewing the system folder selection you can add the account by tapping *Save*.
|
||||
|
||||
If your provider is not in the list of providers, select *Custom*. Enter the domain name, for example *gmail.com* and tap *Get settings*. If your provider supports [auto-discovery](https://tools.ietf.org/html/rfc6186), FairEmail will fill in the hostname and port number, else check the setup instructions of your provider for the right IMAP hostname, port number and protocol (SSL/TLS or STARTTLS). For more about this, please see [here](https://github.com/M66B/FairEmail/blob/master/FAQ.md#authorizing-accounts).
|
||||
|
||||
## Set up identity - to send email
|
||||
|
||||
Similarly, to add an identity, tap *Manage identity* and tap the orange *add* button at the bottom. Enter the name you want to appear in de from address of the emails you send and select a linked account. Tap *Save* to add the identity.
|
||||
|
||||
If the account was configured manually, you likely need to configure the identity manually too. Enter the domain name, for example *gmail.com* and tap *Get settings*. If your provider supports [auto-discovery](https://tools.ietf.org/html/rfc6186), FairEmail will fill in the hostname and port number, else check the setup instructions of your provider for the right SMTP hostname, port number and protocol (SSL/TLS or STARTTLS).
|
||||
|
||||
See [this FAQ](https://github.com/M66B/FairEmail/blob/master/FAQ.md#FAQ9) about using aliases.
|
||||
|
||||
## Grant permissions - to access contact information
|
||||
|
||||
If you want to lookup email addresses, have contact photos shown, etc, you'll need to grant read contacts permission to FairEmail. Just tap *Grant permissions* and select *Allow*.
|
||||
|
||||
## Setup battery optimizations - to continuously receive emails
|
||||
|
||||
On recent Android versions, Android will put apps to sleep when the screen is off for some time to reduce battery usage. If you want to receive new emails without delays, you should disable battery optimizations for FairEmail. Tap *Disable battery optimizations* and follow the instructions.
|
||||
|
||||
## Questions or problems
|
||||
|
||||
If you have a question or problem, please [see here](https://github.com/M66B/FairEmail/blob/master/FAQ.md) or use [this contact form](https://contact.faircode.eu/?product=fairemailsupport) to ask for help (you can use the transaction number "*setup help*").
|
||||
41
app/src/main/assets/SETUP-az.md
Normal file
41
app/src/main/assets/SETUP-az.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# Setup help
|
||||
|
||||
Setting up FairEmail is fairly simple. You'll need to add at least one account to receive email and at least one identity if you want to send email. The quick setup will add an account and an identity in one go for most major providers.
|
||||
|
||||
## Requirements
|
||||
|
||||
An internet connection is required to set up accounts and identities.
|
||||
|
||||
## Quick setup
|
||||
|
||||
Just enter your name, email address and password and tap *Go*.
|
||||
|
||||
This will work for most major email providers.
|
||||
|
||||
If the quick setup doesn't work, you'll need to setup an account and an identity in another way, see below for instructions.
|
||||
|
||||
## Set up account - to receive email
|
||||
|
||||
To add an account, tap *Manage accounts* and tap the orange *add* button at the bottom. Select a provider from the list, enter the username, which is mostly your email address and enter your password. Tap *Check* to let FairEmail connect to the email server and fetch a list of system folders. After reviewing the system folder selection you can add the account by tapping *Save*.
|
||||
|
||||
If your provider is not in the list of providers, select *Custom*. Enter the domain name, for example *gmail.com* and tap *Get settings*. If your provider supports [auto-discovery](https://tools.ietf.org/html/rfc6186), FairEmail will fill in the hostname and port number, else check the setup instructions of your provider for the right IMAP hostname, port number and protocol (SSL/TLS or STARTTLS). For more about this, please see [here](https://github.com/M66B/FairEmail/blob/master/FAQ.md#authorizing-accounts).
|
||||
|
||||
## Set up identity - to send email
|
||||
|
||||
Similarly, to add an identity, tap *Manage identity* and tap the orange *add* button at the bottom. Enter the name you want to appear in de from address of the emails you send and select a linked account. Tap *Save* to add the identity.
|
||||
|
||||
If the account was configured manually, you likely need to configure the identity manually too. Enter the domain name, for example *gmail.com* and tap *Get settings*. If your provider supports [auto-discovery](https://tools.ietf.org/html/rfc6186), FairEmail will fill in the hostname and port number, else check the setup instructions of your provider for the right SMTP hostname, port number and protocol (SSL/TLS or STARTTLS).
|
||||
|
||||
See [this FAQ](https://github.com/M66B/FairEmail/blob/master/FAQ.md#FAQ9) about using aliases.
|
||||
|
||||
## Grant permissions - to access contact information
|
||||
|
||||
If you want to lookup email addresses, have contact photos shown, etc, you'll need to grant read contacts permission to FairEmail. Just tap *Grant permissions* and select *Allow*.
|
||||
|
||||
## Setup battery optimizations - to continuously receive emails
|
||||
|
||||
On recent Android versions, Android will put apps to sleep when the screen is off for some time to reduce battery usage. If you want to receive new emails without delays, you should disable battery optimizations for FairEmail. Tap *Disable battery optimizations* and follow the instructions.
|
||||
|
||||
## Questions or problems
|
||||
|
||||
If you have a question or problem, please [see here](https://github.com/M66B/FairEmail/blob/master/FAQ.md) or use [this contact form](https://contact.faircode.eu/?product=fairemailsupport) to ask for help (you can use the transaction number "*setup help*").
|
||||
41
app/src/main/assets/SETUP-ca.md
Normal file
41
app/src/main/assets/SETUP-ca.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# Setup help
|
||||
|
||||
Setting up FairEmail is fairly simple. You'll need to add at least one account to receive email and at least one identity if you want to send email. The quick setup will add an account and an identity in one go for most major providers.
|
||||
|
||||
## Requirements
|
||||
|
||||
An internet connection is required to set up accounts and identities.
|
||||
|
||||
## Quick setup
|
||||
|
||||
Just enter your name, email address and password and tap *Go*.
|
||||
|
||||
This will work for most major email providers.
|
||||
|
||||
If the quick setup doesn't work, you'll need to setup an account and an identity in another way, see below for instructions.
|
||||
|
||||
## Set up account - to receive email
|
||||
|
||||
To add an account, tap *Manage accounts* and tap the orange *add* button at the bottom. Select a provider from the list, enter the username, which is mostly your email address and enter your password. Tap *Check* to let FairEmail connect to the email server and fetch a list of system folders. After reviewing the system folder selection you can add the account by tapping *Save*.
|
||||
|
||||
If your provider is not in the list of providers, select *Custom*. Enter the domain name, for example *gmail.com* and tap *Get settings*. If your provider supports [auto-discovery](https://tools.ietf.org/html/rfc6186), FairEmail will fill in the hostname and port number, else check the setup instructions of your provider for the right IMAP hostname, port number and protocol (SSL/TLS or STARTTLS). For more about this, please see [here](https://github.com/M66B/FairEmail/blob/master/FAQ.md#authorizing-accounts).
|
||||
|
||||
## Set up identity - to send email
|
||||
|
||||
Similarly, to add an identity, tap *Manage identity* and tap the orange *add* button at the bottom. Enter the name you want to appear in de from address of the emails you send and select a linked account. Tap *Save* to add the identity.
|
||||
|
||||
If the account was configured manually, you likely need to configure the identity manually too. Enter the domain name, for example *gmail.com* and tap *Get settings*. If your provider supports [auto-discovery](https://tools.ietf.org/html/rfc6186), FairEmail will fill in the hostname and port number, else check the setup instructions of your provider for the right SMTP hostname, port number and protocol (SSL/TLS or STARTTLS).
|
||||
|
||||
See [this FAQ](https://github.com/M66B/FairEmail/blob/master/FAQ.md#FAQ9) about using aliases.
|
||||
|
||||
## Grant permissions - to access contact information
|
||||
|
||||
If you want to lookup email addresses, have contact photos shown, etc, you'll need to grant read contacts permission to FairEmail. Just tap *Grant permissions* and select *Allow*.
|
||||
|
||||
## Setup battery optimizations - to continuously receive emails
|
||||
|
||||
On recent Android versions, Android will put apps to sleep when the screen is off for some time to reduce battery usage. If you want to receive new emails without delays, you should disable battery optimizations for FairEmail. Tap *Disable battery optimizations* and follow the instructions.
|
||||
|
||||
## Questions or problems
|
||||
|
||||
If you have a question or problem, please [see here](https://github.com/M66B/FairEmail/blob/master/FAQ.md) or use [this contact form](https://contact.faircode.eu/?product=fairemailsupport) to ask for help (you can use the transaction number "*setup help*").
|
||||
41
app/src/main/assets/SETUP-cs.md
Normal file
41
app/src/main/assets/SETUP-cs.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# Setup help
|
||||
|
||||
Setting up FairEmail is fairly simple. You'll need to add at least one account to receive email and at least one identity if you want to send email. The quick setup will add an account and an identity in one go for most major providers.
|
||||
|
||||
## Requirements
|
||||
|
||||
An internet connection is required to set up accounts and identities.
|
||||
|
||||
## Quick setup
|
||||
|
||||
Just enter your name, email address and password and tap *Go*.
|
||||
|
||||
This will work for most major email providers.
|
||||
|
||||
If the quick setup doesn't work, you'll need to setup an account and an identity in another way, see below for instructions.
|
||||
|
||||
## Set up account - to receive email
|
||||
|
||||
To add an account, tap *Manage accounts* and tap the orange *add* button at the bottom. Select a provider from the list, enter the username, which is mostly your email address and enter your password. Tap *Check* to let FairEmail connect to the email server and fetch a list of system folders. After reviewing the system folder selection you can add the account by tapping *Save*.
|
||||
|
||||
If your provider is not in the list of providers, select *Custom*. Enter the domain name, for example *gmail.com* and tap *Get settings*. If your provider supports [auto-discovery](https://tools.ietf.org/html/rfc6186), FairEmail will fill in the hostname and port number, else check the setup instructions of your provider for the right IMAP hostname, port number and protocol (SSL/TLS or STARTTLS). For more about this, please see [here](https://github.com/M66B/FairEmail/blob/master/FAQ.md#authorizing-accounts).
|
||||
|
||||
## Set up identity - to send email
|
||||
|
||||
Similarly, to add an identity, tap *Manage identity* and tap the orange *add* button at the bottom. Enter the name you want to appear in de from address of the emails you send and select a linked account. Tap *Save* to add the identity.
|
||||
|
||||
If the account was configured manually, you likely need to configure the identity manually too. Enter the domain name, for example *gmail.com* and tap *Get settings*. If your provider supports [auto-discovery](https://tools.ietf.org/html/rfc6186), FairEmail will fill in the hostname and port number, else check the setup instructions of your provider for the right SMTP hostname, port number and protocol (SSL/TLS or STARTTLS).
|
||||
|
||||
See [this FAQ](https://github.com/M66B/FairEmail/blob/master/FAQ.md#FAQ9) about using aliases.
|
||||
|
||||
## Grant permissions - to access contact information
|
||||
|
||||
If you want to lookup email addresses, have contact photos shown, etc, you'll need to grant read contacts permission to FairEmail. Just tap *Grant permissions* and select *Allow*.
|
||||
|
||||
## Setup battery optimizations - to continuously receive emails
|
||||
|
||||
On recent Android versions, Android will put apps to sleep when the screen is off for some time to reduce battery usage. If you want to receive new emails without delays, you should disable battery optimizations for FairEmail. Tap *Disable battery optimizations* and follow the instructions.
|
||||
|
||||
## Questions or problems
|
||||
|
||||
If you have a question or problem, please [see here](https://github.com/M66B/FairEmail/blob/master/FAQ.md) or use [this contact form](https://contact.faircode.eu/?product=fairemailsupport) to ask for help (you can use the transaction number "*setup help*").
|
||||
41
app/src/main/assets/SETUP-da.md
Normal file
41
app/src/main/assets/SETUP-da.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# Opsætningshjælp
|
||||
|
||||
At opsætte FairEmail er ret enkelt. Du skal tilføje mindst én konto for at modtage e-mail og mindst én identitet for at sende e-mail. Den hurtige opsætning tilføjer på én gang en konto og en identitet for de fleste større udbydere.
|
||||
|
||||
## Forudsætninger
|
||||
|
||||
En Internetforbindelse kræves for at opsætte konti og identiteter.
|
||||
|
||||
## Hurtig opsætning
|
||||
|
||||
Angiv blot dit navn, e-mail og adgangskode, og tryk på *Go*.
|
||||
|
||||
Dette vil fungere for de fleste større udbydere.
|
||||
|
||||
Fungerer hurtig-opsætningen ikke, så opsæt på anden vis en konto og en identitet (se instruktioner nedenfor).
|
||||
|
||||
## Opsæt konto - for at modtage e-mail
|
||||
|
||||
For at tilføje en konto, så tryk på*Håndtér Konti* og tryk på orange knap *Tilføj* nederst. Vælg en udbyder på listen, angiv brugernavnet, som for det meste er din e-mail, og angiv dit adgangskode. Tryk *Tjek* for at lade FairEmail forbinde til e-mailserveren og hente en liste over systemmapper. Efter gennemgang af systemmappeudvalget kan du tilføje kontoen ved at trykke på*Gem*.
|
||||
|
||||
Er din udbyder ikke på listen over udbydere, så vælg *Tilpasset*. Angiv domænenavnet, f.eks. *gmail.com*, og tryk på *Hent indstillinger*. Understøtter din udbyder [auto-detektering](https://tools.ietf.org/html/rfc6186), udfylder FairEmail værtsnavnet og portnummeret, ellers tjek opsætningsvejledningen fra din udbyder vedr. korrekt IMAP-værtsnavn, portnummer og protokol (SSL/TLS eller STARTTLS). For mere om dette, se [hér](https://github.com/M66B/FairEmail/blob/master/FAQ.md#authorizing-accounts).
|
||||
|
||||
## Opsæt identitet - for at sende e-mail
|
||||
|
||||
På samme vis, så tryk på *Håndtér Identitet* for at tilføje en identitet og tryk på den orange knap *Tilføj* nederst. Angiv det navn, du ønsker vist i fra-adressen i e-mails, du sender, og vælg en tilknyttet konto. Tryk *Gem* for at tilføje identiteten.
|
||||
|
||||
Blev kontoen opsat manuelt, skal du sandsynligvis også opsætte identiteten manuelt. Angiv domænenavnet, f.eks. *gmail.com*, og tryk på *Hent indstillinger*. Understøtter din udbyder [auto-detektering](https://tools.ietf.org/html/rfc6186), udfylder FairEmail værtsnavnet og portnummeret, ellers tjek opsætningsvejledningen fra din udbyder vedr. korrekt SMTP-værtsnavn, portnummer og protokol (SSL/TLS eller STARTTLS).
|
||||
|
||||
Se [denne FAQ](https://github.com/M66B/FairEmail/blob/master/FAQ.md#FAQ9) vedr. brug af aliasser.
|
||||
|
||||
## Giv tilladelser - for at tilgå kontaktoplysninger
|
||||
|
||||
Hvis du vil slå e-mailadresser op, få vist kontaktfotos mv., skal du give FairEmail læs kontakter-tilladelse. Tryk blot på *Giv tilladelse* og vælg *Tillad*.
|
||||
|
||||
## Opsæt batterioptimeringer - til løbende modtagelse af e-mail
|
||||
|
||||
I nyere Android-versioner sættes apps i dvale for at reducere batteriforbruget, når skærmen har været slukket i nogen tid. Vil du gerne modtage nye e-mails uden forsinkelser, skal du deaktivere batterioptimeringer for FairEmail. Tryk på *Deaktivér Batterioptimeringer* og følg anvisningerne.
|
||||
|
||||
## Spørgsmål eller problemer
|
||||
|
||||
Har du spørgsmål eller problemer, så [tjek hér](https://github.com/M66B/FairEmail/blob/master/FAQ.md) eller benyt [ denne kontaktformular](https://contact.faircode.eu/?product=fairemailsupport) til at bede om hjælp (du kan benytte transaktionsnummeret "*opsætningshjælp*").
|
||||
41
app/src/main/assets/SETUP-de.md
Normal file
41
app/src/main/assets/SETUP-de.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# Setuphilfe
|
||||
|
||||
FairEmail einzurichten, ist ziemlich einfach. Sie müssen mindestens ein Konto hinzufügen, um E-Mail zu erhalten, und mindestens eine Identität, wenn Sie E-Mail senden möchten. Die Schnelleinrichtung wird ein Konto und eine Identität in einem Vorgang für die meisten großen Anbieter erstellen.
|
||||
|
||||
## Anforderungen
|
||||
|
||||
Für die Einrichtung von Konten und Identitäten ist eine Internetverbindung erforderlich.
|
||||
|
||||
## Schnelleinrichtung
|
||||
|
||||
Geben Sie einfach Ihren Namen, Ihre E-Mail-Adresse und Ihr Passwort ein und tippen Sie auf *prüfen*.
|
||||
|
||||
Dies funktioniert für die meisten großen E-Mail-Anbieter.
|
||||
|
||||
Wenn die Schnelleinrichtung nicht funktioniert, müssen Sie ein Konto und eine Identität auf andere Weise einrichten, siehe unten für Anweisungen dazu.
|
||||
|
||||
## Konto einrichten - um E-Mail zu erhalten
|
||||
|
||||
Um ein Konto hinzuzufügen, tippen Sie unter 'Konten einrichten' auf *Verwalten* und tippen Sie dann auf den orange-farbenen Button mit dem *Plus-Zeichen* unten. Wählen Sie einen Anbieter aus der Liste, geben Sie den Benutzernamen ein, der meistens Ihre E-Mail-Adresse ist, und geben Sie Ihr Passwort ein. Tippen Sie auf *Prüfen* um FairEmail mit dem E-Mail-Server zu verbinden und eine Liste von Systemordnern zu laden. Nach der Überprüfung der Systemordner-Auswahl können Sie das Konto hinzufügen, indem Sie auf *Speichern* klicken.
|
||||
|
||||
Wenn Ihr Provider nicht in der Liste der Anbieter ist, wählen Sie *Benutzerdefiniert*. Geben Sie den Domain-Namen ein, zum Beispiel *gmail.com*, und tippen Sie auf *Einstellungen abrufen*. Wenn Ihr Provider [Auto-discovery](https://tools.ietf.org/html/rfc6186) unterstützt, wird FairEmail den Hostnamen und die Portnummer ausfüllen, so dass Sie die Angaben Ihres Providers für den richtigen IMAP-Hostnamen, Port-Nummer und Protokoll (SSL/TLS oder STARTTLS) nur noch überprüfen müssen. Weitere Informationen dazu finden Sie [hier](https://github.com/M66B/FairEmail/blob/master/FAQ.md#authorizing-accounts).
|
||||
|
||||
## Identität einrichten - um E-Mail zu senden
|
||||
|
||||
Vergleichbar mit der Konto-Einrichtung tippen Sie unter 'Identitäten einrichten' auf *Verwalten* und tippen Sie dann auf den orange-farbenen Button mit dem *Plus-Zeichen* unten. Geben Sie den Namen ein, der in E-Mails, die Sie von dieser Absender-Adresse senden, erscheinen soll, und wählen Sie ein verknüpftes Konto. Tippen Sie auf *Speichern*, um die Identität hinzuzufügen.
|
||||
|
||||
Wenn das Konto manuell konfiguriert wurde, müssen Sie wahrscheinlich auch die Identität manuell konfigurieren. Geben Sie den Domain-Namen ein, zum Beispiel *gmail.com*, und tippen Sie auf *Einstellungen abrufen*. Wenn Ihr Provider [Auto-discovery](https://tools.ietf.org/html/rfc6186) unterstützt, wird FairEmail den Hostnamen und die Portnummer ausfüllen, so dass Sie die Angaben Ihres Providers für den richtigen IMAP-Hostnamen, Port-Nummer und Protokoll (SSL/TLS oder STARTTLS) nur noch überprüfen müssen.
|
||||
|
||||
Siehe [diese FAQ](https://github.com/M66B/FairEmail/blob/master/FAQ.md#FAQ9) über die Verwendung von Alias-Adressen.
|
||||
|
||||
## Berechtigungen gewähren - Zugriff auf Kontaktinformationen
|
||||
|
||||
Wenn Sie E-Mail-Adressen suchen wollen, Kontaktfotos anzeigen lassen möchten etc., müssen Sie FairEmail die Lese-Kontaktberechtigung erteilen. Tippe einfach auf *Berechtigungen erteilen* und wählen Sie *Zulassen*.
|
||||
|
||||
## Akku-Optimierungen einrichten - um E-Mails kontinuierlich zu erhalten
|
||||
|
||||
Bei den aktuellen Android-Versionen wird Android alle Apps zum Schlafen bringen, wenn der Bildschirm für einige Zeit ausgeschaltet ist, um die Akkunutzung zu reduzieren. Wenn Sie neue E-Mails ohne Verzögerung erhalten möchten, sollten Sie die Batterie-Optimierung für FairEmail deaktivieren. Tippen Sie auf *Akku-Optimierung einrichten* und folgen Sie den Anweisungen.
|
||||
|
||||
## Fragen oder Probleme
|
||||
|
||||
Wenn Sie Fragen oder Probleme haben, bitte [hier](https://github.com/M66B/FairEmail/blob/master/FAQ.md) nachschauen oder benutzen Sie [dieses Kontaktformular](https://contact.faircode.eu/?product=fairemailsupport), um um Hilfe zu bitten (Sie können dabei die Transaktionsnummer "*Setup help*" verwenden).
|
||||
41
app/src/main/assets/SETUP-el.md
Normal file
41
app/src/main/assets/SETUP-el.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# Setup help
|
||||
|
||||
Setting up FairEmail is fairly simple. You'll need to add at least one account to receive email and at least one identity if you want to send email. The quick setup will add an account and an identity in one go for most major providers.
|
||||
|
||||
## Requirements
|
||||
|
||||
An internet connection is required to set up accounts and identities.
|
||||
|
||||
## Quick setup
|
||||
|
||||
Just enter your name, email address and password and tap *Go*.
|
||||
|
||||
This will work for most major email providers.
|
||||
|
||||
If the quick setup doesn't work, you'll need to setup an account and an identity in another way, see below for instructions.
|
||||
|
||||
## Set up account - to receive email
|
||||
|
||||
To add an account, tap *Manage accounts* and tap the orange *add* button at the bottom. Select a provider from the list, enter the username, which is mostly your email address and enter your password. Tap *Check* to let FairEmail connect to the email server and fetch a list of system folders. After reviewing the system folder selection you can add the account by tapping *Save*.
|
||||
|
||||
If your provider is not in the list of providers, select *Custom*. Enter the domain name, for example *gmail.com* and tap *Get settings*. If your provider supports [auto-discovery](https://tools.ietf.org/html/rfc6186), FairEmail will fill in the hostname and port number, else check the setup instructions of your provider for the right IMAP hostname, port number and protocol (SSL/TLS or STARTTLS). For more about this, please see [here](https://github.com/M66B/FairEmail/blob/master/FAQ.md#authorizing-accounts).
|
||||
|
||||
## Set up identity - to send email
|
||||
|
||||
Similarly, to add an identity, tap *Manage identity* and tap the orange *add* button at the bottom. Enter the name you want to appear in de from address of the emails you send and select a linked account. Tap *Save* to add the identity.
|
||||
|
||||
If the account was configured manually, you likely need to configure the identity manually too. Enter the domain name, for example *gmail.com* and tap *Get settings*. If your provider supports [auto-discovery](https://tools.ietf.org/html/rfc6186), FairEmail will fill in the hostname and port number, else check the setup instructions of your provider for the right SMTP hostname, port number and protocol (SSL/TLS or STARTTLS).
|
||||
|
||||
See [this FAQ](https://github.com/M66B/FairEmail/blob/master/FAQ.md#FAQ9) about using aliases.
|
||||
|
||||
## Grant permissions - to access contact information
|
||||
|
||||
If you want to lookup email addresses, have contact photos shown, etc, you'll need to grant read contacts permission to FairEmail. Just tap *Grant permissions* and select *Allow*.
|
||||
|
||||
## Setup battery optimizations - to continuously receive emails
|
||||
|
||||
On recent Android versions, Android will put apps to sleep when the screen is off for some time to reduce battery usage. If you want to receive new emails without delays, you should disable battery optimizations for FairEmail. Tap *Disable battery optimizations* and follow the instructions.
|
||||
|
||||
## Questions or problems
|
||||
|
||||
If you have a question or problem, please [see here](https://github.com/M66B/FairEmail/blob/master/FAQ.md) or use [this contact form](https://contact.faircode.eu/?product=fairemailsupport) to ask for help (you can use the transaction number "*setup help*").
|
||||
41
app/src/main/assets/SETUP-es-ES.md
Normal file
41
app/src/main/assets/SETUP-es-ES.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# Setup help
|
||||
|
||||
Setting up FairEmail is fairly simple. You'll need to add at least one account to receive email and at least one identity if you want to send email. The quick setup will add an account and an identity in one go for most major providers.
|
||||
|
||||
## Requirements
|
||||
|
||||
An internet connection is required to set up accounts and identities.
|
||||
|
||||
## Quick setup
|
||||
|
||||
Just enter your name, email address and password and tap *Go*.
|
||||
|
||||
This will work for most major email providers.
|
||||
|
||||
If the quick setup doesn't work, you'll need to setup an account and an identity in another way, see below for instructions.
|
||||
|
||||
## Set up account - to receive email
|
||||
|
||||
To add an account, tap *Manage accounts* and tap the orange *add* button at the bottom. Select a provider from the list, enter the username, which is mostly your email address and enter your password. Tap *Check* to let FairEmail connect to the email server and fetch a list of system folders. After reviewing the system folder selection you can add the account by tapping *Save*.
|
||||
|
||||
If your provider is not in the list of providers, select *Custom*. Enter the domain name, for example *gmail.com* and tap *Get settings*. If your provider supports [auto-discovery](https://tools.ietf.org/html/rfc6186), FairEmail will fill in the hostname and port number, else check the setup instructions of your provider for the right IMAP hostname, port number and protocol (SSL/TLS or STARTTLS). For more about this, please see [here](https://github.com/M66B/FairEmail/blob/master/FAQ.md#authorizing-accounts).
|
||||
|
||||
## Set up identity - to send email
|
||||
|
||||
Similarly, to add an identity, tap *Manage identity* and tap the orange *add* button at the bottom. Enter the name you want to appear in de from address of the emails you send and select a linked account. Tap *Save* to add the identity.
|
||||
|
||||
If the account was configured manually, you likely need to configure the identity manually too. Enter the domain name, for example *gmail.com* and tap *Get settings*. If your provider supports [auto-discovery](https://tools.ietf.org/html/rfc6186), FairEmail will fill in the hostname and port number, else check the setup instructions of your provider for the right SMTP hostname, port number and protocol (SSL/TLS or STARTTLS).
|
||||
|
||||
See [this FAQ](https://github.com/M66B/FairEmail/blob/master/FAQ.md#FAQ9) about using aliases.
|
||||
|
||||
## Grant permissions - to access contact information
|
||||
|
||||
If you want to lookup email addresses, have contact photos shown, etc, you'll need to grant read contacts permission to FairEmail. Just tap *Grant permissions* and select *Allow*.
|
||||
|
||||
## Setup battery optimizations - to continuously receive emails
|
||||
|
||||
On recent Android versions, Android will put apps to sleep when the screen is off for some time to reduce battery usage. If you want to receive new emails without delays, you should disable battery optimizations for FairEmail. Tap *Disable battery optimizations* and follow the instructions.
|
||||
|
||||
## Questions or problems
|
||||
|
||||
If you have a question or problem, please [see here](https://github.com/M66B/FairEmail/blob/master/FAQ.md) or use [this contact form](https://contact.faircode.eu/?product=fairemailsupport) to ask for help (you can use the transaction number "*setup help*").
|
||||
41
app/src/main/assets/SETUP-fa.md
Normal file
41
app/src/main/assets/SETUP-fa.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# Setup help
|
||||
|
||||
Setting up FairEmail is fairly simple. You'll need to add at least one account to receive email and at least one identity if you want to send email. The quick setup will add an account and an identity in one go for most major providers.
|
||||
|
||||
## Requirements
|
||||
|
||||
An internet connection is required to set up accounts and identities.
|
||||
|
||||
## Quick setup
|
||||
|
||||
Just enter your name, email address and password and tap *Go*.
|
||||
|
||||
This will work for most major email providers.
|
||||
|
||||
If the quick setup doesn't work, you'll need to setup an account and an identity in another way, see below for instructions.
|
||||
|
||||
## Set up account - to receive email
|
||||
|
||||
To add an account, tap *Manage accounts* and tap the orange *add* button at the bottom. Select a provider from the list, enter the username, which is mostly your email address and enter your password. Tap *Check* to let FairEmail connect to the email server and fetch a list of system folders. After reviewing the system folder selection you can add the account by tapping *Save*.
|
||||
|
||||
If your provider is not in the list of providers, select *Custom*. Enter the domain name, for example *gmail.com* and tap *Get settings*. If your provider supports [auto-discovery](https://tools.ietf.org/html/rfc6186), FairEmail will fill in the hostname and port number, else check the setup instructions of your provider for the right IMAP hostname, port number and protocol (SSL/TLS or STARTTLS). For more about this, please see [here](https://github.com/M66B/FairEmail/blob/master/FAQ.md#authorizing-accounts).
|
||||
|
||||
## Set up identity - to send email
|
||||
|
||||
Similarly, to add an identity, tap *Manage identity* and tap the orange *add* button at the bottom. Enter the name you want to appear in de from address of the emails you send and select a linked account. Tap *Save* to add the identity.
|
||||
|
||||
If the account was configured manually, you likely need to configure the identity manually too. Enter the domain name, for example *gmail.com* and tap *Get settings*. If your provider supports [auto-discovery](https://tools.ietf.org/html/rfc6186), FairEmail will fill in the hostname and port number, else check the setup instructions of your provider for the right SMTP hostname, port number and protocol (SSL/TLS or STARTTLS).
|
||||
|
||||
See [this FAQ](https://github.com/M66B/FairEmail/blob/master/FAQ.md#FAQ9) about using aliases.
|
||||
|
||||
## Grant permissions - to access contact information
|
||||
|
||||
If you want to lookup email addresses, have contact photos shown, etc, you'll need to grant read contacts permission to FairEmail. Just tap *Grant permissions* and select *Allow*.
|
||||
|
||||
## Setup battery optimizations - to continuously receive emails
|
||||
|
||||
On recent Android versions, Android will put apps to sleep when the screen is off for some time to reduce battery usage. If you want to receive new emails without delays, you should disable battery optimizations for FairEmail. Tap *Disable battery optimizations* and follow the instructions.
|
||||
|
||||
## Questions or problems
|
||||
|
||||
If you have a question or problem, please [see here](https://github.com/M66B/FairEmail/blob/master/FAQ.md) or use [this contact form](https://contact.faircode.eu/?product=fairemailsupport) to ask for help (you can use the transaction number "*setup help*").
|
||||
41
app/src/main/assets/SETUP-fi.md
Normal file
41
app/src/main/assets/SETUP-fi.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# Asetusohje
|
||||
|
||||
FairEmailin käyttöönotto on melko yksinkertaista. Tarvitset ainakin yhden tilin vastaanottaaksesi sähköpostia ja ainakin yhden tilin lähettääksesi sähköpostia. Pika-asennus lisää samalla kertaa tilin ja identiteetin useimmilla palveluntarjoajilla.
|
||||
|
||||
## Vaatimukset
|
||||
|
||||
Verkkoyhteys tarvitaan tilien ja identiteettien asettamiseksi.
|
||||
|
||||
## Pika-asennus
|
||||
|
||||
Lisää vain nimesi, sähköpostiosoitteesi ja salasanasi sekä paina *Aloita*.
|
||||
|
||||
Tämä toimii useimmille sähköpostipalveluntarjoajille.
|
||||
|
||||
Jos pika-asennus ei toimi, täytyy tili ja identiteetti asettaa erikseen, katso ohjeet alapuolelta.
|
||||
|
||||
## Aseta tili - sähköpostien vastaanottamiseksi
|
||||
|
||||
Tilin lisäämiseksi napauta *Aseta tilit* ja napauta alhaalla olevaa oranssia *lisää*-painiketta. Valitse palveluntarjoaja listasta, anna käyttäjätunnus, joka useimmiten on sähköpostiosoite, ja anna salasana. Napauta *Tarkista* antaaksesi FairEmailin yhdistää sähköpostipalvelimeen ja hakea listan järjestelmän kansioista. Järjestelmän kansiovalintojen tarkistuksen jälkeen voit lisätä tilin napauttamalla *Tallenna*.
|
||||
|
||||
Jos palveluntarjoajasi ei ole palveluntarjoajalistalla, valitse *Mukautettu*. Lisää verkkotunnus, esimerkiksi *gmail.com*, ja napauta *Hae asetukset*. Jos palveluntarjoajasi tukee [auto-discoverya](https://tools.ietf.org/html/rfc6186), FairEmail täyttää verkko-osoitteen ja porttinumeron. Muussa tapauksessa tarkista palveluntarjoajasi asetusohjeista IMAP verkko-osoite, porttinumero ja protokolla (SSL/TLS tai STARTTLS). Katso [täältä](https://github.com/M66B/FairEmail/blob/master/FAQ.md#authorizing-accounts) lisätietoja.
|
||||
|
||||
## Aseta identiteetti - viestien lähettämiseksi
|
||||
|
||||
Vastaavasti identiteetin lisäämiseksi napauta *Aseta identiteetit* ja napauta alhaalla olevaa oranssia *lisää*-painiketta. Anna nimi, jonka haluat näkyvän lähettämiesi viestien lähetysosoitteessa ja valitse siihen liittyvä tili. Napauta *Tallenna* lisätäksesi identiteetin.
|
||||
|
||||
Jos tili määritettiin manuaalisesti, luultavasti identiteettikin pitää asettaa manuaalisesti. Lisää verkkotunnus, esimerkiksi *gmail.com*, ja napauta *Hae asetukset*. Jos palveluntarjoajasi tukee [auto-discoverya](https://tools.ietf.org/html/rfc6186), FairEmail täyttää verkko-osoitteen ja porttinumeron. Muussa tapauksessa tarkista palveluntarjoajasi asetusohjeista IMAP verkko-osoite, porttinumero ja protokolla (SSL/TLS tai STARTTLS).
|
||||
|
||||
Lue [tämä usein kysytyt kysymykset](https://github.com/M66B/FairEmail/blob/master/FAQ.md#FAQ9) aliasten käytöstä.
|
||||
|
||||
## Myönnä käyttöoikeudet - yhteystietoihin pääsemiseksi
|
||||
|
||||
FairEmail tarvitsee käyttöoikeudet yhteystietoihin, jos haluat etsiä sähköpostiosoitteita, näyttää yhteystietojen kuvia, jne. Napauta vain *Myönnä käyttöoikeudet* ja valitse *Myönnä*.
|
||||
|
||||
## Aseta akun käytön optimointi - sähköpostien vastaanottamiseksi ilman keskeytyksiä
|
||||
|
||||
Viimeisimmissä Android-versiossa sovellukset laitetaan lepotilaan akun käytön vähentämiseksi, kun näyttö on ollut jonkin aikaa pois päältä. Jos haluat vastaanottaa sähköposteja ilman keskeytyksiä, pitää akun käytön optimointi ottaa pois käytöstä FairEmailille. Napauta *Poista akun käytön optimoinnit* ua seuraa ohjeita.
|
||||
|
||||
## Kysymyksiä tai ongelmia
|
||||
|
||||
Jos sinulla on kysymys tai ongelma, [katso täältä](https://github.com/M66B/FairEmail/blob/master/FAQ.md) tai käytä [tätä yhteydenotyolomaketta](https://contact.faircode.eu/?product=fairemailsupport) pyytääksesi apua (voit käyttää tapahtumanumeroa "*asetusohje*").
|
||||
41
app/src/main/assets/SETUP-fr.md
Normal file
41
app/src/main/assets/SETUP-fr.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# Aide de configuration
|
||||
|
||||
La configuration de FairEmail est assez simple. Vous devrez ajouter au moins un compte pour recevoir des courriels et au moins une identité si vous voulez envoyer des courriels. La configuration rapide ajoutera un compte et une identité en une seule opération pour la plupart des principaux fournisseurs.
|
||||
|
||||
## Pré-requis
|
||||
|
||||
Une connexion Internet est nécessaire pour configurer les comptes et les identités.
|
||||
|
||||
## Configuration rapide
|
||||
|
||||
Entrez simplement votre nom, votre adresse e-mail et votre mot de passe et appuyez sur *Aller*.
|
||||
|
||||
Ceci fonctionnera pour la plupart des principaux fournisseurs de messagerie.
|
||||
|
||||
Si la configuration rapide ne fonctionne pas, vous devez configurer un compte et une identité d'une autre manière, voir ci-dessous pour les instructions.
|
||||
|
||||
## Configuration d'un compte - recevoir des e-mails
|
||||
|
||||
Pour ajouter un compte, appuyez sur *Gérer les comptes* et appuyez sur le bouton orange *ajouter* en bas. Sélectionnez un fournisseur dans la liste, entrez le nom d'utilisateur, qui est principalement votre adresse e-mail et entrez votre mot de passe. Appuyez sur *Vérifier* pour permettre à FairEmail de se connecter au serveur de messagerie et de récupérer une liste de dossiers système. Après avoir examiné la sélection des dossiers système, vous pouvez ajouter le compte en appuyant sur *Enregistrer*.
|
||||
|
||||
Si votre fournisseur n'est pas dans la liste des fournisseurs, sélectionnez *Personnalisé*. Entrez le nom de domaine, par exemple *gmail.com* et appuyez sur *Obtenir les paramètres*. Si votre fournisseur supporte l'[auto-découverte](https://tools.ietf.org/html/rfc6186), FairEmail remplira le nom d'hôte et le numéro de port, sinon vérifiez les instructions de configuration de votre fournisseur pour le nom d'hôte IMAP, le numéro de port et le protocole (SSL/TLS ou STARTTLS). Pour plus d'informations, veuillez voir [ici](https://github.com/M66B/FairEmail/blob/master/FAQ.md#authorizing-accounts).
|
||||
|
||||
## Configuration d'une identité - envoyer des e-mails
|
||||
|
||||
De même, pour ajouter une identité, appuyez sur *Gérer les identités* et appuyez sur le bouton orange *ajouter* en bas. Entrez le nom que vous souhaitez voir apparaître dans le champ de: pour les e-mails que vous envoyez et sélectionnez un compte lié. Appuyez sur *Enregistrer* pour ajouter l'identité.
|
||||
|
||||
Si le compte a été configuré manuellement, vous devez probablement configurer l'identité également manuellement. Entrez le nom de domaine, par exemple *gmail.com* et appuyez sur *Obtenir les paramètres*. Si votre fournisseur supporte l'[auto-découverte](https://tools.ietf.org/html/rfc6186), FairEmail remplira le nom d'hôte et le numéro de port, sinon vérifiez les instructions de configuration de votre fournisseur pour le nom d'hôte IMAP, le numéro de port et le protocole (SSL/TLS ou STARTTLS).
|
||||
|
||||
Voir [cette FAQ](https://github.com/M66B/FairEmail/blob/master/FAQ.md#FAQ9) sur l'utilisation des alias.
|
||||
|
||||
## Accorder les permissions - accéder aux informations de contact
|
||||
|
||||
Si vous souhaitez rechercher des adresses e-mail, avoir les photos de contact affichées, etc, vous devez accorder la permission de lire les contacts à FairEmail. Appuyez simplement sur *Accorder les autorisations* et sélectionnez *Autoriser*.
|
||||
|
||||
## Configurer les optimisations de batterie - recevoir des e-mails en continu
|
||||
|
||||
Sur les versions récentes d'Android, Android mettra les applications en veille lorsque l'écran est éteint pendant un certain temps pour réduire l'utilisation de la batterie. Si vous souhaitez recevoir de nouveaux courriels sans retard, vous devez désactiver les optimisations de batterie pour FairEmail. Appuyez sur *Désactiver les optimisations de batterie* et suivez les instructions.
|
||||
|
||||
## Questions ou problèmes
|
||||
|
||||
Si vous avez une question ou un problème, veuillez [voir ici](https://github.com/M66B/FairEmail/blob/master/FAQ.md) ou utiliser [ce formulaire de contact](https://contact.faircode.eu/?product=fairemailsupport) pour demander de l'aide (vous pouvez utiliser le numéro de transaction "*aide d'installation*").
|
||||
41
app/src/main/assets/SETUP-gl.md
Normal file
41
app/src/main/assets/SETUP-gl.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# Setup help
|
||||
|
||||
Setting up FairEmail is fairly simple. You'll need to add at least one account to receive email and at least one identity if you want to send email. The quick setup will add an account and an identity in one go for most major providers.
|
||||
|
||||
## Requirements
|
||||
|
||||
An internet connection is required to set up accounts and identities.
|
||||
|
||||
## Quick setup
|
||||
|
||||
Just enter your name, email address and password and tap *Go*.
|
||||
|
||||
This will work for most major email providers.
|
||||
|
||||
If the quick setup doesn't work, you'll need to setup an account and an identity in another way, see below for instructions.
|
||||
|
||||
## Set up account - to receive email
|
||||
|
||||
To add an account, tap *Manage accounts* and tap the orange *add* button at the bottom. Select a provider from the list, enter the username, which is mostly your email address and enter your password. Tap *Check* to let FairEmail connect to the email server and fetch a list of system folders. After reviewing the system folder selection you can add the account by tapping *Save*.
|
||||
|
||||
If your provider is not in the list of providers, select *Custom*. Enter the domain name, for example *gmail.com* and tap *Get settings*. If your provider supports [auto-discovery](https://tools.ietf.org/html/rfc6186), FairEmail will fill in the hostname and port number, else check the setup instructions of your provider for the right IMAP hostname, port number and protocol (SSL/TLS or STARTTLS). For more about this, please see [here](https://github.com/M66B/FairEmail/blob/master/FAQ.md#authorizing-accounts).
|
||||
|
||||
## Set up identity - to send email
|
||||
|
||||
Similarly, to add an identity, tap *Manage identity* and tap the orange *add* button at the bottom. Enter the name you want to appear in de from address of the emails you send and select a linked account. Tap *Save* to add the identity.
|
||||
|
||||
If the account was configured manually, you likely need to configure the identity manually too. Enter the domain name, for example *gmail.com* and tap *Get settings*. If your provider supports [auto-discovery](https://tools.ietf.org/html/rfc6186), FairEmail will fill in the hostname and port number, else check the setup instructions of your provider for the right SMTP hostname, port number and protocol (SSL/TLS or STARTTLS).
|
||||
|
||||
See [this FAQ](https://github.com/M66B/FairEmail/blob/master/FAQ.md#FAQ9) about using aliases.
|
||||
|
||||
## Grant permissions - to access contact information
|
||||
|
||||
If you want to lookup email addresses, have contact photos shown, etc, you'll need to grant read contacts permission to FairEmail. Just tap *Grant permissions* and select *Allow*.
|
||||
|
||||
## Setup battery optimizations - to continuously receive emails
|
||||
|
||||
On recent Android versions, Android will put apps to sleep when the screen is off for some time to reduce battery usage. If you want to receive new emails without delays, you should disable battery optimizations for FairEmail. Tap *Disable battery optimizations* and follow the instructions.
|
||||
|
||||
## Questions or problems
|
||||
|
||||
If you have a question or problem, please [see here](https://github.com/M66B/FairEmail/blob/master/FAQ.md) or use [this contact form](https://contact.faircode.eu/?product=fairemailsupport) to ask for help (you can use the transaction number "*setup help*").
|
||||
41
app/src/main/assets/SETUP-he.md
Normal file
41
app/src/main/assets/SETUP-he.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# Setup help
|
||||
|
||||
Setting up FairEmail is fairly simple. You'll need to add at least one account to receive email and at least one identity if you want to send email. The quick setup will add an account and an identity in one go for most major providers.
|
||||
|
||||
## Requirements
|
||||
|
||||
An internet connection is required to set up accounts and identities.
|
||||
|
||||
## Quick setup
|
||||
|
||||
Just enter your name, email address and password and tap *Go*.
|
||||
|
||||
This will work for most major email providers.
|
||||
|
||||
If the quick setup doesn't work, you'll need to setup an account and an identity in another way, see below for instructions.
|
||||
|
||||
## Set up account - to receive email
|
||||
|
||||
To add an account, tap *Manage accounts* and tap the orange *add* button at the bottom. Select a provider from the list, enter the username, which is mostly your email address and enter your password. Tap *Check* to let FairEmail connect to the email server and fetch a list of system folders. After reviewing the system folder selection you can add the account by tapping *Save*.
|
||||
|
||||
If your provider is not in the list of providers, select *Custom*. Enter the domain name, for example *gmail.com* and tap *Get settings*. If your provider supports [auto-discovery](https://tools.ietf.org/html/rfc6186), FairEmail will fill in the hostname and port number, else check the setup instructions of your provider for the right IMAP hostname, port number and protocol (SSL/TLS or STARTTLS). For more about this, please see [here](https://github.com/M66B/FairEmail/blob/master/FAQ.md#authorizing-accounts).
|
||||
|
||||
## Set up identity - to send email
|
||||
|
||||
Similarly, to add an identity, tap *Manage identity* and tap the orange *add* button at the bottom. Enter the name you want to appear in de from address of the emails you send and select a linked account. Tap *Save* to add the identity.
|
||||
|
||||
If the account was configured manually, you likely need to configure the identity manually too. Enter the domain name, for example *gmail.com* and tap *Get settings*. If your provider supports [auto-discovery](https://tools.ietf.org/html/rfc6186), FairEmail will fill in the hostname and port number, else check the setup instructions of your provider for the right SMTP hostname, port number and protocol (SSL/TLS or STARTTLS).
|
||||
|
||||
See [this FAQ](https://github.com/M66B/FairEmail/blob/master/FAQ.md#FAQ9) about using aliases.
|
||||
|
||||
## Grant permissions - to access contact information
|
||||
|
||||
If you want to lookup email addresses, have contact photos shown, etc, you'll need to grant read contacts permission to FairEmail. Just tap *Grant permissions* and select *Allow*.
|
||||
|
||||
## Setup battery optimizations - to continuously receive emails
|
||||
|
||||
On recent Android versions, Android will put apps to sleep when the screen is off for some time to reduce battery usage. If you want to receive new emails without delays, you should disable battery optimizations for FairEmail. Tap *Disable battery optimizations* and follow the instructions.
|
||||
|
||||
## Questions or problems
|
||||
|
||||
If you have a question or problem, please [see here](https://github.com/M66B/FairEmail/blob/master/FAQ.md) or use [this contact form](https://contact.faircode.eu/?product=fairemailsupport) to ask for help (you can use the transaction number "*setup help*").
|
||||
41
app/src/main/assets/SETUP-hr.md
Normal file
41
app/src/main/assets/SETUP-hr.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# Pomoć pri postavljanju
|
||||
|
||||
Postavljanje FairEmail-a prilično je jednostavno. Trebate dodati barem jedan račun da biste primili e-poštu i barem jedan identitet ako želite slati e-poštu. Brzo postavljanje dodat će račun i identitet većini glavnih dobavljača e-pošte.
|
||||
|
||||
## Zahtjevi
|
||||
|
||||
Za postavljanje računa i identiteta potrebna je internetska veza.
|
||||
|
||||
## Brzo postavljanje
|
||||
|
||||
Samo unesite svoje ime, adresu e-pošte i lozinku i dodirnite *Idi*.
|
||||
|
||||
Ovo će raditi za većinu glavnih pružatelja usluga e-pošte.
|
||||
|
||||
Ako brzo postavljanje ne uspije, morat ćete postaviti račun i identitet na drugi način, upute potražite u nastavku.
|
||||
|
||||
## Postavljanje računa - za primanje e-pošte
|
||||
|
||||
Da biste dodali račun, dodirnite *Upravljanje računima* i na dnu narančastog botuna *dodaj*. S popisa odaberite provajdera, unesite korisničko ime, koje je uglavnom vaša adresa e-pošte i unesite svoju lozinku. Dodirnite *Provjeri* da biste se omogućili da se FairEmail poveže s poslužiteljem e-pošte i preuzme popis sistemskih mapa. Nakon pregleda izbora mape sustava možete dodati račun dodirom na *Spremi*.
|
||||
|
||||
Ako vaš davatelj nije na popisu pružatelja usluga, odaberite *Custom*. Unesite ime domene, primjerice *gmail.com* i dodirnite *Dohvati postavke*. Ako vaš pružatelj usluga podržava [automatsko otkrivanje](https://tools.ietf.org/html/rfc6186), FairEmail će ispuniti ime računala i broj porta, osim toga, provjerite upute za postavljanje vašeg davatelja za pravo IMAP ime glavnog računala, broj porta i protokol (SSL / TLS ili STARTTLS). Više o ovome pogledajte [ovdje](https://github.com/M66B/FairEmail/blob/master/FAQ.md#authorizing-accounts).
|
||||
|
||||
## Postavljanje identiteta - za slanje e-pošte
|
||||
|
||||
Slično tome, da biste dodali identitet, dodirnite *Upravljanje identitetom* i dodirnite narančastu tipku *dodaj* na dnu. Unesite ime koje želite da se pojavi u adresi e-pošte koju šaljete i odaberite povezani račun. Dodirnite *Spremi* da biste dodali identitet.
|
||||
|
||||
Ako je račun konfiguriran ručno, vjerojatno ćete morati i ručno konfigurirati identitet. Unesite ime domene, primjerice *gmail.com* i dodirnite *Dohvati postavke*. Ako vaš pružatelj usluga podržava [automatsko otkrivanje](https://tools.ietf.org/html/rfc6186), FairEmail će ispuniti ime računala i broj porta, osim toga, provjerite upute za postavljanje vašeg davatelja za pravo IMAP ime glavnog računala, broj porta i protokol (SSL / TLS ili STARTTLS).
|
||||
|
||||
Pogledajte [ovaj FAQ](https://github.com/M66B/FairEmail/blob/master/FAQ.md#FAQ9) o korištenju aliasa.
|
||||
|
||||
## Davanje dozvola - za pristup podacima o kontaktima
|
||||
|
||||
Ako želite pretraživati adrese e-pošte, prikazivati fotografije kontakata itd., Trebate odobriti FairEmailu dozvolu za čitanje kontakata. Samo dodirnite *Davanje dozvola* i odaberite *Dopusti*.
|
||||
|
||||
## Postavljanje optimizacija za baterije - za kontinuirano primanje e-poruka
|
||||
|
||||
Na novijim verzijama Androida Android će staviti uređaje za spavanje kad je ekran neko vrijeme isključen kako bi smanjio potrošnju baterije. Ako želite primati nove poruke e-pošte bez odgađanja, trebali biste onemogućiti optimizaciju baterije za FairEmail. Dodirnite *Onemogući optimizaciju baterije* i slijedite upute.
|
||||
|
||||
## Pitanja ili problemi
|
||||
|
||||
Ako imate pitanje ili problem, molimo [pogledajte ovdje](https://github.com/M66B/FairEmail/blob/master/FAQ.md) ili tražite [ovaj obrazac za kontakt](https://contact.faircode.eu/?product=fairemailsupport) da biste zatražili pomoć (možete koristiti broj transakcije "*pomoć u postavljanju*").
|
||||
41
app/src/main/assets/SETUP-hu.md
Normal file
41
app/src/main/assets/SETUP-hu.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# Setup help
|
||||
|
||||
Setting up FairEmail is fairly simple. You'll need to add at least one account to receive email and at least one identity if you want to send email. The quick setup will add an account and an identity in one go for most major providers.
|
||||
|
||||
## Requirements
|
||||
|
||||
An internet connection is required to set up accounts and identities.
|
||||
|
||||
## Quick setup
|
||||
|
||||
Just enter your name, email address and password and tap *Go*.
|
||||
|
||||
This will work for most major email providers.
|
||||
|
||||
If the quick setup doesn't work, you'll need to setup an account and an identity in another way, see below for instructions.
|
||||
|
||||
## Set up account - to receive email
|
||||
|
||||
To add an account, tap *Manage accounts* and tap the orange *add* button at the bottom. Select a provider from the list, enter the username, which is mostly your email address and enter your password. Tap *Check* to let FairEmail connect to the email server and fetch a list of system folders. After reviewing the system folder selection you can add the account by tapping *Save*.
|
||||
|
||||
If your provider is not in the list of providers, select *Custom*. Enter the domain name, for example *gmail.com* and tap *Get settings*. If your provider supports [auto-discovery](https://tools.ietf.org/html/rfc6186), FairEmail will fill in the hostname and port number, else check the setup instructions of your provider for the right IMAP hostname, port number and protocol (SSL/TLS or STARTTLS). For more about this, please see [here](https://github.com/M66B/FairEmail/blob/master/FAQ.md#authorizing-accounts).
|
||||
|
||||
## Set up identity - to send email
|
||||
|
||||
Similarly, to add an identity, tap *Manage identity* and tap the orange *add* button at the bottom. Enter the name you want to appear in de from address of the emails you send and select a linked account. Tap *Save* to add the identity.
|
||||
|
||||
If the account was configured manually, you likely need to configure the identity manually too. Enter the domain name, for example *gmail.com* and tap *Get settings*. If your provider supports [auto-discovery](https://tools.ietf.org/html/rfc6186), FairEmail will fill in the hostname and port number, else check the setup instructions of your provider for the right SMTP hostname, port number and protocol (SSL/TLS or STARTTLS).
|
||||
|
||||
See [this FAQ](https://github.com/M66B/FairEmail/blob/master/FAQ.md#FAQ9) about using aliases.
|
||||
|
||||
## Grant permissions - to access contact information
|
||||
|
||||
If you want to lookup email addresses, have contact photos shown, etc, you'll need to grant read contacts permission to FairEmail. Just tap *Grant permissions* and select *Allow*.
|
||||
|
||||
## Setup battery optimizations - to continuously receive emails
|
||||
|
||||
On recent Android versions, Android will put apps to sleep when the screen is off for some time to reduce battery usage. If you want to receive new emails without delays, you should disable battery optimizations for FairEmail. Tap *Disable battery optimizations* and follow the instructions.
|
||||
|
||||
## Questions or problems
|
||||
|
||||
If you have a question or problem, please [see here](https://github.com/M66B/FairEmail/blob/master/FAQ.md) or use [this contact form](https://contact.faircode.eu/?product=fairemailsupport) to ask for help (you can use the transaction number "*setup help*").
|
||||
41
app/src/main/assets/SETUP-it.md
Normal file
41
app/src/main/assets/SETUP-it.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# Aiuto impostazione
|
||||
|
||||
La configurazione di FairEmail è abbastanza semplice. Dovrai aggiungere almeno un account per ricevere email e almeno un'identità se vuoi inviare email. La configurazione rapida aggiungerà un account e un'identità in un solo colpo per la maggior parte dei principali provider.
|
||||
|
||||
## Requisiti
|
||||
|
||||
È necessaria una connessione internet per configurare account e identità.
|
||||
|
||||
## Configurazione rapida
|
||||
|
||||
Inserisci il tuo nome, indirizzo email e password e tocca *Vai*.
|
||||
|
||||
Questo funzionerà per la maggior parte dei principali provider di posta elettronica.
|
||||
|
||||
Se la configurazione rapida non funziona, dovrai impostare un account e un'identità in un altro modo, vedere sotto per le istruzioni.
|
||||
|
||||
## Imposta account - per ricevere email
|
||||
|
||||
Per aggiungere un account, tocca *Gestisci account* e tocca il pulsante arancione *aggiungi* in basso. Seleziona un provider dalla lista, inserisci il nome utente, che di solito corrisponde al tuo indirizzo email e inserisci la password. Tocca *Controlla* per consentire a FairEmail di connettersi al server email e recuperare una lista di cartelle di sistema. Dopo aver esaminato la selezione della cartella di sistema è possibile aggiungere l'account toccando *Salva*.
|
||||
|
||||
Se il tuo provider non è nella lista dei provider, seleziona *Custom*. Inserisci il nome del dominio, ad esempio *gmail.com* e tocca *Ottieni impostazioni*. Se il tuo provider supporta [l'auto-discovery](https://tools.ietf.org/html/rfc6186), FairEmail riempirà il nome host e il numero di porta, altrimenti controlla le istruzioni di installazione del tuo provider per il corretto nome host IMAP, il numero di porta e il protocollo (SSL/TLS o STARTTLS). Per ulteriori informazioni, si prega di vedere [qui](https://github.com/M66B/FairEmail/blob/master/FAQ.md#authorizing-accounts).
|
||||
|
||||
## Imposta identità - per inviare email
|
||||
|
||||
Analogamente, per aggiungere un'identità, tocca *Gestisci identità* e tocca il pulsante arancione *aggiungi* in basso. Inserisci il nome che vuoi appaia nell'indirizzo del mittente e seleziona un account collegato. Tocca *Salva* per aggiungere l'identità.
|
||||
|
||||
Se l'account è stato configurato manualmente, è probabile che sia necessario configurare l'identità anche manualmente. Inserisci il nome del dominio, ad esempio *gmail.com* e tocca *Ottieni impostazioni*. Se il tuo provider supporta [l'auto-discovery](https://tools.ietf.org/html/rfc6186), FairEmail riempirà il nome host e il numero di porta, altrimenti controlla le istruzioni di installazione del tuo provider per il nome host SMTP corretto, il numero di porta e il protocollo (SSL/TLS o STARTTLS).
|
||||
|
||||
Vedi [questa FAQ](https://github.com/M66B/FairEmail/blob/master/FAQ.md#FAQ9) sull'utilizzo degli alias.
|
||||
|
||||
## Concedi permessi - per accedere alle informazioni di contatto
|
||||
|
||||
Se vuoi cercare indirizzi email, visualizzare le foto dei contatto, ecc, dovrai concedere il permesso di lettura dei contatti a FairEmail. Basta toccare *Concedi permessi* e selezionare *Consenti*.
|
||||
|
||||
## Imposta ottimizzazioni batteria - per ricevere e-mail senza soluzione di continuità
|
||||
|
||||
Nelle versioni recenti di Android, Android metterà le app in standby quando lo schermo è spento per un certo tempo per ridurre l'uso della batteria. Se vuoi ricevere nuove email senza ritardi, dovresti disabilitare le ottimizzazioni della batteria per FairEmail. Tocca *Disabilita le ottimizzazioni della batteria* e segui le istruzioni.
|
||||
|
||||
## Domande o problemi
|
||||
|
||||
Se hai una domanda o un problema, si prega di [vedere qui](https://github.com/M66B/FairEmail/blob/master/FAQ.md) o utilizzare [questo modulo di contatto](https://contact.faircode.eu/?product=fairemailsupport) per chiedere aiuto (è possibile utilizzare il numero di transazione "*configurare aiuto*").
|
||||
41
app/src/main/assets/SETUP-ja.md
Normal file
41
app/src/main/assets/SETUP-ja.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# Setup help
|
||||
|
||||
Setting up FairEmail is fairly simple. You'll need to add at least one account to receive email and at least one identity if you want to send email. The quick setup will add an account and an identity in one go for most major providers.
|
||||
|
||||
## Requirements
|
||||
|
||||
An internet connection is required to set up accounts and identities.
|
||||
|
||||
## Quick setup
|
||||
|
||||
Just enter your name, email address and password and tap *Go*.
|
||||
|
||||
This will work for most major email providers.
|
||||
|
||||
If the quick setup doesn't work, you'll need to setup an account and an identity in another way, see below for instructions.
|
||||
|
||||
## Set up account - to receive email
|
||||
|
||||
To add an account, tap *Manage accounts* and tap the orange *add* button at the bottom. Select a provider from the list, enter the username, which is mostly your email address and enter your password. Tap *Check* to let FairEmail connect to the email server and fetch a list of system folders. After reviewing the system folder selection you can add the account by tapping *Save*.
|
||||
|
||||
If your provider is not in the list of providers, select *Custom*. Enter the domain name, for example *gmail.com* and tap *Get settings*. If your provider supports [auto-discovery](https://tools.ietf.org/html/rfc6186), FairEmail will fill in the hostname and port number, else check the setup instructions of your provider for the right IMAP hostname, port number and protocol (SSL/TLS or STARTTLS). For more about this, please see [here](https://github.com/M66B/FairEmail/blob/master/FAQ.md#authorizing-accounts).
|
||||
|
||||
## Set up identity - to send email
|
||||
|
||||
Similarly, to add an identity, tap *Manage identity* and tap the orange *add* button at the bottom. Enter the name you want to appear in de from address of the emails you send and select a linked account. Tap *Save* to add the identity.
|
||||
|
||||
If the account was configured manually, you likely need to configure the identity manually too. Enter the domain name, for example *gmail.com* and tap *Get settings*. If your provider supports [auto-discovery](https://tools.ietf.org/html/rfc6186), FairEmail will fill in the hostname and port number, else check the setup instructions of your provider for the right SMTP hostname, port number and protocol (SSL/TLS or STARTTLS).
|
||||
|
||||
See [this FAQ](https://github.com/M66B/FairEmail/blob/master/FAQ.md#FAQ9) about using aliases.
|
||||
|
||||
## Grant permissions - to access contact information
|
||||
|
||||
If you want to lookup email addresses, have contact photos shown, etc, you'll need to grant read contacts permission to FairEmail. Just tap *Grant permissions* and select *Allow*.
|
||||
|
||||
## Setup battery optimizations - to continuously receive emails
|
||||
|
||||
On recent Android versions, Android will put apps to sleep when the screen is off for some time to reduce battery usage. If you want to receive new emails without delays, you should disable battery optimizations for FairEmail. Tap *Disable battery optimizations* and follow the instructions.
|
||||
|
||||
## Questions or problems
|
||||
|
||||
If you have a question or problem, please [see here](https://github.com/M66B/FairEmail/blob/master/FAQ.md) or use [this contact form](https://contact.faircode.eu/?product=fairemailsupport) to ask for help (you can use the transaction number "*setup help*").
|
||||
41
app/src/main/assets/SETUP-ko.md
Normal file
41
app/src/main/assets/SETUP-ko.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# Setup help
|
||||
|
||||
Setting up FairEmail is fairly simple. You'll need to add at least one account to receive email and at least one identity if you want to send email. The quick setup will add an account and an identity in one go for most major providers.
|
||||
|
||||
## Requirements
|
||||
|
||||
An internet connection is required to set up accounts and identities.
|
||||
|
||||
## Quick setup
|
||||
|
||||
Just enter your name, email address and password and tap *Go*.
|
||||
|
||||
This will work for most major email providers.
|
||||
|
||||
If the quick setup doesn't work, you'll need to setup an account and an identity in another way, see below for instructions.
|
||||
|
||||
## Set up account - to receive email
|
||||
|
||||
To add an account, tap *Manage accounts* and tap the orange *add* button at the bottom. Select a provider from the list, enter the username, which is mostly your email address and enter your password. Tap *Check* to let FairEmail connect to the email server and fetch a list of system folders. After reviewing the system folder selection you can add the account by tapping *Save*.
|
||||
|
||||
If your provider is not in the list of providers, select *Custom*. Enter the domain name, for example *gmail.com* and tap *Get settings*. If your provider supports [auto-discovery](https://tools.ietf.org/html/rfc6186), FairEmail will fill in the hostname and port number, else check the setup instructions of your provider for the right IMAP hostname, port number and protocol (SSL/TLS or STARTTLS). For more about this, please see [here](https://github.com/M66B/FairEmail/blob/master/FAQ.md#authorizing-accounts).
|
||||
|
||||
## Set up identity - to send email
|
||||
|
||||
Similarly, to add an identity, tap *Manage identity* and tap the orange *add* button at the bottom. Enter the name you want to appear in de from address of the emails you send and select a linked account. Tap *Save* to add the identity.
|
||||
|
||||
If the account was configured manually, you likely need to configure the identity manually too. Enter the domain name, for example *gmail.com* and tap *Get settings*. If your provider supports [auto-discovery](https://tools.ietf.org/html/rfc6186), FairEmail will fill in the hostname and port number, else check the setup instructions of your provider for the right SMTP hostname, port number and protocol (SSL/TLS or STARTTLS).
|
||||
|
||||
See [this FAQ](https://github.com/M66B/FairEmail/blob/master/FAQ.md#FAQ9) about using aliases.
|
||||
|
||||
## Grant permissions - to access contact information
|
||||
|
||||
If you want to lookup email addresses, have contact photos shown, etc, you'll need to grant read contacts permission to FairEmail. Just tap *Grant permissions* and select *Allow*.
|
||||
|
||||
## Setup battery optimizations - to continuously receive emails
|
||||
|
||||
On recent Android versions, Android will put apps to sleep when the screen is off for some time to reduce battery usage. If you want to receive new emails without delays, you should disable battery optimizations for FairEmail. Tap *Disable battery optimizations* and follow the instructions.
|
||||
|
||||
## Questions or problems
|
||||
|
||||
If you have a question or problem, please [see here](https://github.com/M66B/FairEmail/blob/master/FAQ.md) or use [this contact form](https://contact.faircode.eu/?product=fairemailsupport) to ask for help (you can use the transaction number "*setup help*").
|
||||
41
app/src/main/assets/SETUP-lt.md
Normal file
41
app/src/main/assets/SETUP-lt.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# Setup help
|
||||
|
||||
Setting up FairEmail is fairly simple. You'll need to add at least one account to receive email and at least one identity if you want to send email. The quick setup will add an account and an identity in one go for most major providers.
|
||||
|
||||
## Requirements
|
||||
|
||||
An internet connection is required to set up accounts and identities.
|
||||
|
||||
## Quick setup
|
||||
|
||||
Just enter your name, email address and password and tap *Go*.
|
||||
|
||||
This will work for most major email providers.
|
||||
|
||||
If the quick setup doesn't work, you'll need to setup an account and an identity in another way, see below for instructions.
|
||||
|
||||
## Set up account - to receive email
|
||||
|
||||
To add an account, tap *Manage accounts* and tap the orange *add* button at the bottom. Select a provider from the list, enter the username, which is mostly your email address and enter your password. Tap *Check* to let FairEmail connect to the email server and fetch a list of system folders. After reviewing the system folder selection you can add the account by tapping *Save*.
|
||||
|
||||
If your provider is not in the list of providers, select *Custom*. Enter the domain name, for example *gmail.com* and tap *Get settings*. If your provider supports [auto-discovery](https://tools.ietf.org/html/rfc6186), FairEmail will fill in the hostname and port number, else check the setup instructions of your provider for the right IMAP hostname, port number and protocol (SSL/TLS or STARTTLS). For more about this, please see [here](https://github.com/M66B/FairEmail/blob/master/FAQ.md#authorizing-accounts).
|
||||
|
||||
## Set up identity - to send email
|
||||
|
||||
Similarly, to add an identity, tap *Manage identity* and tap the orange *add* button at the bottom. Enter the name you want to appear in de from address of the emails you send and select a linked account. Tap *Save* to add the identity.
|
||||
|
||||
If the account was configured manually, you likely need to configure the identity manually too. Enter the domain name, for example *gmail.com* and tap *Get settings*. If your provider supports [auto-discovery](https://tools.ietf.org/html/rfc6186), FairEmail will fill in the hostname and port number, else check the setup instructions of your provider for the right SMTP hostname, port number and protocol (SSL/TLS or STARTTLS).
|
||||
|
||||
See [this FAQ](https://github.com/M66B/FairEmail/blob/master/FAQ.md#FAQ9) about using aliases.
|
||||
|
||||
## Grant permissions - to access contact information
|
||||
|
||||
If you want to lookup email addresses, have contact photos shown, etc, you'll need to grant read contacts permission to FairEmail. Just tap *Grant permissions* and select *Allow*.
|
||||
|
||||
## Setup battery optimizations - to continuously receive emails
|
||||
|
||||
On recent Android versions, Android will put apps to sleep when the screen is off for some time to reduce battery usage. If you want to receive new emails without delays, you should disable battery optimizations for FairEmail. Tap *Disable battery optimizations* and follow the instructions.
|
||||
|
||||
## Questions or problems
|
||||
|
||||
If you have a question or problem, please [see here](https://github.com/M66B/FairEmail/blob/master/FAQ.md) or use [this contact form](https://contact.faircode.eu/?product=fairemailsupport) to ask for help (you can use the transaction number "*setup help*").
|
||||
41
app/src/main/assets/SETUP-nl.md
Normal file
41
app/src/main/assets/SETUP-nl.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# Help bij instellen
|
||||
|
||||
Het instellen van FairEmail is vrij eenvoudig. U moet ten minste één account toevoegen om e-mail te ontvangen en ten minste één identiteit om e-mail te verzenden. Snel instellen zal in één keer een account en een identiteit toevoegen voor de meeste grote providers.
|
||||
|
||||
## Vereisten
|
||||
|
||||
Een internetverbinding is vereist om accounts en identiteiten in te stellen.
|
||||
|
||||
## Snel instellen
|
||||
|
||||
Voer gewoon je naam, e-mailadres en wachtwoord in en tik op *Ga*.
|
||||
|
||||
Dit zal werken voor de meeste grote e-mailproviders.
|
||||
|
||||
Als het snelle instellen niet werkt, moet je een account en een identiteit op een andere manier instellen, zie hieronder voor instructies.
|
||||
|
||||
## Account instellen - om e-mail te ontvangen
|
||||
|
||||
Om een account toe te voegen, tik op *Beheer accounts* en tik op de oranje *voeg toe* knop onderaan. Selecteer een provider uit de lijst, voer de gebruikersnaam in, meestal uw e-mailadres, en voer uw wachtwoord in. Tik *Controleer* om FairEmail te laten verbinden met de e-mailserver en een lijst van systeemmappen op te laten halen. Na het controleren van de selectie van systeemmappen kunt u het account toevoegen door op *Bewaren* te klikken.
|
||||
|
||||
Als uw provider niet in de lijst van providers staat, selecteer *Aangepast*. Voer de domeinnaam in, bijvoorbeeld *gmail.com* en tik op *Instellingen ophalen*. Als uw provider [auto-discovery](https://tools.ietf.org/html/rfc6186) ondersteunt, zal FairEmail de hostnaam en poortnummer invullen, controleer anders de instructies van uw provider voor de juiste IMAP host naam, poortnummer en protocol (SSL/TLS of STARTTLS). Voor meer informatie, zie [hier](https://github.com/M66B/FairEmail/blob/master/FAQ.md#authorizing-accounts).
|
||||
|
||||
## Identiteit instellen - om e-mail te verzenden
|
||||
|
||||
Om een identiteit toe te voegen, tik op *Beheer identiteit* en tik op de oranje *voeg toe* knop onderaan. Voer de naam in die u wilt laten verschijnen in het 'van' adres van de e-mails die u verzendt en selecteer een gekoppeld account. Tik op *Opslaan* om de identiteit toe te voegen.
|
||||
|
||||
Als het account handmatig werd geconfigureerd, moet u waarschijnlijk ook de identiteit handmatig configureren. Voer de domeinnaam in, bijvoorbeeld *gmail.com* en tik op *Instellingen ophalen*. Als uw provider [auto-discovery](https://tools.ietf.org/html/rfc6186) ondersteunt, zal FairEmail de hostnaam en poortnummer invullen, controleer anders de instructies van uw provider voor de juiste SMTP host naam, poortnummer en protocol (SSL/TLS of STARTTLS).
|
||||
|
||||
Zie [deze FAQ](https://github.com/M66B/FairEmail/blob/master/FAQ.md#FAQ9) over het gebruik van aliassen.
|
||||
|
||||
## Toestemmingen verlenen - om contact informatie op te vragen
|
||||
|
||||
Als u e-mailadressen wilt opzoeken, contactfoto's wilt zien, enz. moet u aan FairEmail toestemmingen verlenen om contacten te lezen. Tik op *Toestemmingen verlenen* en selecteer *Toestaan*.
|
||||
|
||||
## Accuoptimalisaties instellen - om voortdurend e-mail te ontvangen
|
||||
|
||||
Recente Android versies zetten apps in de slaapstand wanneer het scherm enige tijd uit is om het accugebruik te verminderen. Als u nieuwe e-mails zonder vertraging wilt ontvangen, dan moet u de accuoptimalisaties voor FairEmail uitschakelen. Tik *Schakel accuoptimalisaties uit* en volg de instructies.
|
||||
|
||||
## Vragen of problemen
|
||||
|
||||
Als u een vraag of probleem heeft, kijk [hier](https://github.com/M66B/FairEmail/blob/master/FAQ.md) of gebruik [dit contactformulier](https://contact.faircode.eu/?product=fairemailsupport) om hulp te vragen (u kunt het transactienummer "*setup help*" gebruiken).
|
||||
41
app/src/main/assets/SETUP-nn-NO.md
Normal file
41
app/src/main/assets/SETUP-nn-NO.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# Installasjonshjelp
|
||||
|
||||
Det er ganske enkelt å sette opp FairEmail. Du må legge til minst en konto for å motta e-post og minst en identitet hvis du vil sende e-post. Det raske oppsettet vil legge til en konto og en identitet på en gang for de fleste store leverandører.
|
||||
|
||||
## Krav
|
||||
|
||||
Det kreves en internettforbindelse for å konfigurere kontoer og identiteter.
|
||||
|
||||
## Hurtig oppsett
|
||||
|
||||
Bare skriv inn navn, e-postadresse og passord og trykk *Gå*.
|
||||
|
||||
Dette vil fungere for de fleste store e-postleverandører.
|
||||
|
||||
Hvis hurtigoppsettet ikke fungerer, må du konfigurere en konto og en identitet på en annen måte, se nedenfor for instruksjoner.
|
||||
|
||||
## Oppsett av konto - for å motta e-post
|
||||
|
||||
For å legge til en konto, trykk på *Administrer kontoer* og trykk på den oransje *legg til* knappen nederst. Velg en leverandør fra listen, skriv inn brukernavnet, som for det meste er din e-postadresse, og skriv inn passordet ditt. Trykk på *Kontroller* for å la FairEmail koble til e-postserveren og hente en liste over systemmapper. Etter å ha gjennomgått valg av systemmappe, kan du legge til kontoen ved å trykke *Lagre*.
|
||||
|
||||
Hvis leverandøren din ikke er på listen over leverandører, velger du *Tilpasset*. Skriv inn domenenavnet, for eksempel *gmail.com* og trykk *Hent innstillinger*. Hvis leverandøren din støtter [auto-oppdagelse](https://tools.ietf.org/html/rfc6186), vil FairEmail fylle ut vertsnavnet og portnummeret, ellers sjekk installasjonsinstruksjonene til leverandøren din for riktig IMAP-vertsnavn, portnummer og protokoll (SSL/TLS eller STARTTLS). For mer om dette, vennligst se [her](https://github.com/M66B/FairEmail/blob/master/FAQ.md#authorizing-accounts).
|
||||
|
||||
## Oppsett identitet - for å sende e-post
|
||||
|
||||
For å legge til en identitet, trykk på *Administrer identitet* og trykk på den oransje *legg til* -knappen nederst. Skriv inn navnet du vil skal vises i fra adressen til e-postene du sender, og velg en lenket konto. Trykk på *Lagre* for å legge til identiteten.
|
||||
|
||||
Hvis kontoen ble konfigurert manuelt, må du sannsynligvis konfigurere identiteten manuelt. Skriv inn domenenavnet, for eksempel *gmail.com* og trykk *Hent innstillinger*. Hvis leverandøren din støtter [auto-oppdagelse](https://tools.ietf.org/html/rfc6186), vil FairEmail fylle ut vertsnavnet og portnummeret, ellers sjekk installasjonsinstruksjonene til leverandøren din for riktig SMTP vertsnavn, portnummer og protokoll (SSL/TLS eller STARTTLS).
|
||||
|
||||
Se [denne FAQ](https://github.com/M66B/FairEmail/blob/master/FAQ.md#FAQ9) om bruk av aliaser.
|
||||
|
||||
## Gi tillatelser - for å få tilgang til kontaktinformasjon
|
||||
|
||||
Hvis du vil slå opp e-postadresser, ha kontaktbilder vist osv., Må du gi tillatelse til å lese kontakter til FairEmail. Bare trykk på *Gi tillatelser* og velg *Tillat*.
|
||||
|
||||
## Konfigurer batterioptimaliseringer - for kontinuerlig å motta e-post
|
||||
|
||||
På nyere Android-versjoner vil Android legge appene i dvale når skjermen er slått av en stund for å redusere batteribruken. Hvis du vil motta nye e-postmeldinger uten forsinkelser, bør du deaktivere batterioptimaliseringer for FairEmail. Trykk på *Deaktiver batterioptimaliseringer* og følg instruksjonene.
|
||||
|
||||
## Spørsmål eller problemer
|
||||
|
||||
Hvis du har et spørsmål eller et problem, kan du [se her](https://github.com/M66B/FairEmail/blob/master/FAQ.md) eller bruk [dette kontaktskjemaet](https://contact.faircode.eu/?product=fairemailsupport) for å be om hjelp (du kan bruke transaksjonsnummeret "*konfigurasjonshjelp*").
|
||||
41
app/src/main/assets/SETUP-no.md
Normal file
41
app/src/main/assets/SETUP-no.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# Installasjonshjelp
|
||||
|
||||
Det er ganske enkelt å sette opp FairEmail. Du må legge til minst en konto for å motta e-post og minst en identitet hvis du vil sende e-post. Det raske oppsettet vil legge til en konto og en identitet på en gang for de fleste store leverandører.
|
||||
|
||||
## Krav
|
||||
|
||||
Det kreves en internettforbindelse for å konfigurere kontoer og identiteter.
|
||||
|
||||
## Hurtig oppsett
|
||||
|
||||
Bare skriv inn navn, e-postadresse og passord og trykk *Gå*.
|
||||
|
||||
Dette vil fungere for de fleste store e-postleverandører.
|
||||
|
||||
Hvis hurtigoppsettet ikke fungerer, må du konfigurere en konto og en identitet på en annen måte, se nedenfor for instruksjoner.
|
||||
|
||||
## Oppsett av konto - for å motta e-post
|
||||
|
||||
For å legge til en konto, trykk på *Administrer kontoer* og trykk på den oransje *legg til* knappen nederst. Velg en leverandør fra listen, skriv inn brukernavnet, som for det meste er din e-postadresse, og skriv inn passordet ditt. Trykk på *Kontroller* for å la FairEmail koble til e-postserveren og hente en liste over systemmapper. Etter å ha gjennomgått valg av systemmappe, kan du legge til kontoen ved å trykke *Lagre*.
|
||||
|
||||
Hvis leverandøren din ikke er på listen over leverandører, velger du *Tilpasset*. Skriv inn domenenavnet, for eksempel *gmail.com* og trykk *Hent innstillinger*. Hvis leverandøren din støtter [auto-oppdagelse](https://tools.ietf.org/html/rfc6186), vil FairEmail fylle ut vertsnavnet og portnummeret, ellers sjekk installasjonsinstruksjonene til leverandøren din for riktig IMAP-vertsnavn, portnummer og protokoll (SSL/TLS eller STARTTLS). For mer om dette, vennligst se [her](https://github.com/M66B/FairEmail/blob/master/FAQ.md#authorizing-accounts).
|
||||
|
||||
## Oppsett identitet - for å sende e-post
|
||||
|
||||
For å legge til en identitet, trykk på *Administrer identitet* og trykk på den oransje *legg til* -knappen nederst. Skriv inn navnet du vil skal vises i fra adressen til e-postene du sender, og velg en lenket konto. Trykk på *Lagre* for å legge til identiteten.
|
||||
|
||||
Hvis kontoen ble konfigurert manuelt, må du sannsynligvis konfigurere identiteten manuelt. Skriv inn domenenavnet, for eksempel *gmail.com* og trykk *Hent innstillinger*. Hvis leverandøren din støtter [auto-oppdagelse](https://tools.ietf.org/html/rfc6186), vil FairEmail fylle ut vertsnavnet og portnummeret, ellers sjekk installasjonsinstruksjonene til leverandøren din for riktig SMTP vertsnavn, portnummer og protokoll (SSL/TLS eller STARTTLS).
|
||||
|
||||
Se [denne FAQ](https://github.com/M66B/FairEmail/blob/master/FAQ.md#FAQ9) om bruk av aliaser.
|
||||
|
||||
## Gi tillatelser - for å få tilgang til kontaktinformasjon
|
||||
|
||||
Hvis du vil slå opp e-postadresser, ha kontaktbilder vist osv., Må du gi tillatelse til å lese kontakter til FairEmail. Bare trykk på *Gi tillatelser* og velg *Tillat*.
|
||||
|
||||
## Konfigurer batterioptimaliseringer - for kontinuerlig å motta e-post
|
||||
|
||||
På nyere Android-versjoner vil Android legge appene i dvale når skjermen er slått av en stund for å redusere batteribruken. Hvis du vil motta nye e-postmeldinger uten forsinkelser, bør du deaktivere batterioptimaliseringer for FairEmail. Trykk på *Deaktiver batterioptimaliseringer* og følg instruksjonene.
|
||||
|
||||
## Spørsmål eller problemer
|
||||
|
||||
Hvis du har et spørsmål eller et problem, kan du [se her](https://github.com/M66B/FairEmail/blob/master/FAQ.md) eller bruk [dette kontaktskjemaet](https://contact.faircode.eu/?product=fairemailsupport) for å be om hjelp (du kan bruke transaksjonsnummeret "*konfigurasjonshjelp*").
|
||||
41
app/src/main/assets/SETUP-pl.md
Normal file
41
app/src/main/assets/SETUP-pl.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# Pomoc instalacyjna
|
||||
|
||||
Konfiguracja FairEmail jest dość prosta. Musisz dodać co najmniej jedno konto, aby otrzymywać wiadomości email i przynajmniej jedną tożsamość, jeśli chcesz wysłać wiadomości email. Szybka konfiguracja doda konto i tożsamość za jednym razem dla większości głównych dostawców.
|
||||
|
||||
## Wymagania
|
||||
|
||||
Do skonfigurowania kont i tożsamości wymagane jest połączenie internetowe.
|
||||
|
||||
## Szybka konfiguracja
|
||||
|
||||
Po prostu wpisz swoje imię i nazwisko, adres email i hasło, a następnie naciśnij przycisk *Idź*.
|
||||
|
||||
To działa dla większości głównych dostawców poczty email.
|
||||
|
||||
Jeśli szybka konfiguracja nie zadziała, musisz skonfigurować konto i tożsamość w inny sposób, patrz poniżej, aby uzyskać instrukcje.
|
||||
|
||||
## Konfiguracja konta - aby odbierać wiadomości email
|
||||
|
||||
Aby dodać konto, naciśnij *Zarządzaj kontami* i naciśnij pomarańczowy przycisk *dodaj* na dole. Wybierz dostawcę z listy, wprowadź nazwę użytkownika, która jest najczęściej Twoim adresem email i wprowadź hasło. Naciśnij *Sprawdź*, aby FairEmail mógł połączyć się z serwerem email i pobrać listę folderów systemowych. Po zapoznaniu się z wyborem folderu systemowego możesz dodać konto, naciskając *Zapisz*.
|
||||
|
||||
Jeśli Twój dostawca nie znajduje się na liście dostawców, wybierz *Własne*. Wprowadź nazwę domeny, na przykład *gmail.com* i naciśnij *Pobierz ustawienia*. Jeśli Twój dostawca obsługuje [automatyczne wykrywanie](https://tools.ietf.org/html/rfc6186), FairEmail wypełni nazwę hosta i numer portu, w przeciwnym razie sprawdź instrukcje instalacji swojego dostawcy, aby uzyskać prawidłową nazwę hosta IMAP, numer portu i protokół (SSL/TLS lub STARTTLS). Więcej informacji na ten temat można znaleźć [tutaj](https://github.com/M66B/FairEmail/blob/master/FAQ.md#authorizing-accounts).
|
||||
|
||||
## Konfiguracja tożsamości - aby wysyłać wiadomości email
|
||||
|
||||
Podobnie, aby dodać tożsamość, naciśnij *Zarządzaj tożsamościami* i naciśnij pomarańczowy przycisk *dodaj* na dole. Enter the name you want to appear in de from address of the emails you send and select a linked account. Tap *Save* to add the identity.
|
||||
|
||||
If the account was configured manually, you likely need to configure the identity manually too. Enter the domain name, for example *gmail.com* and tap *Get settings*. If your provider supports [auto-discovery](https://tools.ietf.org/html/rfc6186), FairEmail will fill in the hostname and port number, else check the setup instructions of your provider for the right SMTP hostname, port number and protocol (SSL/TLS or STARTTLS).
|
||||
|
||||
See [this FAQ](https://github.com/M66B/FairEmail/blob/master/FAQ.md#FAQ9) about using aliases.
|
||||
|
||||
## Grant permissions - to access contact information
|
||||
|
||||
If you want to lookup email addresses, have contact photos shown, etc, you'll need to grant read contacts permission to FairEmail. Just tap *Grant permissions* and select *Allow*.
|
||||
|
||||
## Setup battery optimizations - to continuously receive emails
|
||||
|
||||
On recent Android versions, Android will put apps to sleep when the screen is off for some time to reduce battery usage. If you want to receive new emails without delays, you should disable battery optimizations for FairEmail. Tap *Disable battery optimizations* and follow the instructions.
|
||||
|
||||
## Questions or problems
|
||||
|
||||
If you have a question or problem, please [see here](https://github.com/M66B/FairEmail/blob/master/FAQ.md) or use [this contact form](https://contact.faircode.eu/?product=fairemailsupport) to ask for help (you can use the transaction number "*setup help*").
|
||||
41
app/src/main/assets/SETUP-pt-BR.md
Normal file
41
app/src/main/assets/SETUP-pt-BR.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# Setup help
|
||||
|
||||
Setting up FairEmail is fairly simple. You'll need to add at least one account to receive email and at least one identity if you want to send email. The quick setup will add an account and an identity in one go for most major providers.
|
||||
|
||||
## Requirements
|
||||
|
||||
An internet connection is required to set up accounts and identities.
|
||||
|
||||
## Quick setup
|
||||
|
||||
Just enter your name, email address and password and tap *Go*.
|
||||
|
||||
This will work for most major email providers.
|
||||
|
||||
If the quick setup doesn't work, you'll need to setup an account and an identity in another way, see below for instructions.
|
||||
|
||||
## Set up account - to receive email
|
||||
|
||||
To add an account, tap *Manage accounts* and tap the orange *add* button at the bottom. Select a provider from the list, enter the username, which is mostly your email address and enter your password. Tap *Check* to let FairEmail connect to the email server and fetch a list of system folders. After reviewing the system folder selection you can add the account by tapping *Save*.
|
||||
|
||||
If your provider is not in the list of providers, select *Custom*. Enter the domain name, for example *gmail.com* and tap *Get settings*. If your provider supports [auto-discovery](https://tools.ietf.org/html/rfc6186), FairEmail will fill in the hostname and port number, else check the setup instructions of your provider for the right IMAP hostname, port number and protocol (SSL/TLS or STARTTLS). For more about this, please see [here](https://github.com/M66B/FairEmail/blob/master/FAQ.md#authorizing-accounts).
|
||||
|
||||
## Set up identity - to send email
|
||||
|
||||
Similarly, to add an identity, tap *Manage identity* and tap the orange *add* button at the bottom. Enter the name you want to appear in de from address of the emails you send and select a linked account. Tap *Save* to add the identity.
|
||||
|
||||
If the account was configured manually, you likely need to configure the identity manually too. Enter the domain name, for example *gmail.com* and tap *Get settings*. If your provider supports [auto-discovery](https://tools.ietf.org/html/rfc6186), FairEmail will fill in the hostname and port number, else check the setup instructions of your provider for the right SMTP hostname, port number and protocol (SSL/TLS or STARTTLS).
|
||||
|
||||
See [this FAQ](https://github.com/M66B/FairEmail/blob/master/FAQ.md#FAQ9) about using aliases.
|
||||
|
||||
## Grant permissions - to access contact information
|
||||
|
||||
If you want to lookup email addresses, have contact photos shown, etc, you'll need to grant read contacts permission to FairEmail. Just tap *Grant permissions* and select *Allow*.
|
||||
|
||||
## Setup battery optimizations - to continuously receive emails
|
||||
|
||||
On recent Android versions, Android will put apps to sleep when the screen is off for some time to reduce battery usage. If you want to receive new emails without delays, you should disable battery optimizations for FairEmail. Tap *Disable battery optimizations* and follow the instructions.
|
||||
|
||||
## Questions or problems
|
||||
|
||||
If you have a question or problem, please [see here](https://github.com/M66B/FairEmail/blob/master/FAQ.md) or use [this contact form](https://contact.faircode.eu/?product=fairemailsupport) to ask for help (you can use the transaction number "*setup help*").
|
||||
41
app/src/main/assets/SETUP-pt-PT.md
Normal file
41
app/src/main/assets/SETUP-pt-PT.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# Setup help
|
||||
|
||||
Setting up FairEmail is fairly simple. You'll need to add at least one account to receive email and at least one identity if you want to send email. The quick setup will add an account and an identity in one go for most major providers.
|
||||
|
||||
## Requirements
|
||||
|
||||
An internet connection is required to set up accounts and identities.
|
||||
|
||||
## Quick setup
|
||||
|
||||
Just enter your name, email address and password and tap *Go*.
|
||||
|
||||
This will work for most major email providers.
|
||||
|
||||
If the quick setup doesn't work, you'll need to setup an account and an identity in another way, see below for instructions.
|
||||
|
||||
## Set up account - to receive email
|
||||
|
||||
To add an account, tap *Manage accounts* and tap the orange *add* button at the bottom. Select a provider from the list, enter the username, which is mostly your email address and enter your password. Tap *Check* to let FairEmail connect to the email server and fetch a list of system folders. After reviewing the system folder selection you can add the account by tapping *Save*.
|
||||
|
||||
If your provider is not in the list of providers, select *Custom*. Enter the domain name, for example *gmail.com* and tap *Get settings*. If your provider supports [auto-discovery](https://tools.ietf.org/html/rfc6186), FairEmail will fill in the hostname and port number, else check the setup instructions of your provider for the right IMAP hostname, port number and protocol (SSL/TLS or STARTTLS). For more about this, please see [here](https://github.com/M66B/FairEmail/blob/master/FAQ.md#authorizing-accounts).
|
||||
|
||||
## Set up identity - to send email
|
||||
|
||||
Similarly, to add an identity, tap *Manage identity* and tap the orange *add* button at the bottom. Enter the name you want to appear in de from address of the emails you send and select a linked account. Tap *Save* to add the identity.
|
||||
|
||||
If the account was configured manually, you likely need to configure the identity manually too. Enter the domain name, for example *gmail.com* and tap *Get settings*. If your provider supports [auto-discovery](https://tools.ietf.org/html/rfc6186), FairEmail will fill in the hostname and port number, else check the setup instructions of your provider for the right SMTP hostname, port number and protocol (SSL/TLS or STARTTLS).
|
||||
|
||||
See [this FAQ](https://github.com/M66B/FairEmail/blob/master/FAQ.md#FAQ9) about using aliases.
|
||||
|
||||
## Grant permissions - to access contact information
|
||||
|
||||
If you want to lookup email addresses, have contact photos shown, etc, you'll need to grant read contacts permission to FairEmail. Just tap *Grant permissions* and select *Allow*.
|
||||
|
||||
## Setup battery optimizations - to continuously receive emails
|
||||
|
||||
On recent Android versions, Android will put apps to sleep when the screen is off for some time to reduce battery usage. If you want to receive new emails without delays, you should disable battery optimizations for FairEmail. Tap *Disable battery optimizations* and follow the instructions.
|
||||
|
||||
## Questions or problems
|
||||
|
||||
If you have a question or problem, please [see here](https://github.com/M66B/FairEmail/blob/master/FAQ.md) or use [this contact form](https://contact.faircode.eu/?product=fairemailsupport) to ask for help (you can use the transaction number "*setup help*").
|
||||
41
app/src/main/assets/SETUP-ro.md
Normal file
41
app/src/main/assets/SETUP-ro.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# Setup help
|
||||
|
||||
Setting up FairEmail is fairly simple. You'll need to add at least one account to receive email and at least one identity if you want to send email. The quick setup will add an account and an identity in one go for most major providers.
|
||||
|
||||
## Requirements
|
||||
|
||||
An internet connection is required to set up accounts and identities.
|
||||
|
||||
## Quick setup
|
||||
|
||||
Just enter your name, email address and password and tap *Go*.
|
||||
|
||||
This will work for most major email providers.
|
||||
|
||||
If the quick setup doesn't work, you'll need to setup an account and an identity in another way, see below for instructions.
|
||||
|
||||
## Set up account - to receive email
|
||||
|
||||
To add an account, tap *Manage accounts* and tap the orange *add* button at the bottom. Select a provider from the list, enter the username, which is mostly your email address and enter your password. Tap *Check* to let FairEmail connect to the email server and fetch a list of system folders. After reviewing the system folder selection you can add the account by tapping *Save*.
|
||||
|
||||
If your provider is not in the list of providers, select *Custom*. Enter the domain name, for example *gmail.com* and tap *Get settings*. If your provider supports [auto-discovery](https://tools.ietf.org/html/rfc6186), FairEmail will fill in the hostname and port number, else check the setup instructions of your provider for the right IMAP hostname, port number and protocol (SSL/TLS or STARTTLS). For more about this, please see [here](https://github.com/M66B/FairEmail/blob/master/FAQ.md#authorizing-accounts).
|
||||
|
||||
## Set up identity - to send email
|
||||
|
||||
Similarly, to add an identity, tap *Manage identity* and tap the orange *add* button at the bottom. Enter the name you want to appear in de from address of the emails you send and select a linked account. Tap *Save* to add the identity.
|
||||
|
||||
If the account was configured manually, you likely need to configure the identity manually too. Enter the domain name, for example *gmail.com* and tap *Get settings*. If your provider supports [auto-discovery](https://tools.ietf.org/html/rfc6186), FairEmail will fill in the hostname and port number, else check the setup instructions of your provider for the right SMTP hostname, port number and protocol (SSL/TLS or STARTTLS).
|
||||
|
||||
See [this FAQ](https://github.com/M66B/FairEmail/blob/master/FAQ.md#FAQ9) about using aliases.
|
||||
|
||||
## Grant permissions - to access contact information
|
||||
|
||||
If you want to lookup email addresses, have contact photos shown, etc, you'll need to grant read contacts permission to FairEmail. Just tap *Grant permissions* and select *Allow*.
|
||||
|
||||
## Setup battery optimizations - to continuously receive emails
|
||||
|
||||
On recent Android versions, Android will put apps to sleep when the screen is off for some time to reduce battery usage. If you want to receive new emails without delays, you should disable battery optimizations for FairEmail. Tap *Disable battery optimizations* and follow the instructions.
|
||||
|
||||
## Questions or problems
|
||||
|
||||
If you have a question or problem, please [see here](https://github.com/M66B/FairEmail/blob/master/FAQ.md) or use [this contact form](https://contact.faircode.eu/?product=fairemailsupport) to ask for help (you can use the transaction number "*setup help*").
|
||||
41
app/src/main/assets/SETUP-ru.md
Normal file
41
app/src/main/assets/SETUP-ru.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# Помощь по установке
|
||||
|
||||
Настройка FairEmail довольно проста. Вам нужно добавить хотя бы одну учетную запись для получения электронной почты и хотя бы один идентификатор, если вы хотите отправлять письма. Быстрая настройка сразу добавит учетную запись и идентификатор для большинства основных провайдеров.
|
||||
|
||||
## Требования
|
||||
|
||||
Для настройки учетных записей и идентификаторов требуется подключение к Интернету.
|
||||
|
||||
## Быстрая настройка
|
||||
|
||||
Просто введите имя, адрес электронной почты и пароль и нажмите *Перейти*.
|
||||
|
||||
Это будет работать для большинства основных провайдеров электронной почты.
|
||||
|
||||
Если быстрая настройка не работает, вам нужно будет настроить учетную запись и идентификатор другим способом, см. ниже для инструкций.
|
||||
|
||||
## Настройка учётной записи - для получения электронной почты
|
||||
|
||||
Чтобы добавить учётную запись, нажмите *Управление учётными записями* и нажмите на оранжевую кнопку *добавить* внизу. Выберите провайдера из списка, введите имя пользователя, в большинстве случаев ваш адрес электронной почты, и пароль. Нажмите *Проверить* чтобы FairEmail подключился к почтовому серверу и получил список системных папок. После просмотра списка системных папок можно добавить учетную запись, нажав *Сохранить*.
|
||||
|
||||
Если ваш провайдер не входит в список провайдеров, выберите *Пользовательский*. Введите имя домена, например *gmail.com* и нажмите *Получить настройки*. Если ваш провайдер поддерживает [авто-обнаружение](https://tools.ietf.org/html/rfc6186), FairEmail заполнит имя хоста и номер порта, еще прочтите инструкции по установке вашего провайдера для правильного имени IMAP хоста, номера порта и протокола (SSL/TLS или STARTTLS). Подробнее об этом смотрите [здесь](https://github.com/M66B/FairEmail/blob/master/FAQ.md#authorizing-accounts).
|
||||
|
||||
## Настройка идентификатора - чтобы отправлять почту
|
||||
|
||||
Аналогичным образом, чтобы добавить идентификатор, нажмите *Управление идентификаторами* и нажмите на оранжевую кнопку *добавить* внизу. Введите имя, которое вы хотите вставлять в поле адреса "От:" отправленных вами писем и выберите связанную учетную запись. Нажмите *Сохранить* для добавления идентификатора.
|
||||
|
||||
Если учетная запись была настроена вручную, вам, вероятно, также нужно настроить идентификатор вручную. Введите имя домена, например *gmail.com* и нажмите *Получить настройки*. Если ваш провайдер поддерживает [авто-обнаружение](https://tools.ietf.org/html/rfc6186), FairEmail заполнит имя хоста и номер порта, еще прочтите инструкции по установке вашего провайдера для правильного имени хоста SMTP, номера порта и протокола (SSL/TLS или STARTTLS).
|
||||
|
||||
Смотрите [этот FAQ](https://github.com/M66B/FairEmail/blob/master/FAQ.md#FAQ9) об использовании псевдонимов.
|
||||
|
||||
## Предоставить разрешения - доступ к контактной информации
|
||||
|
||||
Если вы хотите находить адреса электронной почты, видеть фотографии контактов, и т. д., вам нужно предоставить FairEmail разрешение на чтение контактов. Просто нажмите *Предоставить разрешения* и выберите *Разрешить*.
|
||||
|
||||
## Настройка оптимизации батареи - для непрерывного получения электронной почты
|
||||
|
||||
Android последних версий будет помещать приложения в спящий режим, когда экран выключен в течение некоторого времени, чтобы уменьшить потребление батареи. Если вы хотите получать новые письма без задержек, то вам следует отключить оптимизацию батареи для FairEmail. Нажмите *Отключить оптимизацию батареи* и следуйте инструкциям.
|
||||
|
||||
## Вопросы или проблемы
|
||||
|
||||
Если у вас есть вопрос или проблема, пожалуйста [см. здесь](https://github.com/M66B/FairEmail/blob/master/FAQ.md) или используйте [эту контактную форму](https://contact.faircode.eu/?product=fairemailsupport) для получения помощи (вы можете использовать номер транзакции "*помощь по установке*").
|
||||
41
app/src/main/assets/SETUP-sr.md
Normal file
41
app/src/main/assets/SETUP-sr.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# Setup help
|
||||
|
||||
Setting up FairEmail is fairly simple. You'll need to add at least one account to receive email and at least one identity if you want to send email. The quick setup will add an account and an identity in one go for most major providers.
|
||||
|
||||
## Requirements
|
||||
|
||||
An internet connection is required to set up accounts and identities.
|
||||
|
||||
## Quick setup
|
||||
|
||||
Just enter your name, email address and password and tap *Go*.
|
||||
|
||||
This will work for most major email providers.
|
||||
|
||||
If the quick setup doesn't work, you'll need to setup an account and an identity in another way, see below for instructions.
|
||||
|
||||
## Set up account - to receive email
|
||||
|
||||
To add an account, tap *Manage accounts* and tap the orange *add* button at the bottom. Select a provider from the list, enter the username, which is mostly your email address and enter your password. Tap *Check* to let FairEmail connect to the email server and fetch a list of system folders. After reviewing the system folder selection you can add the account by tapping *Save*.
|
||||
|
||||
If your provider is not in the list of providers, select *Custom*. Enter the domain name, for example *gmail.com* and tap *Get settings*. If your provider supports [auto-discovery](https://tools.ietf.org/html/rfc6186), FairEmail will fill in the hostname and port number, else check the setup instructions of your provider for the right IMAP hostname, port number and protocol (SSL/TLS or STARTTLS). For more about this, please see [here](https://github.com/M66B/FairEmail/blob/master/FAQ.md#authorizing-accounts).
|
||||
|
||||
## Set up identity - to send email
|
||||
|
||||
Similarly, to add an identity, tap *Manage identity* and tap the orange *add* button at the bottom. Enter the name you want to appear in de from address of the emails you send and select a linked account. Tap *Save* to add the identity.
|
||||
|
||||
If the account was configured manually, you likely need to configure the identity manually too. Enter the domain name, for example *gmail.com* and tap *Get settings*. If your provider supports [auto-discovery](https://tools.ietf.org/html/rfc6186), FairEmail will fill in the hostname and port number, else check the setup instructions of your provider for the right SMTP hostname, port number and protocol (SSL/TLS or STARTTLS).
|
||||
|
||||
See [this FAQ](https://github.com/M66B/FairEmail/blob/master/FAQ.md#FAQ9) about using aliases.
|
||||
|
||||
## Grant permissions - to access contact information
|
||||
|
||||
If you want to lookup email addresses, have contact photos shown, etc, you'll need to grant read contacts permission to FairEmail. Just tap *Grant permissions* and select *Allow*.
|
||||
|
||||
## Setup battery optimizations - to continuously receive emails
|
||||
|
||||
On recent Android versions, Android will put apps to sleep when the screen is off for some time to reduce battery usage. If you want to receive new emails without delays, you should disable battery optimizations for FairEmail. Tap *Disable battery optimizations* and follow the instructions.
|
||||
|
||||
## Questions or problems
|
||||
|
||||
If you have a question or problem, please [see here](https://github.com/M66B/FairEmail/blob/master/FAQ.md) or use [this contact form](https://contact.faircode.eu/?product=fairemailsupport) to ask for help (you can use the transaction number "*setup help*").
|
||||
41
app/src/main/assets/SETUP-sv-SE.md
Normal file
41
app/src/main/assets/SETUP-sv-SE.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# Setup help
|
||||
|
||||
Setting up FairEmail is fairly simple. You'll need to add at least one account to receive email and at least one identity if you want to send email. The quick setup will add an account and an identity in one go for most major providers.
|
||||
|
||||
## Requirements
|
||||
|
||||
An internet connection is required to set up accounts and identities.
|
||||
|
||||
## Quick setup
|
||||
|
||||
Just enter your name, email address and password and tap *Go*.
|
||||
|
||||
This will work for most major email providers.
|
||||
|
||||
If the quick setup doesn't work, you'll need to setup an account and an identity in another way, see below for instructions.
|
||||
|
||||
## Set up account - to receive email
|
||||
|
||||
To add an account, tap *Manage accounts* and tap the orange *add* button at the bottom. Select a provider from the list, enter the username, which is mostly your email address and enter your password. Tap *Check* to let FairEmail connect to the email server and fetch a list of system folders. After reviewing the system folder selection you can add the account by tapping *Save*.
|
||||
|
||||
If your provider is not in the list of providers, select *Custom*. Enter the domain name, for example *gmail.com* and tap *Get settings*. If your provider supports [auto-discovery](https://tools.ietf.org/html/rfc6186), FairEmail will fill in the hostname and port number, else check the setup instructions of your provider for the right IMAP hostname, port number and protocol (SSL/TLS or STARTTLS). For more about this, please see [here](https://github.com/M66B/FairEmail/blob/master/FAQ.md#authorizing-accounts).
|
||||
|
||||
## Set up identity - to send email
|
||||
|
||||
Similarly, to add an identity, tap *Manage identity* and tap the orange *add* button at the bottom. Enter the name you want to appear in de from address of the emails you send and select a linked account. Tap *Save* to add the identity.
|
||||
|
||||
If the account was configured manually, you likely need to configure the identity manually too. Enter the domain name, for example *gmail.com* and tap *Get settings*. If your provider supports [auto-discovery](https://tools.ietf.org/html/rfc6186), FairEmail will fill in the hostname and port number, else check the setup instructions of your provider for the right SMTP hostname, port number and protocol (SSL/TLS or STARTTLS).
|
||||
|
||||
See [this FAQ](https://github.com/M66B/FairEmail/blob/master/FAQ.md#FAQ9) about using aliases.
|
||||
|
||||
## Grant permissions - to access contact information
|
||||
|
||||
If you want to lookup email addresses, have contact photos shown, etc, you'll need to grant read contacts permission to FairEmail. Just tap *Grant permissions* and select *Allow*.
|
||||
|
||||
## Setup battery optimizations - to continuously receive emails
|
||||
|
||||
On recent Android versions, Android will put apps to sleep when the screen is off for some time to reduce battery usage. If you want to receive new emails without delays, you should disable battery optimizations for FairEmail. Tap *Disable battery optimizations* and follow the instructions.
|
||||
|
||||
## Questions or problems
|
||||
|
||||
If you have a question or problem, please [see here](https://github.com/M66B/FairEmail/blob/master/FAQ.md) or use [this contact form](https://contact.faircode.eu/?product=fairemailsupport) to ask for help (you can use the transaction number "*setup help*").
|
||||
41
app/src/main/assets/SETUP-tr.md
Normal file
41
app/src/main/assets/SETUP-tr.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# Setup help
|
||||
|
||||
Setting up FairEmail is fairly simple. You'll need to add at least one account to receive email and at least one identity if you want to send email. The quick setup will add an account and an identity in one go for most major providers.
|
||||
|
||||
## Requirements
|
||||
|
||||
An internet connection is required to set up accounts and identities.
|
||||
|
||||
## Quick setup
|
||||
|
||||
Just enter your name, email address and password and tap *Go*.
|
||||
|
||||
This will work for most major email providers.
|
||||
|
||||
If the quick setup doesn't work, you'll need to setup an account and an identity in another way, see below for instructions.
|
||||
|
||||
## Set up account - to receive email
|
||||
|
||||
To add an account, tap *Manage accounts* and tap the orange *add* button at the bottom. Select a provider from the list, enter the username, which is mostly your email address and enter your password. Tap *Check* to let FairEmail connect to the email server and fetch a list of system folders. After reviewing the system folder selection you can add the account by tapping *Save*.
|
||||
|
||||
If your provider is not in the list of providers, select *Custom*. Enter the domain name, for example *gmail.com* and tap *Get settings*. If your provider supports [auto-discovery](https://tools.ietf.org/html/rfc6186), FairEmail will fill in the hostname and port number, else check the setup instructions of your provider for the right IMAP hostname, port number and protocol (SSL/TLS or STARTTLS). For more about this, please see [here](https://github.com/M66B/FairEmail/blob/master/FAQ.md#authorizing-accounts).
|
||||
|
||||
## Set up identity - to send email
|
||||
|
||||
Similarly, to add an identity, tap *Manage identity* and tap the orange *add* button at the bottom. Enter the name you want to appear in de from address of the emails you send and select a linked account. Tap *Save* to add the identity.
|
||||
|
||||
If the account was configured manually, you likely need to configure the identity manually too. Enter the domain name, for example *gmail.com* and tap *Get settings*. If your provider supports [auto-discovery](https://tools.ietf.org/html/rfc6186), FairEmail will fill in the hostname and port number, else check the setup instructions of your provider for the right SMTP hostname, port number and protocol (SSL/TLS or STARTTLS).
|
||||
|
||||
See [this FAQ](https://github.com/M66B/FairEmail/blob/master/FAQ.md#FAQ9) about using aliases.
|
||||
|
||||
## Grant permissions - to access contact information
|
||||
|
||||
If you want to lookup email addresses, have contact photos shown, etc, you'll need to grant read contacts permission to FairEmail. Just tap *Grant permissions* and select *Allow*.
|
||||
|
||||
## Setup battery optimizations - to continuously receive emails
|
||||
|
||||
On recent Android versions, Android will put apps to sleep when the screen is off for some time to reduce battery usage. If you want to receive new emails without delays, you should disable battery optimizations for FairEmail. Tap *Disable battery optimizations* and follow the instructions.
|
||||
|
||||
## Questions or problems
|
||||
|
||||
If you have a question or problem, please [see here](https://github.com/M66B/FairEmail/blob/master/FAQ.md) or use [this contact form](https://contact.faircode.eu/?product=fairemailsupport) to ask for help (you can use the transaction number "*setup help*").
|
||||
41
app/src/main/assets/SETUP-uk.md
Normal file
41
app/src/main/assets/SETUP-uk.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# Setup help
|
||||
|
||||
Setting up FairEmail is fairly simple. You'll need to add at least one account to receive email and at least one identity if you want to send email. The quick setup will add an account and an identity in one go for most major providers.
|
||||
|
||||
## Requirements
|
||||
|
||||
An internet connection is required to set up accounts and identities.
|
||||
|
||||
## Quick setup
|
||||
|
||||
Just enter your name, email address and password and tap *Go*.
|
||||
|
||||
This will work for most major email providers.
|
||||
|
||||
If the quick setup doesn't work, you'll need to setup an account and an identity in another way, see below for instructions.
|
||||
|
||||
## Set up account - to receive email
|
||||
|
||||
To add an account, tap *Manage accounts* and tap the orange *add* button at the bottom. Select a provider from the list, enter the username, which is mostly your email address and enter your password. Tap *Check* to let FairEmail connect to the email server and fetch a list of system folders. After reviewing the system folder selection you can add the account by tapping *Save*.
|
||||
|
||||
If your provider is not in the list of providers, select *Custom*. Enter the domain name, for example *gmail.com* and tap *Get settings*. If your provider supports [auto-discovery](https://tools.ietf.org/html/rfc6186), FairEmail will fill in the hostname and port number, else check the setup instructions of your provider for the right IMAP hostname, port number and protocol (SSL/TLS or STARTTLS). For more about this, please see [here](https://github.com/M66B/FairEmail/blob/master/FAQ.md#authorizing-accounts).
|
||||
|
||||
## Set up identity - to send email
|
||||
|
||||
Similarly, to add an identity, tap *Manage identity* and tap the orange *add* button at the bottom. Enter the name you want to appear in de from address of the emails you send and select a linked account. Tap *Save* to add the identity.
|
||||
|
||||
If the account was configured manually, you likely need to configure the identity manually too. Enter the domain name, for example *gmail.com* and tap *Get settings*. If your provider supports [auto-discovery](https://tools.ietf.org/html/rfc6186), FairEmail will fill in the hostname and port number, else check the setup instructions of your provider for the right SMTP hostname, port number and protocol (SSL/TLS or STARTTLS).
|
||||
|
||||
See [this FAQ](https://github.com/M66B/FairEmail/blob/master/FAQ.md#FAQ9) about using aliases.
|
||||
|
||||
## Grant permissions - to access contact information
|
||||
|
||||
If you want to lookup email addresses, have contact photos shown, etc, you'll need to grant read contacts permission to FairEmail. Just tap *Grant permissions* and select *Allow*.
|
||||
|
||||
## Setup battery optimizations - to continuously receive emails
|
||||
|
||||
On recent Android versions, Android will put apps to sleep when the screen is off for some time to reduce battery usage. If you want to receive new emails without delays, you should disable battery optimizations for FairEmail. Tap *Disable battery optimizations* and follow the instructions.
|
||||
|
||||
## Questions or problems
|
||||
|
||||
If you have a question or problem, please [see here](https://github.com/M66B/FairEmail/blob/master/FAQ.md) or use [this contact form](https://contact.faircode.eu/?product=fairemailsupport) to ask for help (you can use the transaction number "*setup help*").
|
||||
41
app/src/main/assets/SETUP-vi.md
Normal file
41
app/src/main/assets/SETUP-vi.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# Setup help
|
||||
|
||||
Setting up FairEmail is fairly simple. You'll need to add at least one account to receive email and at least one identity if you want to send email. The quick setup will add an account and an identity in one go for most major providers.
|
||||
|
||||
## Requirements
|
||||
|
||||
An internet connection is required to set up accounts and identities.
|
||||
|
||||
## Quick setup
|
||||
|
||||
Just enter your name, email address and password and tap *Go*.
|
||||
|
||||
This will work for most major email providers.
|
||||
|
||||
If the quick setup doesn't work, you'll need to setup an account and an identity in another way, see below for instructions.
|
||||
|
||||
## Set up account - to receive email
|
||||
|
||||
To add an account, tap *Manage accounts* and tap the orange *add* button at the bottom. Select a provider from the list, enter the username, which is mostly your email address and enter your password. Tap *Check* to let FairEmail connect to the email server and fetch a list of system folders. After reviewing the system folder selection you can add the account by tapping *Save*.
|
||||
|
||||
If your provider is not in the list of providers, select *Custom*. Enter the domain name, for example *gmail.com* and tap *Get settings*. If your provider supports [auto-discovery](https://tools.ietf.org/html/rfc6186), FairEmail will fill in the hostname and port number, else check the setup instructions of your provider for the right IMAP hostname, port number and protocol (SSL/TLS or STARTTLS). For more about this, please see [here](https://github.com/M66B/FairEmail/blob/master/FAQ.md#authorizing-accounts).
|
||||
|
||||
## Set up identity - to send email
|
||||
|
||||
Similarly, to add an identity, tap *Manage identity* and tap the orange *add* button at the bottom. Enter the name you want to appear in de from address of the emails you send and select a linked account. Tap *Save* to add the identity.
|
||||
|
||||
If the account was configured manually, you likely need to configure the identity manually too. Enter the domain name, for example *gmail.com* and tap *Get settings*. If your provider supports [auto-discovery](https://tools.ietf.org/html/rfc6186), FairEmail will fill in the hostname and port number, else check the setup instructions of your provider for the right SMTP hostname, port number and protocol (SSL/TLS or STARTTLS).
|
||||
|
||||
See [this FAQ](https://github.com/M66B/FairEmail/blob/master/FAQ.md#FAQ9) about using aliases.
|
||||
|
||||
## Grant permissions - to access contact information
|
||||
|
||||
If you want to lookup email addresses, have contact photos shown, etc, you'll need to grant read contacts permission to FairEmail. Just tap *Grant permissions* and select *Allow*.
|
||||
|
||||
## Setup battery optimizations - to continuously receive emails
|
||||
|
||||
On recent Android versions, Android will put apps to sleep when the screen is off for some time to reduce battery usage. If you want to receive new emails without delays, you should disable battery optimizations for FairEmail. Tap *Disable battery optimizations* and follow the instructions.
|
||||
|
||||
## Questions or problems
|
||||
|
||||
If you have a question or problem, please [see here](https://github.com/M66B/FairEmail/blob/master/FAQ.md) or use [this contact form](https://contact.faircode.eu/?product=fairemailsupport) to ask for help (you can use the transaction number "*setup help*").
|
||||
41
app/src/main/assets/SETUP-zh-CN.md
Normal file
41
app/src/main/assets/SETUP-zh-CN.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# 设置帮助
|
||||
|
||||
设置FairEmail相当简单 如果您想发送电邮,需要添加至少一个身份,并添加至少一个账户来接受电邮。 快速设置将为大多数主要电邮服务提供商一举添加一个账户和一个身份。
|
||||
|
||||
## 要求
|
||||
|
||||
需要互联网连接以设置账户和身份
|
||||
|
||||
## 快速设置
|
||||
|
||||
只需输入您的姓名、电子邮件地址和密码,并点击*跳转到*。
|
||||
|
||||
这对大多数主要的电子邮件服务供应商行之有效。
|
||||
|
||||
如果快速设置不起作用,您需要以另一种方式设置一个帐户和身份,见下文说明。
|
||||
|
||||
## 设置帐户 - 接收电子邮件
|
||||
|
||||
要添加一个帐户,点击*管理帐户*并点击底部橙色的 *添加*按钮。 从列表中选择一个供应商,输入用户名,大多为您的电子邮件地址,并输入您的密码。 点击 *检查* 让FairEmail连接到电子邮件服务器,并获取系统文件夹列表。 在检查过系统文件夹选择后,您可以通过点击 *保存*添加账户。
|
||||
|
||||
如果您的提供商不在名单中,请选择 *自定义*。 输入域名,例如 *gmail.com*并点击*获取设置*。 如果你的供应商支持 [自动发现](https://tools.ietf.org/html/rfc6186),FairEmail将填写主机名和端口号,请您检查IMAP主机名、端口号和协议(SSL/TLS 或 STARTLS)是否正确。 关于这一问题的更多内容,请见[此处](https://github.com/M66B/FairEmail/blob/master/FAQ.md#authorizing-accounts)。
|
||||
|
||||
## 设置身份 - 发送电子邮件
|
||||
|
||||
类似的,要添加一个帐户,点击*管理身份*并点击底部橙色的 *添加*按钮。 输入您想要在您发送的电子邮件的发件人一栏中出现的名称,并选择一个链接的帐户。 点击 *保存* 来添加身份。
|
||||
|
||||
如果手动配置了帐户,您也可能需要手动配置身份。 输入域名,例如 *gmail.com*并点击*获取设置*。 如果你的供应商支持 [自动发现](https://tools.ietf.org/html/rfc6186),FairEmail将填写主机名和端口号,请您检查SMTP主机名、端口号和协议(SSL/TLS 或 STARTLS)是否正确。
|
||||
|
||||
有关使用邮件别名请参阅此 [FAQ](https://github.com/M66B/FairEmail/blob/master/FAQ.md#FAQ9) 。
|
||||
|
||||
## 授权权限 - 访问联系人信息
|
||||
|
||||
如果您想要查找邮件地址,显示了联系照片等,您需要授予 FairEmail 阅读联系人的权限。 只需点击 *授权*并选择 *允许*。
|
||||
|
||||
## 设置电池优化 - 持续接收电子邮件
|
||||
|
||||
在最近的 Android 版本中,当屏幕关闭一段时间后,系统会让应用进入休眠状态减少电池使用。 如果您希望在无延迟地接收新邮件,您应该禁用针对FairEmail的电量优化。 点击*禁用电池优化*并遵循指示。
|
||||
|
||||
## 疑问或问题
|
||||
|
||||
如果您有问题或疑问,请 [见此处 ](https://github.com/M66B/FairEmail/blob/master/FAQ.md),或使用 [此联系表单](https://contact.faircode.eu/?product=fairemailsupport) 来请求帮助 (您可以使用交易号“*设置帮助*")。
|
||||
41
app/src/main/assets/SETUP-zh-TW.md
Normal file
41
app/src/main/assets/SETUP-zh-TW.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# Setup help
|
||||
|
||||
Setting up FairEmail is fairly simple. You'll need to add at least one account to receive email and at least one identity if you want to send email. The quick setup will add an account and an identity in one go for most major providers.
|
||||
|
||||
## Requirements
|
||||
|
||||
An internet connection is required to set up accounts and identities.
|
||||
|
||||
## Quick setup
|
||||
|
||||
Just enter your name, email address and password and tap *Go*.
|
||||
|
||||
This will work for most major email providers.
|
||||
|
||||
If the quick setup doesn't work, you'll need to setup an account and an identity in another way, see below for instructions.
|
||||
|
||||
## Set up account - to receive email
|
||||
|
||||
To add an account, tap *Manage accounts* and tap the orange *add* button at the bottom. Select a provider from the list, enter the username, which is mostly your email address and enter your password. Tap *Check* to let FairEmail connect to the email server and fetch a list of system folders. After reviewing the system folder selection you can add the account by tapping *Save*.
|
||||
|
||||
If your provider is not in the list of providers, select *Custom*. Enter the domain name, for example *gmail.com* and tap *Get settings*. If your provider supports [auto-discovery](https://tools.ietf.org/html/rfc6186), FairEmail will fill in the hostname and port number, else check the setup instructions of your provider for the right IMAP hostname, port number and protocol (SSL/TLS or STARTTLS). For more about this, please see [here](https://github.com/M66B/FairEmail/blob/master/FAQ.md#authorizing-accounts).
|
||||
|
||||
## Set up identity - to send email
|
||||
|
||||
Similarly, to add an identity, tap *Manage identity* and tap the orange *add* button at the bottom. Enter the name you want to appear in de from address of the emails you send and select a linked account. Tap *Save* to add the identity.
|
||||
|
||||
If the account was configured manually, you likely need to configure the identity manually too. Enter the domain name, for example *gmail.com* and tap *Get settings*. If your provider supports [auto-discovery](https://tools.ietf.org/html/rfc6186), FairEmail will fill in the hostname and port number, else check the setup instructions of your provider for the right SMTP hostname, port number and protocol (SSL/TLS or STARTTLS).
|
||||
|
||||
See [this FAQ](https://github.com/M66B/FairEmail/blob/master/FAQ.md#FAQ9) about using aliases.
|
||||
|
||||
## Grant permissions - to access contact information
|
||||
|
||||
If you want to lookup email addresses, have contact photos shown, etc, you'll need to grant read contacts permission to FairEmail. Just tap *Grant permissions* and select *Allow*.
|
||||
|
||||
## Setup battery optimizations - to continuously receive emails
|
||||
|
||||
On recent Android versions, Android will put apps to sleep when the screen is off for some time to reduce battery usage. If you want to receive new emails without delays, you should disable battery optimizations for FairEmail. Tap *Disable battery optimizations* and follow the instructions.
|
||||
|
||||
## Questions or problems
|
||||
|
||||
If you have a question or problem, please [see here](https://github.com/M66B/FairEmail/blob/master/FAQ.md) or use [this contact form](https://contact.faircode.eu/?product=fairemailsupport) to ask for help (you can use the transaction number "*setup help*").
|
||||
66
app/src/main/assets/SETUP.md
Normal file
66
app/src/main/assets/SETUP.md
Normal file
@@ -0,0 +1,66 @@
|
||||
# Setup help
|
||||
|
||||
Setting up FairEmail is fairly simple.
|
||||
You'll need to add at least one account to receive email and at least one identity if you want to send email.
|
||||
The quick setup will add an account and an identity in one go for most major providers.
|
||||
|
||||
|
||||
## Requirements
|
||||
|
||||
An internet connection is required to set up accounts and identities.
|
||||
|
||||
|
||||
## Quick setup
|
||||
|
||||
Just enter your name, email address and password and tap *Go*.
|
||||
|
||||
This will work for most major email providers.
|
||||
|
||||
If the quick setup doesn't work, you'll need to setup an account and an identity in another way, see below for instructions.
|
||||
|
||||
|
||||
## Set up account - to receive email
|
||||
|
||||
To add an account, tap *Manage accounts* and tap the orange *add* button at the bottom.
|
||||
Select a provider from the list, enter the username, which is mostly your email address and enter your password.
|
||||
Tap *Check* to let FairEmail connect to the email server and fetch a list of system folders.
|
||||
After reviewing the system folder selection you can add the account by tapping *Save*.
|
||||
|
||||
If your provider is not in the list of providers, select *Custom*.
|
||||
Enter the domain name, for example *gmail.com* and tap *Get settings*.
|
||||
If your provider supports [auto-discovery](https://tools.ietf.org/html/rfc6186), FairEmail will fill in the hostname and port number,
|
||||
else check the setup instructions of your provider for the right IMAP hostname, port number and protocol (SSL/TLS or STARTTLS).
|
||||
For more about this, please see [here](https://github.com/M66B/FairEmail/blob/master/FAQ.md#authorizing-accounts).
|
||||
|
||||
|
||||
## Set up identity - to send email
|
||||
|
||||
Similarly, to add an identity, tap *Manage identity* and tap the orange *add* button at the bottom.
|
||||
Enter the name you want to appear in de from address of the emails you send and select a linked account.
|
||||
Tap *Save* to add the identity.
|
||||
|
||||
If the account was configured manually, you likely need to configure the identity manually too.
|
||||
Enter the domain name, for example *gmail.com* and tap *Get settings*.
|
||||
If your provider supports [auto-discovery](https://tools.ietf.org/html/rfc6186), FairEmail will fill in the hostname and port number,
|
||||
else check the setup instructions of your provider for the right SMTP hostname, port number and protocol (SSL/TLS or STARTTLS).
|
||||
|
||||
See [this FAQ](https://github.com/M66B/FairEmail/blob/master/FAQ.md#FAQ9) about using aliases.
|
||||
|
||||
|
||||
## Grant permissions - to access contact information
|
||||
|
||||
If you want to lookup email addresses, have contact photos shown, etc, you'll need to grant read contacts permission to FairEmail.
|
||||
Just tap *Grant permissions* and select *Allow*.
|
||||
|
||||
|
||||
## Setup battery optimizations - to continuously receive emails
|
||||
|
||||
On recent Android versions, Android will put apps to sleep when the screen is off for some time to reduce battery usage.
|
||||
If you want to receive new emails without delays, you should disable battery optimizations for FairEmail.
|
||||
Tap *Disable battery optimizations* and follow the instructions.
|
||||
|
||||
|
||||
## Questions or problems
|
||||
|
||||
If you have a question or problem, please [see here](https://github.com/M66B/FairEmail/blob/master/FAQ.md)
|
||||
or use [this contact form](https://contact.faircode.eu/?product=fairemailsupport) to ask for help (you can use the transaction number "*setup help*").
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
158
app/src/main/java/androidx/lifecycle/ComputableLiveData.java
Normal file
158
app/src/main/java/androidx/lifecycle/ComputableLiveData.java
Normal file
@@ -0,0 +1,158 @@
|
||||
/*
|
||||
* Copyright (C) 2017 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package androidx.lifecycle;
|
||||
|
||||
import androidx.annotation.MainThread;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.RestrictTo;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import androidx.annotation.WorkerThread;
|
||||
import androidx.arch.core.executor.ArchTaskExecutor;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
/**
|
||||
* A LiveData class that can be invalidated & computed when there are active observers.
|
||||
* <p>
|
||||
* It can be invalidated via {@link #invalidate()}, which will result in a call to
|
||||
* {@link #compute()} if there are active observers (or when they start observing)
|
||||
* <p>
|
||||
* This is an internal class for now, might be public if we see the necessity.
|
||||
*
|
||||
* @param <T> The type of the live data
|
||||
* @hide internal
|
||||
*/
|
||||
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
|
||||
public abstract class ComputableLiveData<T> {
|
||||
@SuppressWarnings("WeakerAccess") /* synthetic access */
|
||||
final Executor mExecutor;
|
||||
@SuppressWarnings("WeakerAccess") /* synthetic access */
|
||||
final LiveData<T> mLiveData;
|
||||
@SuppressWarnings("WeakerAccess") /* synthetic access */
|
||||
final AtomicBoolean mInvalid = new AtomicBoolean(true);
|
||||
@SuppressWarnings("WeakerAccess") /* synthetic access */
|
||||
final AtomicBoolean mComputing = new AtomicBoolean(false);
|
||||
|
||||
/**
|
||||
* Creates a computable live data that computes values on the arch IO thread executor.
|
||||
*/
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
public ComputableLiveData() {
|
||||
this(ArchTaskExecutor.getIOThreadExecutor());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a computable live data that computes values on the specified executor.
|
||||
*
|
||||
* @param executor Executor that is used to compute new LiveData values.
|
||||
*/
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
public ComputableLiveData(@NonNull Executor executor) {
|
||||
mExecutor = executor;
|
||||
mLiveData = new LiveData<T>() {
|
||||
@Override
|
||||
protected void onActive() {
|
||||
mExecutor.execute(mRefreshRunnable);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the LiveData managed by this class.
|
||||
*
|
||||
* @return A LiveData that is controlled by ComputableLiveData.
|
||||
*/
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
@NonNull
|
||||
public LiveData<T> getLiveData() {
|
||||
return mLiveData;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
final Runnable mRefreshRunnable = new Runnable() {
|
||||
@WorkerThread
|
||||
@Override
|
||||
public void run() {
|
||||
boolean computed;
|
||||
long last;
|
||||
do {
|
||||
computed = false;
|
||||
// compute can happen only in 1 thread but no reason to lock others.
|
||||
if (mComputing.compareAndSet(false, true)) {
|
||||
// as long as it is invalid, keep computing.
|
||||
try {
|
||||
last = android.os.SystemClock.elapsedRealtime();
|
||||
T value = null;
|
||||
while (mInvalid.compareAndSet(true, false)) {
|
||||
long now = android.os.SystemClock.elapsedRealtime();
|
||||
if (last + 1500 < now && value != null) {
|
||||
eu.faircode.email.Log.i(mLiveData + " post age=" + (now - last));
|
||||
last = now;
|
||||
mLiveData.postValue(value);
|
||||
}
|
||||
computed = true;
|
||||
value = compute();
|
||||
}
|
||||
if (computed) {
|
||||
mLiveData.postValue(value);
|
||||
}
|
||||
} finally {
|
||||
// release compute lock
|
||||
mComputing.set(false);
|
||||
}
|
||||
}
|
||||
// check invalid after releasing compute lock to avoid the following scenario.
|
||||
// Thread A runs compute()
|
||||
// Thread A checks invalid, it is false
|
||||
// Main thread sets invalid to true
|
||||
// Thread B runs, fails to acquire compute lock and skips
|
||||
// Thread A releases compute lock
|
||||
// We've left invalid in set state. The check below recovers.
|
||||
} while (computed && mInvalid.get());
|
||||
}
|
||||
};
|
||||
|
||||
// invalidation check always happens on the main thread
|
||||
@VisibleForTesting
|
||||
final Runnable mInvalidationRunnable = new Runnable() {
|
||||
@MainThread
|
||||
@Override
|
||||
public void run() {
|
||||
boolean isActive = mLiveData.hasActiveObservers();
|
||||
if (mInvalid.compareAndSet(false, true)) {
|
||||
if (isActive) {
|
||||
mExecutor.execute(mRefreshRunnable);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Invalidates the LiveData.
|
||||
* <p>
|
||||
* When there are active observers, this will trigger a call to {@link #compute()}.
|
||||
*/
|
||||
public void invalidate() {
|
||||
ArchTaskExecutor.getInstance().executeOnMainThread(mInvalidationRunnable);
|
||||
}
|
||||
|
||||
// TODO https://issuetracker.google.com/issues/112197238
|
||||
@SuppressWarnings({"WeakerAccess", "UnknownNullness"})
|
||||
@WorkerThread
|
||||
protected abstract T compute();
|
||||
}
|
||||
465
app/src/main/java/androidx/lifecycle/LiveData.java
Normal file
465
app/src/main/java/androidx/lifecycle/LiveData.java
Normal file
@@ -0,0 +1,465 @@
|
||||
/*
|
||||
* Copyright (C) 2017 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package androidx.lifecycle;
|
||||
|
||||
import static androidx.lifecycle.Lifecycle.State.DESTROYED;
|
||||
import static androidx.lifecycle.Lifecycle.State.STARTED;
|
||||
|
||||
import androidx.annotation.MainThread;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.arch.core.executor.ArchTaskExecutor;
|
||||
import androidx.arch.core.internal.SafeIterableMap;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* LiveData is a data holder class that can be observed within a given lifecycle.
|
||||
* This means that an {@link Observer} can be added in a pair with a {@link LifecycleOwner}, and
|
||||
* this observer will be notified about modifications of the wrapped data only if the paired
|
||||
* LifecycleOwner is in active state. LifecycleOwner is considered as active, if its state is
|
||||
* {@link Lifecycle.State#STARTED} or {@link Lifecycle.State#RESUMED}. An observer added via
|
||||
* {@link #observeForever(Observer)} is considered as always active and thus will be always notified
|
||||
* about modifications. For those observers, you should manually call
|
||||
* {@link #removeObserver(Observer)}.
|
||||
*
|
||||
* <p> An observer added with a Lifecycle will be automatically removed if the corresponding
|
||||
* Lifecycle moves to {@link Lifecycle.State#DESTROYED} state. This is especially useful for
|
||||
* activities and fragments where they can safely observe LiveData and not worry about leaks:
|
||||
* they will be instantly unsubscribed when they are destroyed.
|
||||
*
|
||||
* <p>
|
||||
* In addition, LiveData has {@link LiveData#onActive()} and {@link LiveData#onInactive()} methods
|
||||
* to get notified when number of active {@link Observer}s change between 0 and 1.
|
||||
* This allows LiveData to release any heavy resources when it does not have any Observers that
|
||||
* are actively observing.
|
||||
* <p>
|
||||
* This class is designed to hold individual data fields of {@link ViewModel},
|
||||
* but can also be used for sharing data between different modules in your application
|
||||
* in a decoupled fashion.
|
||||
*
|
||||
* @param <T> The type of data held by this instance
|
||||
* @see ViewModel
|
||||
*/
|
||||
public abstract class LiveData<T> {
|
||||
@SuppressWarnings("WeakerAccess") /* synthetic access */
|
||||
final Object mDataLock = new Object();
|
||||
static final int START_VERSION = -1;
|
||||
@SuppressWarnings("WeakerAccess") /* synthetic access */
|
||||
static final Object NOT_SET = new Object();
|
||||
|
||||
private SafeIterableMap<Observer<? super T>, ObserverWrapper> mObservers =
|
||||
new SafeIterableMap<>();
|
||||
|
||||
// how many observers are in active state
|
||||
@SuppressWarnings("WeakerAccess") /* synthetic access */
|
||||
int mActiveCount = 0;
|
||||
private volatile Object mData;
|
||||
// when setData is called, we set the pending data and actual data swap happens on the main
|
||||
// thread
|
||||
@SuppressWarnings("WeakerAccess") /* synthetic access */
|
||||
volatile Object mPendingData = NOT_SET;
|
||||
private int mVersion;
|
||||
|
||||
private boolean mDispatchingValue;
|
||||
@SuppressWarnings("FieldCanBeLocal")
|
||||
private boolean mDispatchInvalidated;
|
||||
private final Runnable mPostValueRunnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Object newValue;
|
||||
synchronized (mDataLock) {
|
||||
newValue = mPendingData;
|
||||
mPendingData = NOT_SET;
|
||||
}
|
||||
//noinspection unchecked
|
||||
setValue((T) newValue);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a LiveData initialized with the given {@code value}.
|
||||
*
|
||||
* @param value initial value
|
||||
*/
|
||||
public LiveData(T value) {
|
||||
mData = value;
|
||||
mVersion = START_VERSION + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a LiveData with no value assigned to it.
|
||||
*/
|
||||
public LiveData() {
|
||||
mData = NOT_SET;
|
||||
mVersion = START_VERSION;
|
||||
}
|
||||
|
||||
private void considerNotify(ObserverWrapper observer) {
|
||||
if (!observer.mActive) {
|
||||
return;
|
||||
}
|
||||
// Check latest state b4 dispatch. Maybe it changed state but we didn't get the event yet.
|
||||
//
|
||||
// we still first check observer.active to keep it as the entrance for events. So even if
|
||||
// the observer moved to an active state, if we've not received that event, we better not
|
||||
// notify for a more predictable notification order.
|
||||
if (!observer.shouldBeActive()) {
|
||||
observer.activeStateChanged(false);
|
||||
return;
|
||||
}
|
||||
if (observer.mLastVersion >= mVersion) {
|
||||
return;
|
||||
}
|
||||
observer.mLastVersion = mVersion;
|
||||
//noinspection unchecked
|
||||
observer.mObserver.onChanged((T) mData);
|
||||
}
|
||||
|
||||
@SuppressWarnings("WeakerAccess") /* synthetic access */
|
||||
void dispatchingValue(@Nullable ObserverWrapper initiator) {
|
||||
if (mDispatchingValue) {
|
||||
mDispatchInvalidated = true;
|
||||
return;
|
||||
}
|
||||
mDispatchingValue = true;
|
||||
do {
|
||||
mDispatchInvalidated = false;
|
||||
if (initiator != null) {
|
||||
considerNotify(initiator);
|
||||
initiator = null;
|
||||
} else {
|
||||
for (Iterator<Map.Entry<Observer<? super T>, ObserverWrapper>> iterator =
|
||||
mObservers.iteratorWithAdditions(); iterator.hasNext(); ) {
|
||||
considerNotify(iterator.next().getValue());
|
||||
if (mDispatchInvalidated) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} while (mDispatchInvalidated);
|
||||
mDispatchingValue = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the given observer to the observers list within the lifespan of the given
|
||||
* owner. The events are dispatched on the main thread. If LiveData already has data
|
||||
* set, it will be delivered to the observer.
|
||||
* <p>
|
||||
* The observer will only receive events if the owner is in {@link Lifecycle.State#STARTED}
|
||||
* or {@link Lifecycle.State#RESUMED} state (active).
|
||||
* <p>
|
||||
* If the owner moves to the {@link Lifecycle.State#DESTROYED} state, the observer will
|
||||
* automatically be removed.
|
||||
* <p>
|
||||
* When data changes while the {@code owner} is not active, it will not receive any updates.
|
||||
* If it becomes active again, it will receive the last available data automatically.
|
||||
* <p>
|
||||
* LiveData keeps a strong reference to the observer and the owner as long as the
|
||||
* given LifecycleOwner is not destroyed. When it is destroyed, LiveData removes references to
|
||||
* the observer & the owner.
|
||||
* <p>
|
||||
* If the given owner is already in {@link Lifecycle.State#DESTROYED} state, LiveData
|
||||
* ignores the call.
|
||||
* <p>
|
||||
* If the given owner, observer tuple is already in the list, the call is ignored.
|
||||
* If the observer is already in the list with another owner, LiveData throws an
|
||||
* {@link IllegalArgumentException}.
|
||||
*
|
||||
* @param owner The LifecycleOwner which controls the observer
|
||||
* @param observer The observer that will receive the events
|
||||
*/
|
||||
@MainThread
|
||||
public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {
|
||||
assertMainThread("observe");
|
||||
if (owner.getLifecycle().getCurrentState() == DESTROYED) {
|
||||
// ignore
|
||||
return;
|
||||
}
|
||||
LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);
|
||||
ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper);
|
||||
if (existing != null && !existing.isAttachedTo(owner)) {
|
||||
throw new IllegalArgumentException("Cannot add the same observer"
|
||||
+ " with different lifecycles");
|
||||
}
|
||||
if (existing != null) {
|
||||
return;
|
||||
}
|
||||
owner.getLifecycle().addObserver(wrapper);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the given observer to the observers list. This call is similar to
|
||||
* {@link LiveData#observe(LifecycleOwner, Observer)} with a LifecycleOwner, which
|
||||
* is always active. This means that the given observer will receive all events and will never
|
||||
* be automatically removed. You should manually call {@link #removeObserver(Observer)} to stop
|
||||
* observing this LiveData.
|
||||
* While LiveData has one of such observers, it will be considered
|
||||
* as active.
|
||||
* <p>
|
||||
* If the observer was already added with an owner to this LiveData, LiveData throws an
|
||||
* {@link IllegalArgumentException}.
|
||||
*
|
||||
* @param observer The observer that will receive the events
|
||||
*/
|
||||
@MainThread
|
||||
public void observeForever(@NonNull Observer<? super T> observer) {
|
||||
assertMainThread("observeForever");
|
||||
AlwaysActiveObserver wrapper = new AlwaysActiveObserver(observer);
|
||||
ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper);
|
||||
if (existing instanceof LiveData.LifecycleBoundObserver) {
|
||||
throw new IllegalArgumentException("Cannot add the same observer"
|
||||
+ " with different lifecycles");
|
||||
}
|
||||
if (existing != null) {
|
||||
return;
|
||||
}
|
||||
wrapper.activeStateChanged(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the given observer from the observers list.
|
||||
*
|
||||
* @param observer The Observer to receive events.
|
||||
*/
|
||||
@MainThread
|
||||
public void removeObserver(@NonNull final Observer<? super T> observer) {
|
||||
assertMainThread("removeObserver");
|
||||
ObserverWrapper removed = mObservers.remove(observer);
|
||||
if (removed == null) {
|
||||
return;
|
||||
}
|
||||
removed.detachObserver();
|
||||
removed.activeStateChanged(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all observers that are tied to the given {@link LifecycleOwner}.
|
||||
*
|
||||
* @param owner The {@code LifecycleOwner} scope for the observers to be removed.
|
||||
*/
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
@MainThread
|
||||
public void removeObservers(@NonNull final LifecycleOwner owner) {
|
||||
assertMainThread("removeObservers");
|
||||
for (Map.Entry<Observer<? super T>, ObserverWrapper> entry : mObservers) {
|
||||
if (entry.getValue().isAttachedTo(owner)) {
|
||||
removeObserver(entry.getKey());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Posts a task to a main thread to set the given value. So if you have a following code
|
||||
* executed in the main thread:
|
||||
* <pre class="prettyprint">
|
||||
* liveData.postValue("a");
|
||||
* liveData.setValue("b");
|
||||
* </pre>
|
||||
* The value "b" would be set at first and later the main thread would override it with
|
||||
* the value "a".
|
||||
* <p>
|
||||
* If you called this method multiple times before a main thread executed a posted task, only
|
||||
* the last value would be dispatched.
|
||||
*
|
||||
* @param value The new value
|
||||
*/
|
||||
protected void postValue(T value) {
|
||||
boolean postTask;
|
||||
synchronized (mDataLock) {
|
||||
postTask = mPendingData == NOT_SET;
|
||||
mPendingData = value;
|
||||
}
|
||||
if (!postTask) {
|
||||
return;
|
||||
}
|
||||
ArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value. If there are active observers, the value will be dispatched to them.
|
||||
* <p>
|
||||
* This method must be called from the main thread. If you need set a value from a background
|
||||
* thread, you can use {@link #postValue(Object)}
|
||||
*
|
||||
* @param value The new value
|
||||
*/
|
||||
@MainThread
|
||||
protected void setValue(T value) {
|
||||
assertMainThread("setValue");
|
||||
mVersion++;
|
||||
mData = value;
|
||||
dispatchingValue(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current value.
|
||||
* Note that calling this method on a background thread does not guarantee that the latest
|
||||
* value set will be received.
|
||||
*
|
||||
* @return the current value
|
||||
*/
|
||||
@Nullable
|
||||
public T getValue() {
|
||||
Object data = mData;
|
||||
if (data != NOT_SET) {
|
||||
//noinspection unchecked
|
||||
return (T) data;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
int getVersion() {
|
||||
return mVersion;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the number of active observers change to 1 from 0.
|
||||
* <p>
|
||||
* This callback can be used to know that this LiveData is being used thus should be kept
|
||||
* up to date.
|
||||
*/
|
||||
protected void onActive() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the number of active observers change from 1 to 0.
|
||||
* <p>
|
||||
* This does not mean that there are no observers left, there may still be observers but their
|
||||
* lifecycle states aren't {@link Lifecycle.State#STARTED} or {@link Lifecycle.State#RESUMED}
|
||||
* (like an Activity in the back stack).
|
||||
* <p>
|
||||
* You can check if there are observers via {@link #hasObservers()}.
|
||||
*/
|
||||
protected void onInactive() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this LiveData has observers.
|
||||
*
|
||||
* @return true if this LiveData has observers
|
||||
*/
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
public boolean hasObservers() {
|
||||
return mObservers.size() > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this LiveData has active observers.
|
||||
*
|
||||
* @return true if this LiveData has active observers
|
||||
*/
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
public boolean hasActiveObservers() {
|
||||
return mActiveCount > 0;
|
||||
}
|
||||
|
||||
class LifecycleBoundObserver extends ObserverWrapper implements LifecycleEventObserver {
|
||||
@NonNull
|
||||
final LifecycleOwner mOwner;
|
||||
|
||||
LifecycleBoundObserver(@NonNull LifecycleOwner owner, Observer<? super T> observer) {
|
||||
super(observer);
|
||||
mOwner = owner;
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean shouldBeActive() {
|
||||
return mOwner.getLifecycle().getCurrentState().isAtLeast(STARTED);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStateChanged(LifecycleOwner source, Lifecycle.Event event) {
|
||||
if (mOwner.getLifecycle().getCurrentState() == DESTROYED) {
|
||||
removeObserver(mObserver);
|
||||
return;
|
||||
}
|
||||
activeStateChanged(shouldBeActive());
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean isAttachedTo(LifecycleOwner owner) {
|
||||
return mOwner == owner;
|
||||
}
|
||||
|
||||
@Override
|
||||
void detachObserver() {
|
||||
mOwner.getLifecycle().removeObserver(this);
|
||||
}
|
||||
}
|
||||
|
||||
private abstract class ObserverWrapper {
|
||||
final Observer<? super T> mObserver;
|
||||
boolean mActive;
|
||||
int mLastVersion = START_VERSION;
|
||||
|
||||
ObserverWrapper(Observer<? super T> observer) {
|
||||
mObserver = observer;
|
||||
}
|
||||
|
||||
abstract boolean shouldBeActive();
|
||||
|
||||
boolean isAttachedTo(LifecycleOwner owner) {
|
||||
return false;
|
||||
}
|
||||
|
||||
void detachObserver() {
|
||||
}
|
||||
|
||||
void activeStateChanged(boolean newActive) {
|
||||
if (newActive == mActive) {
|
||||
return;
|
||||
}
|
||||
// immediately set active state, so we'd never dispatch anything to inactive
|
||||
// owner
|
||||
mActive = newActive;
|
||||
boolean wasInactive = LiveData.this.mActiveCount == 0;
|
||||
LiveData.this.mActiveCount += mActive ? 1 : -1;
|
||||
if (wasInactive && mActive) {
|
||||
onActive();
|
||||
}
|
||||
if (LiveData.this.mActiveCount == 0 && !mActive) {
|
||||
onInactive();
|
||||
}
|
||||
if (mActive) {
|
||||
dispatchingValue(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class AlwaysActiveObserver extends ObserverWrapper {
|
||||
|
||||
AlwaysActiveObserver(Observer<? super T> observer) {
|
||||
super(observer);
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean shouldBeActive() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
static void assertMainThread(String methodName) {
|
||||
if (!ArchTaskExecutor.getInstance().isMainThread()) {
|
||||
throw new IllegalStateException("Cannot invoke " + methodName + " on a background"
|
||||
+ " thread");
|
||||
}
|
||||
}
|
||||
}
|
||||
156
app/src/main/java/androidx/lifecycle/MediatorLiveData.java
Normal file
156
app/src/main/java/androidx/lifecycle/MediatorLiveData.java
Normal file
@@ -0,0 +1,156 @@
|
||||
/*
|
||||
* Copyright (C) 2017 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package androidx.lifecycle;
|
||||
|
||||
import androidx.annotation.CallSuper;
|
||||
import androidx.annotation.MainThread;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.arch.core.internal.SafeIterableMap;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* {@link LiveData} subclass which may observe other {@code LiveData} objects and react on
|
||||
* {@code OnChanged} events from them.
|
||||
* <p>
|
||||
* This class correctly propagates its active/inactive states down to source {@code LiveData}
|
||||
* objects.
|
||||
* <p>
|
||||
* Consider the following scenario: we have 2 instances of {@code LiveData}, let's name them
|
||||
* {@code liveData1} and {@code liveData2}, and we want to merge their emissions in one object:
|
||||
* {@code liveDataMerger}. Then, {@code liveData1} and {@code liveData2} will become sources for
|
||||
* the {@code MediatorLiveData liveDataMerger} and every time {@code onChanged} callback
|
||||
* is called for either of them, we set a new value in {@code liveDataMerger}.
|
||||
*
|
||||
* <pre>
|
||||
* LiveData<Integer> liveData1 = ...;
|
||||
* LiveData<Integer> liveData2 = ...;
|
||||
*
|
||||
* MediatorLiveData<Integer> liveDataMerger = new MediatorLiveData<>();
|
||||
* liveDataMerger.addSource(liveData1, value -> liveDataMerger.setValue(value));
|
||||
* liveDataMerger.addSource(liveData2, value -> liveDataMerger.setValue(value));
|
||||
* </pre>
|
||||
* <p>
|
||||
* Let's consider that we only want 10 values emitted by {@code liveData1}, to be
|
||||
* merged in the {@code liveDataMerger}. Then, after 10 values, we can stop listening to {@code
|
||||
* liveData1} and remove it as a source.
|
||||
* <pre>
|
||||
* liveDataMerger.addSource(liveData1, new Observer<Integer>() {
|
||||
* private int count = 1;
|
||||
*
|
||||
* {@literal @}Override public void onChanged(@Nullable Integer s) {
|
||||
* count++;
|
||||
* liveDataMerger.setValue(s);
|
||||
* if (count > 10) {
|
||||
* liveDataMerger.removeSource(liveData1);
|
||||
* }
|
||||
* }
|
||||
* });
|
||||
* </pre>
|
||||
*
|
||||
* @param <T> The type of data hold by this instance
|
||||
*/
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
public class MediatorLiveData<T> extends MutableLiveData<T> {
|
||||
private SafeIterableMap<LiveData<?>, Source<?>> mSources = new SafeIterableMap<>();
|
||||
|
||||
/**
|
||||
* Starts to listen the given {@code source} LiveData, {@code onChanged} observer will be called
|
||||
* when {@code source} value was changed.
|
||||
* <p>
|
||||
* {@code onChanged} callback will be called only when this {@code MediatorLiveData} is active.
|
||||
* <p> If the given LiveData is already added as a source but with a different Observer,
|
||||
* {@link IllegalArgumentException} will be thrown.
|
||||
*
|
||||
* @param source the {@code LiveData} to listen to
|
||||
* @param onChanged The observer that will receive the events
|
||||
* @param <S> The type of data hold by {@code source} LiveData
|
||||
*/
|
||||
@MainThread
|
||||
public <S> void addSource(@NonNull LiveData<S> source, @NonNull Observer<? super S> onChanged) {
|
||||
Source<S> e = new Source<>(source, onChanged);
|
||||
Source<?> existing = mSources.putIfAbsent(source, e);
|
||||
if (existing != null && existing.mObserver != onChanged) {
|
||||
throw new IllegalArgumentException(
|
||||
"This source was already added with the different observer");
|
||||
}
|
||||
if (existing != null) {
|
||||
return;
|
||||
}
|
||||
if (hasActiveObservers()) {
|
||||
e.plug();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops to listen the given {@code LiveData}.
|
||||
*
|
||||
* @param toRemote {@code LiveData} to stop to listen
|
||||
* @param <S> the type of data hold by {@code source} LiveData
|
||||
*/
|
||||
@MainThread
|
||||
public <S> void removeSource(@NonNull LiveData<S> toRemote) {
|
||||
Source<?> source = mSources.remove(toRemote);
|
||||
if (source != null) {
|
||||
source.unplug();
|
||||
}
|
||||
}
|
||||
|
||||
@CallSuper
|
||||
@Override
|
||||
protected void onActive() {
|
||||
for (Map.Entry<LiveData<?>, Source<?>> source : mSources) {
|
||||
source.getValue().plug();
|
||||
}
|
||||
}
|
||||
|
||||
@CallSuper
|
||||
@Override
|
||||
protected void onInactive() {
|
||||
for (Map.Entry<LiveData<?>, Source<?>> source : mSources) {
|
||||
source.getValue().unplug();
|
||||
}
|
||||
}
|
||||
|
||||
private static class Source<V> implements Observer<V> {
|
||||
final LiveData<V> mLiveData;
|
||||
final Observer<? super V> mObserver;
|
||||
int mVersion = START_VERSION;
|
||||
|
||||
Source(LiveData<V> liveData, final Observer<? super V> observer) {
|
||||
mLiveData = liveData;
|
||||
mObserver = observer;
|
||||
}
|
||||
|
||||
void plug() {
|
||||
mLiveData.observeForever(this);
|
||||
}
|
||||
|
||||
void unplug() {
|
||||
mLiveData.removeObserver(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChanged(@Nullable V v) {
|
||||
if (mVersion != mLiveData.getVersion()) {
|
||||
mVersion = mLiveData.getVersion();
|
||||
mObserver.onChanged(v);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
52
app/src/main/java/androidx/lifecycle/MutableLiveData.java
Normal file
52
app/src/main/java/androidx/lifecycle/MutableLiveData.java
Normal file
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright (C) 2017 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package androidx.lifecycle;
|
||||
|
||||
/**
|
||||
* {@link LiveData} which publicly exposes {@link #setValue(T)} and {@link #postValue(T)} method.
|
||||
*
|
||||
* @param <T> The type of data hold by this instance
|
||||
*/
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
public class MutableLiveData<T> extends LiveData<T> {
|
||||
|
||||
/**
|
||||
* Creates a MutableLiveData initialized with the given {@code value}.
|
||||
*
|
||||
* @param value initial value
|
||||
*/
|
||||
public MutableLiveData(T value) {
|
||||
super(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a MutableLiveData with no value assigned to it.
|
||||
*/
|
||||
public MutableLiveData() {
|
||||
super();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postValue(T value) {
|
||||
super.postValue(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValue(T value) {
|
||||
super.setValue(value);
|
||||
}
|
||||
}
|
||||
32
app/src/main/java/androidx/lifecycle/Observer.java
Normal file
32
app/src/main/java/androidx/lifecycle/Observer.java
Normal file
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright (C) 2017 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package androidx.lifecycle;
|
||||
|
||||
/**
|
||||
* A simple callback that can receive from {@link LiveData}.
|
||||
*
|
||||
* @param <T> The type of the parameter
|
||||
*
|
||||
* @see LiveData LiveData - for a usage description.
|
||||
*/
|
||||
public interface Observer<T> {
|
||||
/**
|
||||
* Called when the data is changed.
|
||||
* @param t The new data
|
||||
*/
|
||||
void onChanged(T t);
|
||||
}
|
||||
194
app/src/main/java/androidx/lifecycle/Transformations.java
Normal file
194
app/src/main/java/androidx/lifecycle/Transformations.java
Normal file
@@ -0,0 +1,194 @@
|
||||
/*
|
||||
* Copyright (C) 2017 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package androidx.lifecycle;
|
||||
|
||||
import androidx.annotation.MainThread;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.arch.core.util.Function;
|
||||
|
||||
/**
|
||||
* Transformation methods for {@link LiveData}.
|
||||
* <p>
|
||||
* These methods permit functional composition and delegation of {@link LiveData} instances. The
|
||||
* transformations are calculated lazily, and will run only when the returned {@link LiveData} is
|
||||
* observed. Lifecycle behavior is propagated from the input {@code source} {@link LiveData} to the
|
||||
* returned one.
|
||||
*/
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
public class Transformations {
|
||||
|
||||
private Transformations() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@code LiveData} mapped from the input {@code source} {@code LiveData} by applying
|
||||
* {@code mapFunction} to each value set on {@code source}.
|
||||
* <p>
|
||||
* This method is analogous to {@link io.reactivex.Observable#map}.
|
||||
* <p>
|
||||
* {@code transform} will be executed on the main thread.
|
||||
* <p>
|
||||
* Here is an example mapping a simple {@code User} struct in a {@code LiveData} to a
|
||||
* {@code LiveData} containing their full name as a {@code String}.
|
||||
*
|
||||
* <pre>
|
||||
* LiveData<User> userLiveData = ...;
|
||||
* LiveData<String> userFullNameLiveData =
|
||||
* Transformations.map(
|
||||
* userLiveData,
|
||||
* user -> user.firstName + user.lastName);
|
||||
* });
|
||||
* </pre>
|
||||
*
|
||||
* @param source the {@code LiveData} to map from
|
||||
* @param mapFunction a function to apply to each value set on {@code source} in order to set
|
||||
* it
|
||||
* on the output {@code LiveData}
|
||||
* @param <X> the generic type parameter of {@code source}
|
||||
* @param <Y> the generic type parameter of the returned {@code LiveData}
|
||||
* @return a LiveData mapped from {@code source} to type {@code <Y>} by applying
|
||||
* {@code mapFunction} to each value set.
|
||||
*/
|
||||
@MainThread
|
||||
@NonNull
|
||||
public static <X, Y> LiveData<Y> map(
|
||||
@NonNull LiveData<X> source,
|
||||
@NonNull final Function<X, Y> mapFunction) {
|
||||
final MediatorLiveData<Y> result = new MediatorLiveData<>();
|
||||
result.addSource(source, new Observer<X>() {
|
||||
@Override
|
||||
public void onChanged(@Nullable X x) {
|
||||
result.setValue(mapFunction.apply(x));
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@code LiveData} mapped from the input {@code source} {@code LiveData} by applying
|
||||
* {@code switchMapFunction} to each value set on {@code source}.
|
||||
* <p>
|
||||
* The returned {@code LiveData} delegates to the most recent {@code LiveData} created by
|
||||
* calling {@code switchMapFunction} with the most recent value set to {@code source}, without
|
||||
* changing the reference. In this way, {@code switchMapFunction} can change the 'backing'
|
||||
* {@code LiveData} transparently to any observer registered to the {@code LiveData} returned
|
||||
* by {@code switchMap()}.
|
||||
* <p>
|
||||
* Note that when the backing {@code LiveData} is switched, no further values from the older
|
||||
* {@code LiveData} will be set to the output {@code LiveData}. In this way, the method is
|
||||
* analogous to {@link io.reactivex.Observable#switchMap}.
|
||||
* <p>
|
||||
* {@code switchMapFunction} will be executed on the main thread.
|
||||
* <p>
|
||||
* Here is an example class that holds a typed-in name of a user
|
||||
* {@code String} (such as from an {@code EditText}) in a {@link MutableLiveData} and
|
||||
* returns a {@code LiveData} containing a List of {@code User} objects for users that have
|
||||
* that name. It populates that {@code LiveData} by requerying a repository-pattern object
|
||||
* each time the typed name changes.
|
||||
* <p>
|
||||
* This {@code ViewModel} would permit the observing UI to update "live" as the user ID text
|
||||
* changes.
|
||||
*
|
||||
* <pre>
|
||||
* class UserViewModel extends AndroidViewModel {
|
||||
* MutableLiveData<String> nameQueryLiveData = ...
|
||||
*
|
||||
* LiveData<List<String>> getUsersWithNameLiveData() {
|
||||
* return Transformations.switchMap(
|
||||
* nameQueryLiveData,
|
||||
* name -> myDataSource.getUsersWithNameLiveData(name));
|
||||
* }
|
||||
*
|
||||
* void setNameQuery(String name) {
|
||||
* this.nameQueryLiveData.setValue(name);
|
||||
* }
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* @param source the {@code LiveData} to map from
|
||||
* @param switchMapFunction a function to apply to each value set on {@code source} to create a
|
||||
* new delegate {@code LiveData} for the returned one
|
||||
* @param <X> the generic type parameter of {@code source}
|
||||
* @param <Y> the generic type parameter of the returned {@code LiveData}
|
||||
* @return a LiveData mapped from {@code source} to type {@code <Y>} by delegating
|
||||
* to the LiveData returned by applying {@code switchMapFunction} to each
|
||||
* value set
|
||||
*/
|
||||
@MainThread
|
||||
@NonNull
|
||||
public static <X, Y> LiveData<Y> switchMap(
|
||||
@NonNull LiveData<X> source,
|
||||
@NonNull final Function<X, LiveData<Y>> switchMapFunction) {
|
||||
final MediatorLiveData<Y> result = new MediatorLiveData<>();
|
||||
result.addSource(source, new Observer<X>() {
|
||||
LiveData<Y> mSource;
|
||||
|
||||
@Override
|
||||
public void onChanged(@Nullable X x) {
|
||||
LiveData<Y> newLiveData = switchMapFunction.apply(x);
|
||||
if (mSource == newLiveData) {
|
||||
return;
|
||||
}
|
||||
if (mSource != null) {
|
||||
result.removeSource(mSource);
|
||||
}
|
||||
mSource = newLiveData;
|
||||
if (mSource != null) {
|
||||
result.addSource(mSource, new Observer<Y>() {
|
||||
@Override
|
||||
public void onChanged(@Nullable Y y) {
|
||||
result.setValue(y);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link LiveData} object that does not emit a value until the source LiveData
|
||||
* value has been changed. The value is considered changed if {@code equals()} yields
|
||||
* {@code false}.
|
||||
*
|
||||
* @param source the input {@link LiveData}
|
||||
* @param <X> the generic type parameter of {@code source}
|
||||
* @return a new {@link LiveData} of type {@code X}
|
||||
*/
|
||||
@MainThread
|
||||
@NonNull
|
||||
public static <X> LiveData<X> distinctUntilChanged(@NonNull LiveData<X> source) {
|
||||
final MediatorLiveData<X> outputLiveData = new MediatorLiveData<>();
|
||||
outputLiveData.addSource(source, new Observer<X>() {
|
||||
|
||||
boolean mFirstTime = true;
|
||||
|
||||
@Override
|
||||
public void onChanged(X currentValue) {
|
||||
final X previousValue = outputLiveData.getValue();
|
||||
if (mFirstTime
|
||||
|| (previousValue == null && currentValue != null)
|
||||
|| (previousValue != null && !previousValue.equals(currentValue))) {
|
||||
mFirstTime = false;
|
||||
outputLiveData.setValue(currentValue);
|
||||
}
|
||||
}
|
||||
});
|
||||
return outputLiveData;
|
||||
}
|
||||
}
|
||||
@@ -16,19 +16,22 @@
|
||||
|
||||
package androidx.recyclerview.selection;
|
||||
|
||||
import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
|
||||
import static androidx.annotation.RestrictTo.Scope.LIBRARY;
|
||||
import static androidx.annotation.VisibleForTesting.PACKAGE_PRIVATE;
|
||||
|
||||
import android.graphics.Point;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.RestrictTo;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
|
||||
/**
|
||||
* Provides support for auto-scrolling a view.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@RestrictTo(LIBRARY_GROUP)
|
||||
@RestrictTo(LIBRARY)
|
||||
@VisibleForTesting(otherwise = PACKAGE_PRIVATE)
|
||||
public abstract class AutoScroller {
|
||||
|
||||
/**
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
package androidx.recyclerview.selection;
|
||||
|
||||
import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
|
||||
import static androidx.annotation.RestrictTo.Scope.LIBRARY;
|
||||
import static androidx.core.util.Preconditions.checkArgument;
|
||||
import static androidx.core.util.Preconditions.checkState;
|
||||
import static androidx.recyclerview.selection.Shared.DEBUG;
|
||||
@@ -49,7 +49,7 @@ import java.util.Set;
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@RestrictTo(LIBRARY_GROUP)
|
||||
@RestrictTo(LIBRARY)
|
||||
public class DefaultSelectionTracker<K> extends SelectionTracker<K> {
|
||||
|
||||
private static final String TAG = "DefaultSelectionTracker";
|
||||
@@ -296,6 +296,11 @@ public class DefaultSelectionTracker<K> extends SelectionTracker<K> {
|
||||
private void extendRange(int position, @RangeType int type) {
|
||||
checkState(isRangeActive(), "Range start point not set.");
|
||||
|
||||
if (position == RecyclerView.NO_POSITION) {
|
||||
Log.w(TAG, "Invalid position: Cannot extend selection to: " + position);
|
||||
return;
|
||||
}
|
||||
|
||||
mRange.extendRange(position, type);
|
||||
|
||||
// We're being lazy here notifying even when something might not have changed.
|
||||
@@ -343,6 +348,10 @@ public class DefaultSelectionTracker<K> extends SelectionTracker<K> {
|
||||
return mRange != null;
|
||||
}
|
||||
|
||||
boolean isOverlapping(int position, int count) {
|
||||
return (mRange != null && mRange.isOverlapping(position, count));
|
||||
}
|
||||
|
||||
private boolean canSetState(@NonNull K key, boolean nextState) {
|
||||
return mSelectionPredicate.canSetStateForKey(key, nextState);
|
||||
}
|
||||
@@ -573,17 +582,21 @@ public class DefaultSelectionTracker<K> extends SelectionTracker<K> {
|
||||
|
||||
@Override
|
||||
public void onItemRangeInserted(int startPosition, int itemCount) {
|
||||
mSelectionTracker.endRange();
|
||||
if (mSelectionTracker.isOverlapping(startPosition, itemCount))
|
||||
mSelectionTracker.endRange();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemRangeRemoved(int startPosition, int itemCount) {
|
||||
mSelectionTracker.endRange();
|
||||
if (mSelectionTracker.isOverlapping(startPosition, itemCount))
|
||||
mSelectionTracker.endRange();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
|
||||
mSelectionTracker.endRange();
|
||||
if (mSelectionTracker.isOverlapping(fromPosition, itemCount) ||
|
||||
mSelectionTracker.isOverlapping(toPosition, itemCount))
|
||||
mSelectionTracker.endRange();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
package androidx.recyclerview.selection;
|
||||
|
||||
import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
|
||||
import static androidx.annotation.RestrictTo.Scope.LIBRARY;
|
||||
import static androidx.annotation.VisibleForTesting.PACKAGE_PRIVATE;
|
||||
import static androidx.core.util.Preconditions.checkArgument;
|
||||
import static androidx.recyclerview.selection.Shared.VERBOSE;
|
||||
@@ -38,7 +38,7 @@ import androidx.recyclerview.widget.RecyclerView;
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@RestrictTo(LIBRARY_GROUP)
|
||||
@RestrictTo(LIBRARY)
|
||||
@VisibleForTesting(otherwise = PACKAGE_PRIVATE)
|
||||
public class EventBridge {
|
||||
|
||||
|
||||
@@ -94,11 +94,17 @@ final class GestureSelectionHelper implements OnItemTouchListener {
|
||||
@Override
|
||||
/** @hide */
|
||||
public boolean onInterceptTouchEvent(@NonNull RecyclerView unused, @NonNull MotionEvent e) {
|
||||
// TODO(b/132447183): For some reason we're not receiving an ACTION_UP
|
||||
// event after a > long-press NOT followed by a ACTION_MOVE < event.
|
||||
if (mStarted) {
|
||||
handleTouch(e);
|
||||
}
|
||||
|
||||
switch (e.getActionMasked()) {
|
||||
case MotionEvent.ACTION_MOVE:
|
||||
case MotionEvent.ACTION_UP:
|
||||
case MotionEvent.ACTION_CANCEL:
|
||||
return mStarted && mSelectionMgr.isRangeActive();
|
||||
case MotionEvent.ACTION_UP:
|
||||
return mStarted;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -127,32 +133,25 @@ final class GestureSelectionHelper implements OnItemTouchListener {
|
||||
* so this methods return value is irrelevant to it.
|
||||
* </ol>
|
||||
*/
|
||||
private boolean handleTouch(MotionEvent e) {
|
||||
if (!mStarted) {
|
||||
return false;
|
||||
}
|
||||
|
||||
private void handleTouch(MotionEvent e) {
|
||||
if (!mSelectionMgr.isRangeActive()) {
|
||||
Log.e(TAG,
|
||||
"Internal state of GestureSelectionHelper out of sync w/ SelectionTracker "
|
||||
+ "(isRangeActive is false). Ignoring event and resetting state.");
|
||||
endSelection();
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (e.getActionMasked()) {
|
||||
case MotionEvent.ACTION_MOVE:
|
||||
handleMoveEvent(e);
|
||||
return true;
|
||||
break;
|
||||
case MotionEvent.ACTION_UP:
|
||||
handleUpEvent();
|
||||
return true;
|
||||
break;
|
||||
case MotionEvent.ACTION_CANCEL:
|
||||
handleCancelEvent();
|
||||
return true;
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
package androidx.recyclerview.selection;
|
||||
|
||||
import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
|
||||
import static androidx.annotation.RestrictTo.Scope.LIBRARY;
|
||||
|
||||
import android.view.MotionEvent;
|
||||
|
||||
@@ -72,7 +72,7 @@ public abstract class ItemDetailsLookup<K> {
|
||||
* @return true if there is an item w/ a stable ID at the event coordinates.
|
||||
* @hide
|
||||
*/
|
||||
@RestrictTo(LIBRARY_GROUP)
|
||||
@RestrictTo(LIBRARY)
|
||||
protected boolean overItemWithSelectionKey(@NonNull MotionEvent e) {
|
||||
return overItem(e) && hasSelectionKey(getItemDetails(e));
|
||||
}
|
||||
|
||||
@@ -170,6 +170,11 @@ final class Range {
|
||||
mCallbacks.updateForRange(begin, end, selected, type);
|
||||
}
|
||||
|
||||
boolean isOverlapping(int position, int count) {
|
||||
return (position >= mBegin && position <= mEnd) ||
|
||||
(position + count >= mBegin && position + count <= mEnd);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Range{begin=" + mBegin + ", end=" + mEnd + "}";
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
package androidx.recyclerview.selection;
|
||||
|
||||
import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
|
||||
import static androidx.annotation.RestrictTo.Scope.LIBRARY;
|
||||
import static androidx.core.util.Preconditions.checkArgument;
|
||||
|
||||
import android.content.Context;
|
||||
@@ -180,7 +180,7 @@ public abstract class SelectionTracker<K> {
|
||||
public abstract boolean deselect(@NonNull K key);
|
||||
|
||||
/** @hide */
|
||||
@RestrictTo(LIBRARY_GROUP)
|
||||
@RestrictTo(LIBRARY)
|
||||
protected abstract AdapterDataObserver getAdapterDataObserver();
|
||||
|
||||
/**
|
||||
@@ -192,7 +192,7 @@ public abstract class SelectionTracker<K> {
|
||||
* work with the established anchor point to define selection ranges.
|
||||
* @hide
|
||||
*/
|
||||
@RestrictTo(LIBRARY_GROUP)
|
||||
@RestrictTo(LIBRARY)
|
||||
public abstract void startRange(int position);
|
||||
|
||||
/**
|
||||
@@ -208,7 +208,7 @@ public abstract class SelectionTracker<K> {
|
||||
* must have been started by a call to {@link #startRange(int)}.
|
||||
* @hide
|
||||
*/
|
||||
@RestrictTo(LIBRARY_GROUP)
|
||||
@RestrictTo(LIBRARY)
|
||||
public abstract void extendRange(int position);
|
||||
|
||||
/**
|
||||
@@ -217,14 +217,14 @@ public abstract class SelectionTracker<K> {
|
||||
* {@link #mergeProvisionalSelection()} is called first.)
|
||||
* @hide
|
||||
*/
|
||||
@RestrictTo(LIBRARY_GROUP)
|
||||
@RestrictTo(LIBRARY)
|
||||
public abstract void endRange();
|
||||
|
||||
/**
|
||||
* @return Whether or not there is a current range selection active.
|
||||
* @hide
|
||||
*/
|
||||
@RestrictTo(LIBRARY_GROUP)
|
||||
@RestrictTo(LIBRARY)
|
||||
public abstract boolean isRangeActive();
|
||||
|
||||
/**
|
||||
@@ -237,7 +237,7 @@ public abstract class SelectionTracker<K> {
|
||||
* @param position the anchor position. Must already be selected.
|
||||
* @hide
|
||||
*/
|
||||
@RestrictTo(LIBRARY_GROUP)
|
||||
@RestrictTo(LIBRARY)
|
||||
public abstract void anchorRange(int position);
|
||||
|
||||
/**
|
||||
@@ -246,7 +246,7 @@ public abstract class SelectionTracker<K> {
|
||||
* @param position the end point.
|
||||
* @hide
|
||||
*/
|
||||
@RestrictTo(LIBRARY_GROUP)
|
||||
@RestrictTo(LIBRARY)
|
||||
protected abstract void extendProvisionalRange(int position);
|
||||
|
||||
/**
|
||||
@@ -254,14 +254,14 @@ public abstract class SelectionTracker<K> {
|
||||
* @param newSelection
|
||||
* @hide
|
||||
*/
|
||||
@RestrictTo(LIBRARY_GROUP)
|
||||
@RestrictTo(LIBRARY)
|
||||
protected abstract void setProvisionalSelection(@NonNull Set<K> newSelection);
|
||||
|
||||
/**
|
||||
* Clears any existing provisional selection
|
||||
* @hide
|
||||
*/
|
||||
@RestrictTo(LIBRARY_GROUP)
|
||||
@RestrictTo(LIBRARY)
|
||||
protected abstract void clearProvisionalSelection();
|
||||
|
||||
/**
|
||||
@@ -269,7 +269,7 @@ public abstract class SelectionTracker<K> {
|
||||
* provisional selection.
|
||||
* @hide
|
||||
*/
|
||||
@RestrictTo(LIBRARY_GROUP)
|
||||
@RestrictTo(LIBRARY)
|
||||
protected abstract void mergeProvisionalSelection();
|
||||
|
||||
/**
|
||||
@@ -769,7 +769,7 @@ public abstract class SelectionTracker<K> {
|
||||
try {
|
||||
gestureHelper.start();
|
||||
} catch (IllegalStateException ex) {
|
||||
ex.printStackTrace();
|
||||
eu.faircode.email.Log.w(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
294
app/src/main/java/androidx/room/DatabaseConfiguration.java
Normal file
294
app/src/main/java/androidx/room/DatabaseConfiguration.java
Normal file
@@ -0,0 +1,294 @@
|
||||
/*
|
||||
* Copyright (C) 2016 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package androidx.room;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RestrictTo;
|
||||
import androidx.sqlite.db.SupportSQLiteOpenHelper;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
/**
|
||||
* Configuration class for a {@link RoomDatabase}.
|
||||
*/
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
public class DatabaseConfiguration {
|
||||
|
||||
/**
|
||||
* The factory to use to access the database.
|
||||
*/
|
||||
@NonNull
|
||||
public final SupportSQLiteOpenHelper.Factory sqliteOpenHelperFactory;
|
||||
/**
|
||||
* The context to use while connecting to the database.
|
||||
*/
|
||||
@NonNull
|
||||
public final Context context;
|
||||
/**
|
||||
* The name of the database file or null if it is an in-memory database.
|
||||
*/
|
||||
@Nullable
|
||||
public final String name;
|
||||
|
||||
/**
|
||||
* Collection of available migrations.
|
||||
*/
|
||||
@NonNull
|
||||
public final RoomDatabase.MigrationContainer migrationContainer;
|
||||
|
||||
@Nullable
|
||||
public final List<RoomDatabase.Callback> callbacks;
|
||||
|
||||
/**
|
||||
* Whether Room should throw an exception for queries run on the main thread.
|
||||
*/
|
||||
public final boolean allowMainThreadQueries;
|
||||
|
||||
/**
|
||||
* The journal mode for this database.
|
||||
*/
|
||||
public final RoomDatabase.JournalMode journalMode;
|
||||
|
||||
/**
|
||||
* The Executor used to execute asynchronous queries.
|
||||
*/
|
||||
@NonNull
|
||||
public final Executor queryExecutor;
|
||||
|
||||
/**
|
||||
* The Executor used to execute asynchronous transactions.
|
||||
*/
|
||||
@NonNull
|
||||
public final Executor transactionExecutor;
|
||||
|
||||
/**
|
||||
* If true, table invalidation in an instance of {@link RoomDatabase} is broadcast and
|
||||
* synchronized with other instances of the same {@link RoomDatabase} file, including those
|
||||
* in a separate process.
|
||||
*/
|
||||
public final boolean multiInstanceInvalidation;
|
||||
|
||||
/**
|
||||
* If true, Room should crash if a migration is missing.
|
||||
*/
|
||||
public final boolean requireMigration;
|
||||
|
||||
/**
|
||||
* If true, Room should perform a destructive migration when downgrading without an available
|
||||
* migration.
|
||||
*/
|
||||
public final boolean allowDestructiveMigrationOnDowngrade;
|
||||
|
||||
/**
|
||||
* The collection of schema versions from which migrations aren't required.
|
||||
*/
|
||||
private final Set<Integer> mMigrationNotRequiredFrom;
|
||||
|
||||
/**
|
||||
* The assets path to a pre-packaged database to copy from.
|
||||
*/
|
||||
@Nullable
|
||||
public final String copyFromAssetPath;
|
||||
|
||||
/**
|
||||
* The pre-packaged database file to copy from.
|
||||
*/
|
||||
@Nullable
|
||||
public final File copyFromFile;
|
||||
|
||||
|
||||
/**
|
||||
* Creates a database configuration with the given values.
|
||||
*
|
||||
* @deprecated Use {@link #DatabaseConfiguration(Context, String,
|
||||
* SupportSQLiteOpenHelper.Factory, RoomDatabase.MigrationContainer, List, boolean,
|
||||
* RoomDatabase.JournalMode, Executor, Executor, boolean, boolean, boolean, Set, String, File)}
|
||||
*
|
||||
* @param context The application context.
|
||||
* @param name Name of the database, can be null if it is in memory.
|
||||
* @param sqliteOpenHelperFactory The open helper factory to use.
|
||||
* @param migrationContainer The migration container for migrations.
|
||||
* @param callbacks The list of callbacks for database events.
|
||||
* @param allowMainThreadQueries Whether to allow main thread reads/writes or not.
|
||||
* @param journalMode The journal mode. This has to be either TRUNCATE or WRITE_AHEAD_LOGGING.
|
||||
* @param queryExecutor The Executor used to execute asynchronous queries.
|
||||
* @param requireMigration True if Room should require a valid migration if version changes,
|
||||
* instead of recreating the tables.
|
||||
* @param migrationNotRequiredFrom The collection of schema versions from which migrations
|
||||
* aren't required.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@Deprecated
|
||||
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
|
||||
public DatabaseConfiguration(@NonNull Context context, @Nullable String name,
|
||||
@NonNull SupportSQLiteOpenHelper.Factory sqliteOpenHelperFactory,
|
||||
@NonNull RoomDatabase.MigrationContainer migrationContainer,
|
||||
@Nullable List<androidx.room.RoomDatabase.Callback> callbacks,
|
||||
boolean allowMainThreadQueries,
|
||||
RoomDatabase.JournalMode journalMode,
|
||||
@NonNull Executor queryExecutor,
|
||||
boolean requireMigration,
|
||||
@Nullable Set<Integer> migrationNotRequiredFrom) {
|
||||
this(context, name, sqliteOpenHelperFactory, migrationContainer, callbacks,
|
||||
allowMainThreadQueries, journalMode, queryExecutor, queryExecutor, false,
|
||||
requireMigration, false, migrationNotRequiredFrom, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a database configuration with the given values.
|
||||
*
|
||||
* @deprecated Use {@link #DatabaseConfiguration(Context, String,
|
||||
* SupportSQLiteOpenHelper.Factory, RoomDatabase.MigrationContainer, List, boolean,
|
||||
* RoomDatabase.JournalMode, Executor, Executor, boolean, boolean, boolean, Set, String, File)}
|
||||
*
|
||||
* @param context The application context.
|
||||
* @param name Name of the database, can be null if it is in memory.
|
||||
* @param sqliteOpenHelperFactory The open helper factory to use.
|
||||
* @param migrationContainer The migration container for migrations.
|
||||
* @param callbacks The list of callbacks for database events.
|
||||
* @param allowMainThreadQueries Whether to allow main thread reads/writes or not.
|
||||
* @param journalMode The journal mode. This has to be either TRUNCATE or WRITE_AHEAD_LOGGING.
|
||||
* @param queryExecutor The Executor used to execute asynchronous queries.
|
||||
* @param transactionExecutor The Executor used to execute asynchronous transactions.
|
||||
* @param multiInstanceInvalidation True if Room should perform multi-instance invalidation.
|
||||
* @param requireMigration True if Room should require a valid migration if version changes,
|
||||
* @param allowDestructiveMigrationOnDowngrade True if Room should recreate tables if no
|
||||
* migration is supplied during a downgrade.
|
||||
* @param migrationNotRequiredFrom The collection of schema versions from which migrations
|
||||
* aren't required.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@Deprecated
|
||||
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
|
||||
public DatabaseConfiguration(@NonNull Context context, @Nullable String name,
|
||||
@NonNull SupportSQLiteOpenHelper.Factory sqliteOpenHelperFactory,
|
||||
@NonNull RoomDatabase.MigrationContainer migrationContainer,
|
||||
@Nullable List<RoomDatabase.Callback> callbacks,
|
||||
boolean allowMainThreadQueries,
|
||||
RoomDatabase.JournalMode journalMode,
|
||||
@NonNull Executor queryExecutor,
|
||||
@NonNull Executor transactionExecutor,
|
||||
boolean multiInstanceInvalidation,
|
||||
boolean requireMigration,
|
||||
boolean allowDestructiveMigrationOnDowngrade,
|
||||
@Nullable Set<Integer> migrationNotRequiredFrom) {
|
||||
this(context, name, sqliteOpenHelperFactory, migrationContainer, callbacks,
|
||||
allowMainThreadQueries, journalMode, queryExecutor, transactionExecutor,
|
||||
multiInstanceInvalidation, requireMigration, allowDestructiveMigrationOnDowngrade,
|
||||
migrationNotRequiredFrom, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a database configuration with the given values.
|
||||
*
|
||||
* @param context The application context.
|
||||
* @param name Name of the database, can be null if it is in memory.
|
||||
* @param sqliteOpenHelperFactory The open helper factory to use.
|
||||
* @param migrationContainer The migration container for migrations.
|
||||
* @param callbacks The list of callbacks for database events.
|
||||
* @param allowMainThreadQueries Whether to allow main thread reads/writes or not.
|
||||
* @param journalMode The journal mode. This has to be either TRUNCATE or WRITE_AHEAD_LOGGING.
|
||||
* @param queryExecutor The Executor used to execute asynchronous queries.
|
||||
* @param transactionExecutor The Executor used to execute asynchronous transactions.
|
||||
* @param multiInstanceInvalidation True if Room should perform multi-instance invalidation.
|
||||
* @param requireMigration True if Room should require a valid migration if version changes,
|
||||
* @param allowDestructiveMigrationOnDowngrade True if Room should recreate tables if no
|
||||
* migration is supplied during a downgrade.
|
||||
* @param migrationNotRequiredFrom The collection of schema versions from which migrations
|
||||
* aren't required.
|
||||
* @param copyFromAssetPath The assets path to the pre-packaged database.
|
||||
* @param copyFromFile The pre-packaged database file.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
|
||||
public DatabaseConfiguration(@NonNull Context context, @Nullable String name,
|
||||
@NonNull SupportSQLiteOpenHelper.Factory sqliteOpenHelperFactory,
|
||||
@NonNull RoomDatabase.MigrationContainer migrationContainer,
|
||||
@Nullable List<RoomDatabase.Callback> callbacks,
|
||||
boolean allowMainThreadQueries,
|
||||
RoomDatabase.JournalMode journalMode,
|
||||
@NonNull Executor queryExecutor,
|
||||
@NonNull Executor transactionExecutor,
|
||||
boolean multiInstanceInvalidation,
|
||||
boolean requireMigration,
|
||||
boolean allowDestructiveMigrationOnDowngrade,
|
||||
@Nullable Set<Integer> migrationNotRequiredFrom,
|
||||
@Nullable String copyFromAssetPath,
|
||||
@Nullable File copyFromFile) {
|
||||
this.sqliteOpenHelperFactory = sqliteOpenHelperFactory;
|
||||
this.context = context;
|
||||
this.name = name;
|
||||
this.migrationContainer = migrationContainer;
|
||||
this.callbacks = callbacks;
|
||||
this.allowMainThreadQueries = allowMainThreadQueries;
|
||||
this.journalMode = journalMode;
|
||||
this.queryExecutor = queryExecutor;
|
||||
this.transactionExecutor = transactionExecutor;
|
||||
this.multiInstanceInvalidation = multiInstanceInvalidation;
|
||||
this.requireMigration = requireMigration;
|
||||
this.allowDestructiveMigrationOnDowngrade = allowDestructiveMigrationOnDowngrade;
|
||||
this.mMigrationNotRequiredFrom = migrationNotRequiredFrom;
|
||||
this.copyFromAssetPath = copyFromAssetPath;
|
||||
this.copyFromFile = copyFromFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether a migration is required from the specified version.
|
||||
*
|
||||
* @param version The schema version.
|
||||
* @return True if a valid migration is required, false otherwise.
|
||||
*
|
||||
* @deprecated Use {@link #isMigrationRequired(int, int)} which takes
|
||||
* {@link #allowDestructiveMigrationOnDowngrade} into account.
|
||||
*/
|
||||
@Deprecated
|
||||
public boolean isMigrationRequiredFrom(int version) {
|
||||
return isMigrationRequired(version, version + 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether a migration is required between two versions.
|
||||
*
|
||||
* @param fromVersion The old schema version.
|
||||
* @param toVersion The new schema version.
|
||||
* @return True if a valid migration is required, false otherwise.
|
||||
*/
|
||||
public boolean isMigrationRequired(int fromVersion, int toVersion) {
|
||||
// Migrations are not required if its a downgrade AND destructive migration during downgrade
|
||||
// has been allowed.
|
||||
final boolean isDowngrade = fromVersion > toVersion;
|
||||
if (isDowngrade && allowDestructiveMigrationOnDowngrade) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Migrations are required between the two versions if we generally require migrations
|
||||
// AND EITHER there are no exceptions OR the supplied fromVersion is not one of the
|
||||
// exceptions.
|
||||
return requireMigration
|
||||
&& (mMigrationNotRequiredFrom == null
|
||||
|| !mMigrationNotRequiredFrom.contains(fromVersion));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
* Copyright (C) 2016 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package androidx.room;
|
||||
|
||||
import androidx.annotation.RestrictTo;
|
||||
import androidx.sqlite.db.SupportSQLiteStatement;
|
||||
|
||||
/**
|
||||
* Implementations of this class knows how to delete or update a particular entity.
|
||||
* <p>
|
||||
* This is an internal library class and all of its implementations are auto-generated.
|
||||
*
|
||||
* @param <T> The type parameter of the entity to be deleted
|
||||
* @hide
|
||||
*/
|
||||
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
|
||||
@SuppressWarnings({"WeakerAccess", "unused"})
|
||||
public abstract class EntityDeletionOrUpdateAdapter<T> extends SharedSQLiteStatement {
|
||||
/**
|
||||
* Creates a DeletionOrUpdateAdapter that can delete or update the entity type T on the given
|
||||
* database.
|
||||
*
|
||||
* @param database The database to delete / update the item in.
|
||||
*/
|
||||
public EntityDeletionOrUpdateAdapter(RoomDatabase database) {
|
||||
super(database);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the deletion or update query
|
||||
*
|
||||
* @return An SQL query that can delete or update instances of T.
|
||||
*/
|
||||
@Override
|
||||
protected abstract String createQuery();
|
||||
|
||||
/**
|
||||
* Binds the entity into the given statement.
|
||||
*
|
||||
* @param statement The SQLite statement that prepared for the query returned from
|
||||
* createQuery.
|
||||
* @param entity The entity of type T.
|
||||
*/
|
||||
protected abstract void bind(SupportSQLiteStatement statement, T entity);
|
||||
|
||||
/**
|
||||
* Deletes or updates the given entities in the database and returns the affected row count.
|
||||
*
|
||||
* @param entity The entity to delete or update
|
||||
* @return The number of affected rows
|
||||
*/
|
||||
public final int handle(T entity) {
|
||||
final SupportSQLiteStatement stmt = acquire();
|
||||
try {
|
||||
bind(stmt, entity);
|
||||
return stmt.executeUpdateDelete();
|
||||
} finally {
|
||||
release(stmt);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes or updates the given entities in the database and returns the affected row count.
|
||||
*
|
||||
* @param entities Entities to delete or update
|
||||
* @return The number of affected rows
|
||||
*/
|
||||
public final int handleMultiple(Iterable<? extends T> entities) {
|
||||
final SupportSQLiteStatement stmt = acquire();
|
||||
try {
|
||||
int total = 0;
|
||||
for (T entity : entities) {
|
||||
bind(stmt, entity);
|
||||
total += stmt.executeUpdateDelete();
|
||||
}
|
||||
return total;
|
||||
} finally {
|
||||
release(stmt);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes or updates the given entities in the database and returns the affected row count.
|
||||
*
|
||||
* @param entities Entities to delete or update
|
||||
* @return The number of affected rows
|
||||
*/
|
||||
public final int handleMultiple(T[] entities) {
|
||||
final SupportSQLiteStatement stmt = acquire();
|
||||
try {
|
||||
int total = 0;
|
||||
for (T entity : entities) {
|
||||
bind(stmt, entity);
|
||||
total += stmt.executeUpdateDelete();
|
||||
}
|
||||
return total;
|
||||
} finally {
|
||||
release(stmt);
|
||||
}
|
||||
}
|
||||
}
|
||||
251
app/src/main/java/androidx/room/EntityInsertionAdapter.java
Normal file
251
app/src/main/java/androidx/room/EntityInsertionAdapter.java
Normal file
@@ -0,0 +1,251 @@
|
||||
/*
|
||||
* Copyright (C) 2016 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package androidx.room;
|
||||
|
||||
import androidx.annotation.RestrictTo;
|
||||
import androidx.sqlite.db.SupportSQLiteStatement;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Implementations of this class knows how to insert a particular entity.
|
||||
* <p>
|
||||
* This is an internal library class and all of its implementations are auto-generated.
|
||||
*
|
||||
* @param <T> The type parameter of the entity to be inserted
|
||||
* @hide
|
||||
*/
|
||||
@SuppressWarnings({"WeakerAccess", "unused"})
|
||||
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
|
||||
public abstract class EntityInsertionAdapter<T> extends SharedSQLiteStatement {
|
||||
/**
|
||||
* Creates an InsertionAdapter that can insert the entity type T into the given database.
|
||||
*
|
||||
* @param database The database to insert into.
|
||||
*/
|
||||
public EntityInsertionAdapter(RoomDatabase database) {
|
||||
super(database);
|
||||
}
|
||||
|
||||
/**
|
||||
* Binds the entity into the given statement.
|
||||
*
|
||||
* @param statement The SQLite statement that prepared for the query returned from
|
||||
* createInsertQuery.
|
||||
* @param entity The entity of type T.
|
||||
*/
|
||||
protected abstract void bind(SupportSQLiteStatement statement, T entity);
|
||||
|
||||
/**
|
||||
* Inserts the entity into the database.
|
||||
*
|
||||
* @param entity The entity to insert
|
||||
*/
|
||||
public final void insert(T entity) {
|
||||
final SupportSQLiteStatement stmt = acquire();
|
||||
try {
|
||||
bind(stmt, entity);
|
||||
stmt.executeInsert();
|
||||
} finally {
|
||||
release(stmt);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts the given entities into the database.
|
||||
*
|
||||
* @param entities Entities to insert
|
||||
*/
|
||||
public final void insert(T[] entities) {
|
||||
final SupportSQLiteStatement stmt = acquire();
|
||||
try {
|
||||
for (T entity : entities) {
|
||||
bind(stmt, entity);
|
||||
stmt.executeInsert();
|
||||
}
|
||||
} finally {
|
||||
release(stmt);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts the given entities into the database.
|
||||
*
|
||||
* @param entities Entities to insert
|
||||
*/
|
||||
public final void insert(Iterable<? extends T> entities) {
|
||||
final SupportSQLiteStatement stmt = acquire();
|
||||
try {
|
||||
for (T entity : entities) {
|
||||
bind(stmt, entity);
|
||||
stmt.executeInsert();
|
||||
}
|
||||
} finally {
|
||||
release(stmt);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts the given entity into the database and returns the row id.
|
||||
*
|
||||
* @param entity The entity to insert
|
||||
* @return The SQLite row id or -1 if no row is inserted
|
||||
*/
|
||||
public final long insertAndReturnId(T entity) {
|
||||
final SupportSQLiteStatement stmt = acquire();
|
||||
try {
|
||||
bind(stmt, entity);
|
||||
return stmt.executeInsert();
|
||||
} finally {
|
||||
release(stmt);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts the given entities into the database and returns the row ids.
|
||||
*
|
||||
* @param entities Entities to insert
|
||||
* @return The SQLite row ids, for entities that are not inserted the row id returned will be -1
|
||||
*/
|
||||
public final long[] insertAndReturnIdsArray(Collection<? extends T> entities) {
|
||||
final SupportSQLiteStatement stmt = acquire();
|
||||
try {
|
||||
final long[] result = new long[entities.size()];
|
||||
int index = 0;
|
||||
for (T entity : entities) {
|
||||
bind(stmt, entity);
|
||||
result[index] = stmt.executeInsert();
|
||||
index++;
|
||||
}
|
||||
return result;
|
||||
} finally {
|
||||
release(stmt);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts the given entities into the database and returns the row ids.
|
||||
*
|
||||
* @param entities Entities to insert
|
||||
* @return The SQLite row ids, for entities that are not inserted the row id returned will be -1
|
||||
*/
|
||||
public final long[] insertAndReturnIdsArray(T[] entities) {
|
||||
final SupportSQLiteStatement stmt = acquire();
|
||||
try {
|
||||
final long[] result = new long[entities.length];
|
||||
int index = 0;
|
||||
for (T entity : entities) {
|
||||
bind(stmt, entity);
|
||||
result[index] = stmt.executeInsert();
|
||||
index++;
|
||||
}
|
||||
return result;
|
||||
} finally {
|
||||
release(stmt);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts the given entities into the database and returns the row ids.
|
||||
*
|
||||
* @param entities Entities to insert
|
||||
* @return The SQLite row ids, for entities that are not inserted the row id returned will be -1
|
||||
*/
|
||||
public final Long[] insertAndReturnIdsArrayBox(Collection<? extends T> entities) {
|
||||
final SupportSQLiteStatement stmt = acquire();
|
||||
try {
|
||||
final Long[] result = new Long[entities.size()];
|
||||
int index = 0;
|
||||
for (T entity : entities) {
|
||||
bind(stmt, entity);
|
||||
result[index] = stmt.executeInsert();
|
||||
index++;
|
||||
}
|
||||
return result;
|
||||
} finally {
|
||||
release(stmt);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts the given entities into the database and returns the row ids.
|
||||
*
|
||||
* @param entities Entities to insert
|
||||
* @return The SQLite row ids, for entities that are not inserted the row id returned will be -1
|
||||
*/
|
||||
public final Long[] insertAndReturnIdsArrayBox(T[] entities) {
|
||||
final SupportSQLiteStatement stmt = acquire();
|
||||
try {
|
||||
final Long[] result = new Long[entities.length];
|
||||
int index = 0;
|
||||
for (T entity : entities) {
|
||||
bind(stmt, entity);
|
||||
result[index] = stmt.executeInsert();
|
||||
index++;
|
||||
}
|
||||
return result;
|
||||
} finally {
|
||||
release(stmt);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts the given entities into the database and returns the row ids.
|
||||
*
|
||||
* @param entities Entities to insert
|
||||
* @return The SQLite row ids, for entities that are not inserted the row id returned will be -1
|
||||
*/
|
||||
public final List<Long> insertAndReturnIdsList(T[] entities) {
|
||||
final SupportSQLiteStatement stmt = acquire();
|
||||
try {
|
||||
final List<Long> result = new ArrayList<>(entities.length);
|
||||
int index = 0;
|
||||
for (T entity : entities) {
|
||||
bind(stmt, entity);
|
||||
result.add(index, stmt.executeInsert());
|
||||
index++;
|
||||
}
|
||||
return result;
|
||||
} finally {
|
||||
release(stmt);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts the given entities into the database and returns the row ids.
|
||||
*
|
||||
* @param entities Entities to insert
|
||||
* @return The SQLite row ids, for entities that are not inserted the row id returned will be -1
|
||||
*/
|
||||
public final List<Long> insertAndReturnIdsList(Collection<? extends T> entities) {
|
||||
final SupportSQLiteStatement stmt = acquire();
|
||||
try {
|
||||
final List<Long> result = new ArrayList<>(entities.size());
|
||||
int index = 0;
|
||||
for (T entity : entities) {
|
||||
bind(stmt, entity);
|
||||
result.add(index, stmt.executeInsert());
|
||||
index++;
|
||||
}
|
||||
return result;
|
||||
} finally {
|
||||
release(stmt);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package androidx.room;
|
||||
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import androidx.lifecycle.LiveData;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
/**
|
||||
* A helper class that maintains {@link RoomTrackingLiveData} instances for an
|
||||
* {@link InvalidationTracker}.
|
||||
* <p>
|
||||
* We keep a strong reference to active LiveData instances to avoid garbage collection in case
|
||||
* developer does not hold onto the returned LiveData.
|
||||
*/
|
||||
class InvalidationLiveDataContainer {
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
@VisibleForTesting
|
||||
final Set<LiveData> mLiveDataSet = Collections.newSetFromMap(
|
||||
new IdentityHashMap<LiveData, Boolean>()
|
||||
);
|
||||
private final RoomDatabase mDatabase;
|
||||
|
||||
InvalidationLiveDataContainer(RoomDatabase database) {
|
||||
mDatabase = database;
|
||||
}
|
||||
|
||||
<T> LiveData<T> create(String[] tableNames, boolean inTransaction,
|
||||
Callable<T> computeFunction) {
|
||||
return new RoomTrackingLiveData<>(mDatabase, this, inTransaction, computeFunction,
|
||||
tableNames);
|
||||
}
|
||||
|
||||
void onActive(LiveData liveData) {
|
||||
mLiveDataSet.add(liveData);
|
||||
}
|
||||
|
||||
void onInactive(LiveData liveData) {
|
||||
mLiveDataSet.remove(liveData);
|
||||
}
|
||||
}
|
||||
853
app/src/main/java/androidx/room/InvalidationTracker.java
Normal file
853
app/src/main/java/androidx/room/InvalidationTracker.java
Normal file
@@ -0,0 +1,853 @@
|
||||
/*
|
||||
* Copyright (C) 2017 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package androidx.room;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteException;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RestrictTo;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import androidx.annotation.WorkerThread;
|
||||
import androidx.arch.core.internal.SafeIterableMap;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.sqlite.db.SimpleSQLiteQuery;
|
||||
import androidx.sqlite.db.SupportSQLiteDatabase;
|
||||
import androidx.sqlite.db.SupportSQLiteStatement;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
|
||||
/**
|
||||
* InvalidationTracker keeps a list of tables modified by queries and notifies its callbacks about
|
||||
* these tables.
|
||||
*/
|
||||
// Some details on how the InvalidationTracker works:
|
||||
// * An in memory table is created with (table_id, invalidated) table_id is a hardcoded int from
|
||||
// initialization, while invalidated is a boolean bit to indicate if the table has been invalidated.
|
||||
// * ObservedTableTracker tracks list of tables we should be watching (e.g. adding triggers for).
|
||||
// * Before each beginTransaction, RoomDatabase invokes InvalidationTracker to sync trigger states.
|
||||
// * After each endTransaction, RoomDatabase invokes InvalidationTracker to refresh invalidated
|
||||
// tables.
|
||||
// * Each update (write operation) on one of the observed tables triggers an update into the
|
||||
// memory table table, flipping the invalidated flag ON.
|
||||
// * When multi-instance invalidation is turned on, MultiInstanceInvalidationClient will be created.
|
||||
// It works as an Observer, and notifies other instances of table invalidation.
|
||||
public class InvalidationTracker {
|
||||
|
||||
private static final String[] TRIGGERS = new String[]{"UPDATE", "DELETE", "INSERT"};
|
||||
|
||||
private static final String UPDATE_TABLE_NAME = "room_table_modification_log";
|
||||
|
||||
private static final String TABLE_ID_COLUMN_NAME = "table_id";
|
||||
|
||||
private static final String INVALIDATED_COLUMN_NAME = "invalidated";
|
||||
|
||||
private static final String CREATE_TRACKING_TABLE_SQL = "CREATE TEMP TABLE " + UPDATE_TABLE_NAME
|
||||
+ "(" + TABLE_ID_COLUMN_NAME + " INTEGER PRIMARY KEY, "
|
||||
+ INVALIDATED_COLUMN_NAME + " INTEGER NOT NULL DEFAULT 0)";
|
||||
|
||||
@VisibleForTesting
|
||||
static final String RESET_UPDATED_TABLES_SQL = "UPDATE " + UPDATE_TABLE_NAME
|
||||
+ " SET " + INVALIDATED_COLUMN_NAME + " = 0 WHERE " + INVALIDATED_COLUMN_NAME + " = 1 ";
|
||||
|
||||
@VisibleForTesting
|
||||
static final String SELECT_UPDATED_TABLES_SQL = "SELECT * FROM " + UPDATE_TABLE_NAME
|
||||
+ " WHERE " + INVALIDATED_COLUMN_NAME + " = 1;";
|
||||
|
||||
@NonNull
|
||||
@VisibleForTesting
|
||||
final HashMap<String, Integer> mTableIdLookup;
|
||||
final String[] mTableNames;
|
||||
|
||||
@NonNull
|
||||
private Map<String, Set<String>> mViewTables;
|
||||
|
||||
@SuppressWarnings("WeakerAccess") /* synthetic access */
|
||||
final RoomDatabase mDatabase;
|
||||
|
||||
AtomicBoolean mPendingRefresh = new AtomicBoolean(false);
|
||||
|
||||
private volatile boolean mInitialized = false;
|
||||
|
||||
@SuppressWarnings("WeakerAccess") /* synthetic access */
|
||||
volatile SupportSQLiteStatement mCleanupStatement;
|
||||
|
||||
private ObservedTableTracker mObservedTableTracker;
|
||||
|
||||
private final InvalidationLiveDataContainer mInvalidationLiveDataContainer;
|
||||
|
||||
// should be accessed with synchronization only.
|
||||
@VisibleForTesting
|
||||
@SuppressLint("RestrictedApi")
|
||||
final SafeIterableMap<Observer, ObserverWrapper> mObserverMap = new SafeIterableMap<>();
|
||||
|
||||
private MultiInstanceInvalidationClient mMultiInstanceInvalidationClient;
|
||||
|
||||
/**
|
||||
* Used by the generated code.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
|
||||
public InvalidationTracker(RoomDatabase database, String... tableNames) {
|
||||
this(database, new HashMap<String, String>(), Collections.<String, Set<String>>emptyMap(),
|
||||
tableNames);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used by the generated code.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
|
||||
public InvalidationTracker(RoomDatabase database, Map<String, String> shadowTablesMap,
|
||||
Map<String, Set<String>> viewTables, String... tableNames) {
|
||||
mDatabase = database;
|
||||
mObservedTableTracker = new ObservedTableTracker(tableNames.length);
|
||||
mTableIdLookup = new HashMap<>();
|
||||
mViewTables = viewTables;
|
||||
mInvalidationLiveDataContainer = new InvalidationLiveDataContainer(mDatabase);
|
||||
final int size = tableNames.length;
|
||||
mTableNames = new String[size];
|
||||
for (int id = 0; id < size; id++) {
|
||||
final String tableName = tableNames[id].toLowerCase(Locale.US);
|
||||
mTableIdLookup.put(tableName, id);
|
||||
String shadowTableName = shadowTablesMap.get(tableNames[id]);
|
||||
if (shadowTableName != null) {
|
||||
mTableNames[id] = shadowTableName.toLowerCase(Locale.US);
|
||||
} else {
|
||||
mTableNames[id] = tableName;
|
||||
}
|
||||
}
|
||||
// Adjust table id lookup for those tables whose shadow table is another already mapped
|
||||
// table (e.g. external content fts tables).
|
||||
for (Map.Entry<String, String> shadowTableEntry : shadowTablesMap.entrySet()) {
|
||||
String shadowTableName = shadowTableEntry.getValue().toLowerCase(Locale.US);
|
||||
if (mTableIdLookup.containsKey(shadowTableName)) {
|
||||
String tableName = shadowTableEntry.getKey().toLowerCase(Locale.US);
|
||||
mTableIdLookup.put(tableName, mTableIdLookup.get(shadowTableName));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal method to initialize table tracking.
|
||||
* <p>
|
||||
* You should never call this method, it is called by the generated code.
|
||||
*/
|
||||
void internalInit(SupportSQLiteDatabase database) {
|
||||
synchronized (this) {
|
||||
if (mInitialized) {
|
||||
Log.e(Room.LOG_TAG, "Invalidation tracker is initialized twice :/.");
|
||||
return;
|
||||
}
|
||||
|
||||
// These actions are not in a transaction because temp_store is not allowed to be
|
||||
// performed on a transaction, and recursive_triggers is not affected by transactions.
|
||||
database.execSQL("PRAGMA temp_store = MEMORY;");
|
||||
database.execSQL("PRAGMA recursive_triggers='ON';");
|
||||
database.execSQL(CREATE_TRACKING_TABLE_SQL);
|
||||
syncTriggers(database);
|
||||
mCleanupStatement = database.compileStatement(RESET_UPDATED_TABLES_SQL);
|
||||
mInitialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
void startMultiInstanceInvalidation(Context context, String name) {
|
||||
mMultiInstanceInvalidationClient = new MultiInstanceInvalidationClient(context, name, this,
|
||||
mDatabase.getQueryExecutor());
|
||||
}
|
||||
|
||||
void stopMultiInstanceInvalidation() {
|
||||
if (mMultiInstanceInvalidationClient != null) {
|
||||
mMultiInstanceInvalidationClient.stop();
|
||||
mMultiInstanceInvalidationClient = null;
|
||||
}
|
||||
}
|
||||
|
||||
private static void appendTriggerName(StringBuilder builder, String tableName,
|
||||
String triggerType) {
|
||||
builder.append("`")
|
||||
.append("room_table_modification_trigger_")
|
||||
.append(tableName)
|
||||
.append("_")
|
||||
.append(triggerType)
|
||||
.append("`");
|
||||
}
|
||||
|
||||
private void stopTrackingTable(SupportSQLiteDatabase writableDb, int tableId) {
|
||||
final String tableName = mTableNames[tableId];
|
||||
StringBuilder stringBuilder = new StringBuilder();
|
||||
for (String trigger : TRIGGERS) {
|
||||
stringBuilder.setLength(0);
|
||||
stringBuilder.append("DROP TRIGGER IF EXISTS ");
|
||||
appendTriggerName(stringBuilder, tableName, trigger);
|
||||
writableDb.execSQL(stringBuilder.toString());
|
||||
}
|
||||
}
|
||||
|
||||
private void startTrackingTable(SupportSQLiteDatabase writableDb, int tableId) {
|
||||
writableDb.execSQL(
|
||||
"INSERT OR IGNORE INTO " + UPDATE_TABLE_NAME + " VALUES(" + tableId + ", 0)");
|
||||
final String tableName = mTableNames[tableId];
|
||||
StringBuilder stringBuilder = new StringBuilder();
|
||||
for (String trigger : TRIGGERS) {
|
||||
stringBuilder.setLength(0);
|
||||
stringBuilder.append("CREATE TEMP TRIGGER IF NOT EXISTS ");
|
||||
appendTriggerName(stringBuilder, tableName, trigger);
|
||||
stringBuilder.append(" AFTER ")
|
||||
.append(trigger)
|
||||
.append(" ON `")
|
||||
.append(tableName)
|
||||
.append("` BEGIN UPDATE ")
|
||||
.append(UPDATE_TABLE_NAME)
|
||||
.append(" SET ").append(INVALIDATED_COLUMN_NAME).append(" = 1")
|
||||
.append(" WHERE ").append(TABLE_ID_COLUMN_NAME).append(" = ").append(tableId)
|
||||
.append(" AND ").append(INVALIDATED_COLUMN_NAME).append(" = 0")
|
||||
.append("; END");
|
||||
writableDb.execSQL(stringBuilder.toString());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the given observer to the observers list and it will be notified if any table it
|
||||
* observes changes.
|
||||
* <p>
|
||||
* Database changes are pulled on another thread so in some race conditions, the observer might
|
||||
* be invoked for changes that were done before it is added.
|
||||
* <p>
|
||||
* If the observer already exists, this is a no-op call.
|
||||
* <p>
|
||||
* If one of the tables in the Observer does not exist in the database, this method throws an
|
||||
* {@link IllegalArgumentException}.
|
||||
*
|
||||
* @param observer The observer which listens the database for changes.
|
||||
*/
|
||||
@SuppressLint("RestrictedApi")
|
||||
@WorkerThread
|
||||
public void addObserver(@NonNull Observer observer) {
|
||||
final String[] tableNames = resolveViews(observer.mTables);
|
||||
int[] tableIds = new int[tableNames.length];
|
||||
final int size = tableNames.length;
|
||||
|
||||
for (int i = 0; i < size; i++) {
|
||||
Integer tableId = mTableIdLookup.get(tableNames[i].toLowerCase(Locale.US));
|
||||
if (tableId == null) {
|
||||
throw new IllegalArgumentException("There is no table with name " + tableNames[i]);
|
||||
}
|
||||
tableIds[i] = tableId;
|
||||
}
|
||||
ObserverWrapper wrapper = new ObserverWrapper(observer, tableIds, tableNames);
|
||||
ObserverWrapper currentObserver;
|
||||
synchronized (mObserverMap) {
|
||||
currentObserver = mObserverMap.putIfAbsent(observer, wrapper);
|
||||
}
|
||||
if (currentObserver == null && mObservedTableTracker.onAdded(tableIds)) {
|
||||
syncTriggers();
|
||||
}
|
||||
}
|
||||
|
||||
private String[] validateAndResolveTableNames(String[] tableNames) {
|
||||
String[] resolved = resolveViews(tableNames);
|
||||
for (String tableName : resolved) {
|
||||
if (!mTableIdLookup.containsKey(tableName.toLowerCase(Locale.US))) {
|
||||
throw new IllegalArgumentException("There is no table with name " + tableName);
|
||||
}
|
||||
}
|
||||
return resolved;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the list of tables and views into a list of unique tables that are underlying them.
|
||||
*
|
||||
* @param names The names of tables or views.
|
||||
* @return The names of the underlying tables.
|
||||
*/
|
||||
private String[] resolveViews(String[] names) {
|
||||
Set<String> tables = new HashSet<>();
|
||||
for (String name : names) {
|
||||
final String lowercase = name.toLowerCase(Locale.US);
|
||||
if (mViewTables.containsKey(lowercase)) {
|
||||
tables.addAll(mViewTables.get(lowercase));
|
||||
} else {
|
||||
tables.add(name);
|
||||
}
|
||||
}
|
||||
return tables.toArray(new String[tables.size()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an observer but keeps a weak reference back to it.
|
||||
* <p>
|
||||
* Note that you cannot remove this observer once added. It will be automatically removed
|
||||
* when the observer is GC'ed.
|
||||
*
|
||||
* @param observer The observer to which InvalidationTracker will keep a weak reference.
|
||||
* @hide
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
|
||||
public void addWeakObserver(Observer observer) {
|
||||
addObserver(new WeakObserver(this, observer));
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the observer from the observers list.
|
||||
*
|
||||
* @param observer The observer to remove.
|
||||
*/
|
||||
@SuppressLint("RestrictedApi")
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
@WorkerThread
|
||||
public void removeObserver(@NonNull final Observer observer) {
|
||||
ObserverWrapper wrapper;
|
||||
synchronized (mObserverMap) {
|
||||
wrapper = mObserverMap.remove(observer);
|
||||
}
|
||||
if (wrapper != null && mObservedTableTracker.onRemoved(wrapper.mTableIds)) {
|
||||
syncTriggers();
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("WeakerAccess") /* synthetic access */
|
||||
boolean ensureInitialization() {
|
||||
if (!mDatabase.isOpen()) {
|
||||
return false;
|
||||
}
|
||||
if (!mInitialized) {
|
||||
// trigger initialization
|
||||
mDatabase.getOpenHelper().getWritableDatabase();
|
||||
}
|
||||
if (!mInitialized) {
|
||||
Log.e(Room.LOG_TAG, "database is not initialized even though it is open");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
Runnable mRefreshRunnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
final Lock closeLock = mDatabase.getCloseLock();
|
||||
Set<Integer> invalidatedTableIds = null;
|
||||
try {
|
||||
closeLock.lock();
|
||||
|
||||
if (!ensureInitialization()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!mPendingRefresh.compareAndSet(true, false)) {
|
||||
// no pending refresh
|
||||
return;
|
||||
}
|
||||
|
||||
if (mDatabase.inTransaction()) {
|
||||
// current thread is in a transaction. when it ends, it will invoke
|
||||
// refreshRunnable again. mPendingRefresh is left as false on purpose
|
||||
// so that the last transaction can flip it on again.
|
||||
return;
|
||||
}
|
||||
|
||||
if (mDatabase.mWriteAheadLoggingEnabled) {
|
||||
// This transaction has to be on the underlying DB rather than the RoomDatabase
|
||||
// in order to avoid a recursive loop after endTransaction.
|
||||
SupportSQLiteDatabase db = mDatabase.getOpenHelper().getWritableDatabase();
|
||||
db.beginTransaction();
|
||||
try {
|
||||
invalidatedTableIds = checkUpdatedTable();
|
||||
db.setTransactionSuccessful();
|
||||
} finally {
|
||||
db.endTransaction();
|
||||
}
|
||||
} else {
|
||||
invalidatedTableIds = checkUpdatedTable();
|
||||
}
|
||||
} catch (IllegalStateException | SQLiteException exception) {
|
||||
// may happen if db is closed. just log.
|
||||
Log.e(Room.LOG_TAG, "Cannot run invalidation tracker. Is the db closed?",
|
||||
exception);
|
||||
} finally {
|
||||
closeLock.unlock();
|
||||
}
|
||||
if (invalidatedTableIds != null && !invalidatedTableIds.isEmpty()) {
|
||||
synchronized (mObserverMap) {
|
||||
for (Map.Entry<Observer, ObserverWrapper> entry : mObserverMap) {
|
||||
entry.getValue().notifyByTableInvalidStatus(invalidatedTableIds);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Set<Integer> checkUpdatedTable() {
|
||||
HashSet<Integer> invalidatedTableIds = new HashSet<>();
|
||||
Cursor cursor = mDatabase.query(new SimpleSQLiteQuery(SELECT_UPDATED_TABLES_SQL));
|
||||
//noinspection TryFinallyCanBeTryWithResources
|
||||
try {
|
||||
while (cursor.moveToNext()) {
|
||||
final int tableId = cursor.getInt(0);
|
||||
invalidatedTableIds.add(tableId);
|
||||
}
|
||||
} finally {
|
||||
cursor.close();
|
||||
}
|
||||
if (!invalidatedTableIds.isEmpty()) {
|
||||
mCleanupStatement.executeUpdateDelete();
|
||||
}
|
||||
return invalidatedTableIds;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Enqueues a task to refresh the list of updated tables.
|
||||
* <p>
|
||||
* This method is automatically called when {@link RoomDatabase#endTransaction()} is called but
|
||||
* if you have another connection to the database or directly use {@link
|
||||
* SupportSQLiteDatabase}, you may need to call this manually.
|
||||
*/
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
public void refreshVersionsAsync() {
|
||||
// TODO we should consider doing this sync instead of async.
|
||||
if (mPendingRefresh.compareAndSet(false, true)) {
|
||||
mDatabase.getQueryExecutor().execute(mRefreshRunnable);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check versions for tables, and run observers synchronously if tables have been updated.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
|
||||
@WorkerThread
|
||||
public void refreshVersionsSync() {
|
||||
syncTriggers();
|
||||
mRefreshRunnable.run();
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies all the registered {@link Observer}s of table changes.
|
||||
* <p>
|
||||
* This can be used for notifying invalidation that cannot be detected by this
|
||||
* {@link InvalidationTracker}, for example, invalidation from another process.
|
||||
*
|
||||
* @param tables The invalidated tables.
|
||||
* @hide
|
||||
*/
|
||||
@RestrictTo(RestrictTo.Scope.LIBRARY)
|
||||
@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
|
||||
public void notifyObserversByTableNames(String... tables) {
|
||||
synchronized (mObserverMap) {
|
||||
for (Map.Entry<Observer, ObserverWrapper> entry : mObserverMap) {
|
||||
if (!entry.getKey().isRemote()) {
|
||||
entry.getValue().notifyByTableNames(tables);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void syncTriggers(SupportSQLiteDatabase database) {
|
||||
if (database.inTransaction()) {
|
||||
// we won't run this inside another transaction.
|
||||
return;
|
||||
}
|
||||
try {
|
||||
// This method runs in a while loop because while changes are synced to db, another
|
||||
// runnable may be skipped. If we cause it to skip, we need to do its work.
|
||||
while (true) {
|
||||
Lock closeLock = mDatabase.getCloseLock();
|
||||
closeLock.lock();
|
||||
try {
|
||||
// there is a potential race condition where another mSyncTriggers runnable
|
||||
// can start running right after we get the tables list to sync.
|
||||
final int[] tablesToSync = mObservedTableTracker.getTablesToSync();
|
||||
if (tablesToSync == null) {
|
||||
return;
|
||||
}
|
||||
final int limit = tablesToSync.length;
|
||||
database.beginTransaction();
|
||||
try {
|
||||
for (int tableId = 0; tableId < limit; tableId++) {
|
||||
switch (tablesToSync[tableId]) {
|
||||
case ObservedTableTracker.ADD:
|
||||
startTrackingTable(database, tableId);
|
||||
break;
|
||||
case ObservedTableTracker.REMOVE:
|
||||
stopTrackingTable(database, tableId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
database.setTransactionSuccessful();
|
||||
} finally {
|
||||
database.endTransaction();
|
||||
}
|
||||
mObservedTableTracker.onSyncCompleted();
|
||||
} finally {
|
||||
closeLock.unlock();
|
||||
}
|
||||
}
|
||||
} catch (IllegalStateException | SQLiteException exception) {
|
||||
// may happen if db is closed. just log.
|
||||
Log.e(Room.LOG_TAG, "Cannot run invalidation tracker. Is the db closed?",
|
||||
exception);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by RoomDatabase before each beginTransaction call.
|
||||
* <p>
|
||||
* It is important that pending trigger changes are applied to the database before any query
|
||||
* runs. Otherwise, we may miss some changes.
|
||||
* <p>
|
||||
* This api should eventually be public.
|
||||
*/
|
||||
void syncTriggers() {
|
||||
if (!mDatabase.isOpen()) {
|
||||
return;
|
||||
}
|
||||
syncTriggers(mDatabase.getOpenHelper().getWritableDatabase());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a LiveData that computes the given function once and for every other invalidation
|
||||
* of the database.
|
||||
* <p>
|
||||
* Holds a strong reference to the created LiveData as long as it is active.
|
||||
*
|
||||
* @deprecated Use {@link #createLiveData(String[], boolean, Callable)}
|
||||
*
|
||||
* @param computeFunction The function that calculates the value
|
||||
* @param tableNames The list of tables to observe
|
||||
* @param <T> The return type
|
||||
* @return A new LiveData that computes the given function when the given list of tables
|
||||
* invalidates.
|
||||
* @hide
|
||||
*/
|
||||
@Deprecated
|
||||
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
|
||||
public <T> LiveData<T> createLiveData(String[] tableNames, Callable<T> computeFunction) {
|
||||
return createLiveData(tableNames, false, computeFunction);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a LiveData that computes the given function once and for every other invalidation
|
||||
* of the database.
|
||||
* <p>
|
||||
* Holds a strong reference to the created LiveData as long as it is active.
|
||||
*
|
||||
* @param tableNames The list of tables to observe
|
||||
* @param inTransaction True if the computeFunction will be done in a transaction, false
|
||||
* otherwise.
|
||||
* @param computeFunction The function that calculates the value
|
||||
* @param <T> The return type
|
||||
* @return A new LiveData that computes the given function when the given list of tables
|
||||
* invalidates.
|
||||
* @hide
|
||||
*/
|
||||
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
|
||||
public <T> LiveData<T> createLiveData(String[] tableNames, boolean inTransaction,
|
||||
Callable<T> computeFunction) {
|
||||
return mInvalidationLiveDataContainer.create(
|
||||
validateAndResolveTableNames(tableNames), inTransaction, computeFunction);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps an observer and keeps the table information.
|
||||
* <p>
|
||||
* Internally table ids are used which may change from database to database so the table
|
||||
* related information is kept here rather than in the Observer.
|
||||
*/
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
static class ObserverWrapper {
|
||||
final int[] mTableIds;
|
||||
private final String[] mTableNames;
|
||||
final Observer mObserver;
|
||||
private final Set<String> mSingleTableSet;
|
||||
|
||||
ObserverWrapper(Observer observer, int[] tableIds, String[] tableNames) {
|
||||
mObserver = observer;
|
||||
mTableIds = tableIds;
|
||||
mTableNames = tableNames;
|
||||
if (tableIds.length == 1) {
|
||||
HashSet<String> set = new HashSet<>();
|
||||
set.add(mTableNames[0]);
|
||||
mSingleTableSet = Collections.unmodifiableSet(set);
|
||||
} else {
|
||||
mSingleTableSet = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies the underlying {@link #mObserver} if any of the observed tables are invalidated
|
||||
* based on the given invalid status set.
|
||||
*
|
||||
* @param invalidatedTablesIds The table ids of the tables that are invalidated.
|
||||
*/
|
||||
void notifyByTableInvalidStatus(Set<Integer> invalidatedTablesIds) {
|
||||
Set<String> invalidatedTables = null;
|
||||
final int size = mTableIds.length;
|
||||
for (int index = 0; index < size; index++) {
|
||||
final int tableId = mTableIds[index];
|
||||
if (invalidatedTablesIds.contains(tableId)) {
|
||||
if (size == 1) {
|
||||
// Optimization for a single-table observer
|
||||
invalidatedTables = mSingleTableSet;
|
||||
} else {
|
||||
if (invalidatedTables == null) {
|
||||
invalidatedTables = new HashSet<>(size);
|
||||
}
|
||||
invalidatedTables.add(mTableNames[index]);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (invalidatedTables != null) {
|
||||
mObserver.onInvalidated(invalidatedTables);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies the underlying {@link #mObserver} if it observes any of the specified
|
||||
* {@code tables}.
|
||||
*
|
||||
* @param tables The invalidated table names.
|
||||
*/
|
||||
void notifyByTableNames(String[] tables) {
|
||||
Set<String> invalidatedTables = null;
|
||||
if (mTableNames.length == 1) {
|
||||
for (String table : tables) {
|
||||
if (table.equalsIgnoreCase(mTableNames[0])) {
|
||||
// Optimization for a single-table observer
|
||||
invalidatedTables = mSingleTableSet;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
HashSet<String> set = new HashSet<>();
|
||||
for (String table : tables) {
|
||||
for (String ourTable : mTableNames) {
|
||||
if (ourTable.equalsIgnoreCase(table)) {
|
||||
set.add(ourTable);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (set.size() > 0) {
|
||||
invalidatedTables = set;
|
||||
}
|
||||
}
|
||||
if (invalidatedTables != null) {
|
||||
mObserver.onInvalidated(invalidatedTables);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An observer that can listen for changes in the database.
|
||||
*/
|
||||
public abstract static class Observer {
|
||||
final String[] mTables;
|
||||
|
||||
/**
|
||||
* Observes the given list of tables and views.
|
||||
*
|
||||
* @param firstTable The name of the table or view.
|
||||
* @param rest More names of tables or views.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
protected Observer(@NonNull String firstTable, String... rest) {
|
||||
mTables = Arrays.copyOf(rest, rest.length + 1);
|
||||
mTables[rest.length] = firstTable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Observes the given list of tables and views.
|
||||
*
|
||||
* @param tables The list of tables or views to observe for changes.
|
||||
*/
|
||||
public Observer(@NonNull String[] tables) {
|
||||
// copy tables in case user modifies them afterwards
|
||||
mTables = Arrays.copyOf(tables, tables.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when one of the observed tables is invalidated in the database.
|
||||
*
|
||||
* @param tables A set of invalidated tables. This is useful when the observer targets
|
||||
* multiple tables and you want to know which table is invalidated. This will
|
||||
* be names of underlying tables when you are observing views.
|
||||
*/
|
||||
public abstract void onInvalidated(@NonNull Set<String> tables);
|
||||
|
||||
boolean isRemote() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Keeps a list of tables we should observe. Invalidation tracker lazily syncs this list w/
|
||||
* triggers in the database.
|
||||
* <p>
|
||||
* This class is thread safe
|
||||
*/
|
||||
static class ObservedTableTracker {
|
||||
static final int NO_OP = 0; // don't change trigger state for this table
|
||||
static final int ADD = 1; // add triggers for this table
|
||||
static final int REMOVE = 2; // remove triggers for this table
|
||||
|
||||
// number of observers per table
|
||||
final long[] mTableObservers;
|
||||
// trigger state for each table at last sync
|
||||
// this field is updated when syncAndGet is called.
|
||||
final boolean[] mTriggerStates;
|
||||
// when sync is called, this field is returned. It includes actions as ADD, REMOVE, NO_OP
|
||||
final int[] mTriggerStateChanges;
|
||||
|
||||
boolean mNeedsSync;
|
||||
|
||||
/**
|
||||
* After we return non-null value from getTablesToSync, we expect a onSyncCompleted before
|
||||
* returning any non-null value from getTablesToSync.
|
||||
* This allows us to workaround any multi-threaded state syncing issues.
|
||||
*/
|
||||
boolean mPendingSync;
|
||||
|
||||
ObservedTableTracker(int tableCount) {
|
||||
mTableObservers = new long[tableCount];
|
||||
mTriggerStates = new boolean[tableCount];
|
||||
mTriggerStateChanges = new int[tableCount];
|
||||
Arrays.fill(mTableObservers, 0);
|
||||
Arrays.fill(mTriggerStates, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if # of triggers is affected.
|
||||
*/
|
||||
boolean onAdded(int... tableIds) {
|
||||
boolean needTriggerSync = false;
|
||||
synchronized (this) {
|
||||
for (int tableId : tableIds) {
|
||||
final long prevObserverCount = mTableObservers[tableId];
|
||||
mTableObservers[tableId] = prevObserverCount + 1;
|
||||
if (prevObserverCount == 0) {
|
||||
mNeedsSync = true;
|
||||
needTriggerSync = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return needTriggerSync;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if # of triggers is affected.
|
||||
*/
|
||||
boolean onRemoved(int... tableIds) {
|
||||
boolean needTriggerSync = false;
|
||||
synchronized (this) {
|
||||
for (int tableId : tableIds) {
|
||||
final long prevObserverCount = mTableObservers[tableId];
|
||||
mTableObservers[tableId] = prevObserverCount - 1;
|
||||
if (prevObserverCount == 1) {
|
||||
mNeedsSync = true;
|
||||
needTriggerSync = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return needTriggerSync;
|
||||
}
|
||||
|
||||
/**
|
||||
* If this returns non-null, you must call onSyncCompleted.
|
||||
*
|
||||
* @return int[] An int array where the index for each tableId has the action for that
|
||||
* table.
|
||||
*/
|
||||
@Nullable
|
||||
int[] getTablesToSync() {
|
||||
synchronized (this) {
|
||||
if (!mNeedsSync || mPendingSync) {
|
||||
return null;
|
||||
}
|
||||
final int tableCount = mTableObservers.length;
|
||||
for (int i = 0; i < tableCount; i++) {
|
||||
final boolean newState = mTableObservers[i] > 0;
|
||||
if (newState != mTriggerStates[i]) {
|
||||
mTriggerStateChanges[i] = newState ? ADD : REMOVE;
|
||||
} else {
|
||||
mTriggerStateChanges[i] = NO_OP;
|
||||
}
|
||||
mTriggerStates[i] = newState;
|
||||
}
|
||||
mPendingSync = true;
|
||||
mNeedsSync = false;
|
||||
return mTriggerStateChanges;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* if getTablesToSync returned non-null, the called should call onSyncCompleted once it
|
||||
* is done.
|
||||
*/
|
||||
void onSyncCompleted() {
|
||||
synchronized (this) {
|
||||
mPendingSync = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An Observer wrapper that keeps a weak reference to the given object.
|
||||
* <p>
|
||||
* This class will automatically unsubscribe when the wrapped observer goes out of memory.
|
||||
*/
|
||||
static class WeakObserver extends Observer {
|
||||
final InvalidationTracker mTracker;
|
||||
final WeakReference<Observer> mDelegateRef;
|
||||
|
||||
WeakObserver(InvalidationTracker tracker, Observer delegate) {
|
||||
super(delegate.mTables);
|
||||
mTracker = tracker;
|
||||
mDelegateRef = new WeakReference<>(delegate);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInvalidated(@NonNull Set<String> tables) {
|
||||
final Observer observer = mDelegateRef.get();
|
||||
if (observer == null) {
|
||||
mTracker.removeObserver(this);
|
||||
} else {
|
||||
observer.onInvalidated(tables);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,200 @@
|
||||
/*
|
||||
* Copyright 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package androidx.room;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.ServiceConnection;
|
||||
import android.os.IBinder;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
/**
|
||||
* Handles all the communication from {@link RoomDatabase} and {@link InvalidationTracker} to
|
||||
* {@link MultiInstanceInvalidationService}.
|
||||
*/
|
||||
class MultiInstanceInvalidationClient {
|
||||
|
||||
/**
|
||||
* The application context.
|
||||
*/
|
||||
// synthetic access
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
final Context mAppContext;
|
||||
|
||||
/**
|
||||
* The name of the database file.
|
||||
*/
|
||||
// synthetic access
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
final String mName;
|
||||
|
||||
/**
|
||||
* The client ID assigned by {@link MultiInstanceInvalidationService}.
|
||||
*/
|
||||
// synthetic access
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
int mClientId;
|
||||
|
||||
// synthetic access
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
final InvalidationTracker mInvalidationTracker;
|
||||
|
||||
// synthetic access
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
final InvalidationTracker.Observer mObserver;
|
||||
|
||||
// synthetic access
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
@Nullable
|
||||
IMultiInstanceInvalidationService mService;
|
||||
|
||||
// synthetic access
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
final Executor mExecutor;
|
||||
|
||||
// synthetic access
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
final IMultiInstanceInvalidationCallback mCallback =
|
||||
new IMultiInstanceInvalidationCallback.Stub() {
|
||||
@Override
|
||||
public void onInvalidation(final String[] tables) {
|
||||
mExecutor.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mInvalidationTracker.notifyObserversByTableNames(tables);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// synthetic access
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
final AtomicBoolean mStopped = new AtomicBoolean(false);
|
||||
|
||||
// synthetic access
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
final ServiceConnection mServiceConnection = new ServiceConnection() {
|
||||
|
||||
@Override
|
||||
public void onServiceConnected(ComponentName name, IBinder service) {
|
||||
mService = IMultiInstanceInvalidationService.Stub.asInterface(service);
|
||||
mExecutor.execute(mSetUpRunnable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceDisconnected(ComponentName name) {
|
||||
mExecutor.execute(mRemoveObserverRunnable);
|
||||
mService = null;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// synthetic access
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
final Runnable mSetUpRunnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
final IMultiInstanceInvalidationService service = mService;
|
||||
if (service != null) {
|
||||
mClientId = service.registerCallback(mCallback, mName);
|
||||
mInvalidationTracker.addObserver(mObserver);
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
Log.w(Room.LOG_TAG, "Cannot register multi-instance invalidation callback", e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// synthetic access
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
final Runnable mRemoveObserverRunnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mInvalidationTracker.removeObserver(mObserver);
|
||||
}
|
||||
};
|
||||
|
||||
private final Runnable mTearDownRunnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mInvalidationTracker.removeObserver(mObserver);
|
||||
try {
|
||||
final IMultiInstanceInvalidationService service = mService;
|
||||
if (service != null) {
|
||||
service.unregisterCallback(mCallback, mClientId);
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
Log.w(Room.LOG_TAG, "Cannot unregister multi-instance invalidation callback", e);
|
||||
}
|
||||
mAppContext.unbindService(mServiceConnection);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @param context The Context to be used for binding
|
||||
* {@link IMultiInstanceInvalidationService}.
|
||||
* @param name The name of the database file.
|
||||
* @param invalidationTracker The {@link InvalidationTracker}
|
||||
* @param executor The background executor.
|
||||
*/
|
||||
MultiInstanceInvalidationClient(Context context, String name,
|
||||
InvalidationTracker invalidationTracker, Executor executor) {
|
||||
mAppContext = context.getApplicationContext();
|
||||
mName = name;
|
||||
mInvalidationTracker = invalidationTracker;
|
||||
mExecutor = executor;
|
||||
mObserver = new InvalidationTracker.Observer(invalidationTracker.mTableNames) {
|
||||
@Override
|
||||
public void onInvalidated(@NonNull Set<String> tables) {
|
||||
if (mStopped.get()) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
final IMultiInstanceInvalidationService service = mService;
|
||||
if (service != null) {
|
||||
service.broadcastInvalidation(mClientId, tables.toArray(new String[0]));
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
Log.w(Room.LOG_TAG, "Cannot broadcast invalidation", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean isRemote() {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
Intent intent = new Intent(mAppContext, MultiInstanceInvalidationService.class);
|
||||
mAppContext.bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
|
||||
}
|
||||
|
||||
void stop() {
|
||||
if (mStopped.compareAndSet(false, true)) {
|
||||
mExecutor.execute(mTearDownRunnable);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
/*
|
||||
* Copyright 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package androidx.room;
|
||||
|
||||
import android.app.Service;
|
||||
import android.content.Intent;
|
||||
import android.os.IBinder;
|
||||
import android.os.RemoteCallbackList;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RestrictTo;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
/**
|
||||
* A {@link Service} for remote invalidation among multiple {@link InvalidationTracker} instances.
|
||||
* This service runs in the main app process. All the instances of {@link InvalidationTracker}
|
||||
* (potentially in other processes) has to connect to this service.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
|
||||
public class MultiInstanceInvalidationService extends Service {
|
||||
|
||||
// synthetic access
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
int mMaxClientId = 0;
|
||||
|
||||
// synthetic access
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
final HashMap<Integer, String> mClientNames = new HashMap<>();
|
||||
|
||||
// synthetic access
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
final RemoteCallbackList<IMultiInstanceInvalidationCallback> mCallbackList =
|
||||
new RemoteCallbackList<IMultiInstanceInvalidationCallback>() {
|
||||
@Override
|
||||
public void onCallbackDied(IMultiInstanceInvalidationCallback callback,
|
||||
Object cookie) {
|
||||
mClientNames.remove((int) cookie);
|
||||
}
|
||||
};
|
||||
|
||||
private final IMultiInstanceInvalidationService.Stub mBinder =
|
||||
new IMultiInstanceInvalidationService.Stub() {
|
||||
|
||||
// Assigns a client ID to the client.
|
||||
@Override
|
||||
public int registerCallback(IMultiInstanceInvalidationCallback callback,
|
||||
String name) {
|
||||
if (name == null) {
|
||||
return 0;
|
||||
}
|
||||
synchronized (mCallbackList) {
|
||||
int clientId = ++mMaxClientId;
|
||||
// Use the client ID as the RemoteCallbackList cookie.
|
||||
if (mCallbackList.register(callback, clientId)) {
|
||||
mClientNames.put(clientId, name);
|
||||
return clientId;
|
||||
} else {
|
||||
--mMaxClientId;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Explicitly removes the client.
|
||||
// The client can die without calling this. In that case, mCallbackList
|
||||
// .onCallbackDied() can take care of removal.
|
||||
@Override
|
||||
public void unregisterCallback(IMultiInstanceInvalidationCallback callback,
|
||||
int clientId) {
|
||||
synchronized (mCallbackList) {
|
||||
mCallbackList.unregister(callback);
|
||||
mClientNames.remove(clientId);
|
||||
}
|
||||
}
|
||||
|
||||
// Broadcasts table invalidation to other instances of the same database file.
|
||||
// The broadcast is not sent to the caller itself.
|
||||
@Override
|
||||
public void broadcastInvalidation(int clientId, String[] tables) {
|
||||
synchronized (mCallbackList) {
|
||||
String name = mClientNames.get(clientId);
|
||||
if (name == null) {
|
||||
Log.w(Room.LOG_TAG, "Remote invalidation client ID not registered");
|
||||
return;
|
||||
}
|
||||
int count = mCallbackList.beginBroadcast();
|
||||
try {
|
||||
for (int i = 0; i < count; i++) {
|
||||
int targetClientId = (int) mCallbackList.getBroadcastCookie(i);
|
||||
String targetName = mClientNames.get(targetClientId);
|
||||
if (clientId == targetClientId // This is the caller itself.
|
||||
|| !name.equals(targetName)) { // Not the same file.
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
IMultiInstanceInvalidationCallback callback =
|
||||
mCallbackList.getBroadcastItem(i);
|
||||
callback.onInvalidation(tables);
|
||||
} catch (RemoteException e) {
|
||||
Log.w(Room.LOG_TAG, "Error invoking a remote callback", e);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
mCallbackList.finishBroadcast();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
return mBinder;
|
||||
}
|
||||
}
|
||||
109
app/src/main/java/androidx/room/Room.java
Normal file
109
app/src/main/java/androidx/room/Room.java
Normal file
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* Copyright (C) 2016 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package androidx.room;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
/**
|
||||
* Utility class for Room.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public class Room {
|
||||
static final String LOG_TAG = "ROOM";
|
||||
/**
|
||||
* The master table where room keeps its metadata information.
|
||||
*/
|
||||
public static final String MASTER_TABLE_NAME = RoomMasterTable.TABLE_NAME;
|
||||
private static final String CURSOR_CONV_SUFFIX = "_CursorConverter";
|
||||
|
||||
/**
|
||||
* Creates a RoomDatabase.Builder for a persistent database. Once a database is built, you
|
||||
* should keep a reference to it and re-use it.
|
||||
*
|
||||
* @param context The context for the database. This is usually the Application context.
|
||||
* @param klass The abstract class which is annotated with {@link Database} and extends
|
||||
* {@link RoomDatabase}.
|
||||
* @param name The name of the database file.
|
||||
* @param <T> The type of the database class.
|
||||
* @return A {@code RoomDatabaseBuilder<T>} which you can use to create the database.
|
||||
*/
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
@NonNull
|
||||
public static <T extends RoomDatabase> RoomDatabase.Builder<T> databaseBuilder(
|
||||
@NonNull Context context, @NonNull Class<T> klass, @NonNull String name) {
|
||||
//noinspection ConstantConditions
|
||||
if (name == null || name.trim().length() == 0) {
|
||||
throw new IllegalArgumentException("Cannot build a database with null or empty name."
|
||||
+ " If you are trying to create an in memory database, use Room"
|
||||
+ ".inMemoryDatabaseBuilder");
|
||||
}
|
||||
return new RoomDatabase.Builder<>(context, klass, name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a RoomDatabase.Builder for an in memory database. Information stored in an in memory
|
||||
* database disappears when the process is killed.
|
||||
* Once a database is built, you should keep a reference to it and re-use it.
|
||||
*
|
||||
* @param context The context for the database. This is usually the Application context.
|
||||
* @param klass The abstract class which is annotated with {@link Database} and extends
|
||||
* {@link RoomDatabase}.
|
||||
* @param <T> The type of the database class.
|
||||
* @return A {@code RoomDatabaseBuilder<T>} which you can use to create the database.
|
||||
*/
|
||||
@NonNull
|
||||
public static <T extends RoomDatabase> RoomDatabase.Builder<T> inMemoryDatabaseBuilder(
|
||||
@NonNull Context context, @NonNull Class<T> klass) {
|
||||
return new RoomDatabase.Builder<>(context, klass, null);
|
||||
}
|
||||
|
||||
@SuppressWarnings({"TypeParameterUnusedInFormals", "ClassNewInstance"})
|
||||
@NonNull
|
||||
static <T, C> T getGeneratedImplementation(Class<C> klass, String suffix) {
|
||||
final String fullPackage = klass.getPackage().getName();
|
||||
String name = klass.getCanonicalName();
|
||||
final String postPackageName = fullPackage.isEmpty()
|
||||
? name
|
||||
: (name.substring(fullPackage.length() + 1));
|
||||
final String implName = postPackageName.replace('.', '_') + suffix;
|
||||
//noinspection TryWithIdenticalCatches
|
||||
try {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
final Class<T> aClass = (Class<T>) Class.forName(
|
||||
fullPackage.isEmpty() ? implName : fullPackage + "." + implName);
|
||||
return aClass.newInstance();
|
||||
} catch (ClassNotFoundException e) {
|
||||
throw new RuntimeException("cannot find implementation for "
|
||||
+ klass.getCanonicalName() + ". " + implName + " does not exist");
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException("Cannot access the constructor"
|
||||
+ klass.getCanonicalName());
|
||||
} catch (InstantiationException e) {
|
||||
throw new RuntimeException("Failed to create an instance of "
|
||||
+ klass.getCanonicalName());
|
||||
}
|
||||
}
|
||||
|
||||
/** @deprecated This type should not be instantiated as it contains only static methods. */
|
||||
@Deprecated
|
||||
@SuppressWarnings("PrivateConstructorForUtilityClass")
|
||||
public Room() {
|
||||
}
|
||||
}
|
||||
1065
app/src/main/java/androidx/room/RoomDatabase.java
Normal file
1065
app/src/main/java/androidx/room/RoomDatabase.java
Normal file
File diff suppressed because it is too large
Load Diff
277
app/src/main/java/androidx/room/RoomOpenHelper.java
Normal file
277
app/src/main/java/androidx/room/RoomOpenHelper.java
Normal file
@@ -0,0 +1,277 @@
|
||||
/*
|
||||
* Copyright (C) 2017 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package androidx.room;
|
||||
|
||||
import android.database.Cursor;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RestrictTo;
|
||||
import androidx.room.migration.Migration;
|
||||
import androidx.sqlite.db.SimpleSQLiteQuery;
|
||||
import androidx.sqlite.db.SupportSQLiteDatabase;
|
||||
import androidx.sqlite.db.SupportSQLiteOpenHelper;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* An open helper that holds a reference to the configuration until the database is opened.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
|
||||
public class RoomOpenHelper extends SupportSQLiteOpenHelper.Callback {
|
||||
@Nullable
|
||||
private DatabaseConfiguration mConfiguration;
|
||||
@NonNull
|
||||
private final Delegate mDelegate;
|
||||
@NonNull
|
||||
private final String mIdentityHash;
|
||||
/**
|
||||
* Room v1 had a bug where the hash was not consistent if fields are reordered.
|
||||
* The new has fixes it but we still need to accept the legacy hash.
|
||||
*/
|
||||
@NonNull // b/64290754
|
||||
private final String mLegacyHash;
|
||||
|
||||
public RoomOpenHelper(@NonNull DatabaseConfiguration configuration, @NonNull Delegate delegate,
|
||||
@NonNull String identityHash, @NonNull String legacyHash) {
|
||||
super(delegate.version);
|
||||
mConfiguration = configuration;
|
||||
mDelegate = delegate;
|
||||
mIdentityHash = identityHash;
|
||||
mLegacyHash = legacyHash;
|
||||
}
|
||||
|
||||
public RoomOpenHelper(@NonNull DatabaseConfiguration configuration, @NonNull Delegate delegate,
|
||||
@NonNull String legacyHash) {
|
||||
this(configuration, delegate, "", legacyHash);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConfigure(SupportSQLiteDatabase db) {
|
||||
super.onConfigure(db);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(SupportSQLiteDatabase db) {
|
||||
boolean isEmptyDatabase = hasEmptySchema(db);
|
||||
mDelegate.createAllTables(db);
|
||||
if (!isEmptyDatabase) {
|
||||
// A 0 version pre-populated database goes through the create path because the
|
||||
// framework's SQLiteOpenHelper thinks the database was just created from scratch. If we
|
||||
// find the database not to be empty, then it is a pre-populated, we must validate it to
|
||||
// see if its suitable for usage.
|
||||
ValidationResult result = mDelegate.onValidateSchema(db);
|
||||
if (!result.isValid) {
|
||||
throw new IllegalStateException("Pre-packaged database has an invalid schema: "
|
||||
+ result.expectedFoundMsg);
|
||||
}
|
||||
}
|
||||
updateIdentity(db);
|
||||
mDelegate.onCreate(db);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpgrade(SupportSQLiteDatabase db, int oldVersion, int newVersion) {
|
||||
boolean migrated = false;
|
||||
if (mConfiguration != null) {
|
||||
List<Migration> migrations = mConfiguration.migrationContainer.findMigrationPath(
|
||||
oldVersion, newVersion);
|
||||
if (migrations != null) {
|
||||
mDelegate.onPreMigrate(db);
|
||||
for (Migration migration : migrations) {
|
||||
migration.migrate(db);
|
||||
}
|
||||
ValidationResult result = mDelegate.onValidateSchema(db);
|
||||
if (!result.isValid) {
|
||||
throw new IllegalStateException("Migration didn't properly handle: "
|
||||
+ result.expectedFoundMsg);
|
||||
}
|
||||
mDelegate.onPostMigrate(db);
|
||||
updateIdentity(db);
|
||||
migrated = true;
|
||||
}
|
||||
}
|
||||
if (!migrated) {
|
||||
if (mConfiguration != null
|
||||
&& !mConfiguration.isMigrationRequired(oldVersion, newVersion)) {
|
||||
mDelegate.dropAllTables(db);
|
||||
mDelegate.createAllTables(db);
|
||||
} else {
|
||||
throw new IllegalStateException("A migration from " + oldVersion + " to "
|
||||
+ newVersion + " was required but not found. Please provide the "
|
||||
+ "necessary Migration path via "
|
||||
+ "RoomDatabase.Builder.addMigration(Migration ...) or allow for "
|
||||
+ "destructive migrations via one of the "
|
||||
+ "RoomDatabase.Builder.fallbackToDestructiveMigration* methods.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDowngrade(SupportSQLiteDatabase db, int oldVersion, int newVersion) {
|
||||
onUpgrade(db, oldVersion, newVersion);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOpen(SupportSQLiteDatabase db) {
|
||||
super.onOpen(db);
|
||||
checkIdentity(db);
|
||||
mDelegate.onOpen(db);
|
||||
// there might be too many configurations etc, just clear it.
|
||||
mConfiguration = null;
|
||||
}
|
||||
|
||||
private void checkIdentity(SupportSQLiteDatabase db) {
|
||||
if (hasRoomMasterTable(db)) {
|
||||
String identityHash = null;
|
||||
Cursor cursor = db.query(new SimpleSQLiteQuery(RoomMasterTable.READ_QUERY));
|
||||
//noinspection TryFinallyCanBeTryWithResources
|
||||
try {
|
||||
if (cursor.moveToFirst()) {
|
||||
identityHash = cursor.getString(0);
|
||||
}
|
||||
} finally {
|
||||
cursor.close();
|
||||
}
|
||||
if (!mIdentityHash.equals(identityHash) && !mLegacyHash.equals(identityHash)) {
|
||||
throw new IllegalStateException("Room cannot verify the data integrity. Looks like"
|
||||
+ " you've changed schema but forgot to update the version number. You can"
|
||||
+ " simply fix this by increasing the version number.");
|
||||
}
|
||||
} else {
|
||||
// No room_master_table, this might an a pre-populated DB, we must validate to see if
|
||||
// its suitable for usage.
|
||||
ValidationResult result = mDelegate.onValidateSchema(db);
|
||||
if (!result.isValid) {
|
||||
throw new IllegalStateException("Pre-packaged database has an invalid schema: "
|
||||
+ result.expectedFoundMsg);
|
||||
}
|
||||
mDelegate.onPostMigrate(db);
|
||||
updateIdentity(db);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateIdentity(SupportSQLiteDatabase db) {
|
||||
createMasterTableIfNotExists(db);
|
||||
db.execSQL(RoomMasterTable.createInsertQuery(mIdentityHash));
|
||||
}
|
||||
|
||||
private void createMasterTableIfNotExists(SupportSQLiteDatabase db) {
|
||||
db.execSQL(RoomMasterTable.CREATE_QUERY);
|
||||
}
|
||||
|
||||
private static boolean hasRoomMasterTable(SupportSQLiteDatabase db) {
|
||||
Cursor cursor = db.query("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name='"
|
||||
+ RoomMasterTable.TABLE_NAME + "'");
|
||||
//noinspection TryFinallyCanBeTryWithResources
|
||||
try {
|
||||
return cursor.moveToFirst() && cursor.getInt(0) != 0;
|
||||
} finally {
|
||||
cursor.close();
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean hasEmptySchema(SupportSQLiteDatabase db) {
|
||||
Cursor cursor = db.query(
|
||||
"SELECT count(*) FROM sqlite_master WHERE name != 'android_metadata'");
|
||||
//noinspection TryFinallyCanBeTryWithResources
|
||||
try {
|
||||
return cursor.moveToFirst() && cursor.getInt(0) == 0;
|
||||
} finally {
|
||||
cursor.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
|
||||
public abstract static class Delegate {
|
||||
public final int version;
|
||||
|
||||
public Delegate(int version) {
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
protected abstract void dropAllTables(SupportSQLiteDatabase database);
|
||||
|
||||
protected abstract void createAllTables(SupportSQLiteDatabase database);
|
||||
|
||||
protected abstract void onOpen(SupportSQLiteDatabase database);
|
||||
|
||||
protected abstract void onCreate(SupportSQLiteDatabase database);
|
||||
|
||||
/**
|
||||
* Called after a migration run to validate database integrity.
|
||||
*
|
||||
* @param db The SQLite database.
|
||||
*
|
||||
* @deprecated Use {@link #onValidateSchema(SupportSQLiteDatabase)}
|
||||
*/
|
||||
@Deprecated
|
||||
protected void validateMigration(SupportSQLiteDatabase db) {
|
||||
throw new UnsupportedOperationException("validateMigration is deprecated");
|
||||
}
|
||||
|
||||
/**
|
||||
* Called after a migration run or pre-package database copy to validate database integrity.
|
||||
*
|
||||
* @param db The SQLite database.
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
@NonNull
|
||||
protected ValidationResult onValidateSchema(@NonNull SupportSQLiteDatabase db) {
|
||||
validateMigration(db);
|
||||
return new ValidationResult(true, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called before migrations execute to perform preliminary work.
|
||||
* @param database The SQLite database.
|
||||
*/
|
||||
protected void onPreMigrate(SupportSQLiteDatabase database) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Called after migrations execute to perform additional work.
|
||||
* @param database The SQLite database.
|
||||
*/
|
||||
protected void onPostMigrate(SupportSQLiteDatabase database) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
|
||||
public static class ValidationResult {
|
||||
|
||||
public final boolean isValid;
|
||||
@Nullable
|
||||
public final String expectedFoundMsg;
|
||||
|
||||
public ValidationResult(boolean isValid, @Nullable String expectedFoundMsg) {
|
||||
this.isValid = isValid;
|
||||
this.expectedFoundMsg = expectedFoundMsg;
|
||||
}
|
||||
}
|
||||
}
|
||||
299
app/src/main/java/androidx/room/RoomSQLiteQuery.java
Normal file
299
app/src/main/java/androidx/room/RoomSQLiteQuery.java
Normal file
@@ -0,0 +1,299 @@
|
||||
/*
|
||||
* Copyright (C) 2017 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package androidx.room;
|
||||
|
||||
import androidx.annotation.IntDef;
|
||||
import androidx.annotation.RestrictTo;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import androidx.sqlite.db.SupportSQLiteProgram;
|
||||
import androidx.sqlite.db.SupportSQLiteQuery;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
|
||||
/**
|
||||
* This class is used as an intermediate place to keep binding arguments so that we can run
|
||||
* Cursor queries with correct types rather than passing everything as a string.
|
||||
* <p>
|
||||
* Because it is relatively a big object, they are pooled and must be released after each use.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
|
||||
public class RoomSQLiteQuery implements SupportSQLiteQuery, SupportSQLiteProgram {
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
@VisibleForTesting
|
||||
// Maximum number of queries we'll keep cached.
|
||||
static final int POOL_LIMIT = 15;
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
@VisibleForTesting
|
||||
// Once we hit POOL_LIMIT, we'll bring the pool size back to the desired number. We always
|
||||
// clear the bigger queries (# of arguments).
|
||||
static final int DESIRED_POOL_SIZE = 10;
|
||||
private volatile String mQuery;
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
@VisibleForTesting
|
||||
final long[] mLongBindings;
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
@VisibleForTesting
|
||||
final double[] mDoubleBindings;
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
@VisibleForTesting
|
||||
final String[] mStringBindings;
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
@VisibleForTesting
|
||||
final byte[][] mBlobBindings;
|
||||
|
||||
@Binding
|
||||
private final int[] mBindingTypes;
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
@VisibleForTesting
|
||||
final int mCapacity;
|
||||
// number of arguments in the query
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
@VisibleForTesting
|
||||
int mArgCount;
|
||||
|
||||
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
@VisibleForTesting
|
||||
static final TreeMap<Integer, RoomSQLiteQuery> sQueryPool = new TreeMap<>();
|
||||
|
||||
/**
|
||||
* Copies the given SupportSQLiteQuery and converts it into RoomSQLiteQuery.
|
||||
*
|
||||
* @param supportSQLiteQuery The query to copy from
|
||||
* @return A new query copied from the provided one.
|
||||
*/
|
||||
public static RoomSQLiteQuery copyFrom(SupportSQLiteQuery supportSQLiteQuery) {
|
||||
final RoomSQLiteQuery query = RoomSQLiteQuery.acquire(
|
||||
supportSQLiteQuery.getSql(),
|
||||
supportSQLiteQuery.getArgCount());
|
||||
supportSQLiteQuery.bindTo(new SupportSQLiteProgram() {
|
||||
@Override
|
||||
public void bindNull(int index) {
|
||||
query.bindNull(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindLong(int index, long value) {
|
||||
query.bindLong(index, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindDouble(int index, double value) {
|
||||
query.bindDouble(index, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindString(int index, String value) {
|
||||
query.bindString(index, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindBlob(int index, byte[] value) {
|
||||
query.bindBlob(index, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearBindings() {
|
||||
query.clearBindings();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
// ignored.
|
||||
}
|
||||
});
|
||||
return query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new RoomSQLiteQuery that can accept the given number of arguments and holds the
|
||||
* given query.
|
||||
*
|
||||
* @param query The query to prepare
|
||||
* @param argumentCount The number of query arguments
|
||||
* @return A RoomSQLiteQuery that holds the given query and has space for the given number of
|
||||
* arguments.
|
||||
*/
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
public static RoomSQLiteQuery acquire(String query, int argumentCount) {
|
||||
synchronized (sQueryPool) {
|
||||
final Map.Entry<Integer, RoomSQLiteQuery> entry =
|
||||
sQueryPool.ceilingEntry(argumentCount);
|
||||
if (entry != null) {
|
||||
sQueryPool.remove(entry.getKey());
|
||||
final RoomSQLiteQuery sqliteQuery = entry.getValue();
|
||||
sqliteQuery.init(query, argumentCount);
|
||||
return sqliteQuery;
|
||||
}
|
||||
}
|
||||
RoomSQLiteQuery sqLiteQuery = new RoomSQLiteQuery(argumentCount);
|
||||
sqLiteQuery.init(query, argumentCount);
|
||||
return sqLiteQuery;
|
||||
}
|
||||
|
||||
private RoomSQLiteQuery(int capacity) {
|
||||
mCapacity = capacity;
|
||||
// because, 1 based indices... we don't want to offsets everything with 1 all the time.
|
||||
int limit = capacity + 1;
|
||||
//noinspection WrongConstant
|
||||
mBindingTypes = new int[limit];
|
||||
mLongBindings = new long[limit];
|
||||
mDoubleBindings = new double[limit];
|
||||
mStringBindings = new String[limit];
|
||||
mBlobBindings = new byte[limit][];
|
||||
}
|
||||
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
void init(String query, int argCount) {
|
||||
mQuery = query;
|
||||
mArgCount = argCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Releases the query back to the pool.
|
||||
* <p>
|
||||
* After released, the statement might be returned when {@link #acquire(String, int)} is called
|
||||
* so you should never re-use it after releasing.
|
||||
*/
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
public void release() {
|
||||
synchronized (sQueryPool) {
|
||||
sQueryPool.put(mCapacity, this);
|
||||
prunePoolLocked();
|
||||
}
|
||||
}
|
||||
|
||||
private static void prunePoolLocked() {
|
||||
if (sQueryPool.size() > POOL_LIMIT) {
|
||||
int toBeRemoved = sQueryPool.size() - DESIRED_POOL_SIZE;
|
||||
final Iterator<Integer> iterator = sQueryPool.descendingKeySet().iterator();
|
||||
while (toBeRemoved-- > 0) {
|
||||
iterator.next();
|
||||
iterator.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSql() {
|
||||
return mQuery;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getArgCount() {
|
||||
return mArgCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindTo(SupportSQLiteProgram program) {
|
||||
for (int index = 1; index <= mArgCount; index++) {
|
||||
switch (mBindingTypes[index]) {
|
||||
case NULL:
|
||||
program.bindNull(index);
|
||||
break;
|
||||
case LONG:
|
||||
program.bindLong(index, mLongBindings[index]);
|
||||
break;
|
||||
case DOUBLE:
|
||||
program.bindDouble(index, mDoubleBindings[index]);
|
||||
break;
|
||||
case STRING:
|
||||
program.bindString(index, mStringBindings[index]);
|
||||
break;
|
||||
case BLOB:
|
||||
program.bindBlob(index, mBlobBindings[index]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindNull(int index) {
|
||||
mBindingTypes[index] = NULL;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindLong(int index, long value) {
|
||||
mBindingTypes[index] = LONG;
|
||||
mLongBindings[index] = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindDouble(int index, double value) {
|
||||
mBindingTypes[index] = DOUBLE;
|
||||
mDoubleBindings[index] = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindString(int index, String value) {
|
||||
mBindingTypes[index] = STRING;
|
||||
mStringBindings[index] = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindBlob(int index, byte[] value) {
|
||||
mBindingTypes[index] = BLOB;
|
||||
mBlobBindings[index] = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
// no-op. not calling release because it is internal API.
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies arguments from another RoomSQLiteQuery into this query.
|
||||
*
|
||||
* @param other The other query, which holds the arguments to be copied.
|
||||
*/
|
||||
public void copyArgumentsFrom(RoomSQLiteQuery other) {
|
||||
int argCount = other.getArgCount() + 1; // +1 for the binding offsets
|
||||
System.arraycopy(other.mBindingTypes, 0, mBindingTypes, 0, argCount);
|
||||
System.arraycopy(other.mLongBindings, 0, mLongBindings, 0, argCount);
|
||||
System.arraycopy(other.mStringBindings, 0, mStringBindings, 0, argCount);
|
||||
System.arraycopy(other.mBlobBindings, 0, mBlobBindings, 0, argCount);
|
||||
System.arraycopy(other.mDoubleBindings, 0, mDoubleBindings, 0, argCount);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearBindings() {
|
||||
Arrays.fill(mBindingTypes, NULL);
|
||||
Arrays.fill(mStringBindings, null);
|
||||
Arrays.fill(mBlobBindings, null);
|
||||
mQuery = null;
|
||||
// no need to clear others
|
||||
}
|
||||
|
||||
private static final int NULL = 1;
|
||||
private static final int LONG = 2;
|
||||
private static final int DOUBLE = 3;
|
||||
private static final int STRING = 4;
|
||||
private static final int BLOB = 5;
|
||||
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@IntDef({NULL, LONG, DOUBLE, STRING, BLOB})
|
||||
@interface Binding {
|
||||
}
|
||||
}
|
||||
175
app/src/main/java/androidx/room/RoomTrackingLiveData.java
Normal file
175
app/src/main/java/androidx/room/RoomTrackingLiveData.java
Normal file
@@ -0,0 +1,175 @@
|
||||
/*
|
||||
* Copyright 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package androidx.room;
|
||||
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
|
||||
import androidx.annotation.MainThread;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.WorkerThread;
|
||||
import androidx.arch.core.executor.ArchTaskExecutor;
|
||||
import androidx.lifecycle.LiveData;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
/**
|
||||
* A LiveData implementation that closely works with {@link InvalidationTracker} to implement
|
||||
* database drive {@link androidx.lifecycle.LiveData} queries that are strongly hold as long
|
||||
* as they are active.
|
||||
* <p>
|
||||
* We need this extra handling for {@link androidx.lifecycle.LiveData} because when they are
|
||||
* observed forever, there is no {@link androidx.lifecycle.Lifecycle} that will keep them in
|
||||
* memory but they should stay. We cannot add-remove observer in {@link LiveData#onActive()},
|
||||
* {@link LiveData#onInactive()} because that would mean missing changes in between or doing an
|
||||
* extra query on every UI rotation.
|
||||
* <p>
|
||||
* This {@link LiveData} keeps a weak observer to the {@link InvalidationTracker} but it is hold
|
||||
* strongly by the {@link InvalidationTracker} as long as it is active.
|
||||
*/
|
||||
class RoomTrackingLiveData<T> extends LiveData<T> {
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
final RoomDatabase mDatabase;
|
||||
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
final boolean mInTransaction;
|
||||
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
final Callable<T> mComputeFunction;
|
||||
|
||||
private final InvalidationLiveDataContainer mContainer;
|
||||
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
final InvalidationTracker.Observer mObserver;
|
||||
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
final AtomicBoolean mInvalid = new AtomicBoolean(true);
|
||||
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
final AtomicBoolean mComputing = new AtomicBoolean(false);
|
||||
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
final AtomicBoolean mRegisteredObserver = new AtomicBoolean(false);
|
||||
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
final Runnable mRefreshRunnable = new Runnable() {
|
||||
@WorkerThread
|
||||
@Override
|
||||
public void run() {
|
||||
if (mRegisteredObserver.compareAndSet(false, true)) {
|
||||
mDatabase.getInvalidationTracker().addWeakObserver(mObserver);
|
||||
}
|
||||
boolean computed;
|
||||
do {
|
||||
computed = false;
|
||||
// compute can happen only in 1 thread but no reason to lock others.
|
||||
if (mComputing.compareAndSet(false, true)) {
|
||||
// as long as it is invalid, keep computing.
|
||||
try {
|
||||
T value = null;
|
||||
int retry = 0;
|
||||
while (mInvalid.compareAndSet(true, false) && !computed) {
|
||||
try {
|
||||
value = mComputeFunction.call();
|
||||
computed = true;
|
||||
} catch (Exception e) {
|
||||
if (++retry > 3)
|
||||
throw new RuntimeException(
|
||||
"Exception while computing database live data.", e);
|
||||
eu.faircode.email.Log.w(e);
|
||||
try {
|
||||
Thread.sleep(3000L);
|
||||
} catch (InterruptedException ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (computed) {
|
||||
postValue(value);
|
||||
}
|
||||
} finally {
|
||||
// release compute lock
|
||||
mComputing.set(false);
|
||||
}
|
||||
}
|
||||
// check invalid after releasing compute lock to avoid the following scenario.
|
||||
// Thread A runs compute()
|
||||
// Thread A checks invalid, it is false
|
||||
// Main thread sets invalid to true
|
||||
// Thread B runs, fails to acquire compute lock and skips
|
||||
// Thread A releases compute lock
|
||||
// We've left invalid in set state. The check below recovers.
|
||||
} while (computed && mInvalid.get());
|
||||
}
|
||||
};
|
||||
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
final Runnable mInvalidationRunnable = new Runnable() {
|
||||
@MainThread
|
||||
@Override
|
||||
public void run() {
|
||||
boolean isActive = hasActiveObservers();
|
||||
if (mInvalid.compareAndSet(false, true)) {
|
||||
if (isActive) {
|
||||
getQueryExecutor().execute(mRefreshRunnable);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@SuppressLint("RestrictedApi")
|
||||
RoomTrackingLiveData(
|
||||
RoomDatabase database,
|
||||
InvalidationLiveDataContainer container,
|
||||
boolean inTransaction,
|
||||
Callable<T> computeFunction,
|
||||
String[] tableNames) {
|
||||
mDatabase = database;
|
||||
mInTransaction = inTransaction;
|
||||
mComputeFunction = computeFunction;
|
||||
mContainer = container;
|
||||
mObserver = new InvalidationTracker.Observer(tableNames) {
|
||||
@Override
|
||||
public void onInvalidated(@NonNull Set<String> tables) {
|
||||
ArchTaskExecutor.getInstance().executeOnMainThread(mInvalidationRunnable);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActive() {
|
||||
super.onActive();
|
||||
mContainer.onActive(this);
|
||||
getQueryExecutor().execute(mRefreshRunnable);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onInactive() {
|
||||
super.onInactive();
|
||||
mContainer.onInactive(this);
|
||||
}
|
||||
|
||||
Executor getQueryExecutor() {
|
||||
if (mInTransaction) {
|
||||
return mDatabase.getTransactionExecutor();
|
||||
} else {
|
||||
return mDatabase.getQueryExecutor();
|
||||
}
|
||||
}
|
||||
}
|
||||
205
app/src/main/java/androidx/room/SQLiteCopyOpenHelper.java
Normal file
205
app/src/main/java/androidx/room/SQLiteCopyOpenHelper.java
Normal file
@@ -0,0 +1,205 @@
|
||||
/*
|
||||
* Copyright 2019 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package androidx.room;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RequiresApi;
|
||||
import androidx.room.util.CopyLock;
|
||||
import androidx.room.util.DBUtil;
|
||||
import androidx.room.util.FileUtil;
|
||||
import androidx.sqlite.db.SupportSQLiteDatabase;
|
||||
import androidx.sqlite.db.SupportSQLiteOpenHelper;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.channels.Channels;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.nio.channels.ReadableByteChannel;
|
||||
|
||||
/**
|
||||
* An open helper that will copy & open a pre-populated database if it doesn't exists in internal
|
||||
* storage.
|
||||
*/
|
||||
class SQLiteCopyOpenHelper implements SupportSQLiteOpenHelper {
|
||||
|
||||
@NonNull
|
||||
private final Context mContext;
|
||||
@Nullable
|
||||
private final String mCopyFromAssetPath;
|
||||
@Nullable
|
||||
private final File mCopyFromFile;
|
||||
private final int mDatabaseVersion;
|
||||
@NonNull
|
||||
private final SupportSQLiteOpenHelper mDelegate;
|
||||
@Nullable
|
||||
private DatabaseConfiguration mDatabaseConfiguration;
|
||||
|
||||
private boolean mVerified;
|
||||
|
||||
SQLiteCopyOpenHelper(
|
||||
@NonNull Context context,
|
||||
@Nullable String copyFromAssetPath,
|
||||
@Nullable File copyFromFile,
|
||||
int databaseVersion,
|
||||
@NonNull SupportSQLiteOpenHelper supportSQLiteOpenHelper) {
|
||||
mContext = context;
|
||||
mCopyFromAssetPath = copyFromAssetPath;
|
||||
mCopyFromFile = copyFromFile;
|
||||
mDatabaseVersion = databaseVersion;
|
||||
mDelegate = supportSQLiteOpenHelper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDatabaseName() {
|
||||
return mDelegate.getDatabaseName();
|
||||
}
|
||||
|
||||
@Override
|
||||
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
|
||||
public void setWriteAheadLoggingEnabled(boolean enabled) {
|
||||
mDelegate.setWriteAheadLoggingEnabled(enabled);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized SupportSQLiteDatabase getWritableDatabase() {
|
||||
if (!mVerified) {
|
||||
verifyDatabaseFile();
|
||||
mVerified = true;
|
||||
}
|
||||
return mDelegate.getWritableDatabase();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized SupportSQLiteDatabase getReadableDatabase() {
|
||||
if (!mVerified) {
|
||||
verifyDatabaseFile();
|
||||
mVerified = true;
|
||||
}
|
||||
return mDelegate.getReadableDatabase();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void close() {
|
||||
mDelegate.close();
|
||||
mVerified = false;
|
||||
}
|
||||
|
||||
// Can't be constructor param because the factory is needed by the database builder which in
|
||||
// turn is the one that actually builds the configuration.
|
||||
void setDatabaseConfiguration(@Nullable DatabaseConfiguration databaseConfiguration) {
|
||||
mDatabaseConfiguration = databaseConfiguration;
|
||||
}
|
||||
|
||||
private void verifyDatabaseFile() {
|
||||
String databaseName = getDatabaseName();
|
||||
File databaseFile = mContext.getDatabasePath(databaseName);
|
||||
boolean processLevelLock = mDatabaseConfiguration == null
|
||||
|| mDatabaseConfiguration.multiInstanceInvalidation;
|
||||
CopyLock copyLock = new CopyLock(databaseName, mContext.getFilesDir(), processLevelLock);
|
||||
try {
|
||||
// Acquire a copy lock, this lock works across threads and processes, preventing
|
||||
// concurrent copy attempts from occurring.
|
||||
copyLock.lock();
|
||||
|
||||
if (!databaseFile.exists()) {
|
||||
try {
|
||||
// No database file found, copy and be done.
|
||||
copyDatabaseFile(databaseFile);
|
||||
return;
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Unable to copy database file.", e);
|
||||
}
|
||||
}
|
||||
|
||||
if (mDatabaseConfiguration == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// A database file is present, check if we need to re-copy it.
|
||||
int currentVersion;
|
||||
try {
|
||||
currentVersion = DBUtil.readVersion(databaseFile);
|
||||
} catch (IOException e) {
|
||||
Log.w(Room.LOG_TAG, "Unable to read database version.", e);
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentVersion == mDatabaseVersion) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mDatabaseConfiguration.isMigrationRequired(currentVersion, mDatabaseVersion)) {
|
||||
// From the current version to the desired version a migration is required, i.e.
|
||||
// we won't be performing a copy destructive migration.
|
||||
return;
|
||||
}
|
||||
|
||||
if (mContext.deleteDatabase(databaseName)) {
|
||||
try {
|
||||
copyDatabaseFile(databaseFile);
|
||||
} catch (IOException e) {
|
||||
// We are more forgiving copying a database on a destructive migration since
|
||||
// there is already a database file that can be opened.
|
||||
Log.w(Room.LOG_TAG, "Unable to copy database file.", e);
|
||||
}
|
||||
} else {
|
||||
Log.w(Room.LOG_TAG, "Failed to delete database file ("
|
||||
+ databaseName + ") for a copy destructive migration.");
|
||||
}
|
||||
} finally {
|
||||
copyLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
private void copyDatabaseFile(File destinationFile) throws IOException {
|
||||
ReadableByteChannel input;
|
||||
if (mCopyFromAssetPath != null) {
|
||||
input = Channels.newChannel(mContext.getAssets().open(mCopyFromAssetPath));
|
||||
} else if (mCopyFromFile != null) {
|
||||
input = new FileInputStream(mCopyFromFile).getChannel();
|
||||
} else {
|
||||
throw new IllegalStateException("copyFromAssetPath and copyFromFile == null!");
|
||||
}
|
||||
|
||||
// An intermediate file is used so that we never end up with a half-copied database file
|
||||
// in the internal directory.
|
||||
File intermediateFile = File.createTempFile(
|
||||
"room-copy-helper", ".tmp", mContext.getCacheDir());
|
||||
intermediateFile.deleteOnExit();
|
||||
FileChannel output = new FileOutputStream(intermediateFile).getChannel();
|
||||
FileUtil.copy(input, output);
|
||||
|
||||
File parent = destinationFile.getParentFile();
|
||||
if (parent != null && !parent.exists() && !parent.mkdirs()) {
|
||||
throw new IOException("Failed to create directories for "
|
||||
+ destinationFile.getAbsolutePath());
|
||||
}
|
||||
|
||||
if (!intermediateFile.renameTo(destinationFile)) {
|
||||
throw new IOException("Failed to move intermediate file ("
|
||||
+ intermediateFile.getAbsolutePath() + ") to destination ("
|
||||
+ destinationFile.getAbsolutePath() + ").");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright 2019 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package androidx.room;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.sqlite.db.SupportSQLiteOpenHelper;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* Implementation of {@link SupportSQLiteOpenHelper.Factory} that creates
|
||||
* {@link SQLiteCopyOpenHelper}.
|
||||
*/
|
||||
class SQLiteCopyOpenHelperFactory implements SupportSQLiteOpenHelper.Factory {
|
||||
|
||||
@Nullable
|
||||
private final String mCopyFromAssetPath;
|
||||
@Nullable
|
||||
private final File mCopyFromFile;
|
||||
@NonNull
|
||||
private final SupportSQLiteOpenHelper.Factory mDelegate;
|
||||
|
||||
SQLiteCopyOpenHelperFactory(
|
||||
@Nullable String copyFromAssetPath,
|
||||
@Nullable File copyFromFile,
|
||||
@NonNull SupportSQLiteOpenHelper.Factory factory) {
|
||||
mCopyFromAssetPath = copyFromAssetPath;
|
||||
mCopyFromFile = copyFromFile;
|
||||
mDelegate = factory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SupportSQLiteOpenHelper create(SupportSQLiteOpenHelper.Configuration configuration) {
|
||||
return new SQLiteCopyOpenHelper(
|
||||
configuration.context,
|
||||
mCopyFromAssetPath,
|
||||
mCopyFromFile,
|
||||
configuration.callback.version,
|
||||
mDelegate.create(configuration));
|
||||
}
|
||||
}
|
||||
100
app/src/main/java/androidx/room/SharedSQLiteStatement.java
Normal file
100
app/src/main/java/androidx/room/SharedSQLiteStatement.java
Normal file
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* Copyright (C) 2016 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package androidx.room;
|
||||
|
||||
import androidx.annotation.RestrictTo;
|
||||
import androidx.sqlite.db.SupportSQLiteStatement;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
/**
|
||||
* Represents a prepared SQLite state that can be re-used multiple times.
|
||||
* <p>
|
||||
* This class is used by generated code. After it is used, {@code release} must be called so that
|
||||
* it can be used by other threads.
|
||||
* <p>
|
||||
* To avoid re-entry even within the same thread, this class allows only 1 time access to the shared
|
||||
* statement until it is released.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@SuppressWarnings({"WeakerAccess", "unused"})
|
||||
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
|
||||
public abstract class SharedSQLiteStatement {
|
||||
private final AtomicBoolean mLock = new AtomicBoolean(false);
|
||||
|
||||
private final RoomDatabase mDatabase;
|
||||
private volatile SupportSQLiteStatement mStmt;
|
||||
|
||||
/**
|
||||
* Creates an SQLite prepared statement that can be re-used across threads. If it is in use,
|
||||
* it automatically creates a new one.
|
||||
*
|
||||
* @param database The database to create the statement in.
|
||||
*/
|
||||
public SharedSQLiteStatement(RoomDatabase database) {
|
||||
mDatabase = database;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the query.
|
||||
*
|
||||
* @return The SQL query to prepare.
|
||||
*/
|
||||
protected abstract String createQuery();
|
||||
|
||||
protected void assertNotMainThread() {
|
||||
mDatabase.assertNotMainThread();
|
||||
}
|
||||
|
||||
private SupportSQLiteStatement createNewStatement() {
|
||||
String query = createQuery();
|
||||
return mDatabase.compileStatement(query);
|
||||
}
|
||||
|
||||
private SupportSQLiteStatement getStmt(boolean canUseCached) {
|
||||
final SupportSQLiteStatement stmt;
|
||||
if (canUseCached) {
|
||||
if (mStmt == null) {
|
||||
mStmt = createNewStatement();
|
||||
}
|
||||
stmt = mStmt;
|
||||
} else {
|
||||
// it is in use, create a one off statement
|
||||
stmt = createNewStatement();
|
||||
}
|
||||
return stmt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Call this to get the statement. Must call {@link #release(SupportSQLiteStatement)} once done.
|
||||
*/
|
||||
public SupportSQLiteStatement acquire() {
|
||||
assertNotMainThread();
|
||||
return getStmt(mLock.compareAndSet(false, true));
|
||||
}
|
||||
|
||||
/**
|
||||
* Must call this when statement will not be used anymore.
|
||||
*
|
||||
* @param statement The statement that was returned from acquire.
|
||||
*/
|
||||
public void release(SupportSQLiteStatement statement) {
|
||||
if (statement == mStmt) {
|
||||
mLock.set(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
62
app/src/main/java/androidx/room/TransactionExecutor.java
Normal file
62
app/src/main/java/androidx/room/TransactionExecutor.java
Normal file
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright 2019 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package androidx.room;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
/**
|
||||
* Executor wrapper for performing database transactions serially.
|
||||
* <p>
|
||||
* Since database transactions are exclusive, this executor ensures that transactions are performed
|
||||
* in-order and one at a time, preventing threads from blocking each other when multiple concurrent
|
||||
* transactions are attempted.
|
||||
*/
|
||||
class TransactionExecutor implements Executor {
|
||||
|
||||
private final Executor mExecutor;
|
||||
private final ArrayDeque<Runnable> mTasks = new ArrayDeque<>();
|
||||
private Runnable mActive;
|
||||
|
||||
TransactionExecutor(@NonNull Executor executor) {
|
||||
mExecutor = executor;
|
||||
}
|
||||
|
||||
public synchronized void execute(final Runnable command) {
|
||||
mTasks.offer(new Runnable() {
|
||||
public void run() {
|
||||
try {
|
||||
command.run();
|
||||
} finally {
|
||||
scheduleNext();
|
||||
}
|
||||
}
|
||||
});
|
||||
if (mActive == null) {
|
||||
scheduleNext();
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
synchronized void scheduleNext() {
|
||||
if ((mActive = mTasks.poll()) != null) {
|
||||
mExecutor.execute(mActive);
|
||||
}
|
||||
}
|
||||
}
|
||||
63
app/src/main/java/androidx/room/migration/Migration.java
Normal file
63
app/src/main/java/androidx/room/migration/Migration.java
Normal file
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright (C) 2017 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package androidx.room.migration;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.sqlite.db.SupportSQLiteDatabase;
|
||||
|
||||
/**
|
||||
* Base class for a database migration.
|
||||
* <p>
|
||||
* Each migration can move between 2 versions that are defined by {@link #startVersion} and
|
||||
* {@link #endVersion}.
|
||||
* <p>
|
||||
* A migration can handle more than 1 version (e.g. if you have a faster path to choose when
|
||||
* going version 3 to 5 without going to version 4). If Room opens a database at version
|
||||
* 3 and latest version is >= 5, Room will use the migration object that can migrate from
|
||||
* 3 to 5 instead of 3 to 4 and 4 to 5.
|
||||
* <p>
|
||||
* If there are not enough migrations provided to move from the current version to the latest
|
||||
* version, Room will clear the database and recreate so even if you have no changes between 2
|
||||
* versions, you should still provide a Migration object to the builder.
|
||||
*/
|
||||
public abstract class Migration {
|
||||
public final int startVersion;
|
||||
public final int endVersion;
|
||||
|
||||
/**
|
||||
* Creates a new migration between {@code startVersion} and {@code endVersion}.
|
||||
*
|
||||
* @param startVersion The start version of the database.
|
||||
* @param endVersion The end version of the database after this migration is applied.
|
||||
*/
|
||||
public Migration(int startVersion, int endVersion) {
|
||||
this.startVersion = startVersion;
|
||||
this.endVersion = endVersion;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should run the necessary migrations.
|
||||
* <p>
|
||||
* This class cannot access any generated Dao in this method.
|
||||
* <p>
|
||||
* This method is already called inside a transaction and that transaction might actually be a
|
||||
* composite transaction of all necessary {@code Migration}s.
|
||||
*
|
||||
* @param database The database instance
|
||||
*/
|
||||
public abstract void migrate(@NonNull SupportSQLiteDatabase database);
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user