mirror of
https://github.com/bitfireAT/davx5-ose.git
synced 2026-01-06 13:57:54 -05:00
Compare commits
381 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
859ef6d29f | ||
|
|
351f5e8447 | ||
|
|
80b1bb8ee2 | ||
|
|
e558c45139 | ||
|
|
a2284de509 | ||
|
|
a61fe817ca | ||
|
|
15349b3d23 | ||
|
|
6eea640647 | ||
|
|
91864f76e4 | ||
|
|
3c303ff760 | ||
|
|
dec4f81faa | ||
|
|
3e691e6aef | ||
|
|
66ae1ddc8d | ||
|
|
d757f45e8b | ||
|
|
374acf1c70 | ||
|
|
b1ab14f311 | ||
|
|
d65a021536 | ||
|
|
8e61320225 | ||
|
|
ef698102f1 | ||
|
|
47e417158f | ||
|
|
65d3986257 | ||
|
|
c350baa863 | ||
|
|
4222a5d2c6 | ||
|
|
718bcebf20 | ||
|
|
c6d3370dd3 | ||
|
|
f03e1d7948 | ||
|
|
cff047c4cb | ||
|
|
7a3a9047e6 | ||
|
|
7edd960d47 | ||
|
|
46a5a8a25a | ||
|
|
a3f2c23a3c | ||
|
|
aaee3fbd9b | ||
|
|
b80608be98 | ||
|
|
531440d5a9 | ||
|
|
be8657433e | ||
|
|
c5af9a735b | ||
|
|
3a582c4534 | ||
|
|
bd3d27f883 | ||
|
|
601cfff788 | ||
|
|
2fec3e3cb8 | ||
|
|
c4725a9b17 | ||
|
|
70bec8e980 | ||
|
|
0d0341fd62 | ||
|
|
b49bdda7e8 | ||
|
|
19b54748cd | ||
|
|
41ce609237 | ||
|
|
aafcc36c4d | ||
|
|
2496a3bf05 | ||
|
|
54e6426dc4 | ||
|
|
4542da7d89 | ||
|
|
977409511a | ||
|
|
ad8c832819 | ||
|
|
389af2b738 | ||
|
|
be2e15e463 | ||
|
|
c7c13520f9 | ||
|
|
317144630c | ||
|
|
34bc27fa79 | ||
|
|
210735a500 | ||
|
|
b30733c64b | ||
|
|
91234a688f | ||
|
|
5675e544b5 | ||
|
|
42a261b84e | ||
|
|
0d1825cbf3 | ||
|
|
9b8fc983cd | ||
|
|
3bde3758fc | ||
|
|
fd1f59d124 | ||
|
|
9886507b7d | ||
|
|
144643d6af | ||
|
|
14875f63ea | ||
|
|
28e567cf78 | ||
|
|
7997606550 | ||
|
|
fb0552de46 | ||
|
|
03c15a6924 | ||
|
|
c3b2929f88 | ||
|
|
eb2537a278 | ||
|
|
0b9727cca6 | ||
|
|
61231b4233 | ||
|
|
59252d7471 | ||
|
|
6ffa6fa9a7 | ||
|
|
03ee9a037b | ||
|
|
7ab13d648e | ||
|
|
25c54cce62 | ||
|
|
f0e45c71f5 | ||
|
|
fa528a64e9 | ||
|
|
c6aed90c96 | ||
|
|
2280f899ee | ||
|
|
a283cbbae5 | ||
|
|
bb95a25b91 | ||
|
|
f1ccd01708 | ||
|
|
c498225064 | ||
|
|
879b137cfc | ||
|
|
84379f7ee1 | ||
|
|
a594fd3d14 | ||
|
|
100b78a6a4 | ||
|
|
758711acb2 | ||
|
|
c90b6075db | ||
|
|
7109915e6e | ||
|
|
e8cf9fd5ab | ||
|
|
3a49815220 | ||
|
|
96881bd986 | ||
|
|
c08a0bdc43 | ||
|
|
773b2ee992 | ||
|
|
c2181c55d3 | ||
|
|
8449684dd2 | ||
|
|
28e7c91658 | ||
|
|
51867c5f3f | ||
|
|
1786b73ac6 | ||
|
|
1df3ddbe74 | ||
|
|
5ee8d76b34 | ||
|
|
5723225475 | ||
|
|
f73f6ca43c | ||
|
|
753c4b05a5 | ||
|
|
2e34fa686d | ||
|
|
a735564bc1 | ||
|
|
552f6b6936 | ||
|
|
50f7006e59 | ||
|
|
6ac5fe0204 | ||
|
|
19bfe5c5f2 | ||
|
|
212cd8ddb0 | ||
|
|
c30195d9ba | ||
|
|
3ca063416e | ||
|
|
940d622402 | ||
|
|
814abc60ed | ||
|
|
220ba4b151 | ||
|
|
777e124b54 | ||
|
|
f32493986b | ||
|
|
5025a61cd1 | ||
|
|
89a516bfd1 | ||
|
|
af71ed8bc5 | ||
|
|
fc29988dc6 | ||
|
|
77c947da14 | ||
|
|
ff901ce91f | ||
|
|
85a6b68a56 | ||
|
|
89050d88c6 | ||
|
|
ba0350c83d | ||
|
|
515969c4b8 | ||
|
|
9a8d29e774 | ||
|
|
2880b05b5d | ||
|
|
d6cff63f2d | ||
|
|
be6aa1b6a2 | ||
|
|
9ec4a4015d | ||
|
|
9dbc32d30b | ||
|
|
b63fc70cfb | ||
|
|
0142e63257 | ||
|
|
aaa7d71ae3 | ||
|
|
4adf3001ac | ||
|
|
5ccdafa074 | ||
|
|
fce2b85991 | ||
|
|
e5ebf10dc0 | ||
|
|
0f0acd62a3 | ||
|
|
2414b42867 | ||
|
|
243dac9952 | ||
|
|
12248b8bb9 | ||
|
|
d872bd06e5 | ||
|
|
065aa3fc84 | ||
|
|
20ee4e03f3 | ||
|
|
241e15404f | ||
|
|
4a00ba647d | ||
|
|
8d00814eaf | ||
|
|
c665744c31 | ||
|
|
2ef278c336 | ||
|
|
34de8431ae | ||
|
|
9d19d9757c | ||
|
|
81d13576e8 | ||
|
|
6f429328ef | ||
|
|
6465d83da4 | ||
|
|
0f5f39a9fe | ||
|
|
3e2459c85c | ||
|
|
8f52bf160e | ||
|
|
661276450c | ||
|
|
c93a89348e | ||
|
|
93464ccf8c | ||
|
|
3646a561c6 | ||
|
|
da9410c1b5 | ||
|
|
82f80fed1c | ||
|
|
94770fb0c8 | ||
|
|
9ddcec5624 | ||
|
|
4b5cb30762 | ||
|
|
58f05986c9 | ||
|
|
dd50f10c58 | ||
|
|
d3c1688407 | ||
|
|
80231dd44b | ||
|
|
4ecca76a95 | ||
|
|
410a04dc11 | ||
|
|
7fc01503d5 | ||
|
|
18542adb2c | ||
|
|
e34abf291e | ||
|
|
20bc5af4a3 | ||
|
|
f344bd3c28 | ||
|
|
419d732195 | ||
|
|
0c819c842b | ||
|
|
d348f54deb | ||
|
|
c2e9b27831 | ||
|
|
808958a69b | ||
|
|
bd77a5be63 | ||
|
|
ab34def8b0 | ||
|
|
d024cdb495 | ||
|
|
4f7f3b851a | ||
|
|
7f4b4855a0 | ||
|
|
bc2d1ba96d | ||
|
|
0bc1a8178a | ||
|
|
d0b928a93d | ||
|
|
b0163e16cd | ||
|
|
98899ab27b | ||
|
|
e4e1053f77 | ||
|
|
bcd2e8d4da | ||
|
|
f7700ba8aa | ||
|
|
a198309df5 | ||
|
|
c1a26fbbb7 | ||
|
|
5bf3aad575 | ||
|
|
97ae121331 | ||
|
|
31f5be01b4 | ||
|
|
d7fff8a760 | ||
|
|
faeb3b7dd0 | ||
|
|
fc1874af85 | ||
|
|
be80b6fde8 | ||
|
|
072c763dec | ||
|
|
6ad74c79f0 | ||
|
|
01d1b1a6c0 | ||
|
|
1c461e9d13 | ||
|
|
5ec4dbb9e7 | ||
|
|
3225a4bbc1 | ||
|
|
ece6be0f9d | ||
|
|
40c6643b41 | ||
|
|
b3afe48179 | ||
|
|
abf04e14d2 | ||
|
|
5b7947034a | ||
|
|
26d9f7284a | ||
|
|
7c1b787410 | ||
|
|
41bae221f0 | ||
|
|
243483a957 | ||
|
|
9d76d57af8 | ||
|
|
44bdd4d0ed | ||
|
|
40bffb78b0 | ||
|
|
5951414b25 | ||
|
|
dcd86c7d86 | ||
|
|
92966a5c57 | ||
|
|
ad733ebff1 | ||
|
|
59088086fd | ||
|
|
0b56d2a966 | ||
|
|
ed2a0419ad | ||
|
|
c6950b1c16 | ||
|
|
a796a1e9b3 | ||
|
|
c8cfbd6b07 | ||
|
|
654af1eec5 | ||
|
|
534953fe4c | ||
|
|
18c08bc9dd | ||
|
|
81d7813614 | ||
|
|
b7f0a9efa9 | ||
|
|
915ed7199b | ||
|
|
2665f6c4e6 | ||
|
|
13ec5a93ae | ||
|
|
a160d56643 | ||
|
|
c3f7c1b97e | ||
|
|
bc7e58232e | ||
|
|
f3e83922f7 | ||
|
|
af011a65db | ||
|
|
aa7e582bc9 | ||
|
|
03517584f2 | ||
|
|
5f3c6045d8 | ||
|
|
cd513683f5 | ||
|
|
011dd15c98 | ||
|
|
8fc86dd12c | ||
|
|
4e690a02ad | ||
|
|
a3ebd72321 | ||
|
|
87df8f880d | ||
|
|
97633c5204 | ||
|
|
33958ab548 | ||
|
|
f19d528739 | ||
|
|
365e04154b | ||
|
|
c707b1eb9d | ||
|
|
5e9fe92520 | ||
|
|
f1eabb6227 | ||
|
|
b5c99265c3 | ||
|
|
f738f74dea | ||
|
|
fb33767e57 | ||
|
|
8afc55dff3 | ||
|
|
9a63dd4693 | ||
|
|
2683012ec3 | ||
|
|
a79a59f409 | ||
|
|
81eabf5961 | ||
|
|
495cdf7c7e | ||
|
|
f6eee6c910 | ||
|
|
a405d07baf | ||
|
|
2696e64a83 | ||
|
|
6d6835c3b7 | ||
|
|
0eb6a56ef1 | ||
|
|
df335335d2 | ||
|
|
35011445e0 | ||
|
|
08789bbb2c | ||
|
|
001b445222 | ||
|
|
7d5ed0bd11 | ||
|
|
392e9f963e | ||
|
|
8d5f815be5 | ||
|
|
100aa665f5 | ||
|
|
caf7adb18a | ||
|
|
13b8c86ce3 | ||
|
|
a15185372d | ||
|
|
03d6dfef02 | ||
|
|
a021b973fe | ||
|
|
7e592d7647 | ||
|
|
38a4f3f53c | ||
|
|
986213dda5 | ||
|
|
d98a9d3673 | ||
|
|
12cb0ff8fe | ||
|
|
3a8f17cc2e | ||
|
|
9b52e51e4e | ||
|
|
be235c39b0 | ||
|
|
1acd2e1a55 | ||
|
|
23dba581e1 | ||
|
|
b29756aff2 | ||
|
|
4e2809910c | ||
|
|
9394c10d29 | ||
|
|
6d55de7c1d | ||
|
|
933f99b563 | ||
|
|
aeca582a7c | ||
|
|
6c998c31c3 | ||
|
|
1a796ade60 | ||
|
|
9e082d930b | ||
|
|
a7115ad39c | ||
|
|
4eb03f78f5 | ||
|
|
a6a7420935 | ||
|
|
3cba163c3b | ||
|
|
ca0ad612a7 | ||
|
|
897ede7582 | ||
|
|
6f5748c464 | ||
|
|
0423e00ffd | ||
|
|
ac46b8f45f | ||
|
|
142e229ff3 | ||
|
|
e65a1b5f16 | ||
|
|
bfbd4569ed | ||
|
|
a52e3507e7 | ||
|
|
49f7bbd8da | ||
|
|
8a75552a4c | ||
|
|
5ce03abad6 | ||
|
|
a7e8221e26 | ||
|
|
0cc23e3ecf | ||
|
|
661738c866 | ||
|
|
d56175652c | ||
|
|
760632302d | ||
|
|
5b21cb4938 | ||
|
|
03a64a43a3 | ||
|
|
1ad25a748a | ||
|
|
51ac34790c | ||
|
|
bc8d63f233 | ||
|
|
9452204f5c | ||
|
|
e98f0bd1ed | ||
|
|
dba35a5296 | ||
|
|
751e2eb5a4 | ||
|
|
4bf613c28b | ||
|
|
3e87f045d5 | ||
|
|
3f1740bc1c | ||
|
|
c8da282db0 | ||
|
|
6f0b9421c1 | ||
|
|
4b250dcb66 | ||
|
|
b6386c1b95 | ||
|
|
378214f62a | ||
|
|
c10fc6dfd1 | ||
|
|
cc1a1741f9 | ||
|
|
83eecc0c72 | ||
|
|
cfc71542f5 | ||
|
|
487509cb0c | ||
|
|
31f3fc80ef | ||
|
|
2e6a3efd25 | ||
|
|
2f5622edaf | ||
|
|
d73f4c5937 | ||
|
|
4c2e66d44c | ||
|
|
8d4c353d8c | ||
|
|
7bfcb2d8ad | ||
|
|
e3a7c7092e | ||
|
|
65376c2d42 | ||
|
|
58efd9ba03 | ||
|
|
fd4e5921ea | ||
|
|
6d9fc8734f | ||
|
|
7501f87d03 | ||
|
|
4205bf34fd | ||
|
|
25c1a1ad51 | ||
|
|
69c94c8d93 | ||
|
|
e9901f38f5 | ||
|
|
b8a728bdb9 | ||
|
|
1ec1db3045 |
102
.gitignore
vendored
102
.gitignore
vendored
@@ -1,64 +1,92 @@
|
||||
### ANDROID
|
||||
# Created by https://www.gitignore.io
|
||||
|
||||
# built application files
|
||||
### Android ###
|
||||
# Built application files
|
||||
*.apk
|
||||
*.ap_
|
||||
|
||||
# files for the dex VM
|
||||
# Files for the Dalvik VM
|
||||
*.dex
|
||||
|
||||
# Java class files
|
||||
*.class
|
||||
|
||||
# generated files
|
||||
# Generated files
|
||||
bin/
|
||||
doc/javadoc/
|
||||
gen/
|
||||
|
||||
# Gradle files
|
||||
.gradle/
|
||||
build/
|
||||
|
||||
# Local configuration file (sdk path, etc)
|
||||
local.properties
|
||||
|
||||
# Eclipse project files
|
||||
.classpath
|
||||
.project
|
||||
|
||||
# Proguard folder generated by Eclipse
|
||||
proguard/
|
||||
|
||||
# Intellij project files
|
||||
# Log Files
|
||||
*.log
|
||||
|
||||
|
||||
### Intellij ###
|
||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm
|
||||
|
||||
*.iml
|
||||
|
||||
## Directory-based project format:
|
||||
.idea/
|
||||
# if you remove the above rule, at least ignore the following:
|
||||
|
||||
# User-specific stuff:
|
||||
# .idea/workspace.xml
|
||||
# .idea/tasks.xml
|
||||
# .idea/dictionaries
|
||||
|
||||
# Sensitive or high-churn files:
|
||||
# .idea/dataSources.ids
|
||||
# .idea/dataSources.xml
|
||||
# .idea/sqlDataSources.xml
|
||||
# .idea/dynamic.xml
|
||||
# .idea/uiDesigner.xml
|
||||
|
||||
# Gradle:
|
||||
# .idea/gradle.xml
|
||||
# .idea/libraries
|
||||
|
||||
# Mongo Explorer plugin:
|
||||
# .idea/mongoSettings.xml
|
||||
|
||||
## File-based project format:
|
||||
*.ipr
|
||||
*.iws
|
||||
.idea/
|
||||
|
||||
# private files
|
||||
.private
|
||||
## Plugin-specific files:
|
||||
|
||||
# IntelliJ
|
||||
out/
|
||||
|
||||
# mpeltonen/sbt-idea plugin
|
||||
.idea_modules/
|
||||
|
||||
# JIRA plugin
|
||||
atlassian-ide-plugin.xml
|
||||
|
||||
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||
com_crashlytics_export_strings.xml
|
||||
crashlytics.properties
|
||||
crashlytics-build.properties
|
||||
|
||||
|
||||
### ECLIPSE
|
||||
### Gradle ###
|
||||
.gradle
|
||||
build/
|
||||
|
||||
*.pydevproject
|
||||
.metadata
|
||||
bin/**
|
||||
tmp/**
|
||||
tmp/**/*
|
||||
*.tmp
|
||||
*.bak
|
||||
*.swp
|
||||
*~.nib
|
||||
local.properties
|
||||
.classpath
|
||||
.settings/
|
||||
.loadpath
|
||||
# Ignore Gradle GUI config
|
||||
gradle-app.setting
|
||||
|
||||
# External tool builders
|
||||
.externalToolBuilders/
|
||||
### external libs ###
|
||||
.svn
|
||||
|
||||
# Locally stored "Eclipse launch configurations"
|
||||
*.launch
|
||||
|
||||
# CDT-specific
|
||||
.cproject
|
||||
|
||||
# PDT-specific
|
||||
.buildpath
|
||||
# Javadoc
|
||||
javadoc/
|
||||
|
||||
12
.gitmodules
vendored
Normal file
12
.gitmodules
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
[submodule "dav4android"]
|
||||
path = dav4android
|
||||
url = ../dav4android.git
|
||||
[submodule "ical4android"]
|
||||
path = ical4android
|
||||
url = ../ical4android.git
|
||||
[submodule "vcard4android"]
|
||||
path = vcard4android
|
||||
url = ../vcard4android.git
|
||||
[submodule "cert4android"]
|
||||
path = cert4android
|
||||
url = ../cert4android.git
|
||||
@@ -1,79 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="at.bitfire.davdroid"
|
||||
android:versionCode="42"
|
||||
android:versionName="0.6.4" android:installLocation="internalOnly">
|
||||
|
||||
<uses-sdk
|
||||
android:minSdkVersion="14"
|
||||
android:targetSdkVersion="19" />
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS" />
|
||||
<uses-permission android:name="android.permission.READ_CONTACTS" />
|
||||
<uses-permission android:name="android.permission.WRITE_CONTACTS" />
|
||||
<uses-permission android:name="android.permission.READ_CALENDAR" />
|
||||
<uses-permission android:name="android.permission.WRITE_CALENDAR" />
|
||||
<uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS" />
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:icon="@drawable/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/AppTheme"
|
||||
android:process=":sync" >
|
||||
<service
|
||||
android:name=".syncadapter.AccountAuthenticatorService"
|
||||
android:exported="false" >
|
||||
<intent-filter>
|
||||
<action android:name="android.accounts.AccountAuthenticator" />
|
||||
</intent-filter>
|
||||
|
||||
<meta-data
|
||||
android:name="android.accounts.AccountAuthenticator"
|
||||
android:resource="@xml/account_authenticator" />
|
||||
</service>
|
||||
<service
|
||||
android:name=".syncadapter.ContactsSyncAdapterService"
|
||||
android:exported="true" >
|
||||
<intent-filter>
|
||||
<action android:name="android.content.SyncAdapter" />
|
||||
</intent-filter>
|
||||
|
||||
<meta-data
|
||||
android:name="android.content.SyncAdapter"
|
||||
android:resource="@xml/sync_contacts" />
|
||||
<meta-data
|
||||
android:name="android.provider.CONTACTS_STRUCTURE"
|
||||
android:resource="@xml/contacts" />
|
||||
</service>
|
||||
<service
|
||||
android:name=".syncadapter.CalendarsSyncAdapterService"
|
||||
android:exported="true" >
|
||||
<intent-filter>
|
||||
<action android:name="android.content.SyncAdapter" />
|
||||
</intent-filter>
|
||||
|
||||
<meta-data
|
||||
android:name="android.content.SyncAdapter"
|
||||
android:resource="@xml/sync_calendars" />
|
||||
</service>
|
||||
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:label="@string/app_name" >
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".syncadapter.AddAccountActivity"
|
||||
android:excludeFromRecents="true" >
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".syncadapter.GeneralSettingsActivity" >
|
||||
</activity>
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
@@ -1,58 +0,0 @@
|
||||
|
||||
DAVdroid is free and open-source software, licensed under the [GPLv3 License](COPYING).
|
||||
If you like our project, please contribute to it.
|
||||
|
||||
# How to contribute
|
||||
|
||||
## Reporting issues
|
||||
|
||||
An issue might be a bug, an enhancement request or something in between. If you think you
|
||||
have found a bug or if you want to request some enhancement, please:
|
||||
|
||||
1. Read the [Configuration](http://davdroid.bitfire.at/configuration) and [FAQ](http://davdroid.bitfire.at/faq/)
|
||||
pages carefully. The most common issues/usage challenges are explained there.
|
||||
2. Search the Web for the problem, maybe ask competent friends or in forums.
|
||||
3. Browse through the [open issues](https://github.com/rfc2822/davdroid/issues). You can
|
||||
also search the issues in the search field on top of the page. Please have a look
|
||||
into the closed issues, too, because many requests have already been handled (and can't/won't
|
||||
be fixed, for instance).
|
||||
4. **[Fetch verbose logs](https://github.com/rfc2822/davdroid/wiki/How-to-view-the-logs) and prepare
|
||||
them. Remove `Authorization: Basic xxxxxx` headers and other private data.** Extracting the
|
||||
logs may be cumbersome work in the first time, but it's absolutely necessary in order to
|
||||
handle your issue.
|
||||
5. [Create a new issue](https://github.com/rfc2822/davdroid/issues/new), containing
|
||||
* a useful summary of the problem ("Crash when syncing contacts with large photos" instead of "CRASH PLEASE HELP"),
|
||||
* your DAVdroid version and source ("DAVdroid 0.5.10 from F-Droid"),
|
||||
* your Android version and device model ("Samsung Galaxy S2 running Android 4.4.2 (CyanogenMod 11-20140504-SNAPSHOT-M6-i9100)"),
|
||||
* your CalDAV/CardDAV server software, version and hosting information ("OwnCloud 6, hosted on virtual server"),
|
||||
* a problem description, including **instructions on how to reproduce the problem** (we need to
|
||||
reproduce the problem before we can fix it!),
|
||||
* **verbose logs including the network traffic** (see step before). Enquote the logs with three backticks ```
|
||||
before and after, or post them onto http://gist.github.com and provide a link.
|
||||
|
||||
|
||||
## Pull requests
|
||||
|
||||
We're very happy about pull requests for
|
||||
|
||||
* source code,
|
||||
* documentation,
|
||||
* translation (strings).
|
||||
|
||||
However, if you want to contribute source code, please talk with us in the
|
||||
corresponding issue before because will only merge pull requests that
|
||||
|
||||
* match our product goals,
|
||||
* have the necessary code quality,
|
||||
* don't interfere with other near-term future development.
|
||||
|
||||
However, feel free to fork the repository and do your changes anyway
|
||||
(that's why it's open-source). Just don't expect your strategic changes to be
|
||||
merged if there's no consensus in the issue before.
|
||||
|
||||
|
||||
## Donations
|
||||
|
||||
If you want to support this project, please also consider [donating to DAVdroid](http://davdroid.bitfire.at/donate)
|
||||
or [purchasing it in one of the commercial stores](http://davdroid.bitfire.at/download).
|
||||
|
||||
33
README.md
33
README.md
@@ -1,21 +1,34 @@
|
||||
|
||||
DAVDROID
|
||||
DAVdroid
|
||||
========
|
||||
|
||||
Please see the [DAVdroid Web site](http://davdroid.bitfire.at) for
|
||||
detailled information about DAVdroid.
|
||||
Please see the [DAVdroid Web site](https://davdroid.bitfire.at) for
|
||||
comprehensive information about DAVdroid.
|
||||
|
||||
DAVdroid is licensed under the [GPLv3 License](COPYING).
|
||||
DAVdroid is licensed under the [GPLv3 License](LICENSE).
|
||||
|
||||
Twitter: [@davdroidapp](https://twitter.com/davdroidapp)
|
||||
News and updates: [@davdroidapp](https://twitter.com/davdroidapp)
|
||||
|
||||
Help and discussion: [DAVdroid forums](https://davdroid.bitfire.at/forums)
|
||||
|
||||
Parts of DAVdroid have been outsourced into these libraries:
|
||||
|
||||
* [cert4android](https://gitlab.com/bitfireAT/cert4android) – custom certificate management
|
||||
* [dav4android](https://gitlab.com/bitfireAT/dav4android) – WebDAV/CalDav/CardDAV framework
|
||||
* [ical4android](https://gitlab.com/bitfireAT/ical4android) – iCalendar processing and Calendar Provider access
|
||||
* [vcard4android](https://gitlab.com/bitfireAT/vcard4android) – VCard processing and Contacts Provider access
|
||||
|
||||
[](https://flattr.com/submit/auto?user_id=bitfire&url=https://davdroid.bitfire.at&title=DAVdroid&category=software)
|
||||
|
||||
|
||||
USED THIRD-PARTY LIBRARIES
|
||||
==========================
|
||||
|
||||
* [Apache HttpClient](http://hc.apache.org) ([httpclientandroidlib](https://code.google.com/p/httpclientandroidlib/) flavour) – [Apache License](http://www.apache.org/licenses/)
|
||||
* [iCal4j](http://ical4j.sourceforge.net/) – [New BSD License](http://sourceforge.net/p/ical4j/ical4j/ci/default/tree/LICENSE)
|
||||
* [ez-vcard](https://code.google.com/p/ez-vcard/) – [New BSD License](http://opensource.org/licenses/BSD-3-Clause)
|
||||
* [Simple XML Serialization](http://simple.sourceforge.net/) – [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0)
|
||||
* [Project Lombok](http://projectlombok.org/) – [MIT License](http://opensource.org/licenses/mit-license.php)
|
||||
Those libraries are used by DAVdroid (alphabetically):
|
||||
|
||||
* [dnsjava](http://www.xbill.org/dnsjava/) – [BSD License](http://www.xbill.org/dnsjava/dnsjava-current/LICENSE)
|
||||
* [ez-vcard](https://code.google.com/p/ez-vcard/) – [New BSD License](http://opensource.org/licenses/BSD-3-Clause)
|
||||
* [iCal4j](http://ical4j.sourceforge.net/) – [New BSD License](http://sourceforge.net/p/ical4j/ical4j/ci/default/tree/LICENSE)
|
||||
* [okhttp](https://square.github.io/okhttp/) – [Apache License, Version 2.0](https://square.github.io/okhttp/#license)
|
||||
* [Project Lombok](http://projectlombok.org/) – [MIT License](http://opensource.org/licenses/mit-license.php)
|
||||
* [SLF4J](http://www.slf4j.org/) – [MIT License](http://www.slf4j.org/license.html)
|
||||
|
||||
2
app/.gitignore
vendored
Normal file
2
app/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
build
|
||||
target
|
||||
111
app/build.gradle
Normal file
111
app/build.gradle
Normal file
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
* Copyright (c) 2013 – 2016 Ricki Hirner (bitfire web engineering).
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the GNU Public License v3.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.gnu.org/licenses/gpl.html
|
||||
*/
|
||||
|
||||
apply plugin: 'com.android.application'
|
||||
|
||||
android {
|
||||
compileSdkVersion 24
|
||||
buildToolsVersion '24.0.3'
|
||||
|
||||
defaultConfig {
|
||||
applicationId "at.bitfire.davdroid"
|
||||
resValue "string", "packageID", applicationId
|
||||
|
||||
versionCode 120
|
||||
buildConfigField "long", "buildTime", System.currentTimeMillis() + "L"
|
||||
|
||||
minSdkVersion 15
|
||||
targetSdkVersion 24
|
||||
|
||||
buildConfigField "boolean", "customCerts", "false"
|
||||
buildConfigField "at.bitfire.vcard4android.GroupMethod", "settingContactGroupMethod", "null"
|
||||
buildConfigField "Boolean", "settingVCardRFC6868", "null"
|
||||
}
|
||||
|
||||
productFlavors {
|
||||
standard {
|
||||
versionName "1.3.2.2"
|
||||
buildConfigField "boolean", "customCerts", "true"
|
||||
}
|
||||
gplay {
|
||||
versionName "1.3.2.2-gplay"
|
||||
buildConfigField "boolean", "customCerts", "true"
|
||||
}
|
||||
icloud {
|
||||
applicationId "at.bitfire.cloudsync"
|
||||
resValue "string", "packageID", applicationId
|
||||
|
||||
versionName "1.3.2.2-cloud"
|
||||
|
||||
buildConfigField "at.bitfire.vcard4android.GroupMethod", "settingContactGroupMethod", "at.bitfire.vcard4android.GroupMethod.GROUP_VCARDS"
|
||||
buildConfigField "Boolean", "settingVCardRFC6868", "true"
|
||||
}
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
standard.java.srcDirs = [ "src/davdroid/java" ]
|
||||
standard.res.srcDirs = [ "src/davdroid/res" ]
|
||||
|
||||
gplay.java.srcDirs = [ "src/gplay/java", "src/davdroid/java" ]
|
||||
gplay.res.srcDirs = [ "src/gplay/res", "src/davdroid/res" ]
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
debug {
|
||||
minifyEnabled false
|
||||
}
|
||||
release {
|
||||
minifyEnabled true
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
|
||||
}
|
||||
}
|
||||
|
||||
lintOptions {
|
||||
disable 'GoogleAppIndexingWarning' // we don't need Google indexing, thanks
|
||||
disable 'GradleDependency'
|
||||
disable 'GradleDynamicVersion'
|
||||
disable 'IconColors'
|
||||
disable 'IconLauncherShape'
|
||||
disable 'IconMissingDensityFolder'
|
||||
disable 'ImpliedQuantity'
|
||||
disable 'MissingTranslation'
|
||||
disable 'MissingQuantity'
|
||||
disable 'Recycle' // doesn't understand Lombok's @Cleanup
|
||||
disable 'RtlEnabled'
|
||||
disable 'RtlHardcoded'
|
||||
disable 'Typos'
|
||||
}
|
||||
packagingOptions {
|
||||
exclude 'LICENSE'
|
||||
exclude 'META-INF/LICENSE.txt'
|
||||
exclude 'META-INF/NOTICE.txt'
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile project(':cert4android')
|
||||
compile project(':dav4android')
|
||||
compile project(':ical4android')
|
||||
compile project(':vcard4android')
|
||||
|
||||
compile 'com.android.support:appcompat-v7:24.+'
|
||||
compile 'com.android.support:cardview-v7:24.+'
|
||||
compile 'com.android.support:design:24.+'
|
||||
compile 'com.android.support:preference-v14:24.+'
|
||||
|
||||
compile 'com.github.yukuku:ambilwarna:2.0.1'
|
||||
|
||||
compile 'dnsjava:dnsjava:2.1.7'
|
||||
compile 'org.apache.commons:commons-lang3:3.4'
|
||||
compile 'org.apache.commons:commons-collections4:4.1'
|
||||
provided 'org.projectlombok:lombok:1.16.10'
|
||||
// for tests
|
||||
testCompile 'junit:junit:4.12'
|
||||
testCompile 'com.squareup.okhttp3:mockwebserver:3.4.1'
|
||||
androidTestCompile 'com.squareup.okhttp3:mockwebserver:3.4.1'
|
||||
}
|
||||
13
app/lint.xml
Normal file
13
app/lint.xml
Normal file
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ Copyright (c) 2013 – 2015 Ricki Hirner (bitfire web engineering).
|
||||
~ All rights reserved. This program and the accompanying materials
|
||||
~ are made available under the terms of the GNU Public License v3.0
|
||||
~ which accompanies this distribution, and is available at
|
||||
~ http://www.gnu.org/licenses/gpl.html
|
||||
-->
|
||||
|
||||
<lint>
|
||||
<issue id="InvalidPackage" severity="ignore" />
|
||||
<issue id="MissingTranslation" severity="warning" />
|
||||
</lint>
|
||||
41
app/proguard-rules.txt
Normal file
41
app/proguard-rules.txt
Normal file
@@ -0,0 +1,41 @@
|
||||
|
||||
# ProGuard usage for DAVdroid:
|
||||
# shrinking yes (main reason for using ProGuard)
|
||||
# optimization yes
|
||||
# obfuscation no (DAVdroid is open-source)
|
||||
# preverification no
|
||||
|
||||
-dontobfuscate
|
||||
|
||||
-optimizations !code/simplification/arithmetic,!code/simplification/cast,!field/*,!class/merging/*
|
||||
-optimizationpasses 5
|
||||
-allowaccessmodification
|
||||
-dontpreverify
|
||||
|
||||
# ez-vcard
|
||||
-dontwarn ezvcard.io.json.** # JSON serializer (for jCards) not used
|
||||
-dontwarn freemarker.** # freemarker templating library (for creating hCards) not used
|
||||
-dontwarn org.jsoup.** # jsoup library (for hCard parsing) not used
|
||||
-dontwarn sun.misc.Perf
|
||||
-keep class ezvcard.property.** { *; } # keep all VCard properties (created at runtime)
|
||||
|
||||
# ical4j: ignore unused dynamic libraries
|
||||
-dontwarn aQute.**
|
||||
-dontwarn groovy.** # Groovy-based ContentBuilder not used
|
||||
-dontwarn org.codehaus.groovy.**
|
||||
-dontwarn org.apache.commons.logging.** # Commons logging is not available
|
||||
-dontwarn net.fortuna.ical4j.model.** # ignore warnings from Groovy dependency
|
||||
-keep class net.fortuna.ical4j.model.** { *; } # keep all model classes (properties/factories, created at runtime)
|
||||
|
||||
# okhttp
|
||||
-dontwarn java.nio.file.** # not available on Android
|
||||
-dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement
|
||||
|
||||
# MemorizingTrustManager
|
||||
-dontwarn de.duenndns.ssl.MemorizingTrustManager
|
||||
|
||||
# dnsjava
|
||||
-dontwarn sun.net.spi.nameservice.** # not available on Android
|
||||
|
||||
# DAVdroid + libs
|
||||
-keep class at.bitfire.** { *; } # all DAVdroid code is required
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright © 2013 – 2015 Ricki Hirner (bitfire web engineering).
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the GNU Public License v3.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.gnu.org/licenses/gpl.html
|
||||
*/
|
||||
|
||||
package at.bitfire.davdroid;
|
||||
|
||||
import android.os.Build;
|
||||
import android.test.InstrumentationTestCase;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.Socket;
|
||||
|
||||
import javax.net.ssl.SSLSocket;
|
||||
|
||||
import at.bitfire.cert4android.CustomCertManager;
|
||||
import okhttp3.mockwebserver.MockWebServer;
|
||||
|
||||
public class SSLSocketFactoryCompatTest extends InstrumentationTestCase {
|
||||
|
||||
SSLSocketFactoryCompat factory;
|
||||
MockWebServer server = new MockWebServer();
|
||||
|
||||
@Override
|
||||
protected void setUp() throws Exception {
|
||||
factory = new SSLSocketFactoryCompat(new CustomCertManager(getInstrumentation().getTargetContext().getApplicationContext(), true));
|
||||
server.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void tearDown() throws Exception {
|
||||
server.shutdown();
|
||||
}
|
||||
|
||||
|
||||
public void testUpgradeTLS() throws IOException {
|
||||
Socket s = factory.createSocket(server.getHostName(), server.getPort());
|
||||
assertTrue(s instanceof SSLSocket);
|
||||
|
||||
SSLSocket ssl = (SSLSocket)s;
|
||||
assertFalse(org.apache.commons.lang3.ArrayUtils.contains(ssl.getEnabledProtocols(), "SSLv3"));
|
||||
assertTrue(org.apache.commons.lang3.ArrayUtils.contains(ssl.getEnabledProtocols(), "TLSv1"));
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 16) {
|
||||
assertTrue(org.apache.commons.lang3.ArrayUtils.contains(ssl.getEnabledProtocols(), "TLSv1.1"));
|
||||
assertTrue(org.apache.commons.lang3.ArrayUtils.contains(ssl.getEnabledProtocols(), "TLSv1.2"));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
* Copyright © 2013 – 2016 Ricki Hirner (bitfire web engineering).
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the GNU Public License v3.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.gnu.org/licenses/gpl.html
|
||||
*/
|
||||
|
||||
package at.bitfire.davdroid.model;
|
||||
|
||||
import android.content.ContentValues;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import at.bitfire.dav4android.DavResource;
|
||||
import at.bitfire.dav4android.exception.DavException;
|
||||
import at.bitfire.dav4android.exception.HttpException;
|
||||
import at.bitfire.dav4android.property.ResourceType;
|
||||
import at.bitfire.davdroid.HttpClient;
|
||||
import at.bitfire.davdroid.model.ServiceDB.Collections;
|
||||
import okhttp3.mockwebserver.MockResponse;
|
||||
import okhttp3.mockwebserver.MockWebServer;
|
||||
|
||||
public class CollectionInfoTest extends TestCase {
|
||||
|
||||
MockWebServer server = new MockWebServer();
|
||||
|
||||
public void testFromDavResource() throws IOException, HttpException, DavException {
|
||||
// r/w address book
|
||||
server.enqueue(new MockResponse()
|
||||
.setResponseCode(207)
|
||||
.setBody("<multistatus xmlns='DAV:' xmlns:CARD='urn:ietf:params:xml:ns:carddav'>" +
|
||||
"<response>" +
|
||||
" <href>/</href>" +
|
||||
" <propstat><prop>" +
|
||||
" <resourcetype><collection/><CARD:addressbook/></resourcetype>" +
|
||||
" <displayname>My Contacts</displayname>" +
|
||||
" <CARD:addressbook-description>My Contacts Description</CARD:addressbook-description>" +
|
||||
" </prop></propstat>" +
|
||||
"</response>" +
|
||||
"</multistatus>"));
|
||||
|
||||
DavResource dav = new DavResource(HttpClient.create(null), server.url("/"));
|
||||
dav.propfind(0, ResourceType.NAME);
|
||||
CollectionInfo info = CollectionInfo.fromDavResource(dav);
|
||||
assertEquals(CollectionInfo.Type.ADDRESS_BOOK, info.type);
|
||||
assertFalse(info.readOnly);
|
||||
assertEquals("My Contacts", info.displayName);
|
||||
assertEquals("My Contacts Description", info.description);
|
||||
|
||||
// read-only calendar, no display name
|
||||
server.enqueue(new MockResponse()
|
||||
.setResponseCode(207)
|
||||
.setBody("<multistatus xmlns='DAV:' xmlns:CAL='urn:ietf:params:xml:ns:caldav' xmlns:ICAL='http://apple.com/ns/ical/'>" +
|
||||
"<response>" +
|
||||
" <href>/</href>" +
|
||||
" <propstat><prop>" +
|
||||
" <resourcetype><collection/><CAL:calendar/></resourcetype>" +
|
||||
" <current-user-privilege-set><privilege><read/></privilege></current-user-privilege-set>" +
|
||||
" <CAL:calendar-description>My Calendar</CAL:calendar-description>" +
|
||||
" <CAL:calendar-timezone>tzdata</CAL:calendar-timezone>" +
|
||||
" <ICAL:calendar-color>#ff0000</ICAL:calendar-color>" +
|
||||
" </prop></propstat>" +
|
||||
"</response>" +
|
||||
"</multistatus>"));
|
||||
|
||||
dav = new DavResource(HttpClient.create(null), server.url("/"));
|
||||
dav.propfind(0, ResourceType.NAME);
|
||||
info = CollectionInfo.fromDavResource(dav);
|
||||
assertEquals(CollectionInfo.Type.CALENDAR, info.type);
|
||||
assertTrue(info.readOnly);
|
||||
assertNull(info.displayName);
|
||||
assertEquals("My Calendar", info.description);
|
||||
assertEquals(0xFFFF0000, (int)info.color);
|
||||
assertEquals("tzdata", info.timeZone);
|
||||
assertTrue(info.supportsVEVENT);
|
||||
assertTrue(info.supportsVTODO);
|
||||
}
|
||||
|
||||
public void testFromDB() {
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(Collections.ID, 1);
|
||||
values.put(Collections.SERVICE_ID, 1);
|
||||
values.put(Collections.URL, "http://example.com");
|
||||
values.put(Collections.READ_ONLY, 1);
|
||||
values.put(Collections.DISPLAY_NAME, "display name");
|
||||
values.put(Collections.DESCRIPTION, "description");
|
||||
values.put(Collections.COLOR, 0xFFFF0000);
|
||||
values.put(Collections.TIME_ZONE, "tzdata");
|
||||
values.put(Collections.SUPPORTS_VEVENT, 1);
|
||||
values.put(Collections.SUPPORTS_VTODO, 1);
|
||||
values.put(Collections.SYNC, 1);
|
||||
|
||||
CollectionInfo info = CollectionInfo.fromDB(values);
|
||||
assertEquals(1, info.id);
|
||||
assertEquals(1, (long)info.serviceID);
|
||||
assertEquals("http://example.com", info.url);
|
||||
assertTrue(info.readOnly);
|
||||
assertEquals("display name", info.displayName);
|
||||
assertEquals("description", info.description);
|
||||
assertEquals(0xFFFF0000, (int)info.color);
|
||||
assertEquals("tzdata", info.timeZone);
|
||||
assertTrue(info.supportsVEVENT);
|
||||
assertTrue(info.supportsVTODO);
|
||||
assertTrue(info.selected);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
/*
|
||||
* Copyright © 2013 – 2016 Ricki Hirner (bitfire web engineering).
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the GNU Public License v3.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.gnu.org/licenses/gpl.html
|
||||
*/
|
||||
|
||||
package at.bitfire.davdroid.ui.setup;
|
||||
|
||||
import android.test.InstrumentationTestCase;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
|
||||
import at.bitfire.dav4android.DavResource;
|
||||
import at.bitfire.dav4android.exception.DavException;
|
||||
import at.bitfire.dav4android.exception.HttpException;
|
||||
import at.bitfire.dav4android.property.AddressbookHomeSet;
|
||||
import at.bitfire.dav4android.property.ResourceType;
|
||||
import at.bitfire.davdroid.App;
|
||||
import at.bitfire.davdroid.HttpClient;
|
||||
import at.bitfire.davdroid.ui.setup.DavResourceFinder;
|
||||
import at.bitfire.davdroid.ui.setup.DavResourceFinder.Configuration.ServiceInfo;
|
||||
import at.bitfire.davdroid.ui.setup.LoginCredentials;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.mockwebserver.Dispatcher;
|
||||
import okhttp3.mockwebserver.MockResponse;
|
||||
import okhttp3.mockwebserver.MockWebServer;
|
||||
import okhttp3.mockwebserver.RecordedRequest;
|
||||
|
||||
public class DavResourceFinderTest extends InstrumentationTestCase {
|
||||
|
||||
MockWebServer server = new MockWebServer();
|
||||
|
||||
DavResourceFinder finder;
|
||||
OkHttpClient client;
|
||||
LoginCredentials credentials;
|
||||
|
||||
private static final String
|
||||
PATH_NO_DAV = "/nodav",
|
||||
|
||||
PATH_CALDAV = "/caldav",
|
||||
PATH_CARDDAV = "/carddav",
|
||||
PATH_CALDAV_AND_CARDDAV = "/both-caldav-carddav",
|
||||
|
||||
SUBPATH_PRINCIPAL = "/principal",
|
||||
SUBPATH_ADDRESSBOOK_HOMESET = "/addressbooks",
|
||||
SUBPATH_ADDRESSBOOK = "/addressbooks/private-contacts";
|
||||
|
||||
@Override
|
||||
protected void setUp() throws Exception {
|
||||
server.setDispatcher(new TestDispatcher());
|
||||
server.start();
|
||||
|
||||
credentials = new LoginCredentials(URI.create("/"), "mock", "12345");
|
||||
finder = new DavResourceFinder(getInstrumentation().getTargetContext(), credentials);
|
||||
|
||||
client = HttpClient.create(null);
|
||||
client = HttpClient.addAuthentication(client, credentials.userName, credentials.password);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void tearDown() throws Exception {
|
||||
server.shutdown();
|
||||
}
|
||||
|
||||
|
||||
public void testRememberIfAddressBookOrHomeset() throws IOException, HttpException, DavException {
|
||||
ServiceInfo info;
|
||||
|
||||
// before dav.propfind(), no info is available
|
||||
DavResource dav = new DavResource(client, server.url(PATH_CARDDAV + SUBPATH_PRINCIPAL));
|
||||
finder.rememberIfAddressBookOrHomeset(dav, info = new ServiceInfo());
|
||||
assertEquals(0, info.collections.size());
|
||||
assertEquals(0, info.homeSets.size());
|
||||
|
||||
// recognize home set
|
||||
dav.propfind(0, AddressbookHomeSet.NAME);
|
||||
finder.rememberIfAddressBookOrHomeset(dav, info = new ServiceInfo());
|
||||
assertEquals(0, info.collections.size());
|
||||
assertEquals(1, info.homeSets.size());
|
||||
assertEquals(server.url(PATH_CARDDAV + SUBPATH_ADDRESSBOOK_HOMESET + "/").uri(), info.homeSets.iterator().next());
|
||||
|
||||
// recognize address book
|
||||
dav = new DavResource(client, server.url(PATH_CARDDAV + SUBPATH_ADDRESSBOOK));
|
||||
dav.propfind(0, ResourceType.NAME);
|
||||
finder.rememberIfAddressBookOrHomeset(dav, info = new ServiceInfo());
|
||||
assertEquals(1, info.collections.size());
|
||||
assertEquals(server.url(PATH_CARDDAV + SUBPATH_ADDRESSBOOK + "/").uri(), info.collections.keySet().iterator().next());
|
||||
assertEquals(0, info.homeSets.size());
|
||||
}
|
||||
|
||||
public void testProvidesService() throws IOException {
|
||||
assertFalse(finder.providesService(server.url(PATH_NO_DAV), DavResourceFinder.Service.CALDAV));
|
||||
assertFalse(finder.providesService(server.url(PATH_NO_DAV), DavResourceFinder.Service.CARDDAV));
|
||||
|
||||
assertTrue(finder.providesService(server.url(PATH_CALDAV), DavResourceFinder.Service.CALDAV));
|
||||
assertFalse(finder.providesService(server.url(PATH_CALDAV), DavResourceFinder.Service.CARDDAV));
|
||||
|
||||
assertTrue(finder.providesService(server.url(PATH_CARDDAV), DavResourceFinder.Service.CARDDAV));
|
||||
assertFalse(finder.providesService(server.url(PATH_CARDDAV), DavResourceFinder.Service.CALDAV));
|
||||
|
||||
assertTrue(finder.providesService(server.url(PATH_CALDAV_AND_CARDDAV), DavResourceFinder.Service.CALDAV));
|
||||
assertTrue(finder.providesService(server.url(PATH_CALDAV_AND_CARDDAV), DavResourceFinder.Service.CARDDAV));
|
||||
}
|
||||
|
||||
public void testGetCurrentUserPrincipal() throws IOException, HttpException, DavException {
|
||||
assertNull(finder.getCurrentUserPrincipal(server.url(PATH_NO_DAV), DavResourceFinder.Service.CALDAV));
|
||||
assertNull(finder.getCurrentUserPrincipal(server.url(PATH_NO_DAV), DavResourceFinder.Service.CARDDAV));
|
||||
|
||||
assertEquals(
|
||||
server.url(PATH_CALDAV + SUBPATH_PRINCIPAL).uri(),
|
||||
finder.getCurrentUserPrincipal(server.url(PATH_CALDAV), DavResourceFinder.Service.CALDAV)
|
||||
);
|
||||
assertNull(finder.getCurrentUserPrincipal(server.url(PATH_CALDAV), DavResourceFinder.Service.CARDDAV));
|
||||
|
||||
assertEquals(
|
||||
server.url(PATH_CARDDAV + SUBPATH_PRINCIPAL).uri(),
|
||||
finder.getCurrentUserPrincipal(server.url(PATH_CARDDAV), DavResourceFinder.Service.CARDDAV)
|
||||
);
|
||||
assertNull(finder.getCurrentUserPrincipal(server.url(PATH_CARDDAV), DavResourceFinder.Service.CALDAV));
|
||||
}
|
||||
|
||||
|
||||
// mock server
|
||||
|
||||
public class TestDispatcher extends Dispatcher {
|
||||
|
||||
@Override
|
||||
public MockResponse dispatch(RecordedRequest rq) throws InterruptedException {
|
||||
if (!checkAuth(rq)) {
|
||||
MockResponse authenticate = new MockResponse().setResponseCode(401);
|
||||
authenticate.setHeader("WWW-Authenticate", "Basic realm=\"test\"");
|
||||
return authenticate;
|
||||
}
|
||||
|
||||
String path = rq.getPath();
|
||||
|
||||
if ("OPTIONS".equalsIgnoreCase(rq.getMethod())) {
|
||||
String dav = null;
|
||||
if (path.startsWith(PATH_CALDAV))
|
||||
dav = "calendar-access";
|
||||
else if (path.startsWith(PATH_CARDDAV))
|
||||
dav = "addressbook";
|
||||
else if (path.startsWith(PATH_CALDAV_AND_CARDDAV))
|
||||
dav = "calendar-access, addressbook";
|
||||
MockResponse response = new MockResponse().setResponseCode(200);
|
||||
if (dav != null)
|
||||
response.addHeader("DAV", dav);
|
||||
return response;
|
||||
|
||||
} else if ("PROPFIND".equalsIgnoreCase(rq.getMethod())) {
|
||||
String props = null;
|
||||
switch (path) {
|
||||
case PATH_CALDAV:
|
||||
case PATH_CARDDAV:
|
||||
props = "<current-user-principal><href>" + path + SUBPATH_PRINCIPAL + "</href></current-user-principal>";
|
||||
break;
|
||||
|
||||
case PATH_CARDDAV + SUBPATH_PRINCIPAL:
|
||||
props = "<CARD:addressbook-home-set>" +
|
||||
" <href>" + PATH_CARDDAV + SUBPATH_ADDRESSBOOK_HOMESET + "</href>" +
|
||||
"</CARD:addressbook-home-set>";
|
||||
break;
|
||||
case PATH_CARDDAV + SUBPATH_ADDRESSBOOK:
|
||||
props = "<resourcetype>" +
|
||||
" <collection/>" +
|
||||
" <CARD:addressbook/>" +
|
||||
"</resourcetype>";
|
||||
break;
|
||||
}
|
||||
App.log.info("Sending props: " + props);
|
||||
return new MockResponse()
|
||||
.setResponseCode(207)
|
||||
.setBody("<multistatus xmlns='DAV:' xmlns:CARD='urn:ietf:params:xml:ns:carddav'>" +
|
||||
"<response>" +
|
||||
" <href>" + rq.getPath() + "</href>" +
|
||||
" <propstat><prop>" + props + "</prop></propstat>" +
|
||||
"</response>" +
|
||||
"</multistatus>");
|
||||
}
|
||||
|
||||
return new MockResponse().setResponseCode(404);
|
||||
}
|
||||
|
||||
private boolean checkAuth(RecordedRequest rq) {
|
||||
return "Basic bW9jazoxMjM0NQ==".equals(rq.getHeader("Authorization"));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
14
app/src/androidTest/res/values/strings.xml
Normal file
14
app/src/androidTest/res/values/strings.xml
Normal file
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright (c) 2013 – 2015 Ricki Hirner (bitfire web engineering).
|
||||
~ All rights reserved. This program and the accompanying materials
|
||||
~ are made available under the terms of the GNU Public License v3.0
|
||||
~ which accompanies this distribution, and is available at
|
||||
~ http://www.gnu.org/licenses/gpl.html
|
||||
-->
|
||||
|
||||
<resources>
|
||||
|
||||
<string name="app_name">DavdroidTest</string>
|
||||
|
||||
</resources>
|
||||
@@ -0,0 +1,48 @@
|
||||
package at.bitfire.davdroid.ui;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.view.MenuItem;
|
||||
|
||||
import at.bitfire.davdroid.App;
|
||||
import at.bitfire.davdroid.BuildConfig;
|
||||
import at.bitfire.davdroid.Constants;
|
||||
import at.bitfire.davdroid.R;
|
||||
|
||||
public class DefaultAccountsDrawerHandler implements IAccountsDrawerHandler {
|
||||
|
||||
@Override
|
||||
public boolean onNavigationItemSelected(@NonNull Activity activity, MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.nav_about:
|
||||
activity.startActivity(new Intent(activity, AboutActivity.class));
|
||||
break;
|
||||
case R.id.nav_app_settings:
|
||||
activity.startActivity(new Intent(activity, AppSettingsActivity.class));
|
||||
break;
|
||||
case R.id.nav_twitter:
|
||||
activity.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("https://twitter.com/davdroidapp")));
|
||||
break;
|
||||
case R.id.nav_website:
|
||||
activity.startActivity(new Intent(Intent.ACTION_VIEW, Constants.webUri));
|
||||
break;
|
||||
case R.id.nav_faq:
|
||||
activity.startActivity(new Intent(Intent.ACTION_VIEW, Constants.webUri.buildUpon().appendEncodedPath("faq/").build()));
|
||||
break;
|
||||
case R.id.nav_forums:
|
||||
activity.startActivity(new Intent(Intent.ACTION_VIEW, Constants.webUri.buildUpon().appendEncodedPath("forums/").build()));
|
||||
break;
|
||||
case R.id.nav_donate:
|
||||
if (BuildConfig.FLAVOR != App.FLAVOR_GOOGLE_PLAY)
|
||||
activity.startActivity(new Intent(Intent.ACTION_VIEW, Constants.webUri.buildUpon().appendEncodedPath("donate/").build()));
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,202 @@
|
||||
/*
|
||||
* Copyright © 2013 – 2016 Ricki Hirner (bitfire web engineering).
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the GNU Public License v3.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.gnu.org/licenses/gpl.html
|
||||
*/
|
||||
|
||||
package at.bitfire.davdroid.ui.setup;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.CompoundButton;
|
||||
import android.widget.EditText;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.RadioButton;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.net.IDN;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import at.bitfire.dav4android.Constants;
|
||||
import at.bitfire.davdroid.R;
|
||||
import at.bitfire.davdroid.ui.widget.EditPassword;
|
||||
|
||||
public class DefaultLoginCredentialsFragment extends Fragment implements CompoundButton.OnCheckedChangeListener {
|
||||
|
||||
RadioButton radioUseEmail;
|
||||
LinearLayout emailDetails;
|
||||
EditText editEmailAddress;
|
||||
EditPassword editEmailPassword;
|
||||
|
||||
RadioButton radioUseURL;
|
||||
LinearLayout urlDetails;
|
||||
EditText editBaseURL, editUserName;
|
||||
EditPassword editUrlPassword;
|
||||
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
View v = inflater.inflate(R.layout.login_credentials_fragment, container, false);
|
||||
|
||||
radioUseEmail = (RadioButton)v.findViewById(R.id.login_type_email);
|
||||
emailDetails = (LinearLayout)v.findViewById(R.id.login_type_email_details);
|
||||
editEmailAddress = (EditText)v.findViewById(R.id.email_address);
|
||||
editEmailPassword = (EditPassword)v.findViewById(R.id.email_password);
|
||||
|
||||
radioUseURL = (RadioButton)v.findViewById(R.id.login_type_url);
|
||||
urlDetails = (LinearLayout)v.findViewById(R.id.login_type_url_details);
|
||||
editBaseURL = (EditText)v.findViewById(R.id.base_url);
|
||||
editUserName = (EditText)v.findViewById(R.id.user_name);
|
||||
editUrlPassword = (EditPassword)v.findViewById(R.id.url_password);
|
||||
|
||||
radioUseEmail.setOnCheckedChangeListener(this);
|
||||
radioUseURL.setOnCheckedChangeListener(this);
|
||||
|
||||
if (savedInstanceState == null) {
|
||||
// first call
|
||||
|
||||
Activity activity = getActivity();
|
||||
Intent intent = (activity != null) ? activity.getIntent() : null;
|
||||
if (intent != null) {
|
||||
// we've got initial login data
|
||||
String url = intent.getStringExtra(LoginActivity.EXTRA_URL),
|
||||
username = intent.getStringExtra(LoginActivity.EXTRA_USERNAME),
|
||||
password = intent.getStringExtra(LoginActivity.EXTRA_PASSWORD);
|
||||
|
||||
if (url != null) {
|
||||
radioUseURL.setChecked(true);
|
||||
editBaseURL.setText(url);
|
||||
editUserName.setText(username);
|
||||
editUrlPassword.setText(password);
|
||||
} else {
|
||||
radioUseEmail.setChecked(true);
|
||||
editEmailAddress.setText(username);
|
||||
editEmailPassword.setText(password);
|
||||
}
|
||||
|
||||
} else
|
||||
radioUseEmail.setChecked(true);
|
||||
}
|
||||
|
||||
final Button login = (Button)v.findViewById(R.id.login);
|
||||
login.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
LoginCredentials credentials = validateLoginData();
|
||||
if (credentials != null)
|
||||
DetectConfigurationFragment.newInstance(credentials).show(getFragmentManager(), null);
|
||||
}
|
||||
});
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
|
||||
if (isChecked) {
|
||||
boolean loginByEmail = buttonView == radioUseEmail;
|
||||
emailDetails.setVisibility(loginByEmail ? View.VISIBLE : View.GONE);
|
||||
urlDetails.setVisibility(loginByEmail ? View.GONE : View.VISIBLE);
|
||||
(loginByEmail ? editEmailAddress : editBaseURL).requestFocus();
|
||||
}
|
||||
}
|
||||
|
||||
protected LoginCredentials validateLoginData() {
|
||||
if (radioUseEmail.isChecked()) {
|
||||
URI uri = null;
|
||||
boolean valid = true;
|
||||
|
||||
String email = editEmailAddress.getText().toString();
|
||||
if (!email.matches(".+@.+")) {
|
||||
editEmailAddress.setError(getString(R.string.login_email_address_error));
|
||||
valid = false;
|
||||
} else
|
||||
try {
|
||||
uri = new URI("mailto", email, null);
|
||||
} catch (URISyntaxException e) {
|
||||
editEmailAddress.setError(e.getLocalizedMessage());
|
||||
valid = false;
|
||||
}
|
||||
|
||||
String password = editEmailPassword.getText().toString();
|
||||
if (password.isEmpty()) {
|
||||
editEmailPassword.setError(getString(R.string.login_password_required));
|
||||
valid = false;
|
||||
}
|
||||
|
||||
return valid ? new LoginCredentials(uri, email, password) : null;
|
||||
|
||||
} else if (radioUseURL.isChecked()) {
|
||||
URI uri = null;
|
||||
boolean valid = true;
|
||||
|
||||
Uri baseUrl = Uri.parse(editBaseURL.getText().toString());
|
||||
String scheme = baseUrl.getScheme();
|
||||
if ("https".equalsIgnoreCase(scheme) || "http".equalsIgnoreCase(scheme)) {
|
||||
String host = baseUrl.getHost();
|
||||
if (StringUtils.isEmpty(host)) {
|
||||
editBaseURL.setError(getString(R.string.login_url_host_name_required));
|
||||
valid = false;
|
||||
} else
|
||||
try {
|
||||
host = IDN.toASCII(host);
|
||||
} catch(IllegalArgumentException e) {
|
||||
Constants.log.log(Level.WARNING, "Host name not conforming to RFC 3490", e);
|
||||
}
|
||||
|
||||
String path = baseUrl.getEncodedPath();
|
||||
int port = baseUrl.getPort();
|
||||
try {
|
||||
uri = new URI(baseUrl.getScheme(), null, host, port, path, null, null);
|
||||
} catch (URISyntaxException e) {
|
||||
editBaseURL.setError(e.getLocalizedMessage());
|
||||
valid = false;
|
||||
}
|
||||
} else {
|
||||
editBaseURL.setError(getString(R.string.login_url_must_be_http_or_https));
|
||||
valid = false;
|
||||
}
|
||||
|
||||
String userName = editUserName.getText().toString();
|
||||
if (userName.isEmpty()) {
|
||||
editUserName.setError(getString(R.string.login_user_name_required));
|
||||
valid = false;
|
||||
}
|
||||
|
||||
String password = editUrlPassword.getText().toString();
|
||||
if (password.isEmpty()) {
|
||||
editUrlPassword.setError(getString(R.string.login_password_required));
|
||||
valid = false;
|
||||
}
|
||||
|
||||
return valid ? new LoginCredentials(uri, userName, password) : null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
public static class Factory implements ILoginCredentialsFragment {
|
||||
|
||||
@Override
|
||||
public Fragment getFragment() {
|
||||
return new DefaultLoginCredentialsFragment();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
18
app/src/davdroid/res/drawable/ic_forum_dark.xml
Normal file
18
app/src/davdroid/res/drawable/ic_forum_dark.xml
Normal file
@@ -0,0 +1,18 @@
|
||||
<!--
|
||||
~ Copyright © 2013 – 2016 Ricki Hirner (bitfire web engineering).
|
||||
~ All rights reserved. This program and the accompanying materials
|
||||
~ are made available under the terms of the GNU Public License v3.0
|
||||
~ which accompanies this distribution, and is available at
|
||||
~ http://www.gnu.org/licenses/gpl.html
|
||||
-->
|
||||
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0"
|
||||
android:alpha="0.54" >
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M21,6h-2v9H6v2c0,0.55 0.45,1 1,1h11l4,4V7c0,-0.55 -0.45,-1 -1,-1zm-4,6V3c0,-0.55 -0.45,-1 -1,-1H3c-0.55,0 -1,0.45 -1,1v14l4,-4h10c0.55,0 1,-0.45 1,-1z"/>
|
||||
</vector>
|
||||
18
app/src/davdroid/res/drawable/ic_help_action.xml
Normal file
18
app/src/davdroid/res/drawable/ic_help_action.xml
Normal file
@@ -0,0 +1,18 @@
|
||||
<!--
|
||||
~ Copyright © 2013 – 2016 Ricki Hirner (bitfire web engineering).
|
||||
~ All rights reserved. This program and the accompanying materials
|
||||
~ are made available under the terms of the GNU Public License v3.0
|
||||
~ which accompanies this distribution, and is available at
|
||||
~ http://www.gnu.org/licenses/gpl.html
|
||||
-->
|
||||
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0"
|
||||
android:alpha="0.54" >
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zm1,17h-2v-2h2v2zm2.07,-7.75l-0.9,0.92C13.45,12.9 13,13.5 13,15h-2v-0.5c0,-1.1 0.45,-2.1 1.17,-2.83l1.24,-1.26c0.37,-0.36 0.59,-0.86 0.59,-1.41 0,-1.1 -0.9,-2 -2,-2s-2,0.9 -2,2H8c0,-2.21 1.79,-4 4,-4s4,1.79 4,4c0,0.88 -0.36,1.68 -0.93,2.25z"/>
|
||||
</vector>
|
||||
5
app/src/davdroid/res/drawable/ic_settings_action.xml
Normal file
5
app/src/davdroid/res/drawable/ic_settings_action.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<vector android:alpha="0.54" android:height="24dp"
|
||||
android:viewportHeight="24.0" android:viewportWidth="24.0"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="#FF000000" android:pathData="M19.43,12.98c0.04,-0.32 0.07,-0.64 0.07,-0.98s-0.03,-0.66 -0.07,-0.98l2.11,-1.65c0.19,-0.15 0.24,-0.42 0.12,-0.64l-2,-3.46c-0.12,-0.22 -0.39,-0.3 -0.61,-0.22l-2.49,1c-0.52,-0.4 -1.08,-0.73 -1.69,-0.98l-0.38,-2.65C14.46,2.18 14.25,2 14,2h-4c-0.25,0 -0.46,0.18 -0.49,0.42l-0.38,2.65c-0.61,0.25 -1.17,0.59 -1.69,0.98l-2.49,-1c-0.23,-0.09 -0.49,0 -0.61,0.22l-2,3.46c-0.13,0.22 -0.07,0.49 0.12,0.64l2.11,1.65c-0.04,0.32 -0.07,0.65 -0.07,0.98s0.03,0.66 0.07,0.98l-2.11,1.65c-0.19,0.15 -0.24,0.42 -0.12,0.64l2,3.46c0.12,0.22 0.39,0.3 0.61,0.22l2.49,-1c0.52,0.4 1.08,0.73 1.69,0.98l0.38,2.65c0.03,0.24 0.24,0.42 0.49,0.42h4c0.25,0 0.46,-0.18 0.49,-0.42l0.38,-2.65c0.61,-0.25 1.17,-0.59 1.69,-0.98l2.49,1c0.23,0.09 0.49,0 0.61,-0.22l2,-3.46c0.12,-0.22 0.07,-0.49 -0.12,-0.64l-2.11,-1.65zM12,15.5c-1.93,0 -3.5,-1.57 -3.5,-3.5s1.57,-3.5 3.5,-3.5 3.5,1.57 3.5,3.5 -1.57,3.5 -3.5,3.5z"/>
|
||||
</vector>
|
||||
14
app/src/davdroid/res/drawable/ic_share_action.xml
Normal file
14
app/src/davdroid/res/drawable/ic_share_action.xml
Normal file
@@ -0,0 +1,14 @@
|
||||
<!--
|
||||
~ Copyright © 2013 – 2016 Ricki Hirner (bitfire web engineering).
|
||||
~ All rights reserved. This program and the accompanying materials
|
||||
~ are made available under the terms of the GNU Public License v3.0
|
||||
~ which accompanies this distribution, and is available at
|
||||
~ http://www.gnu.org/licenses/gpl.html
|
||||
-->
|
||||
|
||||
<vector android:height="24dp"
|
||||
android:viewportHeight="24.0" android:viewportWidth="24.0"
|
||||
android:alpha="0.54"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="#FF000000" android:pathData="M18,16.08c-0.76,0 -1.44,0.3 -1.96,0.77L8.91,12.7c0.05,-0.23 0.09,-0.46 0.09,-0.7s-0.04,-0.47 -0.09,-0.7l7.05,-4.11c0.54,0.5 1.25,0.81 2.04,0.81 1.66,0 3,-1.34 3,-3s-1.34,-3 -3,-3 -3,1.34 -3,3c0,0.24 0.04,0.47 0.09,0.7L8.04,9.81C7.5,9.31 6.79,9 6,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3c0.79,0 1.5,-0.31 2.04,-0.81l7.12,4.16c-0.05,0.21 -0.08,0.43 -0.08,0.65 0,1.61 1.31,2.92 2.92,2.92 1.61,0 2.92,-1.31 2.92,-2.92s-1.31,-2.92 -2.92,-2.92z"/>
|
||||
</vector>
|
||||
5
app/src/davdroid/res/drawable/ic_sync_action.xml
Normal file
5
app/src/davdroid/res/drawable/ic_sync_action.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<vector android:alpha="0.54" android:height="24dp"
|
||||
android:viewportHeight="24.0" android:viewportWidth="24.0"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="#FF000000" android:pathData="M12,4V1L8,5l4,4V6c3.31,0 6,2.69 6,6 0,1.01 -0.25,1.97 -0.7,2.8l1.46,1.46C19.54,15.03 20,13.57 20,12c0,-4.42 -3.58,-8 -8,-8zm0,14c-3.31,0 -6,-2.69 -6,-6 0,-1.01 0.25,-1.97 0.7,-2.8L5.24,7.74C4.46,8.97 4,10.43 4,12c0,4.42 3.58,8 8,8v3l4,-4 -4,-4v3z"/>
|
||||
</vector>
|
||||
33
app/src/davdroid/res/drawable/twitter.xml
Normal file
33
app/src/davdroid/res/drawable/twitter.xml
Normal file
@@ -0,0 +1,33 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="277"
|
||||
android:viewportHeight="277">
|
||||
|
||||
<group android:translateY="-0.7250061"
|
||||
tools:ignore="VectorRaster">
|
||||
<group android:scaleX="0.12466422"
|
||||
android:scaleY="0.12466422">
|
||||
<path
|
||||
android:fillColor="#6aa9e0"
|
||||
android:pathData="M 2198.4655,429.89797 c -80.4733,35.82942 -166.9548,60.04502 -257.7242,70.936
|
||||
92.6495,-55.75608 163.7947,-144.04056 197.2992,-249.2398 -86.6974,51.62778
|
||||
-182.7311,89.11977 -284.9407,109.32754 -81.8574,-87.5536 -198.4753,-142.26556
|
||||
-327.5334,-142.26556 -247.8202,0 -448.7354,201.70015 -448.7354,450.48215
|
||||
0,35.30737 3.976,69.69109 11.6242,102.66123 C 715.52416,753.01338
|
||||
384.88755,573.66545 163.56787,301.11765 c -38.62542,66.53463 -60.74738,143.91205
|
||||
-60.74738,226.47796 0,156.2889 79.21645,294.17718 199.61849,374.96004
|
||||
-73.55401,-2.33722 -142.74319,-22.60121 -203.244156,-56.34239 -0.0337,1.87942
|
||||
-0.0401,3.75884 -0.0401,5.66235 0,218.26959 154.674526,400.34659
|
||||
359.949116,441.72919 -37.65343,10.2934 -77.29807,15.7967 -118.21711,15.7967
|
||||
-28.91654,0 -57.02573,-2.8175 -84.423,-8.079 57.10091,178.9785
|
||||
222.81246,309.2205 419.16928,312.8565 -153.5713,120.8209 -347.05293,192.8427
|
||||
-557.28115,192.8427 -36.219604,0 -71.938914,-2.1275 -107.042597,-6.2871
|
||||
198.581677,127.8136 434.444767,202.3893 687.848917,202.3893 825.36002,0
|
||||
1276.69552,-686.4473 1276.69552,-1281.7474 0,-19.5331 -0.432,-38.96179
|
||||
-1.2961,-58.27803 87.6657,-63.51471 163.7469,-142.85991 223.9079,-233.2005" />
|
||||
</group>
|
||||
</group>
|
||||
</vector>
|
||||
120
app/src/davdroid/res/layout/login_credentials_fragment.xml
Normal file
120
app/src/davdroid/res/layout/login_credentials_fragment.xml
Normal file
@@ -0,0 +1,120 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright © 2013 – 2016 Ricki Hirner (bitfire web engineering).
|
||||
~ All rights reserved. This program and the accompanying materials
|
||||
~ are made available under the terms of the GNU Public License v3.0
|
||||
~ which accompanies this distribution, and is available at
|
||||
~ http://www.gnu.org/licenses/gpl.html
|
||||
-->
|
||||
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<!-- We don't want the keyboard up when the user arrives in this initial screen -->
|
||||
<View android:layout_height="0dp"
|
||||
android:layout_width="0dp"
|
||||
android:focusable="true"
|
||||
android:focusableInTouchMode="true"
|
||||
android:contentDescription="@null"
|
||||
android:importantForAccessibility="no" tools:ignore="UnusedAttribute">
|
||||
<requestFocus/>
|
||||
</View>
|
||||
|
||||
<ScrollView android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
android:layout_margin="@dimen/activity_margin">
|
||||
|
||||
<RadioGroup
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:animateLayoutChanges="true">
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/login_type_email"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/login_type_email"
|
||||
android:paddingLeft="14dp" tools:ignore="RtlSymmetry"
|
||||
style="@style/login_type_headline"/>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/login_type_email_details"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<EditText
|
||||
android:id="@+id/email_address"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/login_email_address"
|
||||
android:inputType="textEmailAddress"/>
|
||||
<at.bitfire.davdroid.ui.widget.EditPassword
|
||||
android:id="@+id/email_password"
|
||||
android:hint="@string/login_password"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"/>
|
||||
</LinearLayout>
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/login_type_url"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/login_type_url"
|
||||
android:layout_marginTop="16dp"
|
||||
android:paddingLeft="14dp" tools:ignore="RtlSymmetry"
|
||||
style="@style/login_type_headline"/>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/login_type_url_details"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<EditText
|
||||
android:id="@+id/base_url"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/login_base_url"
|
||||
android:inputType="textUri"/>
|
||||
<EditText
|
||||
android:id="@+id/user_name"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/login_user_name"
|
||||
android:inputType="textEmailAddress"/>
|
||||
<at.bitfire.davdroid.ui.widget.EditPassword
|
||||
android:id="@+id/url_password"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/login_password"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</RadioGroup>
|
||||
</ScrollView>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/stepper_nav_bar">
|
||||
|
||||
<Space
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="1"
|
||||
style="@style/stepper_nav_button"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/login"
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/login_login"
|
||||
style="@style/stepper_nav_button"/>
|
||||
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
22
app/src/davdroid/res/values-ca/strings.xml
Normal file
22
app/src/davdroid/res/values-ca/strings.xml
Normal file
@@ -0,0 +1,22 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<!--common strings-->
|
||||
<string name="app_name">DAVdroid</string>
|
||||
<string name="help">Ajuda</string>
|
||||
<!--startup dialogs-->
|
||||
<!--AboutActivity-->
|
||||
<!--global settings-->
|
||||
<!--AccountsActivity-->
|
||||
<!--DavService-->
|
||||
<!--AppSettingsActivity-->
|
||||
<!--AccountActivity-->
|
||||
<!--PermissionsActivity-->
|
||||
<!--AddAccountActivity-->
|
||||
<string name="login_type_email">Entra amb una adreça de correu electrònic</string>
|
||||
<string name="login_type_url">Entra amb una URL i un nom d\'usuari</string>
|
||||
<!--AccountSettingsActivity-->
|
||||
<!--collection management-->
|
||||
<!--ExceptionInfoFragment-->
|
||||
<!--sync errors and DebugInfoActivity-->
|
||||
<!--cert4android-->
|
||||
</resources>
|
||||
243
app/src/davdroid/res/values-cs/strings.xml
Normal file
243
app/src/davdroid/res/values-cs/strings.xml
Normal file
@@ -0,0 +1,243 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<!--common strings-->
|
||||
<string name="app_name">DAVdroid</string>
|
||||
<string name="help">Pomoc</string>
|
||||
<string name="manage_accounts">Spravovat účty</string>
|
||||
<string name="please_wait">Chvíli strpení ...</string>
|
||||
<string name="send">Odeslat</string>
|
||||
<!--startup dialogs-->
|
||||
<string name="startup_battery_optimization">Optimalizace využití baterie</string>
|
||||
<string name="startup_battery_optimization_message">Android může po několika dnech vypnout/prodloužit interval synchronizování DAVdroid. Chcete-li tomuto zabránit, vypněte optimalizaci baterie.</string>
|
||||
<string name="startup_battery_optimization_disable">Vypnout pro DAVdroid</string>
|
||||
<string name="startup_dont_show_again">Již nezobrazovat</string>
|
||||
<string name="startup_development_version">DAVdroid preview vydání</string>
|
||||
<string name="startup_development_version_message">Toto je vývojová verze aplikace DAVdroid. Mějte na paměti, že vše nemusí správně fungovat. Budeme rádi za konstruktivní zpětnou vazbu, která pomůže vylepšit DAVdroid.</string>
|
||||
<string name="startup_development_version_give_feedback">Dát zpětnou vazbu</string>
|
||||
<string name="startup_donate">Open Source informace</string>
|
||||
<string name="startup_donate_message">Jsme velice rádi že používáte DAVdroid, software s otevřeným zdrojovým kódem (GPLv3). Vývoj této aplikace je náročný a trval již několik tisíc hodin, velice nás potěší přispějete-li na jeho vývoj.</string>
|
||||
<string name="startup_donate_now">Zobrazit stránku pro obdarování</string>
|
||||
<string name="startup_donate_later">Možná později</string>
|
||||
<string name="startup_google_play_accounts_removed">Informace o chybě DRM Obchodu Play</string>
|
||||
<string name="startup_google_play_accounts_removed_message">Za určitých podmínek může dojít po restartu nebo aktualizaci aplikace DAVdroid k vymazání účtů kvůli chybě DRM Obchodu Play. Pokud jste postiženi touto chybou (ale pouze v tomto případě), nainstalujte prosím z Obchodu Play aplikaci \"DAVdroid JB Workaround\".</string>
|
||||
<string name="startup_google_play_accounts_removed_more_info">Více informací</string>
|
||||
<string name="startup_opentasks_not_installed">OpenTasks není nainstalován</string>
|
||||
<string name="startup_opentasks_not_installed_message">Aplikace OpenTasks není dostupná, proto nebude DAVdroid moci synchronizovat seznam úkolů.</string>
|
||||
<string name="startup_opentasks_reinstall_davdroid">Po instalaci OpenTasks musíte PŘEINSTALOVAT DAVdroid a přidat znovu své účty (Android chyba).</string>
|
||||
<string name="startup_opentasks_not_installed_install">Nainstalovat OpenTasks</string>
|
||||
<!--AboutActivity-->
|
||||
<string name="about_license_terms">Licenční podmínky</string>
|
||||
<string name="about_license_info_no_warranty">Tento program je distribuován BEZ JAKÉKOLIV ZÁRUKY. Je to volně dostupný software a lze jej za určitých podmínek dále distribuovat.</string>
|
||||
<!--global settings-->
|
||||
<string name="logging_davdroid_file_logging">DAVdroid logování do souboru</string>
|
||||
<string name="logging_to_external_storage">Logování do externího úložiště: %s</string>
|
||||
<string name="logging_to_external_storage_warning">Smazat logy jak nejdříve možno!</string>
|
||||
<string name="logging_couldnt_create_file">Nelze vytvořit externí soubor logu: %s</string>
|
||||
<string name="logging_no_external_storage">Externí úložiště nenalezeno</string>
|
||||
<!--AccountsActivity-->
|
||||
<string name="navigation_drawer_open">Otevřít panel navigace</string>
|
||||
<string name="navigation_drawer_close">Zavřít panel navigace</string>
|
||||
<string name="navigation_drawer_subtitle">CalDAV/CardDAV adapter synchronizace</string>
|
||||
<string name="navigation_drawer_about">O aplikaci / Licence</string>
|
||||
<string name="navigation_drawer_settings">Nastavení</string>
|
||||
<string name="navigation_drawer_news_updates">Novinky & aktualizace</string>
|
||||
<string name="navigation_drawer_external_links">Externí odkazy</string>
|
||||
<string name="navigation_drawer_website">Webová stránka</string>
|
||||
<string name="navigation_drawer_faq">FAQ</string>
|
||||
<string name="navigation_drawer_forums">Komunita</string>
|
||||
<string name="navigation_drawer_donate">Obdarovat</string>
|
||||
<string name="account_list_empty">Vítejte v aplikaci DAVdroid!\n\nNyní můžete přidat CalDAV/CardDAV účet.</string>
|
||||
<!--DavService-->
|
||||
<string name="dav_service_refresh_failed">Vyhledání služby selhalo</string>
|
||||
<string name="dav_service_refresh_couldnt_refresh">Nelze obnovit seznam sbírky</string>
|
||||
<!--AppSettingsActivity-->
|
||||
<string name="app_settings">Nastavení</string>
|
||||
<string name="app_settings_user_interface">Uživatelské prostředí</string>
|
||||
<string name="app_settings_reset_hints">Resetovat nápovědu</string>
|
||||
<string name="app_settings_reset_hints_summary">Znovu povolí vypnuté texty nápovědy</string>
|
||||
<string name="app_settings_reset_hints_success">Budou zobrazovány všechny texty nápovědy</string>
|
||||
<string name="app_settings_security">Zabezpečení</string>
|
||||
<string name="app_settings_distrust_system_certs">Nedůvěřovat systémovým certifikátům</string>
|
||||
<string name="app_settings_distrust_system_certs_on">Systémovým a uživatelem přidaným CA nebude důvěřováno</string>
|
||||
<string name="app_settings_distrust_system_certs_off">Systémovým a uživatelem přidaným CA bude důvěřováno (doporučeno)</string>
|
||||
<string name="app_settings_reset_certificates">Resetovat (ne)důvěryhodné certifikáty</string>
|
||||
<string name="app_settings_reset_certificates_summary">Resetovat důvěryhodnost všech vlastních certifikátů</string>
|
||||
<string name="app_settings_reset_certificates_success">Všechny vlastní certifikáty byly resetovány</string>
|
||||
<string name="app_settings_debug">Ladění</string>
|
||||
<string name="app_settings_log_to_external_storage">Logovat do externího souboru</string>
|
||||
<string name="app_settings_log_to_external_storage_on">Logování do externího úložiště (pokud dostupné)</string>
|
||||
<string name="app_settings_log_to_external_storage_off">Logování do externího souboru je vypnuto</string>
|
||||
<string name="app_settings_show_debug_info">Zobrazit ladící informace</string>
|
||||
<string name="app_settings_show_debug_info_details">Zobrazit/sdílet software a detaily konfigurace</string>
|
||||
<!--AccountActivity-->
|
||||
<string name="account_synchronize_now">Synchronizovat nyní</string>
|
||||
<string name="account_synchronizing_now">Probíhá synchronizace</string>
|
||||
<string name="account_settings">Nastavení účtu</string>
|
||||
<string name="account_delete">Smazat účet</string>
|
||||
<string name="account_delete_confirmation_title">Opravdu smazat účet?</string>
|
||||
<string name="account_delete_confirmation_text">Všechny místní kopie adresáře, kalendářů a úkolů budou smazány.</string>
|
||||
<string name="account_refresh_address_book_list">Obnovit seznam adresářů</string>
|
||||
<string name="account_create_new_address_book">Vytvořit nový adresář</string>
|
||||
<string name="account_refresh_calendar_list">Obnovit seznam kalendářů</string>
|
||||
<string name="account_create_new_calendar">Vytvořit nový kalendář</string>
|
||||
<!--PermissionsActivity-->
|
||||
<string name="permissions_title">DAVdroid oprávnění</string>
|
||||
<string name="permissions_calendar">Oprávnění pro kalendáře</string>
|
||||
<string name="permissions_calendar_details">Pro synchronizaci CalDAV událostí s místním kalendářem potřebuje DAVdroid oprávnění přistupovat ke kalendářům.</string>
|
||||
<string name="permissions_calendar_request">Vyžádat oprávnění kalendáře</string>
|
||||
<string name="permissions_contacts">Oprávnění pro kontakty</string>
|
||||
<string name="permissions_contacts_details">Pro synchronizaci CardDAV adresářů s místními kontakty potřebuje DAVdroid oprávnění přistupovat ke kontaktům.</string>
|
||||
<string name="permissions_contacts_request">Vyžádat oprávnění kontaktů</string>
|
||||
<string name="permissions_opentasks">Oprávnění pro OpenTasks</string>
|
||||
<string name="permissions_opentasks_details">Pro synchronizaci CalDAV událostí s místním seznamem úkolů potřebuje DAVdroid oprávnění přistupovat k OpenTasks.</string>
|
||||
<string name="permissions_opentasks_request">Vyžádat oprávnění OpenTasks</string>
|
||||
<!--AddAccountActivity-->
|
||||
<string name="login_title">Přidat účet</string>
|
||||
<string name="login_type_email">Přihlášení s emailovou adresou</string>
|
||||
<string name="login_email_address">Emailová adresa</string>
|
||||
<string name="login_email_address_error">Vyžadován platný email</string>
|
||||
<string name="login_password">Heslo</string>
|
||||
<string name="login_password_required">Vyžadováno heslo</string>
|
||||
<string name="login_type_url">Přihlášení s URL a uživatelským jménem</string>
|
||||
<string name="login_url_must_be_http_or_https">URL musí začínat na http(s)://</string>
|
||||
<string name="login_url_host_name_required">Vyžadováno hostname</string>
|
||||
<string name="login_user_name">Uživatelské jméno</string>
|
||||
<string name="login_user_name_required">Vyžadováno uživatelské jméno</string>
|
||||
<string name="login_base_url">Základní URL</string>
|
||||
<string name="login_login">Login</string>
|
||||
<string name="login_back">Zpět</string>
|
||||
<string name="login_create_account">Vytvořit účet</string>
|
||||
<string name="login_account_name">Jméno účtu</string>
|
||||
<string name="login_account_name_info">Pro jméno účtu použijte svou emailovou adresu, protože Android bude brát jméno účtu jako údaj pro ORGANIZÁTORA vytvořených událostí. Nelze mít dva účty stejného jména.</string>
|
||||
<string name="login_account_contact_group_method">Metoda seskupování kontaktů:</string>
|
||||
<string name="login_account_name_required">Vyžadováno jméno účtu</string>
|
||||
<string name="login_account_not_created">Účet nelze vytvořit</string>
|
||||
<string name="login_configuration_detection">Vyhledání konfigurace</string>
|
||||
<string name="login_querying_server">Chvíli strpení, probíhá dotazování serveru...</string>
|
||||
<string name="login_no_caldav_carddav">Nelze nalézt službu CalDAV nebo CardDAV.</string>
|
||||
<string name="login_view_logs">Prohlížet logy</string>
|
||||
<!--AccountSettingsActivity-->
|
||||
<string name="settings_title">Nastavení: %s</string>
|
||||
<string name="settings_authentication">Ověření</string>
|
||||
<string name="settings_username">Uživatelské jméno</string>
|
||||
<string name="settings_enter_username">Zadat uživatelské jméno</string>
|
||||
<string name="settings_password">Heslo</string>
|
||||
<string name="settings_password_summary">Aktualizovat heslo dle svého serveru.</string>
|
||||
<string name="settings_enter_password">Vložit své heslo:</string>
|
||||
<string name="settings_sync">Synchronizace</string>
|
||||
<string name="settings_sync_interval_contacts">Interval synchronizace kontaktů</string>
|
||||
<string name="settings_sync_summary_manually">Pouze manuálně</string>
|
||||
<string name="settings_sync_summary_periodically" tools:ignore="PluralsCandidate">Každých %d minut a ihned při lokálních změnách</string>
|
||||
<string name="settings_sync_summary_not_available">Nedostupný</string>
|
||||
<string name="settings_sync_interval_calendars">Interval synchronizace kalendáře</string>
|
||||
<string name="settings_sync_interval_tasks">Interval synchronizace úkolů</string>
|
||||
<string-array name="settings_sync_interval_seconds">
|
||||
<item>-1</item>
|
||||
<item>300</item>
|
||||
<item>600</item>
|
||||
<item>900</item>
|
||||
<item>3600</item>
|
||||
<item>7200</item>
|
||||
<item>14400</item>
|
||||
<item>86400</item>
|
||||
</string-array>
|
||||
<string-array name="settings_sync_interval_names">
|
||||
<item>Pouze manuálně</item>
|
||||
<item>Každých 5 minut</item>
|
||||
<item>Každých 10 minut</item>
|
||||
<item>Každých 15 minut</item>
|
||||
<item>Každou hodinu</item>
|
||||
<item>Každé 2 hodiny</item>
|
||||
<item>Každé 4 hodiny</item>
|
||||
<item>Jednou za den</item>
|
||||
</string-array>
|
||||
<string name="settings_sync_wifi_only">Synchronizovat pouze přes WiFi</string>
|
||||
<string name="settings_sync_wifi_only_on">Synchronizace omezena na WiFi připojení</string>
|
||||
<string name="settings_sync_wifi_only_off">Druh připojení není brán v potaz</string>
|
||||
<string name="settings_sync_wifi_only_ssid">Omezení WiFi SSID</string>
|
||||
<string name="settings_sync_wifi_only_ssid_on">Synchronizace pouze přes %s</string>
|
||||
<string name="settings_sync_wifi_only_ssid_off">Použít všechna WiFi připojení</string>
|
||||
<string name="settings_sync_wifi_only_ssid_message">Zadejte jméno WiFi sítě (SSID) pro omezení synchronizace na tutu síť, nebo ponechte prázdné pro použití všech WiFi připojení.</string>
|
||||
<string name="settings_carddav">CardDAV</string>
|
||||
<string name="settings_contact_group_method">Metoda seskupování kontaktů</string>
|
||||
<string-array name="settings_contact_group_method_values">
|
||||
<item>GROUP_VCARDS</item>
|
||||
<item>CATEGORIES</item>
|
||||
</string-array>
|
||||
<string-array name="settings_contact_group_method_entries">
|
||||
<item>Skupiny jsou oddělené soubory VCard</item>
|
||||
<item>Skupiny jsou kategorie na kontakt</item>
|
||||
</string-array>
|
||||
<string name="settings_rfc6868_for_vcards">Použít RFC6868 pro VCard</string>
|
||||
<string name="settings_rfc6868_for_vcards_on">Uvozovky lze použít v hodnotách parametrů</string>
|
||||
<string name="settings_rfc6868_for_vcards_off">Uvozovky nelze použít v hodnotách parametrů</string>
|
||||
<string name="settings_caldav">CalDAV</string>
|
||||
<string name="settings_sync_time_range_past">Časový limit pro staré události</string>
|
||||
<string name="settings_sync_time_range_past_none">Synchronizovat všechny události</string>
|
||||
<plurals name="settings_sync_time_range_past_days">
|
||||
<item quantity="one">Ignorovat události starší než 1 den</item>
|
||||
<item quantity="few">Ignorovat události starší než %d dny</item>
|
||||
<item quantity="other">Ignorovat události starší než %d dnů</item>
|
||||
</plurals>
|
||||
<string name="settings_sync_time_range_past_message">Události z minulosti starší než vyznačený počet dnů budou ignorovány (lze zadat 0). Ponechte prázdné pro synchronizaci všech událostí.</string>
|
||||
<string name="settings_manage_calendar_colors">Spravovat barvy kalendářů</string>
|
||||
<string name="settings_manage_calendar_colors_on">Barvy kalendářů spravuje DAVdroid</string>
|
||||
<string name="settings_manage_calendar_colors_off">Barvy kalendářů nespravuje DAVdroid</string>
|
||||
<string name="settings_version_update">Aktualizace verze DAVdroid</string>
|
||||
<string name="settings_version_update_settings_updated">Vnitřní nastavení byla aktualizována.</string>
|
||||
<string name="settings_version_update_install_hint">Při problémech odinstalujte a znovu nainstalujte DAVdroid.</string>
|
||||
<!--collection management-->
|
||||
<string name="create_addressbook">Vytvořit adresář</string>
|
||||
<string name="create_addressbook_display_name_hint">Můj adresář</string>
|
||||
<string name="create_calendar">Vytvořit CalDAV sbírku</string>
|
||||
<string name="create_calendar_display_name_hint">Můj kalendář</string>
|
||||
<string name="create_calendar_time_zone">Časová zóna:</string>
|
||||
<string name="create_calendar_type">Typ sbírky:</string>
|
||||
<string name="create_calendar_type_only_events">Kalendář (pouze události)</string>
|
||||
<string name="create_calendar_type_only_tasks">Seznam úkolů (pouze úkoly)</string>
|
||||
<string name="create_calendar_type_events_and_tasks">Kombinovaná (události a úkoly)</string>
|
||||
<string name="create_collection_color">Nastavit barvu sbírky</string>
|
||||
<string name="create_collection_creating">Vytváření sbírky</string>
|
||||
<string name="create_collection_display_name">Zobrazit jméno (nadpis) této sbírky:</string>
|
||||
<string name="create_collection_display_name_required">Nadpis je vyžadován</string>
|
||||
<string name="create_collection_description">Popis (volitelný):</string>
|
||||
<string name="create_collection_home_set">Domácí sbírka:</string>
|
||||
<string name="create_collection_create">Vytvořit</string>
|
||||
<string name="delete_collection">Smazat sbírku</string>
|
||||
<string name="delete_collection_confirm_title">Jste si jisti?</string>
|
||||
<string name="delete_collection_confirm_warning">Tato sbírka (%s) a všechna její data budou odstraněna ze serveru.</string>
|
||||
<string name="delete_collection_deleting_collection">Mazání sbírky</string>
|
||||
<!--ExceptionInfoFragment-->
|
||||
<string name="exception">Došlo k chybě.</string>
|
||||
<string name="exception_httpexception">Došlo k HTTP chybě.</string>
|
||||
<string name="exception_ioexception">Došlo k I/O chybě.</string>
|
||||
<string name="exception_show_details">Zobrazit detaily</string>
|
||||
<!--sync errors and DebugInfoActivity-->
|
||||
<string name="debug_info_title">Ladící informace</string>
|
||||
<string name="sync_error_permissions">DAVdroid oprávnění</string>
|
||||
<string name="sync_error_permissions_text">Vyžadována dodatečná oprávnění</string>
|
||||
<string name="sync_error_calendar">Synchronizace kalendáře selhala (%s)</string>
|
||||
<string name="sync_error_contacts">Synchronizace adresáře selhala (%s)</string>
|
||||
<string name="sync_error_tasks">Synchronizace úkolu selhala (%s)</string>
|
||||
<string name="sync_error">Chyba při %s</string>
|
||||
<string name="sync_error_http_dav">Chyba serveru při %s</string>
|
||||
<string name="sync_error_local_storage">Chyba databáze při %s</string>
|
||||
<string-array name="sync_error_phases">
|
||||
<item>příprava synchronizace</item>
|
||||
<item>dotazování možností</item>
|
||||
<item>zpracovávání místně smazaných záznamů</item>
|
||||
<item>příprava vytvořených/upravených záznamů</item>
|
||||
<item>nahrávání vytvořených/upravených záznamů</item>
|
||||
<item>kontrola stavu synchronizace</item>
|
||||
<item>výpis místních záznamů</item>
|
||||
<item>výpis vzdálených záznamů</item>
|
||||
<item>porovnání místních/vzdálených záznamů</item>
|
||||
<item>stahování vzdálených záznamů</item>
|
||||
<item>uzavírání procesu</item>
|
||||
<item>ukládání stavu synchronizace</item>
|
||||
</string-array>
|
||||
<string name="sync_error_unauthorized">Chybné uživatelské jméno/heslo</string>
|
||||
<!--cert4android-->
|
||||
<string name="certificate_notification_connection_security">DAVdroid: Zabezpečení připojení</string>
|
||||
<string name="trust_certificate_unknown_certificate_found">DAVdroid nalezl neznámý certifikát. Chcete mu důvěřovat?</string>
|
||||
</resources>
|
||||
231
app/src/davdroid/res/values-da/strings.xml
Normal file
231
app/src/davdroid/res/values-da/strings.xml
Normal file
@@ -0,0 +1,231 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<!--common strings-->
|
||||
<string name="app_name">DAVdroid</string>
|
||||
<string name="help">Hjælp</string>
|
||||
<string name="manage_accounts">Administrer konti</string>
|
||||
<string name="please_wait">Vent venligst -</string>
|
||||
<string name="send">Send</string>
|
||||
<!--startup dialogs-->
|
||||
<string name="startup_dont_show_again">Vis ikke igen</string>
|
||||
<string name="startup_development_version">DAVdroid Preview</string>
|
||||
<string name="startup_development_version_message">Dette er udviklingsversionen af DAVdroid. Bemærk, at nogle ting måske ikke fungerer, som man forventer. Giv os gerne konstruktiv kritik for at forbedre DAVdroid.</string>
|
||||
<string name="startup_development_version_give_feedback">Giv feedback</string>
|
||||
<string name="startup_donate">Open-Source Information</string>
|
||||
<string name="startup_donate_message">Det glæder os, at du bruger DAVdroid, som er open source-software (GPLv3). Det er hårdt arbejde at udvikle DAVdroid og taget tusindvis af arbejdstimer, så overvej at donere til projektet.</string>
|
||||
<string name="startup_donate_now">Vis donationsside</string>
|
||||
<string name="startup_donate_later">Måske senere</string>
|
||||
<string name="startup_google_play_accounts_removed">Play Store DRM-fejl: Information</string>
|
||||
<string name="startup_google_play_accounts_removed_message">Under visse tekniske omstændigheder kan DRM fra Play Store bevirke, at alle DAVdroid-konti er væk efter en genstart eller opgradering af DAVdroid. Hvis du er udsat for dette problem (og ellers ikke), opfordres du til at installere DAVDroid JB Workaround\" fra Play Store.</string>
|
||||
<string name="startup_google_play_accounts_removed_more_info">Yderligere oplysninger</string>
|
||||
<string name="startup_opentasks_not_installed">OpenTasks ikke installeret</string>
|
||||
<string name="startup_opentasks_not_installed_message">OpenTasks er ikke til rådighedm så DAVdroid vil ikke kunne synkronisere opgavelister.</string>
|
||||
<string name="startup_opentasks_reinstall_davdroid">Efter at have installeret OpenTasks, vil du være nødt til at GENINSTALLERE DAVdroid og dine konti igen (en fejl i Android).</string>
|
||||
<string name="startup_opentasks_not_installed_install">Installer OpenTasks</string>
|
||||
<!--AboutActivity-->
|
||||
<string name="about_license_terms">Licensforhold</string>
|
||||
<string name="about_license_info_no_warranty">Dette program leveres ABSOLUT UDEN GARANTI. Det er fri software, og du er velkommen til at videredistribuere det under visse betingelse.</string>
|
||||
<!--global settings-->
|
||||
<string name="logging_davdroid_file_logging">DAVdroid fil-logning</string>
|
||||
<string name="logging_to_external_storage">Logger til eksternt datalager: %s</string>
|
||||
<string name="logging_to_external_storage_warning">Slet logfiler hurtigst muligt!</string>
|
||||
<string name="logging_couldnt_create_file">Kunne ikke oprette ekstern logfil: %s</string>
|
||||
<string name="logging_no_external_storage">Eksternt lager ikke funder</string>
|
||||
<!--AccountsActivity-->
|
||||
<string name="navigation_drawer_open">Åbn navigationsvindue</string>
|
||||
<string name="navigation_drawer_close">Luk navigationsvindue</string>
|
||||
<string name="navigation_drawer_subtitle">CalDAV/CardDAV Sync-adapter</string>
|
||||
<string name="navigation_drawer_about">Om / Licens</string>
|
||||
<string name="navigation_drawer_settings">Indstillinger</string>
|
||||
<string name="navigation_drawer_news_updates">Nyheder & opdateringer</string>
|
||||
<string name="navigation_drawer_external_links">Eksterne links</string>
|
||||
<string name="navigation_drawer_website">Hjemmeside</string>
|
||||
<string name="navigation_drawer_faq">FAQ</string>
|
||||
<string name="navigation_drawer_forums">Community</string>
|
||||
<string name="navigation_drawer_donate">Donation</string>
|
||||
<string name="account_list_empty">Velkommen til DAVdroid!\n\nDu kan nu tilføje en CaDAV/CardDAV-konto.</string>
|
||||
<!--DavService-->
|
||||
<string name="dav_service_refresh_failed">Registrering af tjeneste kunne ikke foretages</string>
|
||||
<string name="dav_service_refresh_couldnt_refresh">Kunne opdatere liste over sæt</string>
|
||||
<!--AppSettingsActivity-->
|
||||
<string name="app_settings">Indstillinger</string>
|
||||
<string name="app_settings_user_interface">Brugerflade</string>
|
||||
<string name="app_settings_reset_hints">Nulstil vejledende popups</string>
|
||||
<string name="app_settings_reset_hints_summary">Genaktiverer hjælp, som er blevet lukket tidligere</string>
|
||||
<string name="app_settings_reset_hints_success">Al vejledning vil blive vist igen</string>
|
||||
<string name="app_settings_security">Sikkerhed</string>
|
||||
<string name="app_settings_debug">Debugging</string>
|
||||
<string name="app_settings_log_to_external_storage">Log til ekstern fil</string>
|
||||
<string name="app_settings_log_to_external_storage_on">Logger til eksternt lager (hvis muligt)</string>
|
||||
<string name="app_settings_log_to_external_storage_off">Ekstern logning deaktiveret</string>
|
||||
<string name="app_settings_show_debug_info">Vis debug-info</string>
|
||||
<string name="app_settings_show_debug_info_details">Vis/del software og opsætningsoplysninger</string>
|
||||
<!--AccountActivity-->
|
||||
<string name="account_synchronize_now">Synkroniser nu</string>
|
||||
<string name="account_synchronizing_now">Synkroniserer nu</string>
|
||||
<string name="account_settings">Opsætning af konti</string>
|
||||
<string name="account_delete">Slet konto</string>
|
||||
<string name="account_delete_confirmation_title">Ønsker du at slette konto?</string>
|
||||
<string name="account_delete_confirmation_text">Alle lokale kopier af addessebøger, kalendere og opgavelister vil blive slettet.</string>
|
||||
<string name="account_refresh_address_book_list">Opdater adressebogslister</string>
|
||||
<string name="account_create_new_address_book">Opret ny adressebog</string>
|
||||
<string name="account_refresh_calendar_list">Opdater kalenderliste</string>
|
||||
<string name="account_create_new_calendar">Opret ny kalender</string>
|
||||
<!--PermissionsActivity-->
|
||||
<string name="permissions_title">DAVdroid adgangsrettigheder</string>
|
||||
<string name="permissions_calendar">Kalenderadgange</string>
|
||||
<string name="permissions_calendar_details">For at synkronisere CalDAV-begivenheder med dine lokale kalendere, skal DAVdroid have adgang til dine kalendere.</string>
|
||||
<string name="permissions_calendar_request">Anmod om kalenderadgang</string>
|
||||
<string name="permissions_contacts">Kontakter: Adgangsrettigheder</string>
|
||||
<string name="permissions_contacts_details">DAVdroid er nødt til at have adgang til dine kontakter, hvis CardDAV-adressebøger skal kunne synkronisere med dine kontakter.</string>
|
||||
<string name="permissions_contacts_request">Anmod om adgang til kontakter</string>
|
||||
<string name="permissions_opentasks">OpenTasks: Adgangsrettigheder</string>
|
||||
<string name="permissions_opentasks_details">DAVdroid er nødt til at have adgang til OpenTasks, hvis CalDAV-opgaver skal kunne synkronisere med dine lokale opgavelister.</string>
|
||||
<string name="permissions_opentasks_request">Anmod om adgang til OpenTasks</string>
|
||||
<!--AddAccountActivity-->
|
||||
<string name="login_title">Tilføj konto</string>
|
||||
<string name="login_type_email">Log ind med emailadresse</string>
|
||||
<string name="login_email_address">Emailadresse</string>
|
||||
<string name="login_email_address_error">Gyldig emailadresse påkrævet</string>
|
||||
<string name="login_password">Adgangskode</string>
|
||||
<string name="login_password_required">Adgangskode påkrævet</string>
|
||||
<string name="login_type_url">Log ind med URL og brugernavn</string>
|
||||
<string name="login_url_must_be_http_or_https">URL skal begynde med http(s)://</string>
|
||||
<string name="login_url_host_name_required">Værtsnavn påkrævet</string>
|
||||
<string name="login_user_name">Brugernavn</string>
|
||||
<string name="login_user_name_required">Brugernavn påkrævet</string>
|
||||
<string name="login_base_url">Basis-URL</string>
|
||||
<string name="login_login">Login</string>
|
||||
<string name="login_back">Tilbage</string>
|
||||
<string name="login_create_account">Opret konto</string>
|
||||
<string name="login_account_name">Kontonavn</string>
|
||||
<string name="login_account_name_info">Brug emailadressen som kontonavn, for Android vil bruge kontonavnet til ORGANIZER-feltet for aktiviteter, som du opretter. Du kan ikke have to konti med samme navn.</string>
|
||||
<string name="login_account_contact_group_method">Gruppering af kontakter:</string>
|
||||
<string name="login_account_name_required">Kontonavn påkrævet</string>
|
||||
<string name="login_account_not_created">Konto kunne ikke oprettes</string>
|
||||
<string name="login_configuration_detection">Check konfiguration</string>
|
||||
<string name="login_querying_server">Vent, forespørger hos serveren...</string>
|
||||
<string name="login_no_caldav_carddav">Kunne ikke finde CalDAV- eller CardDAV-tjeneste.</string>
|
||||
<string name="login_view_logs">Vis logs.</string>
|
||||
<!--AccountSettingsActivity-->
|
||||
<string name="settings_title">Indstillinger: %s</string>
|
||||
<string name="settings_authentication">Adgangsgodkendelse</string>
|
||||
<string name="settings_username">Brugernavn</string>
|
||||
<string name="settings_enter_username">Indtast brugernavn:</string>
|
||||
<string name="settings_password">Adgangskode</string>
|
||||
<string name="settings_password_summary">Opdater adgangskoden, så den svarer til din server.</string>
|
||||
<string name="settings_enter_password">Indtast adgangskode:</string>
|
||||
<string name="settings_sync">Synkronisering</string>
|
||||
<string name="settings_sync_interval_contacts">Synkroniseringsinterval for kontakter</string>
|
||||
<string name="settings_sync_summary_manually">Kun manuelt</string>
|
||||
<string name="settings_sync_summary_periodically" tools:ignore="PluralsCandidate">Hver %d minutter + øjeblikkeligt ved lokale ændringer</string>
|
||||
<string name="settings_sync_summary_not_available">Ikke tilgængeligt</string>
|
||||
<string name="settings_sync_interval_calendars">Synkroniseringsinterval for kalender</string>
|
||||
<string name="settings_sync_interval_tasks">Synkroniseringsinterval for opgaver</string>
|
||||
<string-array name="settings_sync_interval_seconds">
|
||||
<item>-1</item>
|
||||
<item>300</item>
|
||||
<item>600</item>
|
||||
<item>900</item>
|
||||
<item>3600</item>
|
||||
<item>7200</item>
|
||||
<item>14400</item>
|
||||
<item>86400</item>
|
||||
</string-array>
|
||||
<string-array name="settings_sync_interval_names">
|
||||
<item>Kun manuelt</item>
|
||||
<item>Hver 5. minut</item>
|
||||
<item>Hver 10. minut</item>
|
||||
<item>Hver 15. minut</item>
|
||||
<item>En gang i timen</item>
|
||||
<item>En gang hver 2. time</item>
|
||||
<item>En gang hver 4. time</item>
|
||||
<item>En gang om dagen</item>
|
||||
</string-array>
|
||||
<string name="settings_sync_wifi_only">Synkroniser kun over WiFi</string>
|
||||
<string name="settings_sync_wifi_only_on">Synkronisering er begrænset til WiFi-forbindelser</string>
|
||||
<string name="settings_sync_wifi_only_off">Forbindelsestypen har ingen betydning</string>
|
||||
<string name="settings_sync_wifi_only_ssid">Begræsning til WiFi-SSID</string>
|
||||
<string name="settings_sync_wifi_only_ssid_on">Vil kun blive synkroniseret over %s</string>
|
||||
<string name="settings_sync_wifi_only_ssid_off">Alle WiFi-forbindelse vil kunne bruges</string>
|
||||
<string name="settings_sync_wifi_only_ssid_message">Indtast navnet på et WiFi-netværk (SSID) for at begrænse synkronisering til dette netværk, eller efterlad feltet blank for at acceptere alle WiFi-forbindelser.</string>
|
||||
<string name="settings_carddav">CardDAV</string>
|
||||
<string name="settings_contact_group_method">Gruppering af kontakter</string>
|
||||
<string-array name="settings_contact_group_method_values">
|
||||
<item>GROUP_VCARDS</item>
|
||||
<item>CATEGORIES</item>
|
||||
</string-array>
|
||||
<string-array name="settings_contact_group_method_entries">
|
||||
<item>Grupper er særskilte VCards</item>
|
||||
<item>Grupper er kategorier pr. kontakt</item>
|
||||
</string-array>
|
||||
<string name="settings_rfc6868_for_vcards">Anvend RFC6868 for VCards</string>
|
||||
<string name="settings_rfc6868_for_vcards_on">Dobbelte citationstegn kan bruges til at sætte parametre</string>
|
||||
<string name="settings_rfc6868_for_vcards_off">Dobbelte citationstegn kan ikke bruges til at sætte parametre</string>
|
||||
<string name="settings_caldav">CalDAV</string>
|
||||
<string name="settings_sync_time_range_past">Tidsafgrænsning for tidligere begivenheder</string>
|
||||
<string name="settings_sync_time_range_past_none">Alle begivenheder vil blive synkroniseret</string>
|
||||
<plurals name="settings_sync_time_range_past_days">
|
||||
<item quantity="one">Begivenheder ældre end en dag vil blive ignoreret</item>
|
||||
<item quantity="other">Begivenheder, der er mere end %d dage gamle, vil blive ignoreret</item>
|
||||
</plurals>
|
||||
<string name="settings_sync_time_range_past_message">Begivenheder, som er mere end dette antal dage gamle vil blive ignoreret (kan også være 0). Hvis feltet ikke er udfyldt, vil alle begivenheder blive synkroniseret.</string>
|
||||
<string name="settings_manage_calendar_colors">Administrer farver for kalender</string>
|
||||
<string name="settings_manage_calendar_colors_on">Kalenderfarver administreres af DAVdroid</string>
|
||||
<string name="settings_manage_calendar_colors_off">Kalenderfarver sættes ikke fra DAVdroid</string>
|
||||
<string name="settings_version_update">Versionsopdatering af DAVdroid</string>
|
||||
<string name="settings_version_update_settings_updated">Interne indstillinger er blevet opdateret.</string>
|
||||
<string name="settings_version_update_install_hint">Problemer? Afinstaller DAVdroid og geninstaller.</string>
|
||||
<!--collection management-->
|
||||
<string name="create_addressbook">Opret adressebog</string>
|
||||
<string name="create_addressbook_display_name_hint">Min adressebog</string>
|
||||
<string name="create_calendar">Opret CalDAV-sæt</string>
|
||||
<string name="create_calendar_display_name_hint">Min kalender</string>
|
||||
<string name="create_calendar_time_zone">Tidszone:</string>
|
||||
<string name="create_calendar_type">Type af sæt:</string>
|
||||
<string name="create_calendar_type_only_events">Kalender (kun begivenheder)</string>
|
||||
<string name="create_calendar_type_only_tasks">Opgaveliste (kun opgaver)</string>
|
||||
<string name="create_calendar_type_events_and_tasks">Kombineret (begivenheder og opgaver)</string>
|
||||
<string name="create_collection_color">Sæt en farve på sættet</string>
|
||||
<string name="create_collection_creating">Opretter sæt</string>
|
||||
<string name="create_collection_display_name">Navn (titel) på dette sæt:</string>
|
||||
<string name="create_collection_display_name_required">En titel er påkrævet</string>
|
||||
<string name="create_collection_description">Beskrivelse (valgfri):</string>
|
||||
<string name="create_collection_home_set">Home set:</string>
|
||||
<string name="create_collection_create">Opret</string>
|
||||
<string name="delete_collection">Slet sæt</string>
|
||||
<string name="delete_collection_confirm_title">Er du sikker?</string>
|
||||
<string name="delete_collection_confirm_warning">DAV-sættet (%s) og dets data vil blive fjernet fra serveren.</string>
|
||||
<string name="delete_collection_deleting_collection">Sletter CalDAV-sæt</string>
|
||||
<!--ExceptionInfoFragment-->
|
||||
<string name="exception">Der er opstået en fejl.</string>
|
||||
<string name="exception_httpexception">Der er opstået en HTTP-fejl.</string>
|
||||
<string name="exception_ioexception">Der er opstået en I/O-fejl.</string>
|
||||
<string name="exception_show_details">Vis detaljer</string>
|
||||
<!--sync errors and DebugInfoActivity-->
|
||||
<string name="debug_info_title">Debug-info</string>
|
||||
<string name="sync_error_permissions">DAVdroid-rettigheder</string>
|
||||
<string name="sync_error_permissions_text">Yderligere adgang påkrævet</string>
|
||||
<string name="sync_error_calendar">Synkronisering af kalenderen lykkedes ikke (%s)</string>
|
||||
<string name="sync_error_contacts">Synkronisering af adressebogen lykkedes ikke (%s)</string>
|
||||
<string name="sync_error_tasks">Synkronisering af opgaver lykkedes ikke (%s)</string>
|
||||
<string name="sync_error">Fejl under %s</string>
|
||||
<string name="sync_error_http_dav">Serverfejl under %s</string>
|
||||
<string name="sync_error_local_storage">Databasefejl under %s</string>
|
||||
<string-array name="sync_error_phases">
|
||||
<item>forbereder synkronisering</item>
|
||||
<item>checker understøttelse</item>
|
||||
<item>behandler poster, der er slettet lokalt</item>
|
||||
<item>behandler poster, der er blevet oprettet/redigeret</item>
|
||||
<item>uploader poster, der er blevet oprettet/redigeret</item>
|
||||
<item>checker synkroniseringsstatus</item>
|
||||
<item>laver liste af lokale poster</item>
|
||||
<item>laver lister af poster på server</item>
|
||||
<item>sammenligner poster lokalt og på server</item>
|
||||
<item>downloader poster på server</item>
|
||||
<item>efterbehandler</item>
|
||||
<item>gemmer synkroniseringsstatus</item>
|
||||
</string-array>
|
||||
<string name="sync_error_unauthorized">Fejl i brugernavn/adgangskode</string>
|
||||
<!--cert4android-->
|
||||
</resources>
|
||||
242
app/src/davdroid/res/values-es/strings.xml
Normal file
242
app/src/davdroid/res/values-es/strings.xml
Normal file
@@ -0,0 +1,242 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<!--common strings-->
|
||||
<string name="app_name">DAVdroid</string>
|
||||
<string name="help">Ayuda</string>
|
||||
<string name="manage_accounts">Administrar cuentas</string>
|
||||
<string name="please_wait">Por favor, espere...</string>
|
||||
<string name="send">Enviar</string>
|
||||
<!--startup dialogs-->
|
||||
<string name="startup_battery_optimization">Optimización de batería</string>
|
||||
<string name="startup_battery_optimization_message">Android puede desactivar/reducir la sincronización de DAVdroid después de unos días. Para prevenir esto, desactiva la optimización.</string>
|
||||
<string name="startup_battery_optimization_disable">Apagar para DAVdroid</string>
|
||||
<string name="startup_dont_show_again">No mostrar de nuevo</string>
|
||||
<string name="startup_development_version">Versión candidata final de DAVdroid</string>
|
||||
<string name="startup_development_version_message">Esta es una versión de desarrollo de DAVdroid. Tenga presente que puede que no todo funcione como espera. Por favor, denos una retroalimentación constructiva para mejorar DAVdroid.</string>
|
||||
<string name="startup_development_version_give_feedback">Dar retroalimentación</string>
|
||||
<string name="startup_donate">Información de código abierto</string>
|
||||
<string name="startup_donate_message">Nos alegra que uses DAVdroid, que es software de código abierto (GPLv3). Debido al duro trabajo que supone el desarrollo de DAVdroid y los cientos de horas de trabajo, por favor, considera hacer una donación.</string>
|
||||
<string name="startup_donate_now">Mostrar página de donación</string>
|
||||
<string name="startup_donate_later">Quizás luego</string>
|
||||
<string name="startup_google_play_accounts_removed">Información de error de DRM de Play Store</string>
|
||||
<string name="startup_google_play_accounts_removed_message">Bajo ciertas condiciones, el DRM de Play Store puede causar que todas las cuentas de DAVdroid se desconfiguren tras un reinicio o una actualización de DAVdroid. Si esto le afecta (y sólo en ese caso), por favor, instale \"DAVdroid JB Workaround\" desde Play Store.</string>
|
||||
<string name="startup_google_play_accounts_removed_more_info">Más información</string>
|
||||
<string name="startup_opentasks_not_installed">OpenTasks no está instalado</string>
|
||||
<string name="startup_opentasks_not_installed_message">La aplicación OpenTasks no está disponible. DAVdroid no podrá sincronizar listas de tareas.</string>
|
||||
<string name="startup_opentasks_reinstall_davdroid">Tras instalar OpenTasks, tendrás que re-instalar DAVdroid y añadir tus cuentas de nuevo (por un error de Android).</string>
|
||||
<string name="startup_opentasks_not_installed_install">Instalar OpenTasks</string>
|
||||
<!--AboutActivity-->
|
||||
<string name="about_license_terms">Términos de la licencia</string>
|
||||
<string name="about_license_info_no_warranty">Este programa viene sin NINGÚN TIPO DE GARANTÍA. Es software libre, y cualquier contribución es bienvenida y redistribuida bajo ciertas condiciones.</string>
|
||||
<!--global settings-->
|
||||
<string name="logging_davdroid_file_logging">Archivo de registro de DAVdroid</string>
|
||||
<string name="logging_to_external_storage">Registrar en almacenamiento externo: %s</string>
|
||||
<string name="logging_to_external_storage_warning">¡Eliminar todos los registros tan pronto como sea posible!</string>
|
||||
<string name="logging_couldnt_create_file">No se puede crear el archivo de registro externo: %s</string>
|
||||
<string name="logging_no_external_storage">Almacenamiento externo no encontrado</string>
|
||||
<!--AccountsActivity-->
|
||||
<string name="navigation_drawer_open">Abrir panel de navegación</string>
|
||||
<string name="navigation_drawer_close">Cerrar panel de navegación</string>
|
||||
<string name="navigation_drawer_subtitle">Adaptador de sincronización CalDAV/CardDAV</string>
|
||||
<string name="navigation_drawer_about">Acerca de / Licencia</string>
|
||||
<string name="navigation_drawer_settings">Ajustes</string>
|
||||
<string name="navigation_drawer_news_updates">Noticias y actualizaciones</string>
|
||||
<string name="navigation_drawer_external_links">Enlaces externos</string>
|
||||
<string name="navigation_drawer_website">Sitio web</string>
|
||||
<string name="navigation_drawer_faq">Preguntas frequentes</string>
|
||||
<string name="navigation_drawer_forums">Comunidad</string>
|
||||
<string name="navigation_drawer_donate">Donar</string>
|
||||
<string name="account_list_empty">Bienvenido a DAVdroid!\n\nAhora puedes añadir una cuenta CalDAV/CardDAV.</string>
|
||||
<!--DavService-->
|
||||
<string name="dav_service_refresh_failed">Falló la detección del servicio</string>
|
||||
<string name="dav_service_refresh_couldnt_refresh">No se pudo refrescar lista de colección</string>
|
||||
<!--AppSettingsActivity-->
|
||||
<string name="app_settings">Ajustes</string>
|
||||
<string name="app_settings_user_interface">Interfaz de usuario</string>
|
||||
<string name="app_settings_reset_hints">Restablecer advertencias</string>
|
||||
<string name="app_settings_reset_hints_summary">Habilita las advertencias que han sido rechazadas con anterioridad</string>
|
||||
<string name="app_settings_reset_hints_success">Todas las advertencias se mostrarán nuevamente</string>
|
||||
<string name="app_settings_security">Seguridad</string>
|
||||
<string name="app_settings_distrust_system_certs">Invalidar los certificados del sistema</string>
|
||||
<string name="app_settings_distrust_system_certs_on">Los CA del sistema y los añadidos por el usuario no serán válidos</string>
|
||||
<string name="app_settings_distrust_system_certs_off">Los CA del sistema y los añadidos por el usuario serán usados y de confianza (recomendado)</string>
|
||||
<string name="app_settings_reset_certificates">Reiniciar certificados (in)validados</string>
|
||||
<string name="app_settings_reset_certificates_summary">Reinicia la validez de todos los certificados particulares</string>
|
||||
<string name="app_settings_reset_certificates_success">Todos los certificados particulares han sido limpiados</string>
|
||||
<string name="app_settings_debug">Depuración</string>
|
||||
<string name="app_settings_log_to_external_storage">Registrar en fichero externo</string>
|
||||
<string name="app_settings_log_to_external_storage_on">Registro en almacenamiento externo (si está disponible)</string>
|
||||
<string name="app_settings_log_to_external_storage_off">El archivo de registro externo está deshabilitado</string>
|
||||
<string name="app_settings_show_debug_info">Mostrar la información de depuración</string>
|
||||
<string name="app_settings_show_debug_info_details">Ver/compartir detalles de software y configuración</string>
|
||||
<!--AccountActivity-->
|
||||
<string name="account_synchronize_now">Sincronizar ahora</string>
|
||||
<string name="account_synchronizing_now">Sincronizando...</string>
|
||||
<string name="account_settings">Ajustes de cuenta</string>
|
||||
<string name="account_delete">Eliminar cuenta</string>
|
||||
<string name="account_delete_confirmation_title">¿Seguro que deseas eliminar la cuenta?</string>
|
||||
<string name="account_delete_confirmation_text">Todas las copias locales de tus contactos, calendarios y tareas serán eliminadas.</string>
|
||||
<string name="account_refresh_address_book_list">Refrescar contactos</string>
|
||||
<string name="account_create_new_address_book">Crear nueva lista de contactos</string>
|
||||
<string name="account_refresh_calendar_list">Refrescar calendario</string>
|
||||
<string name="account_create_new_calendar">Crear nuevo calendario</string>
|
||||
<!--PermissionsActivity-->
|
||||
<string name="permissions_title">Permisos de DAVdroid</string>
|
||||
<string name="permissions_calendar">Permisos de calendario</string>
|
||||
<string name="permissions_calendar_details">Para sincronizar eventos CalDAV con tus calendarios locales, DAVdroid necesita acceder a los mismos.</string>
|
||||
<string name="permissions_calendar_request">Solicitar permisos sobre calendario</string>
|
||||
<string name="permissions_contacts">Permisos de contactos</string>
|
||||
<string name="permissions_contacts_details">Para sincronizar libretas de contactos CadDAV con tus contactos locales, DAVdroid necesita acceder a los mismos.</string>
|
||||
<string name="permissions_contacts_request">Solicitar permisos sobre contactos</string>
|
||||
<string name="permissions_opentasks">Permisos de OpenTasks</string>
|
||||
<string name="permissions_opentasks_details">Para sincronizar listas de tareas CalDAV con tus listas de tareas locales, DAVdroid necesita acceder a OpenTasks.</string>
|
||||
<string name="permissions_opentasks_request">Solicitar permisos sobre OpenTasks</string>
|
||||
<!--AddAccountActivity-->
|
||||
<string name="login_title">Añadir cuenta</string>
|
||||
<string name="login_type_email">Acceder con cuenta de correo</string>
|
||||
<string name="login_email_address">Dirección de correo</string>
|
||||
<string name="login_email_address_error">Se requiere una dirección de correo válida</string>
|
||||
<string name="login_password">Contraseña</string>
|
||||
<string name="login_password_required">Contraseña requerida</string>
|
||||
<string name="login_type_url">Acceder con URL y nombre de usuario</string>
|
||||
<string name="login_url_must_be_http_or_https">La URL debe comenzar con http(s)://</string>
|
||||
<string name="login_url_host_name_required">Nombre de servidor requerido</string>
|
||||
<string name="login_user_name">Nombre de usuario</string>
|
||||
<string name="login_user_name_required">Nombre de usuario requerido</string>
|
||||
<string name="login_base_url">URL base</string>
|
||||
<string name="login_login">Registrar</string>
|
||||
<string name="login_back">Volver</string>
|
||||
<string name="login_create_account">Crear cuenta</string>
|
||||
<string name="login_account_name">Nombre de cuenta</string>
|
||||
<string name="login_account_name_info">Usa tu dirección de correo como nombre de cuenta puesto que Android usará el nombre de la cuenta como campo de \"organizador\" en los eventos que cree. No puedes tener dos cuentas con el mismo nombre.</string>
|
||||
<string name="login_account_contact_group_method">Método de contacto de grupo:</string>
|
||||
<string name="login_account_name_required">Nombre de cuenta requerido</string>
|
||||
<string name="login_account_not_created">La cuenta no pudo ser creada</string>
|
||||
<string name="login_configuration_detection">Detectar configuración</string>
|
||||
<string name="login_querying_server">Por favor espera, consultando al servidor...</string>
|
||||
<string name="login_no_caldav_carddav">No se pudo encontrar el servicio CalDAV o CardDAV.</string>
|
||||
<string name="login_view_logs">Ver registros</string>
|
||||
<!--AccountSettingsActivity-->
|
||||
<string name="settings_title">Ajustes: %s</string>
|
||||
<string name="settings_authentication">Autenticación</string>
|
||||
<string name="settings_username">Nombre de usuario</string>
|
||||
<string name="settings_enter_username">Introduce tu nombre de usuario:</string>
|
||||
<string name="settings_password">Contraseña</string>
|
||||
<string name="settings_password_summary">Actualiza la contraseña de acuerdo a tu servidor.</string>
|
||||
<string name="settings_enter_password">Introduce tu contraseña:</string>
|
||||
<string name="settings_sync">Sincronización</string>
|
||||
<string name="settings_sync_interval_contacts">Intervalo de sincronización de contactos</string>
|
||||
<string name="settings_sync_summary_manually">Solo manualmente</string>
|
||||
<string name="settings_sync_summary_periodically" tools:ignore="PluralsCandidate">Cada %d minutos + inmediatamente con cambios locales</string>
|
||||
<string name="settings_sync_summary_not_available">No disponible</string>
|
||||
<string name="settings_sync_interval_calendars">Intervalo de sincronización de calendarios</string>
|
||||
<string name="settings_sync_interval_tasks">Intervalo de sincronizacion de Tasks</string>
|
||||
<string-array name="settings_sync_interval_seconds">
|
||||
<item>-1</item>
|
||||
<item>300</item>
|
||||
<item>600</item>
|
||||
<item>900</item>
|
||||
<item>3600</item>
|
||||
<item>7200</item>
|
||||
<item>14400</item>
|
||||
<item>86400</item>
|
||||
</string-array>
|
||||
<string-array name="settings_sync_interval_names">
|
||||
<item>Solo manualmente</item>
|
||||
<item>Cada 5 minutos</item>
|
||||
<item>Cada 10 minutos</item>
|
||||
<item>Cada 15 minutos</item>
|
||||
<item>Cada hora</item>
|
||||
<item>Cada 2 horas</item>
|
||||
<item>Cada 4 horas</item>
|
||||
<item>Una vez al día</item>
|
||||
</string-array>
|
||||
<string name="settings_sync_wifi_only">Sincronizar sólo sobre WiFi</string>
|
||||
<string name="settings_sync_wifi_only_on">La sincronización está restringida a conexiones WiFi</string>
|
||||
<string name="settings_sync_wifi_only_off">Tipo de conexión no tenido en cuenta</string>
|
||||
<string name="settings_sync_wifi_only_ssid">Restricción WiFi SSID</string>
|
||||
<string name="settings_sync_wifi_only_ssid_on">Sólo se sincronizará sobre %s</string>
|
||||
<string name="settings_sync_wifi_only_ssid_off">Todas las conexiones WiFi pueden ser usadas</string>
|
||||
<string name="settings_sync_wifi_only_ssid_message">Introduce el nombre de una red WiFi (SSID) para restringir la sincronización a esta red, o deja el campo en blanco para usar todas las conexiones WiFi.</string>
|
||||
<string name="settings_carddav">CardDAV</string>
|
||||
<string name="settings_contact_group_method">Método de contacto de grupo</string>
|
||||
<string-array name="settings_contact_group_method_values">
|
||||
<item>GROUP_VCARDS</item>
|
||||
<item>CATEGORIAS</item>
|
||||
</string-array>
|
||||
<string-array name="settings_contact_group_method_entries">
|
||||
<item>Los groups tienen VCards separadas</item>
|
||||
<item>Los groups tienen una categoría por contacto</item>
|
||||
</string-array>
|
||||
<string name="settings_rfc6868_for_vcards">Usar RFC6868 para VCards</string>
|
||||
<string name="settings_rfc6868_for_vcards_on">Las comillas dobles se pueden usar en los valores de los parámetros</string>
|
||||
<string name="settings_rfc6868_for_vcards_off">Las comillas dobles no se pueden usar en los valores de los parámetros</string>
|
||||
<string name="settings_caldav">CalDAV</string>
|
||||
<string name="settings_sync_time_range_past">Límite de tiempo de eventos pasados</string>
|
||||
<string name="settings_sync_time_range_past_none">Todos los eventos serán sincronizados</string>
|
||||
<plurals name="settings_sync_time_range_past_days">
|
||||
<item quantity="one">Los eventos anteriores a un día serán ignorados</item>
|
||||
<item quantity="other">Los eventos anteriores a %d días serán ignorados</item>
|
||||
</plurals>
|
||||
<string name="settings_sync_time_range_past_message">Los eventos anteriores a este número de días serán ignorados (puede ser 0). Deja en blanco el campo para sincronizar todos los eventos.</string>
|
||||
<string name="settings_manage_calendar_colors">Colores de calendario</string>
|
||||
<string name="settings_manage_calendar_colors_on">Los colores de los calendarios son administrados por DAVdroid</string>
|
||||
<string name="settings_manage_calendar_colors_off">Los colores de los calendarios no son establecidos por DAVdroid</string>
|
||||
<string name="settings_version_update">Actualización de la versión de Android</string>
|
||||
<string name="settings_version_update_settings_updated">Los ajustes internos han sido actualizados.</string>
|
||||
<string name="settings_version_update_install_hint">¿Problemas? Desinstala y vuelve a instalar DAVdroid.</string>
|
||||
<!--collection management-->
|
||||
<string name="create_addressbook">Crear nueva lista de contactos</string>
|
||||
<string name="create_addressbook_display_name_hint">Agendas</string>
|
||||
<string name="create_calendar">Crear colección CalDAV</string>
|
||||
<string name="create_calendar_display_name_hint">Mi calendario</string>
|
||||
<string name="create_calendar_time_zone">Zona horaria:</string>
|
||||
<string name="create_calendar_type">Tipo de colección:</string>
|
||||
<string name="create_calendar_type_only_events">Calendario (sólo eventos)</string>
|
||||
<string name="create_calendar_type_only_tasks">Lista de tareas (sólo tareas)</string>
|
||||
<string name="create_calendar_type_events_and_tasks">Combinado (eventos y tareas)</string>
|
||||
<string name="create_collection_color">Establecer un color</string>
|
||||
<string name="create_collection_creating">Crear colección</string>
|
||||
<string name="create_collection_display_name">Nombre mostrado (título):</string>
|
||||
<string name="create_collection_display_name_required">Título requerido</string>
|
||||
<string name="create_collection_description">Descripción (opcional):</string>
|
||||
<string name="create_collection_home_set">Establecer localización:</string>
|
||||
<string name="create_collection_create">Crear</string>
|
||||
<string name="delete_collection">Eliminar colección</string>
|
||||
<string name="delete_collection_confirm_title">¿Estás seguro/a?</string>
|
||||
<string name="delete_collection_confirm_warning">Esta colección (%s) y toda su información será eliminada del servidor.</string>
|
||||
<string name="delete_collection_deleting_collection">Eliminando colección</string>
|
||||
<!--ExceptionInfoFragment-->
|
||||
<string name="exception">Ocurrió un error.</string>
|
||||
<string name="exception_httpexception">Ha ocurrido un error HTTP.</string>
|
||||
<string name="exception_ioexception">Ha ocurrido un error I/O.</string>
|
||||
<string name="exception_show_details">Mostrar detalles</string>
|
||||
<!--sync errors and DebugInfoActivity-->
|
||||
<string name="debug_info_title">Información de depuración</string>
|
||||
<string name="sync_error_permissions">Permisos de DAVdroid</string>
|
||||
<string name="sync_error_permissions_text">Permisos adicionales requeridos</string>
|
||||
<string name="sync_error_calendar">La sincronización de calendario falló (%s)</string>
|
||||
<string name="sync_error_contacts">La sincronización de agenda falló (%s)</string>
|
||||
<string name="sync_error_tasks">La sincronización de tareas falló (%s)</string>
|
||||
<string name="sync_error">Error al %s</string>
|
||||
<string name="sync_error_http_dav">Error de servidor al %s</string>
|
||||
<string name="sync_error_local_storage">Error de base de datos al %s</string>
|
||||
<string-array name="sync_error_phases">
|
||||
<item>preparando sincronización</item>
|
||||
<item>buscando capacidades</item>
|
||||
<item>procesando entradas borradas localmente</item>
|
||||
<item>preparando entradas creadas/modificadas</item>
|
||||
<item>cargando entradas creadas/modificadas</item>
|
||||
<item>comprobando estado de sincronización</item>
|
||||
<item>enumerando entradas locales</item>
|
||||
<item>enumerando entradas remotas</item>
|
||||
<item>comparando entradas locales/remotas</item>
|
||||
<item>descargando entradas remotas</item>
|
||||
<item>post-procesando</item>
|
||||
<item>guardando estado de sincronización</item>
|
||||
</string-array>
|
||||
<string name="sync_error_unauthorized">Nombre de usuario/contraseña erróneo</string>
|
||||
<!--cert4android-->
|
||||
<string name="certificate_notification_connection_security">DAVdroid: Seguridad de conexión</string>
|
||||
<string name="trust_certificate_unknown_certificate_found">DAVdroid ha encontrado un certificado desconocido. ¿Quieres que sea válido?</string>
|
||||
</resources>
|
||||
230
app/src/davdroid/res/values-fr/strings.xml
Normal file
230
app/src/davdroid/res/values-fr/strings.xml
Normal file
@@ -0,0 +1,230 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<!--common strings-->
|
||||
<string name="app_name">DAVdroid</string>
|
||||
<string name="help">Aide</string>
|
||||
<string name="manage_accounts">Gestion des comptes</string>
|
||||
<string name="please_wait">SVP attendez ...</string>
|
||||
<string name="send">Envoyer</string>
|
||||
<!--startup dialogs-->
|
||||
<string name="startup_battery_optimization">Optimisation de la batterie</string>
|
||||
<string name="startup_battery_optimization_message">Android peut désactiver/réduire la synchronisation de DAVdroid après quelques jours. Pour éviter cela, désactivez l\'optimisation de la batterie.</string>
|
||||
<string name="startup_battery_optimization_disable">Désactivez pour DAVdroid</string>
|
||||
<string name="startup_dont_show_again">Ne plus afficher</string>
|
||||
<string name="startup_development_version">Pré-version de DAVdroid</string>
|
||||
<string name="startup_development_version_message">Il s\'agit d\'une version de développement de DAVdroid. Il se peut que les choses ne fonctionnent pas comme prévu. S’il vous plaît faites-nous un retour pour améliorer DAVdroid.</string>
|
||||
<string name="startup_development_version_give_feedback">Faire un commentaire</string>
|
||||
<string name="startup_donate">Open-Source Information</string>
|
||||
<string name="startup_donate_message">Nous sommes heureux que vous utilisez DAVdroid, qui est un logiciel open-source (GPLv3). Parce que développer DAVdroid est un travail difficile et nous a pris de nombreuses heures, s\'il vous plaît envisager de faire un don.</string>
|
||||
<string name="startup_donate_now">Faire un don</string>
|
||||
<string name="startup_donate_later">Plus tard</string>
|
||||
<string name="startup_google_play_accounts_removed">Erreur information Play Store DRM</string>
|
||||
<string name="startup_google_play_accounts_removed_message">Dans certaines conditions, Play Store DRM peut provoquer la disparition de tous les comptes DAVdroid après un redémarrage ou après la mise à niveau de DAVdroid. Si vous êtes concerné par ce problème (et seulement alors), s\'il vous plaît installer \"DAVdroid JB Solution\" du Play Store.</string>
|
||||
<string name="startup_google_play_accounts_removed_more_info">Plus d\'information</string>
|
||||
<string name="startup_opentasks_not_installed">OpenTasks n\'est pas installé</string>
|
||||
<string name="startup_opentasks_not_installed_message">L\'application OpenTasks n\'est pas disponible, donc DAVdroid ne pourra pas synchroniser des listes de tâches.</string>
|
||||
<string name="startup_opentasks_reinstall_davdroid">Après l\'installation OpenTasks, vous devez RE-INSTALLER DAVdroid et ajoutez vos comptes à nouveau (bug Android).</string>
|
||||
<string name="startup_opentasks_not_installed_install">Installer OpenTasks</string>
|
||||
<!--AboutActivity-->
|
||||
<string name="about_license_terms">Conditions d\'utilisation</string>
|
||||
<string name="about_license_info_no_warranty">Ce programme est fourni sans AUCUNE GARANTIE. C\'est un logiciel libre, et vous êtes en droit de le redistribuer sous certaines conditions.</string>
|
||||
<!--global settings-->
|
||||
<string name="logging_davdroid_file_logging">DAVdroid fichier de journalisation</string>
|
||||
<string name="logging_to_external_storage">Se connecter au stockage externe: %s</string>
|
||||
<string name="logging_to_external_storage_warning">Supprimer les journaux dès que possible!</string>
|
||||
<string name="logging_couldnt_create_file">Impossible de créer le fichier journal externe: %s</string>
|
||||
<string name="logging_no_external_storage">Stockage externe introuvable</string>
|
||||
<!--AccountsActivity-->
|
||||
<string name="navigation_drawer_open">Ouvrir le tiroir de navigation</string>
|
||||
<string name="navigation_drawer_close">Fermer le tiroir de navigation</string>
|
||||
<string name="navigation_drawer_about">A propos / Licence</string>
|
||||
<string name="navigation_drawer_settings">Paramètres</string>
|
||||
<string name="navigation_drawer_news_updates">Actualité & mise à jour</string>
|
||||
<string name="navigation_drawer_external_links">Liens externes</string>
|
||||
<string name="navigation_drawer_website">Site Web</string>
|
||||
<string name="navigation_drawer_faq">FAQ</string>
|
||||
<string name="navigation_drawer_forums">Communauté</string>
|
||||
<string name="navigation_drawer_donate">Faire un don</string>
|
||||
<string name="account_list_empty">Bienvenue sur DAVdroid!\n\nVous pouvez maintenant ajouter un compte CalDAV/CardDAV.</string>
|
||||
<!--DavService-->
|
||||
<string name="dav_service_refresh_failed">La détection du service a échoué</string>
|
||||
<string name="dav_service_refresh_couldnt_refresh">Impossible d\'actualiser la liste de collection</string>
|
||||
<!--AppSettingsActivity-->
|
||||
<string name="app_settings">Paramètres</string>
|
||||
<string name="app_settings_user_interface">Interface utilisateur</string>
|
||||
<string name="app_settings_reset_hints">Réinitialiser les astuces</string>
|
||||
<string name="app_settings_reset_hints_summary">Réactiver les astuces qui ont été vu précédemment</string>
|
||||
<string name="app_settings_reset_hints_success">Toutes les astuces seront affichés à nouveau</string>
|
||||
<string name="app_settings_security">Sécurité</string>
|
||||
<string name="app_settings_reset_certificates_summary">Réinitialiser la confiance de tous les certificats personnalisés</string>
|
||||
<string name="app_settings_reset_certificates_success">Tous les certificats personnalisés ont été effacés</string>
|
||||
<string name="app_settings_debug">Débogage</string>
|
||||
<string name="app_settings_log_to_external_storage">Journaliser dans un fichier externe</string>
|
||||
<string name="app_settings_log_to_external_storage_on">Journaliser sur le stockage externe (si disponible)</string>
|
||||
<string name="app_settings_log_to_external_storage_off">Le fichier externe n\'est pas disponible.</string>
|
||||
<string name="app_settings_show_debug_info">Afficher les infos de débogage</string>
|
||||
<string name="app_settings_show_debug_info_details">Voir/partager l\'application et les détails de configuration</string>
|
||||
<!--AccountActivity-->
|
||||
<string name="account_synchronize_now">Synchroniser maintenant</string>
|
||||
<string name="account_synchronizing_now">Synchronisation en cours</string>
|
||||
<string name="account_settings">Paramètres du compte</string>
|
||||
<string name="account_delete">Supprimer le compte</string>
|
||||
<string name="account_delete_confirmation_title">Voulez-vous vraiment supprimer le compte?</string>
|
||||
<string name="account_delete_confirmation_text">Toutes les copies locales des carnets d\'adresses, des calendriers et des listes de tâches seront supprimées.</string>
|
||||
<string name="account_refresh_address_book_list">Actualiser le carnet d\'adresse</string>
|
||||
<string name="account_create_new_address_book">Créer un nouveau carnet d\'adresse</string>
|
||||
<string name="account_refresh_calendar_list">Actualiser le calendrier</string>
|
||||
<string name="account_create_new_calendar">Créer un nouveau calendrier</string>
|
||||
<!--PermissionsActivity-->
|
||||
<string name="permissions_title">Autorisations DAVdroid</string>
|
||||
<string name="permissions_calendar">Autorisations calendrier</string>
|
||||
<string name="permissions_calendar_details">Pour synchroniser les événements CalDAV avec vos calendriers locaux, DAVdroid a besoin d\'accéder à vos calendriers.</string>
|
||||
<string name="permissions_calendar_request">Demande d\'autorisations d\'accéder au calendrier</string>
|
||||
<string name="permissions_contacts">Autorisations contacts</string>
|
||||
<string name="permissions_contacts_details">Pour synchroniser les carnets d\'adresses de CardDAV avec votre carnet d\'adresses local, DAVdroid a besoin d\'accéder à vos contacts.</string>
|
||||
<string name="permissions_contacts_request">Demande d\'autorisations d\'accéder aux contacts</string>
|
||||
<string name="permissions_opentasks">Autorisations OpenTasks</string>
|
||||
<string name="permissions_opentasks_details">Pour synchroniser les tâches de CalDAV avec vos listes de tâches locales, DAVdroid a besoin d\'accéder à OpenTasks.</string>
|
||||
<string name="permissions_opentasks_request">Demande d\'autorisations d\'accéder à OpenTasks</string>
|
||||
<!--AddAccountActivity-->
|
||||
<string name="login_title">Ajouter un compte</string>
|
||||
<string name="login_type_email">Connexion avec une adresse email</string>
|
||||
<string name="login_email_address">Adresse mail</string>
|
||||
<string name="login_email_address_error">Une adresse e-mail valide est requis</string>
|
||||
<string name="login_password">Mot de passe</string>
|
||||
<string name="login_password_required">Mot de passe requis</string>
|
||||
<string name="login_type_url">Connexion avec une URL et un nom d\'utilisateur</string>
|
||||
<string name="login_url_must_be_http_or_https">L\'URL doit commencer par http(s)://</string>
|
||||
<string name="login_url_host_name_required">Nom d\'hôte requis</string>
|
||||
<string name="login_user_name">Nom d\'utilisateur</string>
|
||||
<string name="login_user_name_required">Nom d\'utilisateur requis</string>
|
||||
<string name="login_base_url">URL de base</string>
|
||||
<string name="login_login">Se connecter</string>
|
||||
<string name="login_back">Retour</string>
|
||||
<string name="login_create_account">Créer un compte</string>
|
||||
<string name="login_account_name">Nom du compte</string>
|
||||
<string name="login_account_name_info">Utilisez votre adresse e-mail comme nom de compte car Android utilisera ce nom en tant que champ ORGANISATEUR pour les événements que vous créerez. Vous ne pouvez pas avoir deux comptes avec le même nom.</string>
|
||||
<string name="login_account_name_required">Nom du compte requis</string>
|
||||
<string name="login_account_not_created">Le compte n\'a pas pu être créé</string>
|
||||
<string name="login_configuration_detection">Détection de la configuration</string>
|
||||
<string name="login_querying_server">S\'il vous plaît patienter, nous interrogeons le serveur ...</string>
|
||||
<string name="login_no_caldav_carddav">Aucun service CalDAV ou CardDAV trouvé.</string>
|
||||
<string name="login_view_logs">Voir infos de débogage</string>
|
||||
<!--AccountSettingsActivity-->
|
||||
<string name="settings_title">Paramètres: %s</string>
|
||||
<string name="settings_authentication">Authentification</string>
|
||||
<string name="settings_username">Nom d\'utilisateur</string>
|
||||
<string name="settings_enter_username">Saisissez votre nom d\'utilisateur :</string>
|
||||
<string name="settings_password">Mot de passe</string>
|
||||
<string name="settings_password_summary">Mettre à jour le mot de passe </string>
|
||||
<string name="settings_enter_password">Saisissez votre mot de passe :</string>
|
||||
<string name="settings_sync">Synchronisation</string>
|
||||
<string name="settings_sync_interval_contacts">Interval de synchronisation des carnets d\'adresses</string>
|
||||
<string name="settings_sync_summary_manually">Manuellement</string>
|
||||
<string name="settings_sync_summary_periodically" tools:ignore="PluralsCandidate">Toutes les %d minutes et immédiatement après un changement local</string>
|
||||
<string name="settings_sync_summary_not_available">Indisponible</string>
|
||||
<string name="settings_sync_interval_calendars">Interval de synchronisation des agendas</string>
|
||||
<string name="settings_sync_interval_tasks">Interval de synchronisation des tâches</string>
|
||||
<string-array name="settings_sync_interval_seconds">
|
||||
<item>-1</item>
|
||||
<item>300</item>
|
||||
<item>600</item>
|
||||
<item>900</item>
|
||||
<item>3600</item>
|
||||
<item>7200</item>
|
||||
<item>14400</item>
|
||||
<item>86400</item>
|
||||
</string-array>
|
||||
<string-array name="settings_sync_interval_names">
|
||||
<item>Manuellement</item>
|
||||
<item>Toutes les 5 minutes</item>
|
||||
<item>Toutes les 10 minutes</item>
|
||||
<item>Toutes les 15 minutes</item>
|
||||
<item>Toutes les heures</item>
|
||||
<item>Toutes les 2 heures</item>
|
||||
<item>Toutes les 4 heures</item>
|
||||
<item>Une fois par jour</item>
|
||||
</string-array>
|
||||
<string name="settings_sync_wifi_only">Synchronisation en Wifi seulement</string>
|
||||
<string name="settings_sync_wifi_only_on">La synchronisation est limitée aux connexions WiFi</string>
|
||||
<string name="settings_sync_wifi_only_off">Le type de connexion n\'est pas pris en charge</string>
|
||||
<string name="settings_sync_wifi_only_ssid">Restriction WiFi SSID</string>
|
||||
<string name="settings_sync_wifi_only_ssid_on">Sera seulement synchroniser avec %s</string>
|
||||
<string name="settings_sync_wifi_only_ssid_off">Toutes les connexions WiFi peuvent être utilisées</string>
|
||||
<string name="settings_sync_wifi_only_ssid_message">Entrez le nom d\'un réseau WiFi (SSID) pour restreindre la synchronisation à ce réseau, ou laisser vide pour autoriser toutes les connexions WiFi.</string>
|
||||
<string name="settings_carddav">CardDAV</string>
|
||||
<string-array name="settings_contact_group_method_values">
|
||||
<item>GROUP_VCARDS</item>
|
||||
<item>CATEGORIES</item>
|
||||
</string-array>
|
||||
<string name="settings_rfc6868_for_vcards">Utiliser RFC6868 pour VCards</string>
|
||||
<string name="settings_rfc6868_for_vcards_on">Les guillemets doubles peuvent être utilisés dans les valeurs de paramètre</string>
|
||||
<string name="settings_rfc6868_for_vcards_off">Les guillemets doubles ne peuvent pas être utilisés dans les valeurs de paramètre</string>
|
||||
<string name="settings_caldav">CalDAV</string>
|
||||
<string name="settings_sync_time_range_past">Limite des événements passés</string>
|
||||
<string name="settings_sync_time_range_past_none">Tous les événements seront synchronisés</string>
|
||||
<plurals name="settings_sync_time_range_past_days">
|
||||
<item quantity="one">Les événements de plus d’un jour passé seront ignorés</item>
|
||||
<item quantity="other">Les événements de plus de %d jours passés seront ignorés</item>
|
||||
</plurals>
|
||||
<string name="settings_sync_time_range_past_message">Les événements antérieurs à ce nombre de jours seront ignorés (peut être 0). Laissez vide pour synchroniser tous les événements.</string>
|
||||
<string name="settings_manage_calendar_colors">Choisir couleur du calendrier</string>
|
||||
<string name="settings_manage_calendar_colors_on">Les couleurs de calendrier sont gérées par DAVdroid</string>
|
||||
<string name="settings_manage_calendar_colors_off">Les couleurs de calendrier ne sont pas gérées par DAVdroid</string>
|
||||
<string name="settings_version_update">Mise à jour de la version de DAVdroid</string>
|
||||
<string name="settings_version_update_settings_updated">Les paramètres internes ont été mis à jour.</string>
|
||||
<string name="settings_version_update_install_hint">Un problème? Désinstaller DAVdroid, puis réinstaller.</string>
|
||||
<!--collection management-->
|
||||
<string name="create_addressbook">Créer un carnet d\'adresses</string>
|
||||
<string name="create_addressbook_display_name_hint">Mon carnet d\'adresses</string>
|
||||
<string name="create_calendar">Créer une collection CalDAV</string>
|
||||
<string name="create_calendar_display_name_hint">Mon calendrier</string>
|
||||
<string name="create_calendar_time_zone">Fuseau horaire:</string>
|
||||
<string name="create_calendar_type">Type de collection:</string>
|
||||
<string name="create_calendar_type_only_events">Calendrier (événements seulement)</string>
|
||||
<string name="create_calendar_type_only_tasks">Liste de tâches (tâches seulement)</string>
|
||||
<string name="create_calendar_type_events_and_tasks">Fusionner (événements et tâches)</string>
|
||||
<string name="create_collection_color">Choisir une couleur pour la collection</string>
|
||||
<string name="create_collection_creating">Création collection</string>
|
||||
<string name="create_collection_display_name">Le nom affiché (titre) pour cette collection:</string>
|
||||
<string name="create_collection_display_name_required">Titre requis</string>
|
||||
<string name="create_collection_description">Description (facultatif)</string>
|
||||
<string name="create_collection_home_set">Accueil:</string>
|
||||
<string name="create_collection_create">Créer</string>
|
||||
<string name="delete_collection">Supprimer la collection</string>
|
||||
<string name="delete_collection_confirm_title">Êtes-vous sur?</string>
|
||||
<string name="delete_collection_confirm_warning">Cette collection (%s) et toutes ses données seront supprimées du serveur.</string>
|
||||
<string name="delete_collection_deleting_collection">Suppression de la collection</string>
|
||||
<!--ExceptionInfoFragment-->
|
||||
<string name="exception">Une erreur est survenue.</string>
|
||||
<string name="exception_httpexception">Une erreur HTTP est survenue.</string>
|
||||
<string name="exception_ioexception">Une erreur I/O est survenue.</string>
|
||||
<string name="exception_show_details">Voir détails</string>
|
||||
<!--sync errors and DebugInfoActivity-->
|
||||
<string name="debug_info_title">Infos de débogage</string>
|
||||
<string name="sync_error_permissions">Autorisations DAVdroid</string>
|
||||
<string name="sync_error_permissions_text">Autorisations supplémentaires demandées</string>
|
||||
<string name="sync_error_calendar">Échec de la synchronisation du calendrier (%s)</string>
|
||||
<string name="sync_error_contacts">Échec de la synchronisation du carnet d\'adresse (%s)</string>
|
||||
<string name="sync_error_tasks">Échec de la synchronisation (%s)</string>
|
||||
<string name="sync_error">Erreur durant %s</string>
|
||||
<string name="sync_error_http_dav">Erreur de serveur durant %s</string>
|
||||
<string name="sync_error_local_storage">Erreur de base de donnée durant %s</string>
|
||||
<string-array name="sync_error_phases">
|
||||
<item>prépare la synchronisation</item>
|
||||
<item>demande les autorisations</item>
|
||||
<item>procède à la suppression des entrées locales</item>
|
||||
<item>prépare les entrées créées/modifiées</item>
|
||||
<item>envoi les entrées créées/modifiées</item>
|
||||
<item>vérifie l\'état de la synchronisation</item>
|
||||
<item>liste les entrées locales</item>
|
||||
<item>liste les entrées distantes</item>
|
||||
<item>compare les entrées locales/distantes</item>
|
||||
<item>télécharge les entrées distantes</item>
|
||||
<item>post-traitement</item>
|
||||
<item>enregistre l\'état de la synchronisation</item>
|
||||
</string-array>
|
||||
<string name="sync_error_unauthorized">Nom d\'utilisateur ou mot de passe erroné</string>
|
||||
<!--cert4android-->
|
||||
<string name="trust_certificate_unknown_certificate_found">DAVdroid a rencontré un certificat inconnu. Voulez-vous lui faire confiance?</string>
|
||||
</resources>
|
||||
242
app/src/davdroid/res/values-hu/strings.xml
Normal file
242
app/src/davdroid/res/values-hu/strings.xml
Normal file
@@ -0,0 +1,242 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<!--common strings-->
|
||||
<string name="app_name">DAVdroid</string>
|
||||
<string name="help">Súgó</string>
|
||||
<string name="manage_accounts">Fiókok kezelése</string>
|
||||
<string name="please_wait">Kérjük, várjon ...</string>
|
||||
<string name="send">Küldés</string>
|
||||
<!--startup dialogs-->
|
||||
<string name="startup_battery_optimization">Akkumulátoroptimalizálás </string>
|
||||
<string name="startup_battery_optimization_message">Az operációs rendszer a DAVdroid szinkronizálást pár nap után leállíthatja vagy visszafoghatja. Ennek elkerülésére kapcsolja ki az akkumulátoroptimalizálást.</string>
|
||||
<string name="startup_battery_optimization_disable">Kikapcsolás a DAVdroid kapcsán</string>
|
||||
<string name="startup_dont_show_again">Ne jelenjen meg többet</string>
|
||||
<string name="startup_development_version">DAVdroid előzetes kiadás</string>
|
||||
<string name="startup_development_version_message">Ez a DAVdroid egy fejlesztői verziója. Elképzelhető, hogy nem minden működik úgy, ahogyan kellene. Ha így lenne, kérjük küldjön a tapasztaltakról visszajelzést.</string>
|
||||
<string name="startup_development_version_give_feedback">Visszajelzés küldése</string>
|
||||
<string name="startup_donate">A forrás nyíltságával kapcsolatos információk</string>
|
||||
<string name="startup_donate_message">Örülünk, hogy használja a DAVdroidot. A DAVdroid nyílt forráskódú (GPLv3) szoftver, de a fejlesztése kemény munkát jelent, már eddig több ezer munkaórát emésztett fel, ezért kérjük, fontolja meg, hogy támogassa munkánkat.</string>
|
||||
<string name="startup_donate_now">Támogatás küldése</string>
|
||||
<string name="startup_donate_later">Talán később</string>
|
||||
<string name="startup_google_play_accounts_removed">Play Áruház DRM hibával kapcsolatos információ</string>
|
||||
<string name="startup_google_play_accounts_removed_message">Bizonyos körülmények között a Play Áruház DRM okozhatja azt, hogy az eszköz újraindítását vagy a DAVdroid frissítését követően a DAVdroid fiókok eltűnnek. Amennyiben (és csak amennyiben) érinti Önt ez a probléma, telepítse a \"DAVdroid JB Workaround\" alkalmazást Play Áruházból.</string>
|
||||
<string name="startup_google_play_accounts_removed_more_info">További információk</string>
|
||||
<string name="startup_opentasks_not_installed">Az OpenTasks nincs telepítve</string>
|
||||
<string name="startup_opentasks_not_installed_message">Az OpenTasks alkalmazás nincs telepítve, így a DAVdroid nem lesz képes szinkronizálni feladatlistákat.</string>
|
||||
<string name="startup_opentasks_reinstall_davdroid">Az OpenTasks telepítését követően újra kell telepíteni a DAVdroit alkalmazást és újra fel kell venni a fiókokat (Android hiba).</string>
|
||||
<string name="startup_opentasks_not_installed_install">Az OpenTasks telepítése</string>
|
||||
<!--AboutActivity-->
|
||||
<string name="about_license_terms">Licencfeltételek</string>
|
||||
<string name="about_license_info_no_warranty">Ehhez a program SEMMIFÉLE GARANCIA NEM JÁR. Ez a program szabad szoftver, ami a bizonyos feltételek mellett szabadon terjeszthető.</string>
|
||||
<!--global settings-->
|
||||
<string name="logging_davdroid_file_logging">DAVdroid fájlalapú naplózás</string>
|
||||
<string name="logging_to_external_storage">Naplózás külső tárhelyre: %s</string>
|
||||
<string name="logging_to_external_storage_warning">Törölje a naplófájlt, amint lehetséges!</string>
|
||||
<string name="logging_couldnt_create_file">Külső naplófájl létrehozása sikertelen: %s</string>
|
||||
<string name="logging_no_external_storage">A külső tárhely nem érhető el</string>
|
||||
<!--AccountsActivity-->
|
||||
<string name="navigation_drawer_open">Navigációs fiók megnyitása</string>
|
||||
<string name="navigation_drawer_close">Navigációs fiók lezárása</string>
|
||||
<string name="navigation_drawer_subtitle">CalDAV/CardDAV szinkronizációs adapter</string>
|
||||
<string name="navigation_drawer_about">Névjegy / Licenc</string>
|
||||
<string name="navigation_drawer_settings">Beállítások</string>
|
||||
<string name="navigation_drawer_news_updates">Hírek és frissítések</string>
|
||||
<string name="navigation_drawer_external_links">Weblapok</string>
|
||||
<string name="navigation_drawer_website">Honlap</string>
|
||||
<string name="navigation_drawer_faq">GYIK</string>
|
||||
<string name="navigation_drawer_forums">Közösség</string>
|
||||
<string name="navigation_drawer_donate">Támogatás</string>
|
||||
<string name="account_list_empty">Üdvözöljük a DAVdroid felhasználók között!\n\nMost már felvehet CalDAV/CardDav fiókokat.</string>
|
||||
<!--DavService-->
|
||||
<string name="dav_service_refresh_failed">Szolgáltatások felderítése nem sikerült</string>
|
||||
<string name="dav_service_refresh_couldnt_refresh">Gyűjteménylista frissítése nem sikerült</string>
|
||||
<!--AppSettingsActivity-->
|
||||
<string name="app_settings">Beállítások</string>
|
||||
<string name="app_settings_user_interface">Felhasználói felület</string>
|
||||
<string name="app_settings_reset_hints">Tippek visszaállítása</string>
|
||||
<string name="app_settings_reset_hints_summary">Újra jelenjen meg az összes tipp</string>
|
||||
<string name="app_settings_reset_hints_success">Az összes tipp újra meg fog jelenni</string>
|
||||
<string name="app_settings_security">Biztonság</string>
|
||||
<string name="app_settings_distrust_system_certs">A rendszertanúsítványok elfogadása</string>
|
||||
<string name="app_settings_distrust_system_certs_on">A rendszer által kezelt, előre vagy felhasználó által telepített tanúsítványok figyelmen kívül lesznek hagyva</string>
|
||||
<string name="app_settings_distrust_system_certs_off">A rendszer által kezelt, előre vagy felhasználó által telepített tanúsítványok megbízhatóak (javasolt)</string>
|
||||
<string name="app_settings_reset_certificates">A tanúsítványok megbízhatóságának törlésére</string>
|
||||
<string name="app_settings_reset_certificates_summary">A tanúsítványok megbízhatóságával kapcsolatos beállítások törlésére</string>
|
||||
<string name="app_settings_reset_certificates_success">A tanúsítványok megbízhatóságával kapcsolatos beállítások törölve</string>
|
||||
<string name="app_settings_debug">Hibakeresés</string>
|
||||
<string name="app_settings_log_to_external_storage">Naplózás fájlba</string>
|
||||
<string name="app_settings_log_to_external_storage_on">Naplózás külső tárhelyre (ha elérhető)</string>
|
||||
<string name="app_settings_log_to_external_storage_off">Fájlalapú naplózás kikapcsolva</string>
|
||||
<string name="app_settings_show_debug_info">Hibakeresési információ megtekintése</string>
|
||||
<string name="app_settings_show_debug_info_details">Szoftver- és konfigurációs részletek megtekintése/megosztása</string>
|
||||
<!--AccountActivity-->
|
||||
<string name="account_synchronize_now">Szinkronizálás most</string>
|
||||
<string name="account_synchronizing_now">Szinkronizálás</string>
|
||||
<string name="account_settings">Fiókbeállítások</string>
|
||||
<string name="account_delete">Fiók törlése</string>
|
||||
<string name="account_delete_confirmation_title">Valóban törölni akarja a fiókot?</string>
|
||||
<string name="account_delete_confirmation_text">Az összes címjegyzék, naptár és feladatlista helyi példányai törölve lesznek.</string>
|
||||
<string name="account_refresh_address_book_list">Címjegyzék-lista frissítése</string>
|
||||
<string name="account_create_new_address_book">Új címjegyzék létrehozása</string>
|
||||
<string name="account_refresh_calendar_list">Naptárlista frissítése</string>
|
||||
<string name="account_create_new_calendar">Új naptár létrehozása</string>
|
||||
<!--PermissionsActivity-->
|
||||
<string name="permissions_title">DAVdroid engedélyek </string>
|
||||
<string name="permissions_calendar">Naptárengedély</string>
|
||||
<string name="permissions_calendar_details">A CalDAV naptárak és a helyi naptárak szinkronizálásához a DAVdroid naptárhozzáférést igényel.</string>
|
||||
<string name="permissions_calendar_request">Naptárhozzáférés igénylése</string>
|
||||
<string name="permissions_contacts">Névjegyengedélyek</string>
|
||||
<string name="permissions_contacts_details">A CardDAV címlisták és a helyi címlisták szinkronizálásához a névjegyhozzáférést igényel.</string>
|
||||
<string name="permissions_contacts_request">Névjegyengedélyek igénylése</string>
|
||||
<string name="permissions_opentasks">OpenTasks engedélyek</string>
|
||||
<string name="permissions_opentasks_details">A CalDAV feladatlisták és a helyi feladatlisták szinkronizálásához a DAVdroid OpenTasks hozzáférést igényel.</string>
|
||||
<string name="permissions_opentasks_request">OpenTasks engedélyek igénylése</string>
|
||||
<!--AddAccountActivity-->
|
||||
<string name="login_title">Fiók hozzáadása</string>
|
||||
<string name="login_type_email">Bejelentkezés email cím segítségével</string>
|
||||
<string name="login_email_address">Email cím:</string>
|
||||
<string name="login_email_address_error">Érvényes email cím megadása feltétlenül szükséges</string>
|
||||
<string name="login_password">Jelszó</string>
|
||||
<string name="login_password_required">A jelszó megadása szükséges</string>
|
||||
<string name="login_type_url">Bejelentkezés URL és felhasználónév segítségével</string>
|
||||
<string name="login_url_must_be_http_or_https">Az URL elején szerepeljen http(s)://</string>
|
||||
<string name="login_url_host_name_required">A szervernév megadása feltétlenül szükséges</string>
|
||||
<string name="login_user_name">Felhasználónév</string>
|
||||
<string name="login_user_name_required">A felhasználónév megadása feltétlenül szükséges</string>
|
||||
<string name="login_base_url">URL-törzs</string>
|
||||
<string name="login_login">Bejelentkezés</string>
|
||||
<string name="login_back">Vissza</string>
|
||||
<string name="login_create_account">Fiók létrehozása</string>
|
||||
<string name="login_account_name">A fiók neve</string>
|
||||
<string name="login_account_name_info">Használja az email címet fióknévként, mert később a létrehozandó események szervezőjeként (ORGANIZER mező) az Android ezt fogja használni. Két fiókot nem lehet azonos néven létrehozni.</string>
|
||||
<string name="login_account_contact_group_method">A csoportok kezelésének módja:</string>
|
||||
<string name="login_account_name_required">A fióknév megadása feltétlenül szükséges</string>
|
||||
<string name="login_account_not_created">A fiók létrehozása nem sikerült</string>
|
||||
<string name="login_configuration_detection">A konfiguráció felderítése</string>
|
||||
<string name="login_querying_server">Kérjük, várjon, a szerver lekérdezése...</string>
|
||||
<string name="login_no_caldav_carddav">Nem található CalDAV vagy CardDAV szolgáltatás.</string>
|
||||
<string name="login_view_logs">Naplóbejegyzések megtekintése</string>
|
||||
<!--AccountSettingsActivity-->
|
||||
<string name="settings_title">Beállítások: %s</string>
|
||||
<string name="settings_authentication">Authentikáció</string>
|
||||
<string name="settings_username">Felhasználónév</string>
|
||||
<string name="settings_enter_username">Adja meg a felhasználónevet:</string>
|
||||
<string name="settings_password">Jelszó</string>
|
||||
<string name="settings_password_summary">Adja meg a szerveren érvényes új jelszót.</string>
|
||||
<string name="settings_enter_password">Adja meg a jelszót:</string>
|
||||
<string name="settings_sync">Szinkronizálás</string>
|
||||
<string name="settings_sync_interval_contacts">Névjegyszinkronizálás sűrűsége</string>
|
||||
<string name="settings_sync_summary_manually">Manuális</string>
|
||||
<string name="settings_sync_summary_periodically" tools:ignore="PluralsCandidate">Minden %d percben + az eszközön történt módosítás után</string>
|
||||
<string name="settings_sync_summary_not_available">Nem elérhető</string>
|
||||
<string name="settings_sync_interval_calendars">Naptárszinkronizálás sűrűsége</string>
|
||||
<string name="settings_sync_interval_tasks">Feladatlisták szinkronizálásának sűrűsége</string>
|
||||
<string-array name="settings_sync_interval_seconds">
|
||||
<item>-1</item>
|
||||
<item>300</item>
|
||||
<item>600</item>
|
||||
<item>900</item>
|
||||
<item>3600</item>
|
||||
<item>7200</item>
|
||||
<item>14400</item>
|
||||
<item>86400</item>
|
||||
</string-array>
|
||||
<string-array name="settings_sync_interval_names">
|
||||
<item>Manuális</item>
|
||||
<item>5 percenként</item>
|
||||
<item>10 percenként</item>
|
||||
<item>15 percenként</item>
|
||||
<item>Óránként</item>
|
||||
<item>2 óránként</item>
|
||||
<item>4 óránként</item>
|
||||
<item>Naponta</item>
|
||||
</string-array>
|
||||
<string name="settings_sync_wifi_only">Szinkronizálás csak WIFI-n</string>
|
||||
<string name="settings_sync_wifi_only_on">Csak WIFI kapcsolat keresztül történjen szinkronizálás</string>
|
||||
<string name="settings_sync_wifi_only_off">Szinkronizálás a kapcsolat típusától függetlenül</string>
|
||||
<string name="settings_sync_wifi_only_ssid">WIFI SSID szűkítés</string>
|
||||
<string name="settings_sync_wifi_only_ssid_on">Szinkronizálás csak a(z) %s hálózatra kapcsolódva</string>
|
||||
<string name="settings_sync_wifi_only_ssid_off">Szinkronizálás bármely WIFI hálózaton</string>
|
||||
<string name="settings_sync_wifi_only_ssid_message">Adja meg a WIFI hálózat nevét (SSID) a szinkronizálás egy hálózatra való korlátozához, vagy hagyja üresen, ha nem akar ilyen szűkítést.</string>
|
||||
<string name="settings_carddav">CardDAV</string>
|
||||
<string name="settings_contact_group_method">A csoportok kezelésének módja</string>
|
||||
<string-array name="settings_contact_group_method_values">
|
||||
<item>GROUP_VCARDS</item>
|
||||
<item>CATEGORIES</item>
|
||||
</string-array>
|
||||
<string-array name="settings_contact_group_method_entries">
|
||||
<item>Minden csoport egy különálló VCard</item>
|
||||
<item>A csoportok a kapcsolatonkéni kategóriák</item>
|
||||
</string-array>
|
||||
<string name="settings_rfc6868_for_vcards">RFC6868 szabványnak megfelelő VCards használata</string>
|
||||
<string name="settings_rfc6868_for_vcards_on">A paraméterértékek tartalmazhatnak idézőjelet</string>
|
||||
<string name="settings_rfc6868_for_vcards_off">A paraméterértékek nem tartalmazhatnak idézőjelet</string>
|
||||
<string name="settings_caldav">CalDAV</string>
|
||||
<string name="settings_sync_time_range_past">Múltbéli események időkorlátja</string>
|
||||
<string name="settings_sync_time_range_past_none">Minden esemény szinkronizálása</string>
|
||||
<plurals name="settings_sync_time_range_past_days">
|
||||
<item quantity="one">Az egy napnál régebbi események figyelmen kívül hagyása</item>
|
||||
<item quantity="other">A %d napnál régebbi események figyelmen kívül hagyása</item>
|
||||
</plurals>
|
||||
<string name="settings_sync_time_range_past_message">Az ennyi napnál (lehet 0) régebbi események figyelmen kívül lesznek hagyva. Hagyja üresen, ha minden múltbéli eseményt szinkronizálni akar.</string>
|
||||
<string name="settings_manage_calendar_colors">Naptárszínek kezelése</string>
|
||||
<string name="settings_manage_calendar_colors_on">A naptárszíneket a DAVdroid kezeli</string>
|
||||
<string name="settings_manage_calendar_colors_off">A naptárszíneket nem a DAVdroid kezeli</string>
|
||||
<string name="settings_version_update">DAVdroid frissítése</string>
|
||||
<string name="settings_version_update_settings_updated">A belső beállítások frissítve lettek.</string>
|
||||
<string name="settings_version_update_install_hint">Probléma? Próbálja meg törölni majd újratelepíteni a DAVdroid-ot.</string>
|
||||
<!--collection management-->
|
||||
<string name="create_addressbook">Címjegyzék létrehozása</string>
|
||||
<string name="create_addressbook_display_name_hint">Új címjegyzék</string>
|
||||
<string name="create_calendar">CalDAV gyűjtemény létrehozása</string>
|
||||
<string name="create_calendar_display_name_hint">Új naptár</string>
|
||||
<string name="create_calendar_time_zone">Időzóna:</string>
|
||||
<string name="create_calendar_type">Gyűjtemény típusa:</string>
|
||||
<string name="create_calendar_type_only_events">Naptár (csak események)</string>
|
||||
<string name="create_calendar_type_only_tasks">Feladatlista (csak feladatok)</string>
|
||||
<string name="create_calendar_type_events_and_tasks">Kombinált (események és feladatok)</string>
|
||||
<string name="create_collection_color">A gyűjtemény színének kiválasztása</string>
|
||||
<string name="create_collection_creating">Gyűjtemény létrehozása</string>
|
||||
<string name="create_collection_display_name">A gyűjtemény megnevezése (címe)</string>
|
||||
<string name="create_collection_display_name_required">A cím megadása feltétlenül szükséges</string>
|
||||
<string name="create_collection_description">Leírás (opcionális):</string>
|
||||
<string name="create_collection_home_set">Gyűjtemények tárolója:</string>
|
||||
<string name="create_collection_create">Létrehozás</string>
|
||||
<string name="delete_collection">Gyűjtemény törlése</string>
|
||||
<string name="delete_collection_confirm_title">Biztos?</string>
|
||||
<string name="delete_collection_confirm_warning">A gyűjtemény (%s) és a hozzá tartozó adatok törölve lesznek a szerverről.</string>
|
||||
<string name="delete_collection_deleting_collection">Gyűjtemény törlése</string>
|
||||
<!--ExceptionInfoFragment-->
|
||||
<string name="exception">Hiba történt.</string>
|
||||
<string name="exception_httpexception">HTTP hiba történt.</string>
|
||||
<string name="exception_ioexception">I/O hiba történt.</string>
|
||||
<string name="exception_show_details">Részletek megjelenítése</string>
|
||||
<!--sync errors and DebugInfoActivity-->
|
||||
<string name="debug_info_title">Hibakeresési információ</string>
|
||||
<string name="sync_error_permissions">DAVdroid engedélyek </string>
|
||||
<string name="sync_error_permissions_text">További engedélyek szükségesek</string>
|
||||
<string name="sync_error_calendar">A naptár szinkronizálása nem sikerült (%s)</string>
|
||||
<string name="sync_error_contacts">A címjegyzék szinkronizálása nem sikerült (%s)</string>
|
||||
<string name="sync_error_tasks">A feladatok szinkronizálása nem sikerült (%s)</string>
|
||||
<string name="sync_error">Hiba az alábbi művelet közben: %s</string>
|
||||
<string name="sync_error_http_dav">Szerver oldali hiba az alábbi művelet közben: %s</string>
|
||||
<string name="sync_error_local_storage">Adatbázishiba az alábbi művelet közben: %s</string>
|
||||
<string-array name="sync_error_phases">
|
||||
<item>szinkronizáció előkészítése </item>
|
||||
<item>szerver képességeinek lekérdezése</item>
|
||||
<item>a helyben törölt bejegyzések feldolgozása</item>
|
||||
<item>az új vagy módosított bejegyzések gyűjtése </item>
|
||||
<item>az új vagy módosított bejegyzések feltöltése</item>
|
||||
<item>szinkronizációs állapot ellenőrzése</item>
|
||||
<item>helyi bejegyzések listázása</item>
|
||||
<item>távoli bejegyzések listázása</item>
|
||||
<item>helyi és távoli bejegyzések összehasonlítása</item>
|
||||
<item>távoli bejegyzések letöltése</item>
|
||||
<item>utófeldolgozás</item>
|
||||
<item>szinkronizációs állapot mentése</item>
|
||||
</string-array>
|
||||
<string name="sync_error_unauthorized">A felhasználónév vagy jelszó hibás</string>
|
||||
<!--cert4android-->
|
||||
<string name="certificate_notification_connection_security">DAVdroid: kapcsolatbiztonság</string>
|
||||
<string name="trust_certificate_unknown_certificate_found">Egy eddig ismeretlen tanúsítvány érkezett. Megbízhatónak kívánja elfogadni?</string>
|
||||
</resources>
|
||||
85
app/src/davdroid/res/values-it/strings.xml
Normal file
85
app/src/davdroid/res/values-it/strings.xml
Normal file
@@ -0,0 +1,85 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<!--common strings-->
|
||||
<string name="app_name">DAVdroid</string>
|
||||
<string name="help">Aiuto</string>
|
||||
<string name="manage_accounts">Gestione account</string>
|
||||
<string name="please_wait">Attendere prego …</string>
|
||||
<!--startup dialogs-->
|
||||
<string name="startup_dont_show_again">Non mostrare più</string>
|
||||
<string name="startup_donate_now">Mostra pagina donazioni</string>
|
||||
<string name="startup_google_play_accounts_removed_more_info">Maggiori informazioni</string>
|
||||
<string name="startup_opentasks_not_installed">OpenTasks non installata</string>
|
||||
<string name="startup_opentasks_not_installed_install">Installa OpenTasks</string>
|
||||
<!--AboutActivity-->
|
||||
<string name="about_license_terms">Termini di licenza</string>
|
||||
<!--global settings-->
|
||||
<!--AccountsActivity-->
|
||||
<string name="navigation_drawer_about">Informazioni / Licenza</string>
|
||||
<string name="navigation_drawer_settings">Impostazioni</string>
|
||||
<string name="navigation_drawer_news_updates">Notizie & aggiornamenti</string>
|
||||
<string name="navigation_drawer_external_links">Link esterni</string>
|
||||
<string name="navigation_drawer_website">Sito web</string>
|
||||
<string name="navigation_drawer_faq">Domande Frequenti</string>
|
||||
<string name="navigation_drawer_forums">Comunità</string>
|
||||
<string name="navigation_drawer_donate">Donazione</string>
|
||||
<!--DavService-->
|
||||
<!--AppSettingsActivity-->
|
||||
<string name="app_settings">Impostazioni</string>
|
||||
<string name="app_settings_user_interface">Interfaccia utente</string>
|
||||
<string name="app_settings_security">Sicurezza</string>
|
||||
<!--AccountActivity-->
|
||||
<string name="account_synchronize_now">Sincronizza adesso</string>
|
||||
<string name="account_synchronizing_now">Sincronizzazione in corso</string>
|
||||
<string name="account_settings">Impostazioni account</string>
|
||||
<string name="account_delete">Elimina account</string>
|
||||
<string name="account_delete_confirmation_text">Tutte le copie locali delle rubriche, dei calendari e degli elenchi attività verranno eliminate.</string>
|
||||
<string name="account_refresh_calendar_list">Aggiorna lista calendari</string>
|
||||
<string name="account_create_new_calendar">Crea nuovo calendario</string>
|
||||
<!--PermissionsActivity-->
|
||||
<string name="permissions_title">Permessi DAVdroid</string>
|
||||
<string name="permissions_calendar">Permessi Calendario</string>
|
||||
<string name="permissions_contacts">Permessi Contatti</string>
|
||||
<string name="permissions_opentasks">Permessi OpenTasks</string>
|
||||
<!--AddAccountActivity-->
|
||||
<string name="login_title">Aggiungi account</string>
|
||||
<string name="login_type_email">Accedi con indirizzo email</string>
|
||||
<string name="login_email_address">Indirizzo email</string>
|
||||
<string name="login_password">Password</string>
|
||||
<string name="login_type_url">Accedi con URL e nome utente</string>
|
||||
<string name="login_url_must_be_http_or_https">L\'URL deve iniziare con http(s)://</string>
|
||||
<string name="login_user_name">Nome utente</string>
|
||||
<string name="login_back">Indietro</string>
|
||||
<string name="login_create_account">Crea account</string>
|
||||
<string name="login_account_name">Nome account</string>
|
||||
<string name="login_configuration_detection">Rilevazione configurazione</string>
|
||||
<!--AccountSettingsActivity-->
|
||||
<string name="settings_title">Impostazioni: %s</string>
|
||||
<string name="settings_authentication">Autenticazione</string>
|
||||
<string name="settings_username">Nome utente</string>
|
||||
<string name="settings_enter_username">Inserisci nome utente:</string>
|
||||
<string name="settings_password">Password</string>
|
||||
<string name="settings_enter_password">Inserisci la tua password:</string>
|
||||
<string name="settings_sync_interval_contacts">Intervallo sincr. Contatti</string>
|
||||
<string name="settings_sync_summary_manually">Solo manualmente</string>
|
||||
<string name="settings_sync_interval_calendars">Intervallo sincr. Calendari</string>
|
||||
<string name="settings_sync_interval_tasks">Intervallo sincr. Attività</string>
|
||||
<string name="settings_sync_wifi_only">Sincr. solo tramite WiFi</string>
|
||||
<string name="settings_sync_wifi_only_on">La sincronizzazione è limitata alle connessioni WiFi</string>
|
||||
<string name="settings_caldav">CalDAV</string>
|
||||
<string name="settings_sync_time_range_past_none">Verranno sincronizzati tutti gli eventi</string>
|
||||
<string name="settings_version_update_install_hint">Problemi? Disinstalla e poi re-installa DAVdroid.</string>
|
||||
<!--collection management-->
|
||||
<string name="create_calendar_time_zone">Fuso orario:</string>
|
||||
<string name="create_calendar_type_only_events">Calendario (solo eventi)</string>
|
||||
<string name="create_calendar_type_only_tasks">Elenco attività (solo attività)</string>
|
||||
<string name="delete_collection_confirm_title">Sei sicuro?</string>
|
||||
<!--ExceptionInfoFragment-->
|
||||
<string name="exception">Si è verificato un errore.</string>
|
||||
<string name="exception_httpexception">Si è verificato un errore HTTP.</string>
|
||||
<string name="exception_ioexception">Si è verificato un errore di I/O.</string>
|
||||
<string name="exception_show_details">Mostra dettagli</string>
|
||||
<!--sync errors and DebugInfoActivity-->
|
||||
<string name="sync_error_permissions">Permessi DAVdroid</string>
|
||||
<!--cert4android-->
|
||||
</resources>
|
||||
241
app/src/davdroid/res/values-ja/strings.xml
Normal file
241
app/src/davdroid/res/values-ja/strings.xml
Normal file
@@ -0,0 +1,241 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<!--common strings-->
|
||||
<string name="app_name">DAVdroid</string>
|
||||
<string name="help">ヘルプ</string>
|
||||
<string name="manage_accounts">アカウントの管理</string>
|
||||
<string name="please_wait">しばらくお待ちください …</string>
|
||||
<string name="send">送信</string>
|
||||
<!--startup dialogs-->
|
||||
<string name="startup_battery_optimization">バッテリー最適化</string>
|
||||
<string name="startup_battery_optimization_message">Android は数日後に DAVdroid の同期を無効にする/減らすことがあります。これを防止するには、バッテリー最適化をオフにしてください。</string>
|
||||
<string name="startup_battery_optimization_disable">DAVdroid 用にオフにする</string>
|
||||
<string name="startup_dont_show_again">次回から表示しない</string>
|
||||
<string name="startup_development_version">DAVdroid プレビュー リリース</string>
|
||||
<string name="startup_development_version_message">これは DAVdroid の開発版です。期待した通りに動作しない可能性があることにご注意ください。私たちが DAVdroid を改善するために、建設的なフィードバックをお願いします。</string>
|
||||
<string name="startup_development_version_give_feedback">フィードバックする</string>
|
||||
<string name="startup_donate">オープンソース情報</string>
|
||||
<string name="startup_donate_message">あなたがオープンソース ソフトウェア (GPLv3) の DAVdroid を使用していただくことに、私たちは満足しています。 DAVdroid の開発はハードワークで、何千もの作業時間がかかりました。寄付をご検討ください。</string>
|
||||
<string name="startup_donate_now">寄付ページを表示</string>
|
||||
<string name="startup_donate_later">たぶん後で</string>
|
||||
<string name="startup_google_play_accounts_removed">Play ストア DRM バグ情報</string>
|
||||
<string name="startup_google_play_accounts_removed_message">特定の条件下で、DAVdroid を再起動後またはアップグレードした後、Play ストア DRM によりすべての DAVdroid アカウントがなくなる可能性があります。この問題の影響を受けている場合 (のみ)、Play ストアから「DAVdroid JB 回避策」をインストールしてください。</string>
|
||||
<string name="startup_google_play_accounts_removed_more_info">追加情報</string>
|
||||
<string name="startup_opentasks_not_installed">OpenTasks がインストールされていません</string>
|
||||
<string name="startup_opentasks_not_installed_message">OpenTasks アプリが利用できないため、DAVdroid はタスクリストを同期することができません。</string>
|
||||
<string name="startup_opentasks_reinstall_davdroid">OpenTasks をインストールした後で、DAVdroidを再インストールして、再度アカウントを追加してください (Android のバグ)。</string>
|
||||
<string name="startup_opentasks_not_installed_install">OpenTasks をインストール</string>
|
||||
<!--AboutActivity-->
|
||||
<string name="about_license_terms">ライセンス規約</string>
|
||||
<string name="about_license_info_no_warranty">このプログラムは完全に無保証で提供されます。これはフリーソフトウェアで、特定の条件下での再頒布を歓迎します。</string>
|
||||
<!--global settings-->
|
||||
<string name="logging_davdroid_file_logging">DAVdroid ファイルログ</string>
|
||||
<string name="logging_to_external_storage">外部ストレージにログ: %s</string>
|
||||
<string name="logging_to_external_storage_warning">できる限り早くログを削除してください!</string>
|
||||
<string name="logging_couldnt_create_file">外部ログファイルを作成できませんでした: %s</string>
|
||||
<string name="logging_no_external_storage">外部ストレージが見つかりません</string>
|
||||
<!--AccountsActivity-->
|
||||
<string name="navigation_drawer_open">ナビゲーションドロワーを開く</string>
|
||||
<string name="navigation_drawer_close">ナビゲーションドロワーを閉じる</string>
|
||||
<string name="navigation_drawer_subtitle">CalDAV/CardDAV 同期アダプター</string>
|
||||
<string name="navigation_drawer_about">アプリについて / ライセンス</string>
|
||||
<string name="navigation_drawer_settings">設定</string>
|
||||
<string name="navigation_drawer_news_updates">ニュース & 更新</string>
|
||||
<string name="navigation_drawer_external_links">外部リンク</string>
|
||||
<string name="navigation_drawer_website">Web サイト</string>
|
||||
<string name="navigation_drawer_faq">FAQ</string>
|
||||
<string name="navigation_drawer_forums">コミュニティ</string>
|
||||
<string name="navigation_drawer_donate">寄付</string>
|
||||
<string name="account_list_empty">DAVdroid にようこそ!\n\nCalDAV/CardDAV アカウントを追加できるようになりました。</string>
|
||||
<!--DavService-->
|
||||
<string name="dav_service_refresh_failed">サービスの検出に失敗しました</string>
|
||||
<string name="dav_service_refresh_couldnt_refresh">コレクション リストを更新できません</string>
|
||||
<!--AppSettingsActivity-->
|
||||
<string name="app_settings">設定</string>
|
||||
<string name="app_settings_user_interface">ユーザーインターフェイス</string>
|
||||
<string name="app_settings_reset_hints">ヒントをリセット</string>
|
||||
<string name="app_settings_reset_hints_summary">以前非表示にしたヒントを、再度有効にします</string>
|
||||
<string name="app_settings_reset_hints_success">すべてのヒントが再度表示されます</string>
|
||||
<string name="app_settings_security">セキュリティ</string>
|
||||
<string name="app_settings_distrust_system_certs">システム証明書の信頼を解除</string>
|
||||
<string name="app_settings_distrust_system_certs_on">システムとユーザーが追加したCAを信頼しない</string>
|
||||
<string name="app_settings_distrust_system_certs_off">システムとユーザーが追加したCAを信頼する (推奨)</string>
|
||||
<string name="app_settings_reset_certificates">(未)信頼済証明書をリセット</string>
|
||||
<string name="app_settings_reset_certificates_summary">すべてのカスタム証明書の信頼をリセット</string>
|
||||
<string name="app_settings_reset_certificates_success">すべてのカスタム証明書をクリアしました</string>
|
||||
<string name="app_settings_debug">デバッグ</string>
|
||||
<string name="app_settings_log_to_external_storage">外部ファイルにログ出力</string>
|
||||
<string name="app_settings_log_to_external_storage_on">(可能な場合) 外部ストレージにログ出力します</string>
|
||||
<string name="app_settings_log_to_external_storage_off">外部ファイルログ出力は無効です</string>
|
||||
<string name="app_settings_show_debug_info">デバッグ情報を表示</string>
|
||||
<string name="app_settings_show_debug_info_details">ソフトウェアと設定の詳細を表示/共有</string>
|
||||
<!--AccountActivity-->
|
||||
<string name="account_synchronize_now">今すぐ同期</string>
|
||||
<string name="account_synchronizing_now">同期中</string>
|
||||
<string name="account_settings">アカウント設定</string>
|
||||
<string name="account_delete">アカウントを削除</string>
|
||||
<string name="account_delete_confirmation_title">アカウントを削除してもよろしいですか?</string>
|
||||
<string name="account_delete_confirmation_text">アドレス帳、カレンダー、タスクリストのローカルコピーがすべて削除されます。</string>
|
||||
<string name="account_refresh_address_book_list">アドレス帳リストを更新</string>
|
||||
<string name="account_create_new_address_book">新しいアドレス帳を作成</string>
|
||||
<string name="account_refresh_calendar_list">カレンダーリストを更新</string>
|
||||
<string name="account_create_new_calendar">新しいカレンダーを作成</string>
|
||||
<!--PermissionsActivity-->
|
||||
<string name="permissions_title">DAVdroid アクセス許可</string>
|
||||
<string name="permissions_calendar">カレンダー アクセス許可</string>
|
||||
<string name="permissions_calendar_details">ローカルのカレンダーと CalDAV イベントを同期するため、DAVdroid がカレンダーにアクセスする必要があります。</string>
|
||||
<string name="permissions_calendar_request">カレンダー アクセス許可の要求</string>
|
||||
<string name="permissions_contacts">連絡先アクセス許可</string>
|
||||
<string name="permissions_contacts_details">ローカルの連絡先と CalDAV アドレス帳を同期するため、DAVdroid が連絡先にアクセスする必要があります。</string>
|
||||
<string name="permissions_contacts_request">連絡先アクセス許可の要求</string>
|
||||
<string name="permissions_opentasks">OpenTasks アクセス許可</string>
|
||||
<string name="permissions_opentasks_details">ローカルのタスクリストと CalDAV タスクを同期するため、DAVdroid が OpenTasks にアクセスする必要があります。</string>
|
||||
<string name="permissions_opentasks_request">OpenTasks アクセス許可の要求</string>
|
||||
<!--AddAccountActivity-->
|
||||
<string name="login_title">アカウントを追加</string>
|
||||
<string name="login_type_email">メールアドレスでログイン</string>
|
||||
<string name="login_email_address">メールアドレス</string>
|
||||
<string name="login_email_address_error">有効なメールアドレスが必要です</string>
|
||||
<string name="login_password">パスワード</string>
|
||||
<string name="login_password_required">パスワードが必要です</string>
|
||||
<string name="login_type_url">URL とユーザー名でログイン</string>
|
||||
<string name="login_url_must_be_http_or_https">URL は http(s):// で始まる必要があります</string>
|
||||
<string name="login_url_host_name_required">ホスト名が必要です</string>
|
||||
<string name="login_user_name">ユーザー名</string>
|
||||
<string name="login_user_name_required">ユーザー名が必要です</string>
|
||||
<string name="login_base_url">ベース URL</string>
|
||||
<string name="login_login">ログイン</string>
|
||||
<string name="login_back">戻る</string>
|
||||
<string name="login_create_account">アカウントを作成</string>
|
||||
<string name="login_account_name">アカウント名</string>
|
||||
<string name="login_account_name_info">Android はあなたが作成した予定の ORGANIZER フィールドとしてアカウント名を使用するので、アカウント名としてメールアドレスを使用してください。同じ名前のアカウントを 2 つ持つことはできません。</string>
|
||||
<string name="login_account_contact_group_method">連絡先グループ方法:</string>
|
||||
<string name="login_account_name_required">アカウント名が必要です</string>
|
||||
<string name="login_account_not_created">アカウントを作成できません</string>
|
||||
<string name="login_configuration_detection">設定の検出</string>
|
||||
<string name="login_querying_server">しばらくお待ちください。サーバーに問い合わせ中…</string>
|
||||
<string name="login_no_caldav_carddav">CalDAV または CardDAV サービスが見つかりません。</string>
|
||||
<string name="login_view_logs">ログを表示</string>
|
||||
<!--AccountSettingsActivity-->
|
||||
<string name="settings_title">設定: %s</string>
|
||||
<string name="settings_authentication">認証</string>
|
||||
<string name="settings_username">ユーザー名</string>
|
||||
<string name="settings_enter_username">ユーザー名を入力:</string>
|
||||
<string name="settings_password">パスワード</string>
|
||||
<string name="settings_password_summary">ご利用のサーバーに従ってパスワードを更新します。</string>
|
||||
<string name="settings_enter_password">パスワードを入力:</string>
|
||||
<string name="settings_sync">同期</string>
|
||||
<string name="settings_sync_interval_contacts">連絡先同期間隔</string>
|
||||
<string name="settings_sync_summary_manually">手動のみ</string>
|
||||
<string name="settings_sync_summary_periodically" tools:ignore="PluralsCandidate">%d 分ごと + ローカルの変更時はすぐに</string>
|
||||
<string name="settings_sync_summary_not_available">利用不可</string>
|
||||
<string name="settings_sync_interval_calendars">カレンダー同期間隔</string>
|
||||
<string name="settings_sync_interval_tasks">タスク同期間隔</string>
|
||||
<string-array name="settings_sync_interval_seconds">
|
||||
<item>-1</item>
|
||||
<item>300</item>
|
||||
<item>600</item>
|
||||
<item>900</item>
|
||||
<item>3600</item>
|
||||
<item>7200</item>
|
||||
<item>14400</item>
|
||||
<item>86400</item>
|
||||
</string-array>
|
||||
<string-array name="settings_sync_interval_names">
|
||||
<item>手動のみ</item>
|
||||
<item>5 分ごと</item>
|
||||
<item>10 分ごと</item>
|
||||
<item>15 分ごと</item>
|
||||
<item>1 時間ごと</item>
|
||||
<item>2 時間ごと</item>
|
||||
<item>4 時間ごと</item>
|
||||
<item>1 日 1 回</item>
|
||||
</string-array>
|
||||
<string name="settings_sync_wifi_only">WiFi でのみ同期</string>
|
||||
<string name="settings_sync_wifi_only_on">同期は WiFi 接続に制限されます</string>
|
||||
<string name="settings_sync_wifi_only_off">接続の種類は考慮されません</string>
|
||||
<string name="settings_sync_wifi_only_ssid">WiFi SSID 制限</string>
|
||||
<string name="settings_sync_wifi_only_ssid_on">%s でのみ同期します</string>
|
||||
<string name="settings_sync_wifi_only_ssid_off">すべての WiFi 接続を使用することができます</string>
|
||||
<string name="settings_sync_wifi_only_ssid_message">このネットワークで同期を制限する WiFi ネットワーク (SSID) の名前を入力してください。すべての WiFi 接続は空白のままにします。</string>
|
||||
<string name="settings_carddav">CardDAV</string>
|
||||
<string name="settings_contact_group_method">連絡先グループ方法</string>
|
||||
<string-array name="settings_contact_group_method_values">
|
||||
<item>GROUP_VCARDS</item>
|
||||
<item>CATEGORIES</item>
|
||||
</string-array>
|
||||
<string-array name="settings_contact_group_method_entries">
|
||||
<item>グループは個別の VCards</item>
|
||||
<item>グループは連絡先カテゴリーごと</item>
|
||||
</string-array>
|
||||
<string name="settings_rfc6868_for_vcards">RFC6868 の VCards を使用する</string>
|
||||
<string name="settings_rfc6868_for_vcards_on">二重引用符を、パラメーター値に使用することができます</string>
|
||||
<string name="settings_rfc6868_for_vcards_off">二重引用符を、パラメーター値に使用することができません</string>
|
||||
<string name="settings_caldav">CalDAV</string>
|
||||
<string name="settings_sync_time_range_past">過去イベントの時間限度</string>
|
||||
<string name="settings_sync_time_range_past_none">すべてのイベントが同期されます</string>
|
||||
<plurals name="settings_sync_time_range_past_days">
|
||||
<item quantity="other">%d 日より前のイベントは無視されます</item>
|
||||
</plurals>
|
||||
<string name="settings_sync_time_range_past_message">この日数より過去のイベントは無視されます (0 も可)。すべてのイベントを同期させるには、空白のままにしてください。</string>
|
||||
<string name="settings_manage_calendar_colors">カレンダーの色を管理</string>
|
||||
<string name="settings_manage_calendar_colors_on">カレンダーの色は DAVdroid が管理します</string>
|
||||
<string name="settings_manage_calendar_colors_off">カレンダーの色を DAVdroid が設定しません</string>
|
||||
<string name="settings_version_update">DAVdroid バージョンの更新</string>
|
||||
<string name="settings_version_update_settings_updated">内部設定が更新されました。</string>
|
||||
<string name="settings_version_update_install_hint">問題がありますか? DAVdroid をアンインストールして、再度インストールしてください。</string>
|
||||
<!--collection management-->
|
||||
<string name="create_addressbook">アドレス帳を作成</string>
|
||||
<string name="create_addressbook_display_name_hint">マイ アドレス帳</string>
|
||||
<string name="create_calendar">CalDAV コレクションを作成</string>
|
||||
<string name="create_calendar_display_name_hint">マイ カレンダー</string>
|
||||
<string name="create_calendar_time_zone">タイムゾーン:</string>
|
||||
<string name="create_calendar_type">コレクションの種類:</string>
|
||||
<string name="create_calendar_type_only_events">カレンダー (イベントのみ)</string>
|
||||
<string name="create_calendar_type_only_tasks">タスクリスト (タスクのみ)</string>
|
||||
<string name="create_calendar_type_events_and_tasks">組み合わせ (イベントとタスク)</string>
|
||||
<string name="create_collection_color">コレクションの色を設定</string>
|
||||
<string name="create_collection_creating">コレクションの作成中</string>
|
||||
<string name="create_collection_display_name">このコレクションの表示名 (タイトル):</string>
|
||||
<string name="create_collection_display_name_required">タイトルが必要です</string>
|
||||
<string name="create_collection_description">説明 (オプション):</string>
|
||||
<string name="create_collection_home_set">ホーム セット:</string>
|
||||
<string name="create_collection_create">作成</string>
|
||||
<string name="delete_collection">コレクションを削除</string>
|
||||
<string name="delete_collection_confirm_title">よろしいですか?</string>
|
||||
<string name="delete_collection_confirm_warning">このコレクション (%s) とそのすべてのデータがサーバーから削除されます。</string>
|
||||
<string name="delete_collection_deleting_collection">コレクションの削除中</string>
|
||||
<!--ExceptionInfoFragment-->
|
||||
<string name="exception">エラーが発生しました。</string>
|
||||
<string name="exception_httpexception">HTTP エラーが発生しました。</string>
|
||||
<string name="exception_ioexception">I/O エラーが発生しました。</string>
|
||||
<string name="exception_show_details">詳細を表示</string>
|
||||
<!--sync errors and DebugInfoActivity-->
|
||||
<string name="debug_info_title">デバッグ情報</string>
|
||||
<string name="sync_error_permissions">DAVdroid アクセス許可</string>
|
||||
<string name="sync_error_permissions_text">追加のアクセス許可が必要です</string>
|
||||
<string name="sync_error_calendar">カレンダーの同期に失敗しました (%s)</string>
|
||||
<string name="sync_error_contacts">アドレス帳の同期に失敗しました (%s)</string>
|
||||
<string name="sync_error_tasks">タスクの同期に失敗しました (%s)</string>
|
||||
<string name="sync_error">%s 時にエラー</string>
|
||||
<string name="sync_error_http_dav">%s 時にサーバーエラー</string>
|
||||
<string name="sync_error_local_storage">%s 時にデータベースエラー</string>
|
||||
<string-array name="sync_error_phases">
|
||||
<item>同期の準備中</item>
|
||||
<item>機能の問い合わせ中</item>
|
||||
<item>ローカルで削除されたエントリーの処理中</item>
|
||||
<item>作成済/更新済エントリーの準備中</item>
|
||||
<item>作成済/更新済エントリーのアップロード中</item>
|
||||
<item>同期の状態を確認中</item>
|
||||
<item>ローカルのエントリーをリスト中</item>
|
||||
<item>リモートのエントリーをリスト中</item>
|
||||
<item>ローカル/リモートのエントリーを比較中</item>
|
||||
<item>リモートのエントリーをダウンロード中</item>
|
||||
<item>後処理中</item>
|
||||
<item>同期の状態を保存中</item>
|
||||
</string-array>
|
||||
<string name="sync_error_unauthorized">ユーザー名/パスワードが間違っています</string>
|
||||
<!--cert4android-->
|
||||
<string name="certificate_notification_connection_security">DAVdroid: 接続セキュリティ</string>
|
||||
<string name="trust_certificate_unknown_certificate_found">DAVdroidは、未知の証明書を検出しました。それを信頼しますか?</string>
|
||||
</resources>
|
||||
231
app/src/davdroid/res/values-nl/strings.xml
Normal file
231
app/src/davdroid/res/values-nl/strings.xml
Normal file
@@ -0,0 +1,231 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<!--common strings-->
|
||||
<string name="app_name">DAVdroid</string>
|
||||
<string name="help">Help</string>
|
||||
<string name="manage_accounts">Beheer accounts</string>
|
||||
<string name="please_wait">Een moment geduld...</string>
|
||||
<string name="send">Verzenden</string>
|
||||
<!--startup dialogs-->
|
||||
<string name="startup_dont_show_again">Niet opnieuw weergeven</string>
|
||||
<string name="startup_development_version">DAVdroid voorlopige versie</string>
|
||||
<string name="startup_development_version_message">Dit is een ontwikkelversie van DAVdroid. Het kan zijn dat dingen niet werken zoals verwacht. Geef ons constructieve feedback om DAVdroid te verbeteren.</string>
|
||||
<string name="startup_development_version_give_feedback">Feedback geven</string>
|
||||
<string name="startup_donate">Open-Source informatie</string>
|
||||
<string name="startup_donate_message">We zijn blij dat je DAVdroid gebruikt, wat open-source software (GPLv3) is. Omdat de ontwikkeling van DAVdroid hard werk is en duizenden uren in beslag neemt. overweeg alstublieft een donatie.</string>
|
||||
<string name="startup_donate_now">Toon donatie pagina</string>
|
||||
<string name="startup_donate_later">Misschien later</string>
|
||||
<string name="startup_google_play_accounts_removed">Play Store DRM fout-informatie</string>
|
||||
<string name="startup_google_play_accounts_removed_message">Onder bepaalde omstandigheden, kan Play Store DRM ervoor zorgen dat accounts kwijt zijn na een herstart of na een DAVdroid update. Als dit probleem zich bij je voordoet (en alleen dan), Installeer dan \"DAVdroid JB Workaround\" vanuit de Play Store</string>
|
||||
<string name="startup_google_play_accounts_removed_more_info">Meer informatie</string>
|
||||
<string name="startup_opentasks_not_installed">OpenTasks niet geinstalleerd</string>
|
||||
<string name="startup_opentasks_not_installed_message">De OpenTasks app is niet beschikbaar, Hierdoor is het voor DAVdroid niet mogelijk om uw taken te synchroniseren.</string>
|
||||
<string name="startup_opentasks_reinstall_davdroid">Na installatie van OpenTasks dient u DAVdroid opnieuw te installeren en de accounts toe te voegen (Android bug).</string>
|
||||
<string name="startup_opentasks_not_installed_install">OpenTasks installeren</string>
|
||||
<!--AboutActivity-->
|
||||
<string name="about_license_terms">Licentie voorwaarden</string>
|
||||
<string name="about_license_info_no_warranty">Dit programma kom met ABSOLUUT GEEN GARANTIE. Het is gratis software, en je bent welkom dit te herdistribueren onder bepaalde voorwaarden.</string>
|
||||
<!--global settings-->
|
||||
<string name="logging_davdroid_file_logging">DAVDroid bestand loggen</string>
|
||||
<string name="logging_to_external_storage">Loggen naar externe opslag: %s</string>
|
||||
<string name="logging_to_external_storage_warning">Verwijder logs zo snel mogelijk</string>
|
||||
<string name="logging_couldnt_create_file">Kon extern log bestand niet verwijderen: %s</string>
|
||||
<string name="logging_no_external_storage">Externe opslag niet gevonden</string>
|
||||
<!--AccountsActivity-->
|
||||
<string name="navigation_drawer_open">Open navigatie drawer</string>
|
||||
<string name="navigation_drawer_close">Sluit navigatie drawer</string>
|
||||
<string name="navigation_drawer_subtitle">CalDAV/CardDav Sync adapter</string>
|
||||
<string name="navigation_drawer_about">Over / Licentie</string>
|
||||
<string name="navigation_drawer_settings">Instellingen</string>
|
||||
<string name="navigation_drawer_news_updates">Nieuws & updates</string>
|
||||
<string name="navigation_drawer_external_links">Externe links</string>
|
||||
<string name="navigation_drawer_website">Website</string>
|
||||
<string name="navigation_drawer_faq">FAQ</string>
|
||||
<string name="navigation_drawer_forums">Community</string>
|
||||
<string name="navigation_drawer_donate">Doneren</string>
|
||||
<string name="account_list_empty">Welkom bij DAVdroid!\n\nJe kunt nu een CalDAV/CardDAv account toevoegen.</string>
|
||||
<!--DavService-->
|
||||
<string name="dav_service_refresh_failed">Service herkenning is mislukt</string>
|
||||
<string name="dav_service_refresh_couldnt_refresh">Kon de collectie lijst niet vernieuwen</string>
|
||||
<!--AppSettingsActivity-->
|
||||
<string name="app_settings">Instellingen</string>
|
||||
<string name="app_settings_user_interface">Gebruikers interface</string>
|
||||
<string name="app_settings_reset_hints">Hints resetten </string>
|
||||
<string name="app_settings_reset_hints_summary">Hints die al gezien zijn opnieuw weergeven</string>
|
||||
<string name="app_settings_reset_hints_success">Alle hints worden opnieuw weergegeven</string>
|
||||
<string name="app_settings_security">Beveiliging</string>
|
||||
<string name="app_settings_debug">Debuggen</string>
|
||||
<string name="app_settings_log_to_external_storage">Log naar extern bestand</string>
|
||||
<string name="app_settings_log_to_external_storage_on">Loggen naar externe opslag (wanneer beschikbaar)</string>
|
||||
<string name="app_settings_log_to_external_storage_off">Extern bestands loggen uitgeschakeld</string>
|
||||
<string name="app_settings_show_debug_info">Debug info tonen</string>
|
||||
<string name="app_settings_show_debug_info_details">Bekijk/deel software configuratie details</string>
|
||||
<!--AccountActivity-->
|
||||
<string name="account_synchronize_now">Synchroniseer nu</string>
|
||||
<string name="account_synchronizing_now">Aan het synchronizeren...</string>
|
||||
<string name="account_settings">Account instellingen</string>
|
||||
<string name="account_delete">Account verwijderen</string>
|
||||
<string name="account_delete_confirmation_title">Account echt verwijderen?</string>
|
||||
<string name="account_delete_confirmation_text">Alle lokale kopieën van adresboeken, agenda\'s en taken worden verwijderd.</string>
|
||||
<string name="account_refresh_address_book_list">Adresboeken vernieuwen</string>
|
||||
<string name="account_create_new_address_book">Maak een nieuw adresboek</string>
|
||||
<string name="account_refresh_calendar_list">Agenda\'s vernieuwen</string>
|
||||
<string name="account_create_new_calendar">Maak een nieuwe agenda</string>
|
||||
<!--PermissionsActivity-->
|
||||
<string name="permissions_title">DAVdroid rechten</string>
|
||||
<string name="permissions_calendar">Agenda rechten</string>
|
||||
<string name="permissions_calendar_details">Om CalDAV afspraken te synchroniseren met u agenda dient DAVdroid toegang te verkrijgen. </string>
|
||||
<string name="permissions_calendar_request">Agenda rechten verkrijgen</string>
|
||||
<string name="permissions_contacts">Contact rechten</string>
|
||||
<string name="permissions_contacts_details">Om CalDAV afspraken te synchroniseren met u contacten dient DAVdroid toegang te verkrijgen. </string>
|
||||
<string name="permissions_contacts_request">Contacten rechten verkrijgen</string>
|
||||
<string name="permissions_opentasks">OpenTasks rechten</string>
|
||||
<string name="permissions_opentasks_details">Om CalDAV taken te synchroniseren met uw local takenlijst dient DAVdroid toegang te hebben tot OpenTasks</string>
|
||||
<string name="permissions_opentasks_request">OpenTasks rechten verkrijgen</string>
|
||||
<!--AddAccountActivity-->
|
||||
<string name="login_title">Account toevoegen</string>
|
||||
<string name="login_type_email">Inloggen met e-mailadres</string>
|
||||
<string name="login_email_address">Email adres</string>
|
||||
<string name="login_email_address_error">Geldig email adres vereist</string>
|
||||
<string name="login_password">Wachtwoord</string>
|
||||
<string name="login_password_required">Wachtwoord vereist</string>
|
||||
<string name="login_type_url">Inloggen met URL en gebruikersnaam</string>
|
||||
<string name="login_url_must_be_http_or_https">URL moet met http(s):// beginnen</string>
|
||||
<string name="login_url_host_name_required">Hostnaam vereist</string>
|
||||
<string name="login_user_name">Gebruikersnaam</string>
|
||||
<string name="login_user_name_required">Gebruikersnaam vereist</string>
|
||||
<string name="login_base_url">Basis URL</string>
|
||||
<string name="login_login">Login</string>
|
||||
<string name="login_back">Terug</string>
|
||||
<string name="login_create_account">Maak een account</string>
|
||||
<string name="login_account_name">Accountnaam</string>
|
||||
<string name="login_account_name_info">Gebruik je email adres als account naam want Android zal je account naam gebruiken als ORGANIZER veld voor gemaakte afspraken. Je kunt geen 2 accounts met dezelfde naam hebben,</string>
|
||||
<string name="login_account_contact_group_method">Contact groep methode:</string>
|
||||
<string name="login_account_name_required">Accountnaam vereist</string>
|
||||
<string name="login_account_not_created">Account kon niet gemaakt worden.</string>
|
||||
<string name="login_configuration_detection">Configuratie detectie</string>
|
||||
<string name="login_querying_server">Even geduld, verzoek naar server...</string>
|
||||
<string name="login_no_caldav_carddav">Kon geen CalDAV of CardDAV service vinden.</string>
|
||||
<string name="login_view_logs">Bekijk logs</string>
|
||||
<!--AccountSettingsActivity-->
|
||||
<string name="settings_title">Instellingen: %s</string>
|
||||
<string name="settings_authentication">Authenticatie</string>
|
||||
<string name="settings_username">Gebruikersnaam</string>
|
||||
<string name="settings_enter_username">Gebruikersnaam invoeren:</string>
|
||||
<string name="settings_password">Wachtwoord</string>
|
||||
<string name="settings_password_summary">Gebruik het zelfde wachtwoord als op de server.</string>
|
||||
<string name="settings_enter_password">Wachtwoord invoeren:</string>
|
||||
<string name="settings_sync">Synchronisatie</string>
|
||||
<string name="settings_sync_interval_contacts">Contacten verversen</string>
|
||||
<string name="settings_sync_summary_manually">Alleen handmatig</string>
|
||||
<string name="settings_sync_summary_periodically" tools:ignore="PluralsCandidate">Elke %d minuten + meteen na wijziging</string>
|
||||
<string name="settings_sync_summary_not_available">Niet beschikbaar</string>
|
||||
<string name="settings_sync_interval_calendars">Agenda\'s verversen</string>
|
||||
<string name="settings_sync_interval_tasks">Taak sync. tussentijd</string>
|
||||
<string-array name="settings_sync_interval_seconds">
|
||||
<item>-1</item>
|
||||
<item>300</item>
|
||||
<item>600</item>
|
||||
<item>900</item>
|
||||
<item>3600</item>
|
||||
<item>7200</item>
|
||||
<item>14400</item>
|
||||
<item>86400</item>
|
||||
</string-array>
|
||||
<string-array name="settings_sync_interval_names">
|
||||
<item>Alleen handmatig</item>
|
||||
<item>Elke 5 minuten</item>
|
||||
<item>Elke 10 minuten</item>
|
||||
<item>Elke 15 minuten</item>
|
||||
<item>Elk uur</item>
|
||||
<item>Elke 2 uur</item>
|
||||
<item>Elke 4 uur</item>
|
||||
<item>Dagelijks</item>
|
||||
</string-array>
|
||||
<string name="settings_sync_wifi_only">Sync alleen tijdens WiFi</string>
|
||||
<string name="settings_sync_wifi_only_on">Synchronisatie is voorbehouden tijdens WiFi verbindingen</string>
|
||||
<string name="settings_sync_wifi_only_off">Verbinding type is niet overwogen</string>
|
||||
<string name="settings_sync_wifi_only_ssid">WiFi SSID beperking</string>
|
||||
<string name="settings_sync_wifi_only_ssid_on">Zal alleen synchroniseren over %s</string>
|
||||
<string name="settings_sync_wifi_only_ssid_off">Alle WiFI verbindingen mogen worden gebruikt</string>
|
||||
<string name="settings_sync_wifi_only_ssid_message">Type de naam van het WiFi netwerk (SSID) om synchronisatie tot dit netwerk te beperken. Leeg laten voor sync over alle netwerken.</string>
|
||||
<string name="settings_carddav">CardDAV</string>
|
||||
<string name="settings_contact_group_method">Contact groep methode</string>
|
||||
<string-array name="settings_contact_group_method_values">
|
||||
<item>GROUP_VCARDS</item>
|
||||
<item>CATEGORIES</item>
|
||||
</string-array>
|
||||
<string-array name="settings_contact_group_method_entries">
|
||||
<item>Groepen zijn apparte VCards</item>
|
||||
<item>Groepen zijn per-contact categories</item>
|
||||
</string-array>
|
||||
<string name="settings_rfc6868_for_vcards">Gebruik RFC6868 voor VCards</string>
|
||||
<string name="settings_rfc6868_for_vcards_on">Aanhalingstekens kunnen worden gebruikt voor parameter waardes</string>
|
||||
<string name="settings_rfc6868_for_vcards_off">Aanhalingstekens kunnen niet worden gebruikt voor parameter waardes</string>
|
||||
<string name="settings_caldav">CalDAV</string>
|
||||
<string name="settings_sync_time_range_past">Tijdslimiet verleden afspraken</string>
|
||||
<string name="settings_sync_time_range_past_none">Alle afspraken worden gesynchronizeerd</string>
|
||||
<plurals name="settings_sync_time_range_past_days">
|
||||
<item quantity="one">Afspraken ouder dan een dag worden genegeerd</item>
|
||||
<item quantity="other">Afspraken ouder dan %d dagen worden genegeerd</item>
|
||||
</plurals>
|
||||
<string name="settings_sync_time_range_past_message">Afspraken ouder dan dit aantal dagen worden genegeerd (mag 0 zijn). Laat leeg om alle afspraken te synchronizeren.</string>
|
||||
<string name="settings_manage_calendar_colors">Agenda kleuren beheren</string>
|
||||
<string name="settings_manage_calendar_colors_on">Agenda kleuren worden door DAVdroid beheerd.</string>
|
||||
<string name="settings_manage_calendar_colors_off">Agenda kleuren worden niet door DAVdroid ingesteld</string>
|
||||
<string name="settings_version_update">DAVdroid versie update</string>
|
||||
<string name="settings_version_update_settings_updated">Interne instellingen zijn bijgewerkt.</string>
|
||||
<string name="settings_version_update_install_hint">Problemen? Deïnstalleer DAVdroid, daarna herinstalleren.</string>
|
||||
<!--collection management-->
|
||||
<string name="create_addressbook">Maak adresboek</string>
|
||||
<string name="create_addressbook_display_name_hint">Mijn adresboek</string>
|
||||
<string name="create_calendar">Maak CalDAV collectie</string>
|
||||
<string name="create_calendar_display_name_hint">Mijn agenda</string>
|
||||
<string name="create_calendar_time_zone">Tijdzone:</string>
|
||||
<string name="create_calendar_type">Collectie type:</string>
|
||||
<string name="create_calendar_type_only_events">Agenda (alleen afspraken)</string>
|
||||
<string name="create_calendar_type_only_tasks">Takenlijst (alleen taken)</string>
|
||||
<string name="create_calendar_type_events_and_tasks">Gecombineerd (afspraken en taken)</string>
|
||||
<string name="create_collection_color">Stel een collectie kleur in</string>
|
||||
<string name="create_collection_creating">Collectie aan het maken</string>
|
||||
<string name="create_collection_display_name">Weergave naam (titel) van deze collectie:</string>
|
||||
<string name="create_collection_display_name_required">Titel is vereist</string>
|
||||
<string name="create_collection_description">Beschrijving (optioneel):</string>
|
||||
<string name="create_collection_home_set">Begin map:</string>
|
||||
<string name="create_collection_create">Maak</string>
|
||||
<string name="delete_collection">Verwijder collectie</string>
|
||||
<string name="delete_collection_confirm_title">Weet je het zeker?</string>
|
||||
<string name="delete_collection_confirm_warning">Deze collectie (%s) en alle data zal verwijderd worden van de server.</string>
|
||||
<string name="delete_collection_deleting_collection">Collectie aan het verwijderen</string>
|
||||
<!--ExceptionInfoFragment-->
|
||||
<string name="exception">Er is een fout opgetreden.</string>
|
||||
<string name="exception_httpexception">Er is een HTTP fout opgetreden.</string>
|
||||
<string name="exception_ioexception">Er is een I/O fout opgetreden.</string>
|
||||
<string name="exception_show_details">Toon details</string>
|
||||
<!--sync errors and DebugInfoActivity-->
|
||||
<string name="debug_info_title">Debug informatie</string>
|
||||
<string name="sync_error_permissions">DAVdroid rechten</string>
|
||||
<string name="sync_error_permissions_text">Aanvullende rechten vereist</string>
|
||||
<string name="sync_error_calendar">Agenda synchronisatie is mislukt (%s)</string>
|
||||
<string name="sync_error_contacts">Adresboek synchronisatie is mislukt (%s)</string>
|
||||
<string name="sync_error_tasks">Taak synchronisatie is mislukt (%s)</string>
|
||||
<string name="sync_error">Fout tijdens %s</string>
|
||||
<string name="sync_error_http_dav">Serverfout tijdens %s</string>
|
||||
<string name="sync_error_local_storage">Database fout tijdens %s</string>
|
||||
<string-array name="sync_error_phases">
|
||||
<item>synchronisatie voorbereiden</item>
|
||||
<item>querying mogelijkheden</item>
|
||||
<item>verwerken van lokaal verwijderde data</item>
|
||||
<item>voorberteiding maken/wijzigen data</item>
|
||||
<item>uploaden maken/bewerken data</item>
|
||||
<item>controleren syngronisatie voortgang</item>
|
||||
<item>lijst lokale data</item>
|
||||
<item>lijst remote data</item>
|
||||
<item>vergelijken lokale/remote data</item>
|
||||
<item>downloaden remote data</item>
|
||||
<item>nabewerking</item>
|
||||
<item>opslaan sync voortgang</item>
|
||||
</string-array>
|
||||
<string name="sync_error_unauthorized">Gebruikersnaam/wachtwoord onjuist</string>
|
||||
<!--cert4android-->
|
||||
</resources>
|
||||
239
app/src/davdroid/res/values-pl/strings.xml
Normal file
239
app/src/davdroid/res/values-pl/strings.xml
Normal file
@@ -0,0 +1,239 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<!--common strings-->
|
||||
<string name="app_name">DAVdroid</string>
|
||||
<string name="help">Pomoc</string>
|
||||
<string name="manage_accounts">Zadządzaj kontami</string>
|
||||
<string name="please_wait">Proszę czekać</string>
|
||||
<string name="send">Wyślij</string>
|
||||
<!--startup dialogs-->
|
||||
<string name="startup_battery_optimization">Optymalizacja baterii</string>
|
||||
<string name="startup_battery_optimization_message">Android może wyłączyć/zmniejszyć synchronizacje DAVdroid po kilku dniach. Aby temu zapobiec należy wyłączyć optymalizację baterii.</string>
|
||||
<string name="startup_battery_optimization_disable">Wyłącz dla DAVdroid</string>
|
||||
<string name="startup_dont_show_again">Nie pokazuj ponownie</string>
|
||||
<string name="startup_development_version">DAVdroid Preview Release</string>
|
||||
<string name="startup_development_version_message">Jest to wersja rozwojowa DAVdroid. Należy pamiętać, że rzeczy mogą nie działać zgodnie z oczekiwaniami. Prosimy o konstruktywny informacje zwrotne, aby ulepszyć DAVdroid.</string>
|
||||
<string name="startup_development_version_give_feedback">Przekaż opinię</string>
|
||||
<string name="startup_donate">Informacje Open-Source</string>
|
||||
<string name="startup_donate_message">Jesteśmy szczęśliwi, że używasz DAVdroid, który jest oprogramowaniem open-source (GPLv3). Ponieważ rozwijanie DAVdroid jest ciężką pracą i zajęło nam tysiące godzin pracy, prosimy o rozważenie darowizny.</string>
|
||||
<string name="startup_donate_now">Pokaż stronę darowizny</string>
|
||||
<string name="startup_donate_later">Może później</string>
|
||||
<string name="startup_google_play_accounts_removed">Informacje o błędzie DRM Sklepu Play</string>
|
||||
<string name="startup_google_play_accounts_removed_message">Pod pewnymi warunkami, DRM Sklepu Play może powodować, że wszystkie konta DAVdroid mogą zostać usunięte po uruchomieniu lub po uaktualnieniu DAVdroid. Jeśli jesteś dotknięty tym problemem (i tylko wtedy) należy zainstalować \"DAVdroid JB Obejście\" ze Sklepu Play.</string>
|
||||
<string name="startup_google_play_accounts_removed_more_info">Więcej informacji</string>
|
||||
<string name="startup_opentasks_not_installed">OpenTasks nie jest zainstalowany</string>
|
||||
<string name="startup_opentasks_not_installed_message">Aplikacja Open Tasks nie jest dostępna, więc DAVdroid nie będzie mógł synchronizować listy zadań. </string>
|
||||
<string name="startup_opentasks_reinstall_davdroid">Po zainstalowaniu OpenTasks konieczne jest PRZEINSTALOWANIE DAVdroid i ponowne dodanie twoich kont (błąd Androida).</string>
|
||||
<string name="startup_opentasks_not_installed_install">Zainstaluj OpenTasks</string>
|
||||
<!--AboutActivity-->
|
||||
<string name="about_license_terms">Warunki licencji</string>
|
||||
<string name="about_license_info_no_warranty">Ten program jest ABSOLUTNIE BEZ GWARANCJI. To jest wolne oprogramowanie i mile widziane jest dalsze rozpowszechnianie go pod pewnymi warunkami.</string>
|
||||
<!--global settings-->
|
||||
<string name="logging_davdroid_file_logging">Plik logów DAVdroid</string>
|
||||
<string name="logging_to_external_storage">Logowanie do zewnątrznej pamięci: %s</string>
|
||||
<string name="logging_to_external_storage_warning">Usuń logi tak szybko jak to możliwe!</string>
|
||||
<string name="logging_couldnt_create_file">Nie można stworzyć zewnętrznego pliku logów: %s</string>
|
||||
<string name="logging_no_external_storage">Zewnętrzna pamięci nie została naleziona</string>
|
||||
<!--AccountsActivity-->
|
||||
<string name="navigation_drawer_open">Otwórz menu nawigacji</string>
|
||||
<string name="navigation_drawer_close">Zamknij menu nawigacji</string>
|
||||
<string name="navigation_drawer_subtitle">Adapter synchronizacji CalDAV/CardDAV</string>
|
||||
<string name="navigation_drawer_about">O DAVdroid / Licencja</string>
|
||||
<string name="navigation_drawer_settings">Ustawienia</string>
|
||||
<string name="navigation_drawer_news_updates">Nowości & aktualizacje</string>
|
||||
<string name="navigation_drawer_external_links">Zewnętrzne odnośniki</string>
|
||||
<string name="navigation_drawer_website">Strona WWW</string>
|
||||
<string name="navigation_drawer_faq">FQA</string>
|
||||
<string name="navigation_drawer_forums">Społeczność</string>
|
||||
<string name="navigation_drawer_donate">Dotacja</string>
|
||||
<string name="account_list_empty">Witamy w DAVdroid!\n\nMożesz teraz dodać konto CalDAV/CardDAV.</string>
|
||||
<!--DavService-->
|
||||
<string name="dav_service_refresh_failed">Wykrycie serwisu nie powiodło się</string>
|
||||
<string name="dav_service_refresh_couldnt_refresh">Nie można odświeżyć listy kolekcji</string>
|
||||
<!--AppSettingsActivity-->
|
||||
<string name="app_settings">Ustawienia</string>
|
||||
<string name="app_settings_user_interface">Interfejs użytkownika</string>
|
||||
<string name="app_settings_reset_hints">Zresetuj podpowiedzi</string>
|
||||
<string name="app_settings_reset_hints_summary">Ponownie włącz wskazówki, które zostały usunięte wcześniej</string>
|
||||
<string name="app_settings_reset_hints_success">Wszystkie wskazówki pojawią się ponownie</string>
|
||||
<string name="app_settings_security">Bezpieczeństwo</string>
|
||||
<string name="app_settings_distrust_system_certs">Usuń certyfikaty systemowe</string>
|
||||
<string name="app_settings_distrust_system_certs_on">CA systemowe i użytkownika nie zostaną dodane</string>
|
||||
<string name="app_settings_distrust_system_certs_off">CA systemowe i użytkownika zostaną dodane (zalecane)</string>
|
||||
<string name="app_settings_reset_certificates">Zresetuj (nie)zaufane certyfikaty</string>
|
||||
<string name="app_settings_reset_certificates_summary">Zresetuj wszystkie niestandardowe certyfikaty.</string>
|
||||
<string name="app_settings_reset_certificates_success">Wszystkie niestandardowe certyfikaty zostały wyczyszczone</string>
|
||||
<string name="app_settings_debug">Debugowanie</string>
|
||||
<string name="app_settings_log_to_external_storage">Loguj to zewnętrznego pliku</string>
|
||||
<string name="app_settings_log_to_external_storage_on">Logowanie do zewnętrznej pamięci (jeśli jest dostępna)</string>
|
||||
<string name="app_settings_log_to_external_storage_off">Logowanie do zewnętrznego pliku jest niedostępne</string>
|
||||
<string name="app_settings_show_debug_info">Pokaż informacje do debug\'owania</string>
|
||||
<string name="app_settings_show_debug_info_details">Przeglądaj/udostępnij oprogramowanie i szczegóły konfiguracji </string>
|
||||
<!--AccountActivity-->
|
||||
<string name="account_synchronize_now">Synchronizuj teraz</string>
|
||||
<string name="account_synchronizing_now">Synchronizcja w toku</string>
|
||||
<string name="account_settings">Ustawienia konta</string>
|
||||
<string name="account_delete">Usuń konto</string>
|
||||
<string name="account_delete_confirmation_title">Naprawdę chcesz usunąć konto?</string>
|
||||
<string name="account_delete_confirmation_text">Wszystkie lokalne kopie książek adresowych, kalendarzy i list zadań zostaną usunięte.</string>
|
||||
<string name="account_refresh_address_book_list">Odśwież list książek adresowych</string>
|
||||
<string name="account_create_new_address_book">Stwórz nową książkę adresową</string>
|
||||
<string name="account_refresh_calendar_list">Odśwież liste kalendarzy</string>
|
||||
<string name="account_create_new_calendar">Stwórz nowy kalendarz</string>
|
||||
<!--PermissionsActivity-->
|
||||
<string name="permissions_title">Uprawnienia DAVdroid</string>
|
||||
<string name="permissions_calendar">Uprawnienia kalendarza</string>
|
||||
<string name="permissions_calendar_details">Aby synchronizować wydarzenia CalDav z lokalnymi kalendarzami, DAVdroid potrzebuje dostępu do twoich kalendarzy.</string>
|
||||
<string name="permissions_calendar_request">Zezwól na uprawnienia kalendarza</string>
|
||||
<string name="permissions_contacts">Uprawnienia kontaktów</string>
|
||||
<string name="permissions_contacts_details">Aby synchronizować książki adresowe CardDAV z lokalnymi kontaktami, DAVdroid potrzebuje dostępu do twoich kontaktów.</string>
|
||||
<string name="permissions_contacts_request">Zezwól na uprawnienia kontaktów</string>
|
||||
<string name="permissions_opentasks">Uprawnienia OpenTasks</string>
|
||||
<string name="permissions_opentasks_details">Aby synchronizować zadania CalDav z lokalnymi listami zadań, DAVdroid potrzebuje dostępu do OpenTasks.</string>
|
||||
<string name="permissions_opentasks_request">Zezwól na uprawnienia OpenTasks</string>
|
||||
<!--AddAccountActivity-->
|
||||
<string name="login_title">Dodaj konto</string>
|
||||
<string name="login_type_email">Logowanie za pomocą adresu e-mail</string>
|
||||
<string name="login_email_address">Adres e-mail</string>
|
||||
<string name="login_email_address_error">Wymanagny poprawny adres e-mail</string>
|
||||
<string name="login_password">Hasło</string>
|
||||
<string name="login_password_required">Wymagane hasło</string>
|
||||
<string name="login_type_url">Logowanie za pomocą adresu URL i nazwy użytkownika</string>
|
||||
<string name="login_url_must_be_http_or_https">URL musi zaczynać się z http(s)://</string>
|
||||
<string name="login_url_host_name_required">Wymagana nazwa hosta</string>
|
||||
<string name="login_user_name">Nazwa użytkownika</string>
|
||||
<string name="login_user_name_required">Wymagana nazwa użtkonika</string>
|
||||
<string name="login_base_url">Podstawowy URL</string>
|
||||
<string name="login_login">Zaloguj</string>
|
||||
<string name="login_back">Wróć</string>
|
||||
<string name="login_create_account">Stwórz konto</string>
|
||||
<string name="login_account_name">Nazwa konta</string>
|
||||
<string name="login_account_name_info">Użyj swojego adresu e-mail jako nazwy konta, ponieważ Android będzie używał nazwy konta jako pola ORGANIZATOR dla wydarzeń, które stworzysz. Nie możesz posiadać dwóch kont o takiej samej nazwie.</string>
|
||||
<string name="login_account_contact_group_method">Metoda grupowania kontaktów:</string>
|
||||
<string name="login_account_name_required">Wymagana nazwa konta</string>
|
||||
<string name="login_account_not_created">Konto nie mogło zostać stworzone</string>
|
||||
<string name="login_configuration_detection">Wykrywanie konfiguracji</string>
|
||||
<string name="login_querying_server">Proszę czekać, odpytywanie serwera...</string>
|
||||
<string name="login_no_caldav_carddav">Nie można znaleźć usługi CalDAV lub CardDAV.</string>
|
||||
<string name="login_view_logs">Pokaż logi</string>
|
||||
<!--AccountSettingsActivity-->
|
||||
<string name="settings_title">Ustawienia: %s</string>
|
||||
<string name="settings_authentication">Uwierzytelnianie</string>
|
||||
<string name="settings_username">Nazwa użytkownika</string>
|
||||
<string name="settings_enter_username">Wpisz nazwe użytkownika:</string>
|
||||
<string name="settings_password">Hasło</string>
|
||||
<string name="settings_password_summary">Zaktualizuj hasło zgodnie z serwerem.</string>
|
||||
<string name="settings_enter_password">Wpisz hasło:</string>
|
||||
<string name="settings_sync">Synchronizacja</string>
|
||||
<string name="settings_sync_interval_contacts">Okres synchronizacji kontktów</string>
|
||||
<string name="settings_sync_summary_manually">Tylko ręcznie</string>
|
||||
<string name="settings_sync_summary_periodically" tools:ignore="PluralsCandidate">Co %d minut oraz natychmiast przy zmianach lokalnych</string>
|
||||
<string name="settings_sync_summary_not_available">Niedostępne</string>
|
||||
<string name="settings_sync_interval_calendars">Okres synchronizacji kalendarzy</string>
|
||||
<string name="settings_sync_interval_tasks">Okres synchronizacji list zadań</string>
|
||||
<string-array name="settings_sync_interval_seconds">
|
||||
<item>-1</item>
|
||||
<item>300</item>
|
||||
<item>600</item>
|
||||
<item>900</item>
|
||||
<item>3600</item>
|
||||
<item>7200</item>
|
||||
<item>14400</item>
|
||||
<item>86400</item>
|
||||
</string-array>
|
||||
<string-array name="settings_sync_interval_names">
|
||||
<item>Tylko ręcznie</item>
|
||||
<item>Co 5 minut</item>
|
||||
<item>Co 10 minut</item>
|
||||
<item>Co 15 minut</item>
|
||||
<item>Co godzinę</item>
|
||||
<item>Co 2 godziny</item>
|
||||
<item>Co 4 godziny</item>
|
||||
<item>Raz dziennie</item>
|
||||
</string-array>
|
||||
<string name="settings_sync_wifi_only">Synchronizuj tylko przez WiFi</string>
|
||||
<string name="settings_sync_wifi_only_on">Synchronizacja jest ograniczony do połączeń WiFi</string>
|
||||
<string name="settings_sync_wifi_only_off">Rodzaj połączenia nie jest brany pod uwagę</string>
|
||||
<string name="settings_sync_wifi_only_ssid">Organicznie WiFi SSID</string>
|
||||
<string name="settings_sync_wifi_only_ssid_on">Będzie synchronizować tylko przez %s.</string>
|
||||
<string name="settings_sync_wifi_only_ssid_off">Wszystkie połączenia WiFi mogą zostać wykorzystane</string>
|
||||
<string name="settings_sync_wifi_only_ssid_message">Wprowadź nazwę sieci WiFi (SSID), aby ograniczyć synchronizację do tej sieci lub pozostaw puste dla wszystkich połączeń WiFi.</string>
|
||||
<string name="settings_carddav">CardDAV</string>
|
||||
<string name="settings_contact_group_method">Metoda grupowania kontaktów</string>
|
||||
<string-array name="settings_contact_group_method_values">
|
||||
<item>GROUP_VCARDS</item>
|
||||
<item>CATEGORIES</item>
|
||||
</string-array>
|
||||
<string name="settings_rfc6868_for_vcards">Użyj RFC6868 dla VCards</string>
|
||||
<string name="settings_rfc6868_for_vcards_on">Podwójne apostrofy można stosować w wartościach parametrów</string>
|
||||
<string name="settings_rfc6868_for_vcards_off">Podwójne apostrofy nie można stosować w wartościach parametrów</string>
|
||||
<string name="settings_caldav">CalDAV</string>
|
||||
<string name="settings_sync_time_range_past">Limit czasowy przeszłych wydarzeń</string>
|
||||
<string name="settings_sync_time_range_past_none">Wszystkie wydarzenia zostaną synchronizowane</string>
|
||||
<plurals name="settings_sync_time_range_past_days">
|
||||
<item quantity="one">Wydarzenia starsze niż jeden dzień zostaną zignorowane.</item>
|
||||
<item quantity="few">Wydarzenia starsze niż %d dni zostaną zignorowane.</item>
|
||||
<item quantity="other">Wydarzenia starsze niż %d dni zostaną zignorowane.</item>
|
||||
</plurals>
|
||||
<string name="settings_sync_time_range_past_message">Wydarzenia, które są starsze niż podana liczba dni zostaną zignorowane (może być 0). Zostaw puste, aby synchronizować wszystkie wydarzenia.</string>
|
||||
<string name="settings_manage_calendar_colors">Zarządzaj kolorami kalendarza</string>
|
||||
<string name="settings_manage_calendar_colors_on">Kolory kalendarza są zarządzane przez DAVdroid</string>
|
||||
<string name="settings_manage_calendar_colors_off">Kolory kalendarze nie są ustawiane przez DAVdroid</string>
|
||||
<string name="settings_version_update">Aktualizacji wersji DAVdroid</string>
|
||||
<string name="settings_version_update_settings_updated">Ustawienia wewnętrzne zostały zaktualizowane.</string>
|
||||
<string name="settings_version_update_install_hint">Problemy? Odinstaluj DAVdroid i zainstaluj ponownie.</string>
|
||||
<!--collection management-->
|
||||
<string name="create_addressbook">Stwórz książkę adresową</string>
|
||||
<string name="create_addressbook_display_name_hint">Moja książka adresowa</string>
|
||||
<string name="create_calendar">Stwórz kolekcje CalDAV</string>
|
||||
<string name="create_calendar_display_name_hint">Mój kalendarz</string>
|
||||
<string name="create_calendar_time_zone">Strefa czasowa:</string>
|
||||
<string name="create_calendar_type">Typ kolekcji:</string>
|
||||
<string name="create_calendar_type_only_events">Kalendarz (tylko wydarzenia)</string>
|
||||
<string name="create_calendar_type_only_tasks">Lista zadań (tylko zadań)</string>
|
||||
<string name="create_calendar_type_events_and_tasks">Połączone (wydarzenia i zadania)</string>
|
||||
<string name="create_collection_color">Ustaw kolor kolekcji</string>
|
||||
<string name="create_collection_creating">Tworzenie kolekcji</string>
|
||||
<string name="create_collection_display_name">Nazwa wyświetlana (tytuł) kolekcji:</string>
|
||||
<string name="create_collection_display_name_required">Tytuł jest wymagany</string>
|
||||
<string name="create_collection_description">Opis (opcjoalnie)</string>
|
||||
<string name="create_collection_home_set">Ustaw początek:</string>
|
||||
<string name="create_collection_create">Stwórz</string>
|
||||
<string name="delete_collection">Usuń kolekcje</string>
|
||||
<string name="delete_collection_confirm_title">Jesteś pewien?</string>
|
||||
<string name="delete_collection_confirm_warning">Kolekcja (%s) i jej wszystkie dane zostaną usunięte z serwera.</string>
|
||||
<string name="delete_collection_deleting_collection">Usuwanie kolekcji</string>
|
||||
<!--ExceptionInfoFragment-->
|
||||
<string name="exception">Wystąpił błąd.</string>
|
||||
<string name="exception_httpexception">Wystąpił błąd HTTP.</string>
|
||||
<string name="exception_ioexception">Wystąpił błąd I/O.</string>
|
||||
<string name="exception_show_details">Pokaż szczegóły</string>
|
||||
<!--sync errors and DebugInfoActivity-->
|
||||
<string name="debug_info_title">Informacje debugowe</string>
|
||||
<string name="sync_error_permissions">Uprawnienia DAVdroid</string>
|
||||
<string name="sync_error_permissions_text">Wymagane dodatkowe uprawnienia</string>
|
||||
<string name="sync_error_calendar">Synchronizacja kalendarza nie powiodała się (%s)</string>
|
||||
<string name="sync_error_contacts">Synchronizacja książki adresowej nie powiodała się (%s)</string>
|
||||
<string name="sync_error_tasks">Synchronizacja zadań nie powiodała się (%s)</string>
|
||||
<string name="sync_error">Błąd podczas %s</string>
|
||||
<string name="sync_error_http_dav">Błąd servera podczas %s</string>
|
||||
<string name="sync_error_local_storage">Bład bazy danych podczas %s</string>
|
||||
<string-array name="sync_error_phases">
|
||||
<item>przygotowanie synchronizacji</item>
|
||||
<item>odpytywanie możliwości</item>
|
||||
<item>przetwarzanie lokalnie usuniętych wpisów</item>
|
||||
<item>przygotowanie stworzonych/zmodyfikowanych wpisów</item>
|
||||
<item>wysyłanie stworzonych/zmodyfikowanych wpisów</item>
|
||||
<item>sprawdzanie stanu synchronizacji</item>
|
||||
<item>listowanie lokalnych wpisów</item>
|
||||
<item>listowanie zdalnych wpisów</item>
|
||||
<item>porównywanie lokalnych/zdalnych wpisów</item>
|
||||
<item>pobieranie zdalnych wpisów</item>
|
||||
<item>przetwarzanie końcowe</item>
|
||||
<item>zapisywanie stanu synchronizacji</item>
|
||||
</string-array>
|
||||
<string name="sync_error_unauthorized">Błędna nazwa użytkownika lub hasło</string>
|
||||
<!--cert4android-->
|
||||
<string name="certificate_notification_connection_security">DAVdroid: Bezpieczeństwo połączenia</string>
|
||||
<string name="trust_certificate_unknown_certificate_found">DAVdroid napotkał nieznany certyfikat. Czy chcesz go dodać?</string>
|
||||
</resources>
|
||||
242
app/src/davdroid/res/values-pt-rBR/strings.xml
Normal file
242
app/src/davdroid/res/values-pt-rBR/strings.xml
Normal file
@@ -0,0 +1,242 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<!--common strings-->
|
||||
<string name="app_name">DAVdroid</string>
|
||||
<string name="help">Ajuda</string>
|
||||
<string name="manage_accounts">Gerenciar contas</string>
|
||||
<string name="please_wait">Por favor, aguarde...</string>
|
||||
<string name="send">Enviar</string>
|
||||
<!--startup dialogs-->
|
||||
<string name="startup_battery_optimization">Otimização da bateria</string>
|
||||
<string name="startup_battery_optimization_message">O Android pode desativar/reduzir a sincronização do DAVdroid depois de alguns dias. Para evitar isso, desligue a otimização da bateria.</string>
|
||||
<string name="startup_battery_optimization_disable">Desligar para o DAVdroid</string>
|
||||
<string name="startup_dont_show_again">Não mostrar novamente</string>
|
||||
<string name="startup_development_version">Versão prévia do DAVdroid</string>
|
||||
<string name="startup_development_version_message">Esta é uma versão de desenvolvimento do DAVdroid. Lembre-se de que as coisas podem não funcionar como esperado. Por favor, envie sugestões e comentários construtivos para melhorar o DAVdroid.</string>
|
||||
<string name="startup_development_version_give_feedback">Enviar sugestões</string>
|
||||
<string name="startup_donate">Informação sobre Código Aberto</string>
|
||||
<string name="startup_donate_message">Estamos felizes que você usa o DAVdroid, um software de código aberto (GPLv3). O desenvolvimento do DAVdroid é trabalhoso e consome muitas horas de trabalho. Por esse motivo, considere fazer uma doação.</string>
|
||||
<string name="startup_donate_now">Mostrar a página de doações</string>
|
||||
<string name="startup_donate_later">Talvez depois</string>
|
||||
<string name="startup_google_play_accounts_removed">Informação sobre o erro de DRM da Play Store</string>
|
||||
<string name="startup_google_play_accounts_removed_message">Sob certas condições, o DRM da Play Store pode fazer com que todas as contas DAVdroid sejam perdidas depois de uma reinicialização ou atualização do DAVdroid. Se você for afetado por esse problema, instale o \"DAVdroid JB Workaround\" a partir da Play Store.</string>
|
||||
<string name="startup_google_play_accounts_removed_more_info">Mais informações</string>
|
||||
<string name="startup_opentasks_not_installed">O OpenTasks não está instalado</string>
|
||||
<string name="startup_opentasks_not_installed_message">O aplicativo OpenTasks não está disponível, não sendo possível sincronizar as listas de tarefas pelo DAVdroid.</string>
|
||||
<string name="startup_opentasks_reinstall_davdroid">Depois da instalação do OpenTasks, torna-se necessário REINSTALAR o DAVdroid e adicionar suas contas novamente (erro do Android).</string>
|
||||
<string name="startup_opentasks_not_installed_install">Instalar o OpenTasks</string>
|
||||
<!--AboutActivity-->
|
||||
<string name="about_license_terms">Termos da Licença</string>
|
||||
<string name="about_license_info_no_warranty">Este programa é distribuído SEM NENHUMA GARANTIA. Ele é software livre e pode ser redistribuído sob algumas condições.</string>
|
||||
<!--global settings-->
|
||||
<string name="logging_davdroid_file_logging">Registro do arquivo do DAVdroid</string>
|
||||
<string name="logging_to_external_storage">Registrando no arquivo externo: %s</string>
|
||||
<string name="logging_to_external_storage_warning">Excluir os registros assim que possível!</string>
|
||||
<string name="logging_couldnt_create_file">Não foi possível criar o arquivo de registro externo: %s</string>
|
||||
<string name="logging_no_external_storage">Armazenamento externo não encontrado</string>
|
||||
<!--AccountsActivity-->
|
||||
<string name="navigation_drawer_open">Abrir a gaveta de navegação</string>
|
||||
<string name="navigation_drawer_close">Fechar gaveta de navegação</string>
|
||||
<string name="navigation_drawer_subtitle">Sincronização de CalDAV/CardDAV</string>
|
||||
<string name="navigation_drawer_about">Sobre / Licença</string>
|
||||
<string name="navigation_drawer_settings">Configurações</string>
|
||||
<string name="navigation_drawer_news_updates">Novidades e atualizações</string>
|
||||
<string name="navigation_drawer_external_links">Links externos</string>
|
||||
<string name="navigation_drawer_website">Site na Web</string>
|
||||
<string name="navigation_drawer_faq">Perguntas fequentes</string>
|
||||
<string name="navigation_drawer_forums">Comunidade</string>
|
||||
<string name="navigation_drawer_donate">Doações</string>
|
||||
<string name="account_list_empty">Bem-vindo ao DAVdroid!\n\nVocê pode adicionar uma conta CalDAV/CardDAV agora.</string>
|
||||
<!--DavService-->
|
||||
<string name="dav_service_refresh_failed">Falha na detecção do serviço</string>
|
||||
<string name="dav_service_refresh_couldnt_refresh">Não foi possível atualizar a lista da coleção</string>
|
||||
<!--AppSettingsActivity-->
|
||||
<string name="app_settings">Configurações</string>
|
||||
<string name="app_settings_user_interface">Interface de usuário</string>
|
||||
<string name="app_settings_reset_hints">Restaurar sugestões</string>
|
||||
<string name="app_settings_reset_hints_summary">Restaura as sugestões que foram descartadas anteriormente</string>
|
||||
<string name="app_settings_reset_hints_success">Todas as sugestões serão exibidas novamente</string>
|
||||
<string name="app_settings_security">Segurança</string>
|
||||
<string name="app_settings_distrust_system_certs">Desconfiar dos certificados de sistema</string>
|
||||
<string name="app_settings_distrust_system_certs_on">ACs adicionadas pelo usuário e pelo sistema não serão confiáveis</string>
|
||||
<string name="app_settings_distrust_system_certs_off">ACs adicionadas pelo usuário e pelo sistema serão confiáveis (recomendado)</string>
|
||||
<string name="app_settings_reset_certificates">Restaurar certificados não-confiáveis</string>
|
||||
<string name="app_settings_reset_certificates_summary">Restaura a confiança de todos os certificados personalizados</string>
|
||||
<string name="app_settings_reset_certificates_success">Todos os certificados personalizados foram restaurados</string>
|
||||
<string name="app_settings_debug">Depuração</string>
|
||||
<string name="app_settings_log_to_external_storage">Registrar em arquivo externo</string>
|
||||
<string name="app_settings_log_to_external_storage_on">Registrando no armazenamento externo (se disponível)</string>
|
||||
<string name="app_settings_log_to_external_storage_off">O registro em arquivo externo está desativado</string>
|
||||
<string name="app_settings_show_debug_info">Mostrar informações de depuração</string>
|
||||
<string name="app_settings_show_debug_info_details">Exibe/compartilha o software e os detalhes da configuração</string>
|
||||
<!--AccountActivity-->
|
||||
<string name="account_synchronize_now">Sincronizar agora</string>
|
||||
<string name="account_synchronizing_now">Sincronizando</string>
|
||||
<string name="account_settings">Configurações da conta</string>
|
||||
<string name="account_delete">Excluir conta</string>
|
||||
<string name="account_delete_confirmation_title">Deseja excluir a conta?</string>
|
||||
<string name="account_delete_confirmation_text">Todas as cópias locais dos livros de endereços, calendários e listas de tarefas serão excluídas.</string>
|
||||
<string name="account_refresh_address_book_list">Atualizar lista de livros de endereços</string>
|
||||
<string name="account_create_new_address_book">Criar novo livro de endereços</string>
|
||||
<string name="account_refresh_calendar_list">Atualizar lista de calendários</string>
|
||||
<string name="account_create_new_calendar">Criar novo calendário</string>
|
||||
<!--PermissionsActivity-->
|
||||
<string name="permissions_title">Permissões do DAVdroid</string>
|
||||
<string name="permissions_calendar">Permissões do calendário</string>
|
||||
<string name="permissions_calendar_details">Para sincronizar os eventos CalDAV com seus calendários locais, o DAVdroid precisa acessar seus calendários.</string>
|
||||
<string name="permissions_calendar_request">Solicitar permissão do calendário</string>
|
||||
<string name="permissions_contacts">Permissões dos contados</string>
|
||||
<string name="permissions_contacts_details">Para sincronizar livros de endereços CardDAV com seus contatos locais, o DAVdroid precisa acessar seus contatos.</string>
|
||||
<string name="permissions_contacts_request">Solicitar permissão dos contatos</string>
|
||||
<string name="permissions_opentasks">Permissões do OpenTasks</string>
|
||||
<string name="permissions_opentasks_details">Para sincronizar tarefas CalDAV com suas listas de tarefas locais, o DAVdroid precisa acessar o OpenTasks.</string>
|
||||
<string name="permissions_opentasks_request">Solicitar permissão do OpenTasks</string>
|
||||
<!--AddAccountActivity-->
|
||||
<string name="login_title">Adicionar conta</string>
|
||||
<string name="login_type_email">Autenticação com endereço de e-mail</string>
|
||||
<string name="login_email_address">Endereço de e-mail</string>
|
||||
<string name="login_email_address_error">É necessário um e-mail válido</string>
|
||||
<string name="login_password">Senha</string>
|
||||
<string name="login_password_required">É necessário uma senha</string>
|
||||
<string name="login_type_url">Autenticação com usuário e URL</string>
|
||||
<string name="login_url_must_be_http_or_https">A URL deve começar com http(s)://</string>
|
||||
<string name="login_url_host_name_required">É necessário um nome de máquina</string>
|
||||
<string name="login_user_name">Usuário</string>
|
||||
<string name="login_user_name_required">É necessário um nome de usuário</string>
|
||||
<string name="login_base_url">URL base</string>
|
||||
<string name="login_login">Autenticar</string>
|
||||
<string name="login_back">Voltar</string>
|
||||
<string name="login_create_account">Criar conta</string>
|
||||
<string name="login_account_name">Nome da conta</string>
|
||||
<string name="login_account_name_info">Use seu endereço de e-mail como nome da conta porque o Android irá usar esse nome como campo AGENDA nos eventos que você criar. Não é possível ter duas contas com o mesmo nome.</string>
|
||||
<string name="login_account_contact_group_method">Método do grupo Contato:</string>
|
||||
<string name="login_account_name_required">É necessário um nome de conta</string>
|
||||
<string name="login_account_not_created">A conta não pôde ser criada</string>
|
||||
<string name="login_configuration_detection">Detecção de configuração</string>
|
||||
<string name="login_querying_server">Aguarde, procurando servidor...</string>
|
||||
<string name="login_no_caldav_carddav">Não foi possível encontrar o serviço CalDAV ou CardDAV.</string>
|
||||
<string name="login_view_logs">Exibir registros</string>
|
||||
<!--AccountSettingsActivity-->
|
||||
<string name="settings_title">Configurações: %s</string>
|
||||
<string name="settings_authentication">Autenticação</string>
|
||||
<string name="settings_username">Nome do usuário</string>
|
||||
<string name="settings_enter_username">Digite o nome do usuário:</string>
|
||||
<string name="settings_password">Senha</string>
|
||||
<string name="settings_password_summary">Atualize a senha de acordo com seu servidor</string>
|
||||
<string name="settings_enter_password">Digite sua senha:</string>
|
||||
<string name="settings_sync">Sincronização</string>
|
||||
<string name="settings_sync_interval_contacts">Intervalo sinc. de contatos</string>
|
||||
<string name="settings_sync_summary_manually">Apenas manualmente</string>
|
||||
<string name="settings_sync_summary_periodically" tools:ignore="PluralsCandidate">A cada %d minutos + imediatamente nas alterações locais</string>
|
||||
<string name="settings_sync_summary_not_available">Indisponível</string>
|
||||
<string name="settings_sync_interval_calendars">Intervalo sinc. de calendários</string>
|
||||
<string name="settings_sync_interval_tasks">Intervalo sinc. de tarefas</string>
|
||||
<string-array name="settings_sync_interval_seconds">
|
||||
<item>-1</item>
|
||||
<item>300</item>
|
||||
<item>600</item>
|
||||
<item>900</item>
|
||||
<item>3600</item>
|
||||
<item>7200</item>
|
||||
<item>14400</item>
|
||||
<item>86400</item>
|
||||
</string-array>
|
||||
<string-array name="settings_sync_interval_names">
|
||||
<item>Apenas manualmente</item>
|
||||
<item>A cada 5 minutos</item>
|
||||
<item>A cada 10 minutos</item>
|
||||
<item>A cada 15 minutos</item>
|
||||
<item>A cada hora</item>
|
||||
<item>A cada 2 horas</item>
|
||||
<item>A cada 4 horas</item>
|
||||
<item>Uma vez por dia</item>
|
||||
</string-array>
|
||||
<string name="settings_sync_wifi_only">Sincronizar apenas por Wi-Fi</string>
|
||||
<string name="settings_sync_wifi_only_on">Sincronização restrita a conexões Wi-Fi</string>
|
||||
<string name="settings_sync_wifi_only_off">O tipo de conexão não é considerado</string>
|
||||
<string name="settings_sync_wifi_only_ssid">Restrição de SSID Wi-Fi</string>
|
||||
<string name="settings_sync_wifi_only_ssid_on">Sincronizará apenas com %s</string>
|
||||
<string name="settings_sync_wifi_only_ssid_off">Qualquer conexão Wi-Fi pode ser utilizada</string>
|
||||
<string name="settings_sync_wifi_only_ssid_message">Informe o nome da rede Wi-Fi (SSID) que será usada para sincronização ou deixe em branco para usar qualquer conexão Wi-Fi.</string>
|
||||
<string name="settings_carddav">CardDAV</string>
|
||||
<string name="settings_contact_group_method">Método do grupo Contato</string>
|
||||
<string-array name="settings_contact_group_method_values">
|
||||
<item>GROUP_VCARDS</item>
|
||||
<item>CATEGORIES</item>
|
||||
</string-array>
|
||||
<string-array name="settings_contact_group_method_entries">
|
||||
<item>Grupos são VCards separados</item>
|
||||
<item>Grupos são categorias por contato</item>
|
||||
</string-array>
|
||||
<string name="settings_rfc6868_for_vcards">Usar RFC6868 para VCards</string>
|
||||
<string name="settings_rfc6868_for_vcards_on">Aspas podem ser utilizadas nos valores dos parâmetros</string>
|
||||
<string name="settings_rfc6868_for_vcards_off">Aspas não podem ser utilizadas nos valores dos parâmetros</string>
|
||||
<string name="settings_caldav">CalDAV</string>
|
||||
<string name="settings_sync_time_range_past">Limite de tempo para eventos passados</string>
|
||||
<string name="settings_sync_time_range_past_none">Todos os eventos serão sincronizados</string>
|
||||
<plurals name="settings_sync_time_range_past_days">
|
||||
<item quantity="one">Os eventos que ocorreram a mais de um dia serão ignorados</item>
|
||||
<item quantity="other">Eventos que ocorreram a mais de %d dias serão ignorados</item>
|
||||
</plurals>
|
||||
<string name="settings_sync_time_range_past_message">Os eventos que ocorreram antes desse número de dias serão ignorados (pode ser 0). Deixe em branco para sincronizar todos os eventos.</string>
|
||||
<string name="settings_manage_calendar_colors">Gerenciar cores dos calendários</string>
|
||||
<string name="settings_manage_calendar_colors_on">Cores dos calendários definidas pelo DAVdroid</string>
|
||||
<string name="settings_manage_calendar_colors_off">Cores dos calendários não definidas pelo DAVdroid</string>
|
||||
<string name="settings_version_update">Atualização da versão do DAVdroid</string>
|
||||
<string name="settings_version_update_settings_updated">As configurações internas foram atualizadas.</string>
|
||||
<string name="settings_version_update_install_hint">Problemas? Desinstale o DAVdroid e instale novamente.</string>
|
||||
<!--collection management-->
|
||||
<string name="create_addressbook">Criar livro de endereços</string>
|
||||
<string name="create_addressbook_display_name_hint">Meu livro de endereços</string>
|
||||
<string name="create_calendar">Criar coleção CalDAV</string>
|
||||
<string name="create_calendar_display_name_hint">Meu calendário</string>
|
||||
<string name="create_calendar_time_zone">Fuso horário:</string>
|
||||
<string name="create_calendar_type">Tipo de coleção:</string>
|
||||
<string name="create_calendar_type_only_events">Calendário (apenas eventos)</string>
|
||||
<string name="create_calendar_type_only_tasks">Lista de tarefas (apenas tarefas)</string>
|
||||
<string name="create_calendar_type_events_and_tasks">Combinado (eventos e tarefas)</string>
|
||||
<string name="create_collection_color">Definir a cor da coleção</string>
|
||||
<string name="create_collection_creating">Criando a coleção</string>
|
||||
<string name="create_collection_display_name">Mostrar o nome (título) desta coleção:</string>
|
||||
<string name="create_collection_display_name_required">É necessário um título</string>
|
||||
<string name="create_collection_description">Descrição (opcional):</string>
|
||||
<string name="create_collection_home_set">Indique o endereço:</string>
|
||||
<string name="create_collection_create">Criar</string>
|
||||
<string name="delete_collection">Excluir coleção</string>
|
||||
<string name="delete_collection_confirm_title">Tem certeza?</string>
|
||||
<string name="delete_collection_confirm_warning">Esta coleção (%s) e todos os seus dados serão removidos do servidor.</string>
|
||||
<string name="delete_collection_deleting_collection">Excluindo coleção</string>
|
||||
<!--ExceptionInfoFragment-->
|
||||
<string name="exception">Ocorreu um erro.</string>
|
||||
<string name="exception_httpexception">Ocorreu um erro de HTTP.</string>
|
||||
<string name="exception_ioexception">Ocorreu um erro de leitura/gravação.</string>
|
||||
<string name="exception_show_details">Mostrar detalhes</string>
|
||||
<!--sync errors and DebugInfoActivity-->
|
||||
<string name="debug_info_title">Informações de depuração</string>
|
||||
<string name="sync_error_permissions">Permissões do DAVdroid</string>
|
||||
<string name="sync_error_permissions_text">É necessário permissões adicionais</string>
|
||||
<string name="sync_error_calendar">Falha na sincronização do calendário (%s)</string>
|
||||
<string name="sync_error_contacts">Falha na sincronização do livro de endereços (%s)</string>
|
||||
<string name="sync_error_tasks">Falha na sincronização das tarefas (%s)</string>
|
||||
<string name="sync_error">Erro ao %s</string>
|
||||
<string name="sync_error_http_dav">Erro do servidor ao %s</string>
|
||||
<string name="sync_error_local_storage">Erro do banco de dados ao %s</string>
|
||||
<string-array name="sync_error_phases">
|
||||
<item>preparando sincronização</item>
|
||||
<item>procurando habilidades</item>
|
||||
<item>processando os itens excluídos localmente</item>
|
||||
<item>preparando os itens criados/modificados</item>
|
||||
<item>enviando os itens criados/modificados</item>
|
||||
<item>verificando o estado da sincronização</item>
|
||||
<item>listando os itens locais</item>
|
||||
<item>listando os itens remotos</item>
|
||||
<item>comparando os itens locais/remotos</item>
|
||||
<item>baixando os itens remotos</item>
|
||||
<item>pós-processamento</item>
|
||||
<item>salvando o estado da sincronização</item>
|
||||
</string-array>
|
||||
<string name="sync_error_unauthorized">Usuário/senha incorreto</string>
|
||||
<!--cert4android-->
|
||||
<string name="certificate_notification_connection_security">DAVdroid: Segurança da conexão</string>
|
||||
<string name="trust_certificate_unknown_certificate_found">O DAVdroid encontrou um certificado desconhecido. Deseja torná-lo confiável?</string>
|
||||
</resources>
|
||||
54
app/src/davdroid/res/values-pt/strings.xml
Normal file
54
app/src/davdroid/res/values-pt/strings.xml
Normal file
@@ -0,0 +1,54 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<!--common strings-->
|
||||
<string name="app_name">DAVDroid</string>
|
||||
<string name="help">Ajuda</string>
|
||||
<!--startup dialogs-->
|
||||
<!--AboutActivity-->
|
||||
<!--global settings-->
|
||||
<!--AccountsActivity-->
|
||||
<!--DavService-->
|
||||
<!--AppSettingsActivity-->
|
||||
<!--AccountActivity-->
|
||||
<!--PermissionsActivity-->
|
||||
<!--AddAccountActivity-->
|
||||
<string name="login_type_email">Login com seu enderço de email</string>
|
||||
<string name="login_type_url">Login com URL e nome do usuário</string>
|
||||
<!--AccountSettingsActivity-->
|
||||
<string name="settings_authentication">Autenticação</string>
|
||||
<string name="settings_username">Nome de usuário</string>
|
||||
<string name="settings_enter_username">Informe o nome de usuário:</string>
|
||||
<string name="settings_password">Senha</string>
|
||||
<string name="settings_password_summary">Atualize sua denha de acordo com a do seu servidor.</string>
|
||||
<string name="settings_enter_password">Informe sua senha:</string>
|
||||
<string name="settings_sync">Sincronização</string>
|
||||
<string name="settings_sync_interval_contacts">Intervalo de sincronização dos contatos</string>
|
||||
<string name="settings_sync_summary_manually">Manualmente apenas</string>
|
||||
<string name="settings_sync_summary_periodically" tools:ignore="PluralsCandidate">A cada %d minutos + imediatamente em alterações locais</string>
|
||||
<string name="settings_sync_summary_not_available">Não avaliado</string>
|
||||
<string name="settings_sync_interval_calendars">Intervalo de sincronização do calendário</string>
|
||||
<string-array name="settings_sync_interval_seconds">
|
||||
<item>-1</item>
|
||||
<item>300</item>
|
||||
<item>600</item>
|
||||
<item>900</item>
|
||||
<item>3600</item>
|
||||
<item>7200</item>
|
||||
<item>14400</item>
|
||||
<item>86400</item>
|
||||
</string-array>
|
||||
<string-array name="settings_sync_interval_names">
|
||||
<item>Apenas manualmente</item>
|
||||
<item>A cada 5 minutos</item>
|
||||
<item>A cada 10 minutos</item>
|
||||
<item>A cada 15 minutos</item>
|
||||
<item>A cada hora</item>
|
||||
<item>A cada 2 horas</item>
|
||||
<item>A cada 4 horas</item>
|
||||
<item>Diáriamente</item>
|
||||
</string-array>
|
||||
<!--collection management-->
|
||||
<!--ExceptionInfoFragment-->
|
||||
<!--sync errors and DebugInfoActivity-->
|
||||
<!--cert4android-->
|
||||
</resources>
|
||||
64
app/src/davdroid/res/values-ru/strings.xml
Normal file
64
app/src/davdroid/res/values-ru/strings.xml
Normal file
@@ -0,0 +1,64 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<!--common strings-->
|
||||
<string name="app_name">DAVdroid</string>
|
||||
<string name="help">Помощь</string>
|
||||
<string name="send">Отправить</string>
|
||||
<!--startup dialogs-->
|
||||
<!--AboutActivity-->
|
||||
<!--global settings-->
|
||||
<!--AccountsActivity-->
|
||||
<!--DavService-->
|
||||
<!--AppSettingsActivity-->
|
||||
<!--AccountActivity-->
|
||||
<!--PermissionsActivity-->
|
||||
<!--AddAccountActivity-->
|
||||
<string name="login_type_email">Вход по адресу email</string>
|
||||
<string name="login_type_url">Вход через URL и имя пользователя</string>
|
||||
<!--AccountSettingsActivity-->
|
||||
<string name="settings_authentication">Идентификация</string>
|
||||
<string name="settings_username">Имя</string>
|
||||
<string name="settings_enter_username">Введите имя пользователя:</string>
|
||||
<string name="settings_password">Пароль</string>
|
||||
<string name="settings_password_summary">Обновить пароль в соответствии с вашим сервером.</string>
|
||||
<string name="settings_enter_password">Введите свой пароль:</string>
|
||||
<string name="settings_sync">Синхронизация</string>
|
||||
<string name="settings_sync_interval_contacts">Интервал синхронизации контактов</string>
|
||||
<string name="settings_sync_summary_manually">Вручную</string>
|
||||
<string name="settings_sync_summary_periodically" tools:ignore="PluralsCandidate">Каждые %d минут и немедленно при локальных изменениях</string>
|
||||
<string name="settings_sync_summary_not_available">Недоступно</string>
|
||||
<string name="settings_sync_interval_calendars">Период синхронизации календарей</string>
|
||||
<string name="settings_sync_interval_tasks">Период синхронизации задач</string>
|
||||
<string-array name="settings_sync_interval_seconds">
|
||||
<item>-1</item>
|
||||
<item>300</item>
|
||||
<item>600</item>
|
||||
<item>900</item>
|
||||
<item>3600</item>
|
||||
<item>7200</item>
|
||||
<item>14400</item>
|
||||
<item>86400</item>
|
||||
</string-array>
|
||||
<string-array name="settings_sync_interval_names">
|
||||
<item>Вручную</item>
|
||||
<item>Каждые 5 минут</item>
|
||||
<item>Каждые 10 минут</item>
|
||||
<item>Каждые 15 минут</item>
|
||||
<item>Каждый час</item>
|
||||
<item>Каждые 2 часа</item>
|
||||
<item>Каждые 4 часа</item>
|
||||
<item>Раз в сутки</item>
|
||||
</string-array>
|
||||
<!--collection management-->
|
||||
<!--ExceptionInfoFragment-->
|
||||
<!--sync errors and DebugInfoActivity-->
|
||||
<string name="debug_info_title">Отладочная информация</string>
|
||||
<string name="sync_error_calendar">Синхронизация календаря завершена с ошибкой (%s)</string>
|
||||
<string name="sync_error_contacts">Синхронизация контактов завершена с ошибкой (%s)</string>
|
||||
<string name="sync_error_tasks">Синхронизация задачи завершена с ошибкой (%s)</string>
|
||||
<string name="sync_error">Ошибка %s</string>
|
||||
<string name="sync_error_http_dav">Ошибка сервера %s</string>
|
||||
<string name="sync_error_local_storage">Ошибка базы данных в процессе %s</string>
|
||||
<string name="sync_error_unauthorized">Имя пользователя/пароль неверные</string>
|
||||
<!--cert4android-->
|
||||
</resources>
|
||||
243
app/src/davdroid/res/values-sr/strings.xml
Normal file
243
app/src/davdroid/res/values-sr/strings.xml
Normal file
@@ -0,0 +1,243 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<!--common strings-->
|
||||
<string name="app_name">ДАВдроид</string>
|
||||
<string name="help">Помоћ</string>
|
||||
<string name="manage_accounts">Управљај налозима</string>
|
||||
<string name="please_wait">Сачекајте…</string>
|
||||
<string name="send">Пошаљи</string>
|
||||
<!--startup dialogs-->
|
||||
<string name="startup_battery_optimization">Оптимизација батерије</string>
|
||||
<string name="startup_battery_optimization_message">Андроид може да искључи/умањи синхронизацију ДАВдроида након неколико дана. Да бисте спречили ово, искључите оптимизацију батерије.</string>
|
||||
<string name="startup_battery_optimization_disable">Искључи за ДАВдроид</string>
|
||||
<string name="startup_dont_show_again">Не приказуј поново</string>
|
||||
<string name="startup_development_version">ДАВдроид прелиминарно издање</string>
|
||||
<string name="startup_development_version_message">Ово је развојно издање ДАВдроида. Имајте на уму да можда неће радити очекивано. Замољавамо вас за конструктивне повратне информације како бисмо га побољшали.</string>
|
||||
<string name="startup_development_version_give_feedback">Повратне информације</string>
|
||||
<string name="startup_donate">Подаци о отвореном кôду</string>
|
||||
<string name="startup_donate_message">Драго нам је да користите ДАВдроид, софтвер отвореног кôда (ГПЛв3). Развој ДАВдроида није баш лак посао и захтева хиљаде радних сати, стога вас молимо да размотрите донацију.</string>
|
||||
<string name="startup_donate_now">Прикажи страницу за донације</string>
|
||||
<string name="startup_donate_later">Можда касније</string>
|
||||
<string name="startup_google_play_accounts_removed">Грешка ДРМ-а Плеј продавнице</string>
|
||||
<string name="startup_google_play_accounts_removed_message">Под одређеним околностима ДРМ Плеј продавнице може да узрокује да сви налози ДАВдроида нестане након рестарта или ажурирања ДАВдроида. Ако и само ако имате овај проблем, инсталирајте „DAVdroid JB Workaround“ са Плеј продавнице.</string>
|
||||
<string name="startup_google_play_accounts_removed_more_info">Још информација</string>
|
||||
<string name="startup_opentasks_not_installed">Отворени задаци нису инсталирани</string>
|
||||
<string name="startup_opentasks_not_installed_message">Отворени задаци нису доступни па ДАВдроид неће моћи да синхронизује листе задатака. </string>
|
||||
<string name="startup_opentasks_reinstall_davdroid">Након инсталирања Отворених задатака, морате поново да инсталирате ДАВдроид и поново додате ваше налоге (због грешке у Андроиду).</string>
|
||||
<string name="startup_opentasks_not_installed_install">Инсталирај Отворене задатке</string>
|
||||
<!--AboutActivity-->
|
||||
<string name="about_license_terms">Услови лиценце</string>
|
||||
<string name="about_license_info_no_warranty">Овај програм НЕМА НИКАКВЕ ГАРАНЦИЈЕ. Бесплатан је софтвер којег можете слободно да делите под одређеним условима.</string>
|
||||
<!--global settings-->
|
||||
<string name="logging_davdroid_file_logging">ДАВдроид евиденција</string>
|
||||
<string name="logging_to_external_storage">Уписивање евиденције у спољашње складиште: %s</string>
|
||||
<string name="logging_to_external_storage_warning">Обришите записе што пре!</string>
|
||||
<string name="logging_couldnt_create_file">Не могох да направим спољашњи фајл записа: %s</string>
|
||||
<string name="logging_no_external_storage">Спољашње складиште није нађено</string>
|
||||
<!--AccountsActivity-->
|
||||
<string name="navigation_drawer_open">Отвори навигациону фиоку</string>
|
||||
<string name="navigation_drawer_close">Затвори навигациону фиоку</string>
|
||||
<string name="navigation_drawer_subtitle">КалДАВ/КардДАВ адаптер синхронизације</string>
|
||||
<string name="navigation_drawer_about">О програму/лиценца</string>
|
||||
<string name="navigation_drawer_settings">Поставке</string>
|
||||
<string name="navigation_drawer_news_updates">Новости и ажурирања</string>
|
||||
<string name="navigation_drawer_external_links">Вањске везе</string>
|
||||
<string name="navigation_drawer_website">Веб-сајт</string>
|
||||
<string name="navigation_drawer_faq">ЧПП</string>
|
||||
<string name="navigation_drawer_forums">Заједница</string>
|
||||
<string name="navigation_drawer_donate">Донирај</string>
|
||||
<string name="account_list_empty">Добро дошли у ДАВдроид!\n\nМожете сада да додате КалДАВ/КардДАВ налог.</string>
|
||||
<!--DavService-->
|
||||
<string name="dav_service_refresh_failed">Откривање услуге није успело</string>
|
||||
<string name="dav_service_refresh_couldnt_refresh">Не могох да освежим списак збирки</string>
|
||||
<!--AppSettingsActivity-->
|
||||
<string name="app_settings">Поставке</string>
|
||||
<string name="app_settings_user_interface">Корисничко сучеље</string>
|
||||
<string name="app_settings_reset_hints">Ресетуј савете</string>
|
||||
<string name="app_settings_reset_hints_summary">Поновно приказивање претходно одбачених савета</string>
|
||||
<string name="app_settings_reset_hints_success">Сви савети ће поново бити приказани</string>
|
||||
<string name="app_settings_security">Безбедност</string>
|
||||
<string name="app_settings_distrust_system_certs">Посумњај у системске сертификате</string>
|
||||
<string name="app_settings_distrust_system_certs_on">Системски и кориснички додати сертификати неће бити поуздани</string>
|
||||
<string name="app_settings_distrust_system_certs_off">Системски и кориснички додати сертификати ће бити поуздани (препоручљиво)</string>
|
||||
<string name="app_settings_reset_certificates">Ресетуј (не)поуздане сертификате</string>
|
||||
<string name="app_settings_reset_certificates_summary">Ресетуј поуздање свих прилагођених сертификата</string>
|
||||
<string name="app_settings_reset_certificates_success">Сви прилагођени сертификати су уклоњени</string>
|
||||
<string name="app_settings_debug">Тражење грешака</string>
|
||||
<string name="app_settings_log_to_external_storage">Уписуј у спољашњи фајл</string>
|
||||
<string name="app_settings_log_to_external_storage_on">Уписивање евиденције у спољашње складиште (ако је доступно)</string>
|
||||
<string name="app_settings_log_to_external_storage_off">Уписивање евиденције у спољашње складиште је искључено</string>
|
||||
<string name="app_settings_show_debug_info">Прикажи податке за исправљање грешака</string>
|
||||
<string name="app_settings_show_debug_info_details">Приказ/дељење детаља софтвера и поставки</string>
|
||||
<!--AccountActivity-->
|
||||
<string name="account_synchronize_now">Синхронизуј одмах</string>
|
||||
<string name="account_synchronizing_now">Синхронизујем</string>
|
||||
<string name="account_settings">Поставке налога</string>
|
||||
<string name="account_delete">Обриши налог</string>
|
||||
<string name="account_delete_confirmation_title">Заиста обрисати налог?</string>
|
||||
<string name="account_delete_confirmation_text">Све локалне копије адресара, календара и листи задатака ће бити обрисане.</string>
|
||||
<string name="account_refresh_address_book_list">Освежи списак адресара</string>
|
||||
<string name="account_create_new_address_book">Направи нови адресар</string>
|
||||
<string name="account_refresh_calendar_list">Освежи списак календара</string>
|
||||
<string name="account_create_new_calendar">Направи нови календар</string>
|
||||
<!--PermissionsActivity-->
|
||||
<string name="permissions_title">ДАВдроид дозволе</string>
|
||||
<string name="permissions_calendar">Дозволе за календар</string>
|
||||
<string name="permissions_calendar_details">Да би синхронизовао КалДАВ догађаје са вашим локалним календарима, ДАВдроиду треба приступ вашим календарима.</string>
|
||||
<string name="permissions_calendar_request">Захтевај дозволе за календар</string>
|
||||
<string name="permissions_contacts">Дозволе за контакте</string>
|
||||
<string name="permissions_contacts_details">Да би синхронизовао КардДАВ адресаре са вашим локалним контактима, ДАВдроиду треба приступ вашим контактима.</string>
|
||||
<string name="permissions_contacts_request">Захтевај дозволе за контакте</string>
|
||||
<string name="permissions_opentasks">Дозволе за задатке</string>
|
||||
<string name="permissions_opentasks_details">Да би синхронизовао КалДАВ задатке са вашим локалним листама задатака, ДАВдроиду треба приступ Задацима.</string>
|
||||
<string name="permissions_opentasks_request">Захтевај дозволе за задатке</string>
|
||||
<!--AddAccountActivity-->
|
||||
<string name="login_title">Додај налог</string>
|
||||
<string name="login_type_email">Пријавите се адресом е-поште</string>
|
||||
<string name="login_email_address">Е-адреса</string>
|
||||
<string name="login_email_address_error">Исправна е-адреса је обавезна</string>
|
||||
<string name="login_password">Лозинка</string>
|
||||
<string name="login_password_required">Лозинка је обавезна</string>
|
||||
<string name="login_type_url">Пријавите се УРЛ-ом и корисничким именом</string>
|
||||
<string name="login_url_must_be_http_or_https">УРЛ мора започињати са http(s)://</string>
|
||||
<string name="login_url_host_name_required">Име домаћина је обавезно</string>
|
||||
<string name="login_user_name">Корисничко име</string>
|
||||
<string name="login_user_name_required">Корисничко име је обавезно</string>
|
||||
<string name="login_base_url">Корени УРЛ</string>
|
||||
<string name="login_login">Пријава</string>
|
||||
<string name="login_back">Назад</string>
|
||||
<string name="login_create_account">Направи налог</string>
|
||||
<string name="login_account_name">Назив налога</string>
|
||||
<string name="login_account_name_info">Користите вашу е-адресу за назив налога јер Андроид користи назив налога за поље ОРГАНИЗАТОР за догађаје које направите. Не можете имати два налога истог назива.</string>
|
||||
<string name="login_account_contact_group_method">Режим група контаката:</string>
|
||||
<string name="login_account_name_required">Назив налога је обавезан</string>
|
||||
<string name="login_account_not_created">Не могох направити налог</string>
|
||||
<string name="login_configuration_detection">Откривање конфигурације</string>
|
||||
<string name="login_querying_server">Сачекајте, шаљем упит серверу…</string>
|
||||
<string name="login_no_caldav_carddav">Не могох да нађем КалДАВ или КардДАВ услугу.</string>
|
||||
<string name="login_view_logs">Прикажи записе</string>
|
||||
<!--AccountSettingsActivity-->
|
||||
<string name="settings_title">Поставке: %s</string>
|
||||
<string name="settings_authentication">Аутентификација</string>
|
||||
<string name="settings_username">Корисничко име</string>
|
||||
<string name="settings_enter_username">Унесите корисничко име:</string>
|
||||
<string name="settings_password">Лозинка</string>
|
||||
<string name="settings_password_summary">Ажурирајте лозинку за ваш сервер.</string>
|
||||
<string name="settings_enter_password">Унесите лозинку:</string>
|
||||
<string name="settings_sync">Синхронизација</string>
|
||||
<string name="settings_sync_interval_contacts">Интервал синх. контаката</string>
|
||||
<string name="settings_sync_summary_manually">Само ручно</string>
|
||||
<string name="settings_sync_summary_periodically" tools:ignore="PluralsCandidate">Сваких %d минута + одмах по локалним изменама</string>
|
||||
<string name="settings_sync_summary_not_available">Није доступно</string>
|
||||
<string name="settings_sync_interval_calendars">Интервал синх. календара</string>
|
||||
<string name="settings_sync_interval_tasks">Интервал синх. задатака</string>
|
||||
<string-array name="settings_sync_interval_seconds">
|
||||
<item>-1</item>
|
||||
<item>300</item>
|
||||
<item>600</item>
|
||||
<item>900</item>
|
||||
<item>3600</item>
|
||||
<item>7200</item>
|
||||
<item>14400</item>
|
||||
<item>86400</item>
|
||||
</string-array>
|
||||
<string-array name="settings_sync_interval_names">
|
||||
<item>Само ручно</item>
|
||||
<item>Сваких 5 минута</item>
|
||||
<item>Сваких 10 минута</item>
|
||||
<item>Сваких 15 минута</item>
|
||||
<item>Сваког сата</item>
|
||||
<item>Свака 2 сата</item>
|
||||
<item>Свака 4 сата</item>
|
||||
<item>Једном дневно</item>
|
||||
</string-array>
|
||||
<string name="settings_sync_wifi_only">Само преко бежичног</string>
|
||||
<string name="settings_sync_wifi_only_on">Синхронизовање само преко бежичних мрежа</string>
|
||||
<string name="settings_sync_wifi_only_off">Тип везе није узет у обзир</string>
|
||||
<string name="settings_sync_wifi_only_ssid">Ограничења ССИД-а бежичних</string>
|
||||
<string name="settings_sync_wifi_only_ssid_on">Синхронизовање само преко %s</string>
|
||||
<string name="settings_sync_wifi_only_ssid_off">Коришћење свих бежичних мрежа</string>
|
||||
<string name="settings_sync_wifi_only_ssid_message">Унесите назив бежичне мреже (њен ССИД) да бисте ограничили синхронизацију само на ту мрежу, или оставите празно за синхронизовање преко било које бежичне мреже.</string>
|
||||
<string name="settings_carddav">КардДАВ</string>
|
||||
<string name="settings_contact_group_method">Режим група контаката</string>
|
||||
<string-array name="settings_contact_group_method_values">
|
||||
<item>GROUP_VCARDS</item>
|
||||
<item>CATEGORIES</item>
|
||||
</string-array>
|
||||
<string-array name="settings_contact_group_method_entries">
|
||||
<item>Групе су одвојене В-карте</item>
|
||||
<item>Групе су категорије по контакту</item>
|
||||
</string-array>
|
||||
<string name="settings_rfc6868_for_vcards">Користи РФЦ6868 за В-карте</string>
|
||||
<string name="settings_rfc6868_for_vcards_on">Двоструки наводници могу да се користе у вредностима параметара</string>
|
||||
<string name="settings_rfc6868_for_vcards_off">Двоструки наводници не могу да се користе у вредностима параметара</string>
|
||||
<string name="settings_caldav">КалДАВ</string>
|
||||
<string name="settings_sync_time_range_past">Ограничење догађаја у прошлости</string>
|
||||
<string name="settings_sync_time_range_past_none">Сви догађаји се синхронизују</string>
|
||||
<plurals name="settings_sync_time_range_past_days">
|
||||
<item quantity="one">Догађаји старији од једног дана ће бити занемарени</item>
|
||||
<item quantity="few">Догађаји старији од %d дана ће бити занемарени</item>
|
||||
<item quantity="other">Догађаји старији од %d дана ће бити занемарени</item>
|
||||
</plurals>
|
||||
<string name="settings_sync_time_range_past_message">Догађаји старији од овог броја дана ће бити занемарени (може бити 0). Оставите празно за синхронизацију свих догађаја.</string>
|
||||
<string name="settings_manage_calendar_colors">Управљај бојама календара</string>
|
||||
<string name="settings_manage_calendar_colors_on">Бојама календара управља ДАВдроид</string>
|
||||
<string name="settings_manage_calendar_colors_off">Боје календара није поставио ДАВдроид</string>
|
||||
<string name="settings_version_update">Надоградња ДАВдроид издања</string>
|
||||
<string name="settings_version_update_settings_updated">Унутрашње поставке су ажуриране.</string>
|
||||
<string name="settings_version_update_install_hint">Проблеми? Уклоните ДАВдроид па га поново инсталирајте.</string>
|
||||
<!--collection management-->
|
||||
<string name="create_addressbook">Направи адресар</string>
|
||||
<string name="create_addressbook_display_name_hint">Мој адресар</string>
|
||||
<string name="create_calendar">Направи КалДАВ збирку</string>
|
||||
<string name="create_calendar_display_name_hint">Мој календар</string>
|
||||
<string name="create_calendar_time_zone">Временска зона:</string>
|
||||
<string name="create_calendar_type">Тип збирке:</string>
|
||||
<string name="create_calendar_type_only_events">Календар (само догађаји)</string>
|
||||
<string name="create_calendar_type_only_tasks">Листе задатака (само задаци)</string>
|
||||
<string name="create_calendar_type_events_and_tasks">Комбиновано (догађаји и задаци)</string>
|
||||
<string name="create_collection_color">Боја збирке</string>
|
||||
<string name="create_collection_creating">Правим збирку</string>
|
||||
<string name="create_collection_display_name">Назив (наслов) ове збирке:</string>
|
||||
<string name="create_collection_display_name_required">Наслов је обавезан</string>
|
||||
<string name="create_collection_description">Опис (необавезан):</string>
|
||||
<string name="create_collection_home_set">Домаћа фасцикла:</string>
|
||||
<string name="create_collection_create">Направи</string>
|
||||
<string name="delete_collection">Обриши збирку</string>
|
||||
<string name="delete_collection_confirm_title">Да ли сте сигурни?</string>
|
||||
<string name="delete_collection_confirm_warning">Ова збирка (%s) и сви њени подаци ће бити уклоњени са сервера.</string>
|
||||
<string name="delete_collection_deleting_collection">Бришем збирку</string>
|
||||
<!--ExceptionInfoFragment-->
|
||||
<string name="exception">Десила се грешка.</string>
|
||||
<string name="exception_httpexception">Десила се ХТТП грешка.</string>
|
||||
<string name="exception_ioexception">Десила се У/И грешка.</string>
|
||||
<string name="exception_show_details">Прикажи детаље</string>
|
||||
<!--sync errors and DebugInfoActivity-->
|
||||
<string name="debug_info_title">Подаци за исправљање грешака</string>
|
||||
<string name="sync_error_permissions">ДАВдроид дозволе</string>
|
||||
<string name="sync_error_permissions_text">Потребне су додатне доволе</string>
|
||||
<string name="sync_error_calendar">Синхронизација календара није успела (%s)</string>
|
||||
<string name="sync_error_contacts">Синхронизација адресара није успела (%s)</string>
|
||||
<string name="sync_error_tasks">Синхронизација задатака није успела (%s)</string>
|
||||
<string name="sync_error">Грешка током %s</string>
|
||||
<string name="sync_error_http_dav">Грешка сервера током %s</string>
|
||||
<string name="sync_error_local_storage">Грешка базе података током %s</string>
|
||||
<string-array name="sync_error_phases">
|
||||
<item>припремам синхронизацију</item>
|
||||
<item>проверавам могућности</item>
|
||||
<item>обрађујем локално обрисане уносе</item>
|
||||
<item>припремам направљене/измењене уносе</item>
|
||||
<item>отпремам направљене/измењене уносе</item>
|
||||
<item>проверавам стање синхронизације</item>
|
||||
<item>излиставам локалне уносе</item>
|
||||
<item>излиставам удаљене уносе</item>
|
||||
<item>упоређујем локалне/удаљене уносе</item>
|
||||
<item>преузимам удаљене уносе</item>
|
||||
<item>додатна обрада</item>
|
||||
<item>уписујем стање синхронизације</item>
|
||||
</string-array>
|
||||
<string name="sync_error_unauthorized">Корисничко име или лозинка погрешни</string>
|
||||
<!--cert4android-->
|
||||
<string name="certificate_notification_connection_security">ДАВдроид: Безбедност везе</string>
|
||||
<string name="trust_certificate_unknown_certificate_found">ДАВдроид је наишао на непознат сертификат. Желите ли да се поуздате у њега?</string>
|
||||
</resources>
|
||||
204
app/src/davdroid/res/values-tr-rTR/strings.xml
Normal file
204
app/src/davdroid/res/values-tr-rTR/strings.xml
Normal file
@@ -0,0 +1,204 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<!--common strings-->
|
||||
<string name="app_name">DAVdroid</string>
|
||||
<string name="help">Yardım</string>
|
||||
<string name="manage_accounts">Hesapları yönet</string>
|
||||
<string name="please_wait">Lütfen bekle ...</string>
|
||||
<string name="send">Gönder</string>
|
||||
<!--startup dialogs-->
|
||||
<string name="startup_dont_show_again">Bir daha gösterme</string>
|
||||
<string name="startup_development_version">DAVDroid Önizlenim Dağıtımı</string>
|
||||
<string name="startup_development_version_message">Bu DAVdroid\'in bir geliştirme sürümüdür. Bazı şeyler beklendiği gibi çalışmayabilir. DAVdroid\'i iyileştirmek için bize lütfen yapıcı eleştirilerini ilet.</string>
|
||||
<string name="startup_development_version_give_feedback">Geribildirim ver</string>
|
||||
<string name="startup_donate">Açık-Kaynak Bilgisi</string>
|
||||
<string name="startup_donate_message">Açık kaynaklı yazılım (GPLv3) olan DAVdroid\'i kullandığına çok mutluyuz. DAVdroid\'i geliştirmek zor bir iş ve üzerinde binlerce saat çalıştığımızdan, lütfen bir bağışta bulunmayı düşün.</string>
|
||||
<string name="startup_donate_now">Bağış sayfasını göster</string>
|
||||
<string name="startup_donate_later">Belki sonra</string>
|
||||
<string name="startup_google_play_accounts_removed">Play Store DRM hata bilgisi</string>
|
||||
<string name="startup_google_play_accounts_removed_message">Bazı durumlarda Play Store DRM\'i, cihazı yeniden başlatınca veya DAVdroid\'i yükseltince DAVdroid hesaplarının yokolmasına sebep olabiliyor. Bu sorundan etkileniyorsan (ve sadece bu durumda), lütfen Play Store\'daki \"DAVdroid JB Workaround\" uygulamasını kur.</string>
|
||||
<string name="startup_google_play_accounts_removed_more_info">Daha fazla bilgi</string>
|
||||
<string name="startup_opentasks_not_installed">OpenTasks kurulu değil</string>
|
||||
<string name="startup_opentasks_not_installed_message">OpenTasks uygulaması yok, dolayısıyla DAVdroid iş listelerini senkronize edemeyecektir.</string>
|
||||
<string name="startup_opentasks_reinstall_davdroid">OpenTasks\'i kurduktan sonra, DAVdroid\'i YENİDEN KURMAN ve hesaplarını yeniden eklemen gerek. (Android hatası).</string>
|
||||
<string name="startup_opentasks_not_installed_install">OpenTasks kur</string>
|
||||
<!--AboutActivity-->
|
||||
<string name="about_license_terms">Lisans şartları</string>
|
||||
<string name="about_license_info_no_warranty">Bu uygulama HİÇ BİR GARANTİ ile gelmemektedir. Bedava bir yazılımdır ve belli koşullar altında dağıtabilirsiniz.</string>
|
||||
<!--global settings-->
|
||||
<string name="logging_davdroid_file_logging">DAVdroid dosya jurnallemesi</string>
|
||||
<string name="logging_to_external_storage">Harici depolamaya jurnalleniyor: %s</string>
|
||||
<string name="logging_to_external_storage_warning">Jurnalleri bir an önce silin!</string>
|
||||
<string name="logging_couldnt_create_file">Harici jurnal dosyası yaratılamadı: %s </string>
|
||||
<string name="logging_no_external_storage">Harici depolama alanı bulunamadı</string>
|
||||
<!--AccountsActivity-->
|
||||
<string name="navigation_drawer_open">Navigasyon çekmecesini aç</string>
|
||||
<string name="navigation_drawer_close">Navigasyon çekmecesini kapat</string>
|
||||
<string name="navigation_drawer_subtitle">CalDAV/CardDAV Senkronizasyon Adaptörü</string>
|
||||
<string name="navigation_drawer_about">Hakkında / Lisans</string>
|
||||
<string name="navigation_drawer_settings">Ayarlar</string>
|
||||
<string name="navigation_drawer_news_updates">Haberler & güncellemeler </string>
|
||||
<string name="navigation_drawer_external_links">Harici bağlantılar</string>
|
||||
<string name="navigation_drawer_website">Web sitesi</string>
|
||||
<string name="navigation_drawer_faq">SSS</string>
|
||||
<string name="navigation_drawer_forums">Camia</string>
|
||||
<string name="navigation_drawer_donate">Bağış yap</string>
|
||||
<string name="account_list_empty">DAVdroid\'e hoşgeldin!\n\nŞimdi bir CalDAV/CardDAV hesabı ekleyebilirsin.</string>
|
||||
<!--DavService-->
|
||||
<string name="dav_service_refresh_failed">Servis keşfi başarısız</string>
|
||||
<string name="dav_service_refresh_couldnt_refresh">Kolleksiyon listesi yenilenemedi</string>
|
||||
<!--AppSettingsActivity-->
|
||||
<string name="app_settings">Ayarlar</string>
|
||||
<string name="app_settings_user_interface">Kullanıcı arayüzü</string>
|
||||
<string name="app_settings_reset_hints">İpuçlarını sıfırla</string>
|
||||
<string name="app_settings_reset_hints_summary">Daha önceden azat edilen ipuçlarını yeniden etkinleştirir</string>
|
||||
<string name="app_settings_reset_hints_success">Tüm ipuçları artık gösterilecek</string>
|
||||
<string name="app_settings_security">Güvenlik</string>
|
||||
<string name="app_settings_debug">Hata ayıklama</string>
|
||||
<string name="app_settings_log_to_external_storage">Harici dosyaya jurnalle</string>
|
||||
<string name="app_settings_log_to_external_storage_on">Harici depolamaya jurnalleniyor (eğer uygunsa)</string>
|
||||
<string name="app_settings_log_to_external_storage_off">Harici depolama mevcut değil</string>
|
||||
<string name="app_settings_show_debug_info">Hata ayıklama bilgilerini göster</string>
|
||||
<string name="app_settings_show_debug_info_details">Yazılım ve konfigürasyon detaylarına bak/paylaş</string>
|
||||
<!--AccountActivity-->
|
||||
<string name="account_synchronize_now">Şimdi senkronize et</string>
|
||||
<string name="account_synchronizing_now">Senkronize ediyor</string>
|
||||
<string name="account_settings">Hesap ayarları</string>
|
||||
<string name="account_delete">Hesabı sil</string>
|
||||
<string name="account_delete_confirmation_title">Hesap gerçekten silinsin mi?</string>
|
||||
<string name="account_delete_confirmation_text">Rehber, takvim ve iş listelerinin tüm yerel kopyaları silinecektir.</string>
|
||||
<string name="account_refresh_address_book_list">Rehber listesini yenile</string>
|
||||
<string name="account_create_new_address_book">Yeni rehber oluştur</string>
|
||||
<string name="account_refresh_calendar_list">Takvim listesini yenile</string>
|
||||
<string name="account_create_new_calendar">Yeni takvim oluştur</string>
|
||||
<!--PermissionsActivity-->
|
||||
<string name="permissions_title">DAVdroid izinleri</string>
|
||||
<string name="permissions_calendar">Takvim izinleri</string>
|
||||
<string name="permissions_calendar_details">Takvim (sadece olaylar)
|
||||
</string>
|
||||
<string name="permissions_calendar_request">Takvim izinleri iste</string>
|
||||
<string name="permissions_contacts">Kişiler izinleri</string>
|
||||
<string name="permissions_contacts_details">CardDAV rehberlerinin cihazınızdaki kişilerinizle senkronize edebilmek için, DAVdroid cihazınızdaki kişilerinize erişime ihtiyacı vardır.</string>
|
||||
<string name="permissions_contacts_request">Kişiler izinleri iste</string>
|
||||
<string name="permissions_opentasks">OpenTasks izinleri</string>
|
||||
<string name="permissions_opentasks_details">CalDav iş listelerinizi yerel iş listelerinizle senkronize edebilmek için DAVdroid\'in OpenTasks\'e erişime ihtiyacı vardır.</string>
|
||||
<string name="permissions_opentasks_request">OpenTasks izinleri iste</string>
|
||||
<!--AddAccountActivity-->
|
||||
<string name="login_title">Hesap ekle</string>
|
||||
<string name="login_type_email">Eposta adresi ile giriş yap</string>
|
||||
<string name="login_email_address">Eposta adresi</string>
|
||||
<string name="login_email_address_error">Geçerli eposta adresi zorunludur</string>
|
||||
<string name="login_password">Parola</string>
|
||||
<string name="login_password_required">Parola zorunludur</string>
|
||||
<string name="login_type_url">URL ve kullanıcı adı ile giriş yap</string>
|
||||
<string name="login_url_must_be_http_or_https">URL http(s):// ile başlamalıdır</string>
|
||||
<string name="login_url_host_name_required">Sunucu adı zorunludur</string>
|
||||
<string name="login_user_name">Kullanıcı adı</string>
|
||||
<string name="login_user_name_required">Kullanıcı adı zorunludur</string>
|
||||
<string name="login_base_url">Baz URL</string>
|
||||
<string name="login_login">Giriş</string>
|
||||
<string name="login_back">Geri</string>
|
||||
<string name="login_create_account">Hesap yarat</string>
|
||||
<string name="login_account_name">Hesap adı</string>
|
||||
<string name="login_account_name_info">Hesap ismi olarak e-posta adresini kullan çünkü Android hesap ismini yarattığın olaylarda DÜZENLEYEN alanında kullanacaktır. Aynı isimde iki faklı hesabın olamaz.</string>
|
||||
<string name="login_account_name_required">Hesap adı zorunludur</string>
|
||||
<string name="login_account_not_created">Hesap yaratılamadı</string>
|
||||
<string name="login_configuration_detection">Konfigürasyon keşfi</string>
|
||||
<string name="login_querying_server">Lütfen bekle, sunucu sorgulanıyor...</string>
|
||||
<string name="login_no_caldav_carddav">CalDAV veya CardDAV servisi bulunamadı.</string>
|
||||
<string name="login_view_logs">Jurnallere bak</string>
|
||||
<!--AccountSettingsActivity-->
|
||||
<string name="settings_title">Ayarlar: %s</string>
|
||||
<string name="settings_authentication">Doğrulama</string>
|
||||
<string name="settings_username">Kullanıcı adı</string>
|
||||
<string name="settings_enter_username">Kullanıcı adı girin:</string>
|
||||
<string name="settings_password">Parola</string>
|
||||
<string name="settings_password_summary">Parolayı sunucunuza göre güncelleyin.</string>
|
||||
<string name="settings_enter_password">Parola girin:</string>
|
||||
<string name="settings_sync">Senkronizasyon</string>
|
||||
<string name="settings_sync_interval_contacts">Kişiler senk. aralığı</string>
|
||||
<string name="settings_sync_summary_manually">Sadece elle</string>
|
||||
<string name="settings_sync_summary_periodically" tools:ignore="PluralsCandidate">Her %d dakika + yerel değişikliklerde hemen</string>
|
||||
<string name="settings_sync_summary_not_available">Mevcut değil</string>
|
||||
<string name="settings_sync_interval_calendars">Takvimler senk. aralığı</string>
|
||||
<string name="settings_sync_interval_tasks">İşler senk. aralığı</string>
|
||||
<string-array name="settings_sync_interval_seconds">
|
||||
<item>-1</item>
|
||||
<item>300</item>
|
||||
<item>600</item>
|
||||
<item>900</item>
|
||||
<item>3600</item>
|
||||
<item>7200</item>
|
||||
<item>14400</item>
|
||||
<item>86400</item>
|
||||
</string-array>
|
||||
<string-array name="settings_sync_interval_names">
|
||||
<item>Sadece elle</item>
|
||||
<item>Her 5 dakikada bir</item>
|
||||
<item>Her 10 dakikada bir</item>
|
||||
<item>Her 15 dakikada bir</item>
|
||||
<item>Her saatte bir</item>
|
||||
<item>Her 2 saatte bir</item>
|
||||
<item>Her 4 saatte bir</item>
|
||||
<item>Günde bir</item>
|
||||
</string-array>
|
||||
<string name="settings_sync_wifi_only">Sadece WiFi üzerinden senkronize et</string>
|
||||
<string name="settings_sync_wifi_only_on">Senkronizasyon WiFi bağlantıları ile kısıtlıdır</string>
|
||||
<string name="settings_sync_wifi_only_off">Bağlantı tipi göz önünde bulundurulmaz</string>
|
||||
<string name="settings_sync_wifi_only_ssid">WiFi SSID kısıtlaması</string>
|
||||
<string name="settings_sync_wifi_only_ssid_on">Sadece %s üzerinden senkronize olur</string>
|
||||
<string name="settings_sync_wifi_only_ssid_off">Tüm WiFi bağlantıları kullanılabilir</string>
|
||||
<string name="settings_sync_wifi_only_ssid_message">Senkronizasyonu sadece bir WiFi ağına kısıtlamak için bu ağın adını (SSID) gir, veya tüm WiFi bağlantıları için boş bırak.</string>
|
||||
<string name="settings_caldav">CalDAV</string>
|
||||
<string name="settings_sync_time_range_past">Geçmiş olay zaman sınırı</string>
|
||||
<string name="settings_sync_time_range_past_none">Tüm olaylar senkronize edilecek</string>
|
||||
<plurals name="settings_sync_time_range_past_days">
|
||||
<item quantity="other">%d günden daha eski olaylar göz ardı edilecektir</item>
|
||||
</plurals>
|
||||
<string name="settings_sync_time_range_past_message">Bu sayıdan daha eski olan olaylar yok sayılacaktır (0 olabilir). Tüm olayları senkronize etmek için boş bırak.</string>
|
||||
<string name="settings_manage_calendar_colors">Takvim renklerini yönet</string>
|
||||
<string name="settings_manage_calendar_colors_on">Takvim renkleri DAVdroid tarafından yönetilmekte</string>
|
||||
<string name="settings_manage_calendar_colors_off">Takvim renkleri DAVdroid tarafından ayarlanmadı</string>
|
||||
<string name="settings_version_update">DAVdroid sürüm güncellemesi</string>
|
||||
<string name="settings_version_update_settings_updated">Dahili ayarlar güncellendi.</string>
|
||||
<string name="settings_version_update_install_hint">Sorunlar mı var? DAVdroid\'i kaldırıp, yeniden kurun.</string>
|
||||
<!--collection management-->
|
||||
<string name="create_addressbook">Rehber yarat</string>
|
||||
<string name="create_addressbook_display_name_hint">Benim Rehberim</string>
|
||||
<string name="create_calendar">CalDav koleksiyonu yarat</string>
|
||||
<string name="create_calendar_display_name_hint">Benim Takvimim</string>
|
||||
<string name="create_calendar_time_zone">Saat dilimi:</string>
|
||||
<string name="create_calendar_type">Koleksiyon Tipi:</string>
|
||||
<string name="create_calendar_type_only_events">Takvim (sadece olaylar)</string>
|
||||
<string name="create_calendar_type_only_tasks">İşler listesi (sadece işler)
|
||||
</string>
|
||||
<string name="create_calendar_type_events_and_tasks">Birleşik (olaylar ve işler)</string>
|
||||
<string name="create_collection_color">Bir koleksiyon rengi tanımla</string>
|
||||
<string name="create_collection_creating">Koleksiyon yaratılıyor</string>
|
||||
<string name="create_collection_display_name">Bu koleksiyonun gösterilen adı (başlığı):</string>
|
||||
<string name="create_collection_display_name_required">Başlık zorunlu</string>
|
||||
<string name="create_collection_description">Açıklama (isteğe bağlı):</string>
|
||||
<string name="create_collection_home_set">Ev seti:</string>
|
||||
<string name="create_collection_create">Yarat</string>
|
||||
<string name="delete_collection">Koleksiyonu sil</string>
|
||||
<string name="delete_collection_confirm_title">Emin misin?</string>
|
||||
<string name="delete_collection_confirm_warning">Bu koleksiyon (%s) ve tüm verisi sunucudan silinecek.</string>
|
||||
<string name="delete_collection_deleting_collection">Koleksiyon siliniyor</string>
|
||||
<!--ExceptionInfoFragment-->
|
||||
<string name="exception">Bir hata oluştu.</string>
|
||||
<string name="exception_httpexception">Bir HTTP hatası oluştu.</string>
|
||||
<string name="exception_ioexception">Bir I/O hatası oluştu.</string>
|
||||
<string name="exception_show_details">Detayları göster</string>
|
||||
<!--sync errors and DebugInfoActivity-->
|
||||
<string name="debug_info_title">Hata ayıklama bilgisi</string>
|
||||
<string name="sync_error_permissions">DAVdroid izinleri</string>
|
||||
<string name="sync_error_permissions_text">Ek izinler zorunludur</string>
|
||||
<string name="sync_error_calendar">Takvim senkronizasyonu başarısız (%s)</string>
|
||||
<string name="sync_error_contacts">Rehber senkronizasyonu başarısız (%s)</string>
|
||||
<string name="sync_error_tasks">İş senkronizasyonu başarısız (%s)</string>
|
||||
<string name="sync_error">%s yaparken hata</string>
|
||||
<string name="sync_error_http_dav">%s yaparken sunucu hatası</string>
|
||||
<string name="sync_error_local_storage">%s yaparken veritabanı hatası</string>
|
||||
<string name="sync_error_unauthorized">Kullanıcı adı/parola yanlış</string>
|
||||
<!--cert4android-->
|
||||
</resources>
|
||||
55
app/src/davdroid/res/values-uk/strings.xml
Normal file
55
app/src/davdroid/res/values-uk/strings.xml
Normal file
@@ -0,0 +1,55 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<!--common strings-->
|
||||
<string name="app_name">DAVdroid</string>
|
||||
<string name="help">Допомога</string>
|
||||
<!--startup dialogs-->
|
||||
<!--AboutActivity-->
|
||||
<!--global settings-->
|
||||
<!--AccountsActivity-->
|
||||
<!--DavService-->
|
||||
<!--AppSettingsActivity-->
|
||||
<!--AccountActivity-->
|
||||
<!--PermissionsActivity-->
|
||||
<!--AddAccountActivity-->
|
||||
<string name="login_type_email">Увійти за допомогою електронної пошти</string>
|
||||
<string name="login_type_url">Увійти за допомогою URL та імені користувача</string>
|
||||
<!--AccountSettingsActivity-->
|
||||
<string name="settings_authentication">Автентифікація</string>
|
||||
<string name="settings_username">Ім\'я користувача</string>
|
||||
<string name="settings_enter_username">Введіть ім\'я користувача:</string>
|
||||
<string name="settings_password">Пароль</string>
|
||||
<string name="settings_password_summary">Оновити пароль, згідно налаштувань Вашого сервера.</string>
|
||||
<string name="settings_enter_password">Введіть Ваш пароль:</string>
|
||||
<string name="settings_sync">Синхронізація</string>
|
||||
<string name="settings_sync_interval_contacts">Інтервал синхронізації контактів</string>
|
||||
<string name="settings_sync_summary_manually">Лише вручну</string>
|
||||
<string name="settings_sync_summary_periodically" tools:ignore="PluralsCandidate">Кожних %d хвилин, а також негайно при внесенні локальних змін</string>
|
||||
<string name="settings_sync_summary_not_available">Не доступно</string>
|
||||
<string name="settings_sync_interval_calendars">Інтервал синхронізації календарів</string>
|
||||
<string name="settings_sync_interval_tasks">Інтервал синхронізації завдань</string>
|
||||
<string-array name="settings_sync_interval_seconds">
|
||||
<item>-1</item>
|
||||
<item>300</item>
|
||||
<item>600</item>
|
||||
<item>900</item>
|
||||
<item>3600</item>
|
||||
<item>7200</item>
|
||||
<item>14400</item>
|
||||
<item>86400</item>
|
||||
</string-array>
|
||||
<string-array name="settings_sync_interval_names">
|
||||
<item>Лише вручну</item>
|
||||
<item>Кожних 5 хвилин</item>
|
||||
<item>Кожних 10 хвилин</item>
|
||||
<item>Кожних 15 хвилин</item>
|
||||
<item>Щогодини</item>
|
||||
<item>Кожних 2 години</item>
|
||||
<item>Кожних 4 години</item>
|
||||
<item>Раз на добу</item>
|
||||
</string-array>
|
||||
<!--collection management-->
|
||||
<!--ExceptionInfoFragment-->
|
||||
<!--sync errors and DebugInfoActivity-->
|
||||
<!--cert4android-->
|
||||
</resources>
|
||||
18
app/src/davdroid/res/values-v21/styles.xml
Normal file
18
app/src/davdroid/res/values-v21/styles.xml
Normal file
@@ -0,0 +1,18 @@
|
||||
<!--
|
||||
~ Copyright © 2013 – 2016 Ricki Hirner (bitfire web engineering).
|
||||
~ All rights reserved. This program and the accompanying materials
|
||||
~ are made available under the terms of the GNU Public License v3.0
|
||||
~ which accompanies this distribution, and is available at
|
||||
~ http://www.gnu.org/licenses/gpl.html
|
||||
-->
|
||||
|
||||
<resources>
|
||||
|
||||
<style name="AppTheme.NoActionBar">
|
||||
<item name="windowActionBar">false</item>
|
||||
<item name="windowNoTitle">true</item>
|
||||
<item name="android:windowDrawsSystemBarBackgrounds">true</item>
|
||||
<item name="android:statusBarColor">@android:color/transparent</item>
|
||||
</style>
|
||||
|
||||
</resources>
|
||||
241
app/src/davdroid/res/values-zh-rCN/strings.xml
Normal file
241
app/src/davdroid/res/values-zh-rCN/strings.xml
Normal file
@@ -0,0 +1,241 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<!--common strings-->
|
||||
<string name="app_name">DAVdroid</string>
|
||||
<string name="help">帮助</string>
|
||||
<string name="manage_accounts">管理账户</string>
|
||||
<string name="please_wait">请稍等...</string>
|
||||
<string name="send">发送</string>
|
||||
<!--startup dialogs-->
|
||||
<string name="startup_battery_optimization">电池优化</string>
|
||||
<string name="startup_battery_optimization_message">系统可能会在几天后减少或停用 DAVdroid 同步。为了避免这一情况,请对 DAVdroid 忽略电池优化。</string>
|
||||
<string name="startup_battery_optimization_disable">忽略电池优化</string>
|
||||
<string name="startup_dont_show_again">不再显示</string>
|
||||
<string name="startup_development_version">DAVdroid 预览版</string>
|
||||
<string name="startup_development_version_message">这是 DAVdroid 的开发版本,部分功能可能无法正常工作。请您提出建设性反馈,帮助我们完善 DAVdroid。</string>
|
||||
<string name="startup_development_version_give_feedback">反馈</string>
|
||||
<string name="startup_donate">开源信息</string>
|
||||
<string name="startup_donate_message">欢迎使用 DAVdroid,这是一款开源软件 (GPLv3)。开发 DAVdroid 的工作花费了数千小时,请您考虑捐助我们。</string>
|
||||
<string name="startup_donate_now">显示捐助页面</string>
|
||||
<string name="startup_donate_later">稍后提示</string>
|
||||
<string name="startup_google_play_accounts_removed">Play 商店 DRM 问题提醒</string>
|
||||
<string name="startup_google_play_accounts_removed_message">在部分情况下,Play 商店的 DRM 可能会导致所有 DAVdroid 账户在设备重启或升级 DAVdroid 后消失。如果你遇到了该问题(并且只有这一问题),请从 Play 商店安装“DAVdroid JB Workaround”。</string>
|
||||
<string name="startup_google_play_accounts_removed_more_info">更多信息</string>
|
||||
<string name="startup_opentasks_not_installed">OpenTasks 未安装</string>
|
||||
<string name="startup_opentasks_not_installed_message">未安装 OpenTasks 应用,故 DAVdroid 无法同步任务列表。</string>
|
||||
<string name="startup_opentasks_reinstall_davdroid">安装 OpenTasks 后,由于 Android 的限制,请重新安装 DAVdroid 并重新创建账户。</string>
|
||||
<string name="startup_opentasks_not_installed_install">安装 OpenTasks</string>
|
||||
<!--AboutActivity-->
|
||||
<string name="about_license_terms">许可协议</string>
|
||||
<string name="about_license_info_no_warranty">本程序不附带任何担保。这是一款自由软件,你可以有条件地传播它。</string>
|
||||
<!--global settings-->
|
||||
<string name="logging_davdroid_file_logging">DAVdroid 文件日志</string>
|
||||
<string name="logging_to_external_storage">记录日志到外部存储 %s</string>
|
||||
<string name="logging_to_external_storage_warning">请尽快删除日志!</string>
|
||||
<string name="logging_couldnt_create_file">无法创建外部日志文件 %s</string>
|
||||
<string name="logging_no_external_storage">找不到外部存储</string>
|
||||
<!--AccountsActivity-->
|
||||
<string name="navigation_drawer_open">打开导航抽屉</string>
|
||||
<string name="navigation_drawer_close">关闭导航抽屉</string>
|
||||
<string name="navigation_drawer_subtitle">CalDAV/CardDAV 同步器</string>
|
||||
<string name="navigation_drawer_about">关于 / 许可</string>
|
||||
<string name="navigation_drawer_settings">设置</string>
|
||||
<string name="navigation_drawer_news_updates">最新消息</string>
|
||||
<string name="navigation_drawer_external_links">外部网站</string>
|
||||
<string name="navigation_drawer_website">应用网站</string>
|
||||
<string name="navigation_drawer_faq">常见问题</string>
|
||||
<string name="navigation_drawer_forums">社区</string>
|
||||
<string name="navigation_drawer_donate">捐助</string>
|
||||
<string name="account_list_empty">欢迎使用 DAVdroid!\n\n现在你可以增加 CalDAV/CardDAV 账户。</string>
|
||||
<!--DavService-->
|
||||
<string name="dav_service_refresh_failed">服务配置检测失败</string>
|
||||
<string name="dav_service_refresh_couldnt_refresh">无法刷新集合列表</string>
|
||||
<!--AppSettingsActivity-->
|
||||
<string name="app_settings">设置</string>
|
||||
<string name="app_settings_user_interface">用户界面</string>
|
||||
<string name="app_settings_reset_hints">重设提示</string>
|
||||
<string name="app_settings_reset_hints_summary">重新显示之前忽略过的提示</string>
|
||||
<string name="app_settings_reset_hints_success">所有提示将会再次显示</string>
|
||||
<string name="app_settings_security">安全</string>
|
||||
<string name="app_settings_distrust_system_certs">不信任系统证书</string>
|
||||
<string name="app_settings_distrust_system_certs_on">系统和用户增加的发布者不会被信任</string>
|
||||
<string name="app_settings_distrust_system_certs_off">系统和用户增加的发布者会被信任(推荐)</string>
|
||||
<string name="app_settings_reset_certificates">重设证书信任状态</string>
|
||||
<string name="app_settings_reset_certificates_summary">重设所有自定义证书的信任状态</string>
|
||||
<string name="app_settings_reset_certificates_success">所有自定义证书已清除</string>
|
||||
<string name="app_settings_debug">调试</string>
|
||||
<string name="app_settings_log_to_external_storage">外部文件日志</string>
|
||||
<string name="app_settings_log_to_external_storage_on">记录日志到外部存储(如果可用)</string>
|
||||
<string name="app_settings_log_to_external_storage_off">外部文件日志已禁用</string>
|
||||
<string name="app_settings_show_debug_info">显示调试信息</string>
|
||||
<string name="app_settings_show_debug_info_details">查看软件和配置信息</string>
|
||||
<!--AccountActivity-->
|
||||
<string name="account_synchronize_now"> 立即同步</string>
|
||||
<string name="account_synchronizing_now">正在同步</string>
|
||||
<string name="account_settings">账户设置</string>
|
||||
<string name="account_delete">删除账户</string>
|
||||
<string name="account_delete_confirmation_title">真的要删除账户吗?</string>
|
||||
<string name="account_delete_confirmation_text">所有通讯录、日历和任务列表的本机存储将被删除。</string>
|
||||
<string name="account_refresh_address_book_list">刷新通讯录列表</string>
|
||||
<string name="account_create_new_address_book">创建通讯录</string>
|
||||
<string name="account_refresh_calendar_list">刷新日历列表</string>
|
||||
<string name="account_create_new_calendar">创建日历</string>
|
||||
<!--PermissionsActivity-->
|
||||
<string name="permissions_title">DAVdroid 权限</string>
|
||||
<string name="permissions_calendar">日历权限</string>
|
||||
<string name="permissions_calendar_details">要把 CalDAV 事件与本地日历同步,DAVdroid 需要日历权限。</string>
|
||||
<string name="permissions_calendar_request">请求日历权限</string>
|
||||
<string name="permissions_contacts">通讯录权限</string>
|
||||
<string name="permissions_contacts_details">要把 CardDAV 通讯录与本地通讯录同步,DAVdroid 需要通讯录权限。</string>
|
||||
<string name="permissions_contacts_request">请求通讯录权限</string>
|
||||
<string name="permissions_opentasks">OpenTasks 权限</string>
|
||||
<string name="permissions_opentasks_details">要把 CalDAV 任务与本地任务列表同步,DAVdroid 需要访问 OpenTasks。</string>
|
||||
<string name="permissions_opentasks_request">请求 OpenTasks 权限</string>
|
||||
<!--AddAccountActivity-->
|
||||
<string name="login_title">增加账户</string>
|
||||
<string name="login_type_email">使用邮箱地址登录</string>
|
||||
<string name="login_email_address">Email 地址</string>
|
||||
<string name="login_email_address_error">请输入有效 Email 地址</string>
|
||||
<string name="login_password">密码</string>
|
||||
<string name="login_password_required">请输入密码</string>
|
||||
<string name="login_type_url">使用 URL 和用户名登录</string>
|
||||
<string name="login_url_must_be_http_or_https">URL 必须以 http(s):// 为开头</string>
|
||||
<string name="login_url_host_name_required">请输入主机名</string>
|
||||
<string name="login_user_name">用户名</string>
|
||||
<string name="login_user_name_required">请输入用户名</string>
|
||||
<string name="login_base_url">根地址</string>
|
||||
<string name="login_login">登录</string>
|
||||
<string name="login_back">返回</string>
|
||||
<string name="login_create_account">创建账户</string>
|
||||
<string name="login_account_name">账户显示名</string>
|
||||
<string name="login_account_name_info">请使用你的邮箱地址作为帐户名,因为 Android 会将你创建的日历事件的创建者项设置为帐户名。你不能拥有多个帐户名相同的账户。</string>
|
||||
<string name="login_account_contact_group_method">联系人分组方式</string>
|
||||
<string name="login_account_name_required">请输入账户名</string>
|
||||
<string name="login_account_not_created">账户无法创建</string>
|
||||
<string name="login_configuration_detection">正在配置</string>
|
||||
<string name="login_querying_server">正在与服务器通信,请稍等...</string>
|
||||
<string name="login_no_caldav_carddav">找不到 CalDAV 或 CardDAV 服务。</string>
|
||||
<string name="login_view_logs">查看日志</string>
|
||||
<!--AccountSettingsActivity-->
|
||||
<string name="settings_title">设置:%s</string>
|
||||
<string name="settings_authentication">认证</string>
|
||||
<string name="settings_username">用户名</string>
|
||||
<string name="settings_enter_username">输入用户名</string>
|
||||
<string name="settings_password">密码</string>
|
||||
<string name="settings_password_summary">修改服务器密码</string>
|
||||
<string name="settings_enter_password">输入密码</string>
|
||||
<string name="settings_sync">同步</string>
|
||||
<string name="settings_sync_interval_contacts">通讯录自动同步间隔</string>
|
||||
<string name="settings_sync_summary_manually">手动同步</string>
|
||||
<string name="settings_sync_summary_periodically" tools:ignore="PluralsCandidate">每 %d 分钟或本地修改后</string>
|
||||
<string name="settings_sync_summary_not_available">不可用</string>
|
||||
<string name="settings_sync_interval_calendars">日历自动同步间隔</string>
|
||||
<string name="settings_sync_interval_tasks">任务自动同步间隔</string>
|
||||
<string-array name="settings_sync_interval_seconds">
|
||||
<item>-1</item>
|
||||
<item>300</item>
|
||||
<item>600</item>
|
||||
<item>900</item>
|
||||
<item>3600</item>
|
||||
<item>7200</item>
|
||||
<item>14400</item>
|
||||
<item>86400</item>
|
||||
</string-array>
|
||||
<string-array name="settings_sync_interval_names">
|
||||
<item>手动同步</item>
|
||||
<item>每 5 分钟</item>
|
||||
<item>每 10 分钟</item>
|
||||
<item>每 15 分钟</item>
|
||||
<item>每 1 小时</item>
|
||||
<item>每 2 小时</item>
|
||||
<item>每 4 小时</item>
|
||||
<item>每 24 小时</item>
|
||||
</string-array>
|
||||
<string name="settings_sync_wifi_only">只在 WiFi 下同步</string>
|
||||
<string name="settings_sync_wifi_only_on">同步只在 WiFi 连接下进行</string>
|
||||
<string name="settings_sync_wifi_only_off">同步不受数据连接类型限制</string>
|
||||
<string name="settings_sync_wifi_only_ssid">WiFi SSID 限制</string>
|
||||
<string name="settings_sync_wifi_only_ssid_on">同步只在 %s 网络下进行</string>
|
||||
<string name="settings_sync_wifi_only_ssid_off">任何 WiFi 网络下均会同步</string>
|
||||
<string name="settings_sync_wifi_only_ssid_message">输入 WiFi 网络的名称 (SSID) ,即可限制同步只在此网络下进行。留空则不限制。</string>
|
||||
<string name="settings_carddav">CardDAV</string>
|
||||
<string name="settings_contact_group_method">联系人分组方式</string>
|
||||
<string-array name="settings_contact_group_method_values">
|
||||
<item>GROUP_VCARDS</item>
|
||||
<item>CATEGORIES</item>
|
||||
</string-array>
|
||||
<string-array name="settings_contact_group_method_entries">
|
||||
<item>分为不同的 VCards</item>
|
||||
<item>每个联系人的分类</item>
|
||||
</string-array>
|
||||
<string name="settings_rfc6868_for_vcards">为 VCards 启用 RFC6868</string>
|
||||
<string name="settings_rfc6868_for_vcards_on">属性值中可使用双引号</string>
|
||||
<string name="settings_rfc6868_for_vcards_off">属性值中不可使用双引号</string>
|
||||
<string name="settings_caldav">CalDAV</string>
|
||||
<string name="settings_sync_time_range_past">旧日程时间限制</string>
|
||||
<string name="settings_sync_time_range_past_none">同步所有日程</string>
|
||||
<plurals name="settings_sync_time_range_past_days">
|
||||
<item quantity="other">%d 天前的日程不会被同步</item>
|
||||
</plurals>
|
||||
<string name="settings_sync_time_range_past_message">超过这个数字的天数的旧日程将会被忽略(可以为 0)。留空则同步所有日程。</string>
|
||||
<string name="settings_manage_calendar_colors">管理日历颜色</string>
|
||||
<string name="settings_manage_calendar_colors_on">日历颜色由 DAVdroid 设置</string>
|
||||
<string name="settings_manage_calendar_colors_off">日历颜色不由 DAVdroid 设置</string>
|
||||
<string name="settings_version_update">DAVdroid 版本升级</string>
|
||||
<string name="settings_version_update_settings_updated">应用设置已更新。</string>
|
||||
<string name="settings_version_update_install_hint">出现问题了?请卸载 DAVdroid,再重新安装。</string>
|
||||
<!--collection management-->
|
||||
<string name="create_addressbook">创建通讯录</string>
|
||||
<string name="create_addressbook_display_name_hint">我的通讯录</string>
|
||||
<string name="create_calendar">创建 CalDAV 集合</string>
|
||||
<string name="create_calendar_display_name_hint">我的日历</string>
|
||||
<string name="create_calendar_time_zone">时区</string>
|
||||
<string name="create_calendar_type">集合类型</string>
|
||||
<string name="create_calendar_type_only_events">日历(只有日程)</string>
|
||||
<string name="create_calendar_type_only_tasks">任务列表(只有任务)</string>
|
||||
<string name="create_calendar_type_events_and_tasks">混合(日程和任务)</string>
|
||||
<string name="create_collection_color">设置集合颜色</string>
|
||||
<string name="create_collection_creating">正在创建集合</string>
|
||||
<string name="create_collection_display_name">集合显示名称(标题)</string>
|
||||
<string name="create_collection_display_name_required">请输入标题</string>
|
||||
<string name="create_collection_description">简介(可选)</string>
|
||||
<string name="create_collection_home_set">主集合</string>
|
||||
<string name="create_collection_create">创建</string>
|
||||
<string name="delete_collection">删除集合</string>
|
||||
<string name="delete_collection_confirm_title">你确定吗?</string>
|
||||
<string name="delete_collection_confirm_warning">这个集合 %s 及其所有数据将会从服务器删除。</string>
|
||||
<string name="delete_collection_deleting_collection">正在删除集合</string>
|
||||
<!--ExceptionInfoFragment-->
|
||||
<string name="exception">出现错误</string>
|
||||
<string name="exception_httpexception">出现 HTTP 错误</string>
|
||||
<string name="exception_ioexception">出现 I/O 错误</string>
|
||||
<string name="exception_show_details">显示详情</string>
|
||||
<!--sync errors and DebugInfoActivity-->
|
||||
<string name="debug_info_title">调试信息</string>
|
||||
<string name="sync_error_permissions">DAVdroid 权限</string>
|
||||
<string name="sync_error_permissions_text">需要额外权限</string>
|
||||
<string name="sync_error_calendar">日历同步失败(%s)</string>
|
||||
<string name="sync_error_contacts">通讯录同步失败(%s)</string>
|
||||
<string name="sync_error_tasks">任务同步失败(%s)</string>
|
||||
<string name="sync_error">%s时错误</string>
|
||||
<string name="sync_error_http_dav">%s时服务器错误</string>
|
||||
<string name="sync_error_local_storage">%s时数据库错误</string>
|
||||
<string-array name="sync_error_phases">
|
||||
<item>准备同步</item>
|
||||
<item>请求功能列表</item>
|
||||
<item>处理本地删除项目</item>
|
||||
<item>准备创建/修改项目</item>
|
||||
<item>上传创建/修改项目</item>
|
||||
<item>检查同步状态</item>
|
||||
<item>检查本地数据</item>
|
||||
<item>检查远程数据</item>
|
||||
<item>比较本地和远程数据</item>
|
||||
<item>下载远程数据</item>
|
||||
<item>预处理</item>
|
||||
<item>保存同步状态</item>
|
||||
</string-array>
|
||||
<string name="sync_error_unauthorized">用户名或密码错误</string>
|
||||
<!--cert4android-->
|
||||
<string name="certificate_notification_connection_security">DAVdroid: 连接安全性</string>
|
||||
<string name="trust_certificate_unknown_certificate_found">DAVdroid 遇到了未知证书。你是否要信任该证书?</string>
|
||||
</resources>
|
||||
54
app/src/gplay/res/menu/activity_accounts_drawer.xml
Normal file
54
app/src/gplay/res/menu/activity_accounts_drawer.xml
Normal file
@@ -0,0 +1,54 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright © 2013 – 2016 Ricki Hirner (bitfire web engineering).
|
||||
~ All rights reserved. This program and the accompanying materials
|
||||
~ are made available under the terms of the GNU Public License v3.0
|
||||
~ which accompanies this distribution, and is available at
|
||||
~ http://www.gnu.org/licenses/gpl.html
|
||||
-->
|
||||
|
||||
<menu xmlns:tools="http://schemas.android.com/tools"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<item
|
||||
android:id="@+id/nav_about"
|
||||
android:icon="@drawable/ic_info_dark"
|
||||
android:title="@string/navigation_drawer_about"/>
|
||||
<item
|
||||
android:id="@+id/nav_app_settings"
|
||||
android:icon="@drawable/ic_settings_action"
|
||||
android:title="@string/navigation_drawer_settings"/>
|
||||
|
||||
<item android:title="@string/navigation_drawer_news_updates">
|
||||
<menu>
|
||||
<item
|
||||
android:id="@+id/nav_twitter"
|
||||
android:icon="@drawable/twitter"
|
||||
android:title="\@davdroidapp"
|
||||
tools:ignore="HardcodedText"/>
|
||||
</menu>
|
||||
</item>
|
||||
|
||||
<item android:title="@string/navigation_drawer_external_links">
|
||||
<menu>
|
||||
<item
|
||||
android:id="@+id/nav_website"
|
||||
android:icon="@drawable/ic_home_dark"
|
||||
android:title="@string/navigation_drawer_website"/>
|
||||
<item
|
||||
android:id="@+id/nav_faq"
|
||||
android:icon="@drawable/ic_help_action"
|
||||
android:title="@string/navigation_drawer_faq"/>
|
||||
<item
|
||||
android:id="@+id/nav_forums"
|
||||
android:icon="@drawable/ic_forum_dark"
|
||||
android:title="@string/navigation_drawer_forums"/>
|
||||
<item
|
||||
android:id="@+id/nav_donate"
|
||||
android:icon="@drawable/ic_attach_money_dark"
|
||||
android:title="@string/navigation_drawer_donate"
|
||||
android:visible="false"/>
|
||||
</menu>
|
||||
</item>
|
||||
|
||||
</menu>
|
||||
191
app/src/main/AndroidManifest.xml
Normal file
191
app/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,191 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright (c) 2013 – 2015 Ricki Hirner (bitfire web engineering).
|
||||
~ All rights reserved. This program and the accompanying materials
|
||||
~ are made available under the terms of the GNU Public License v3.0
|
||||
~ which accompanies this distribution, and is available at
|
||||
~ http://www.gnu.org/licenses/gpl.html
|
||||
-->
|
||||
<manifest package="at.bitfire.davdroid"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:installLocation="internalOnly">
|
||||
|
||||
<!-- normal permissions -->
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
|
||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<uses-permission android:name="android.permission.READ_SYNC_SETTINGS"/>
|
||||
<uses-permission android:name="android.permission.READ_SYNC_STATS"/>
|
||||
<uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS"/>
|
||||
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"/>
|
||||
|
||||
<!-- account management permissions not required for own accounts since API level 22 -->
|
||||
<uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS" android:maxSdkVersion="22"/>
|
||||
<uses-permission android:name="android.permission.GET_ACCOUNTS" android:maxSdkVersion="22"/>
|
||||
<uses-permission android:name="android.permission.MANAGE_ACCOUNTS" android:maxSdkVersion="22"/>
|
||||
|
||||
<!--
|
||||
for writing external log files; permission only required for SDK <= 18 because since then,
|
||||
writing to app-private directory doesn't require extra permissions
|
||||
-->
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="18"/>
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="18"/>
|
||||
|
||||
<!-- other permissions -->
|
||||
<!-- android.permission-group.CONTACTS -->
|
||||
<uses-permission android:name="android.permission.READ_CONTACTS"/>
|
||||
<uses-permission android:name="android.permission.WRITE_CONTACTS"/>
|
||||
<!-- android.permission-group.CALENDAR -->
|
||||
<uses-permission android:name="android.permission.READ_CALENDAR"/>
|
||||
<uses-permission android:name="android.permission.WRITE_CALENDAR"/>
|
||||
|
||||
<!-- ical4android declares task access permissions -->
|
||||
|
||||
<application
|
||||
android:name=".App"
|
||||
android:allowBackup="true"
|
||||
android:fullBackupContent="false"
|
||||
android:networkSecurityConfig="@xml/network_security_config"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/AppTheme"
|
||||
tools:ignore="UnusedAttribute">
|
||||
|
||||
<receiver
|
||||
android:name=".App$ReinitSettingsReceiver"
|
||||
android:exported="false"
|
||||
android:process=":sync">
|
||||
<intent-filter>
|
||||
<action android:name="at.bitfire.davdroid.REINIT_SETTINGS"/>
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
<receiver
|
||||
android:name=".AccountSettings$AppUpdatedReceiver"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.PACKAGE_REPLACED" />
|
||||
<data android:scheme="package" android:path="at.bitfire.davdroid" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<service
|
||||
android:name=".syncadapter.AccountAuthenticatorService"
|
||||
android:exported="false">
|
||||
<intent-filter>
|
||||
<action android:name="android.accounts.AccountAuthenticator"/>
|
||||
</intent-filter>
|
||||
|
||||
<meta-data
|
||||
android:name="android.accounts.AccountAuthenticator"
|
||||
android:resource="@xml/account_authenticator"/>
|
||||
</service>
|
||||
<service
|
||||
android:name=".syncadapter.ContactsSyncAdapterService"
|
||||
android:exported="true"
|
||||
android:process=":sync"
|
||||
tools:ignore="ExportedService">
|
||||
<intent-filter>
|
||||
<action android:name="android.content.SyncAdapter"/>
|
||||
</intent-filter>
|
||||
|
||||
<meta-data
|
||||
android:name="android.content.SyncAdapter"
|
||||
android:resource="@xml/sync_contacts"/>
|
||||
<meta-data
|
||||
android:name="android.provider.CONTACTS_STRUCTURE"
|
||||
android:resource="@xml/contacts"/>
|
||||
</service>
|
||||
<service
|
||||
android:name=".syncadapter.CalendarsSyncAdapterService"
|
||||
android:exported="true"
|
||||
android:process=":sync"
|
||||
tools:ignore="ExportedService">
|
||||
<intent-filter>
|
||||
<action android:name="android.content.SyncAdapter"/>
|
||||
</intent-filter>
|
||||
|
||||
<meta-data
|
||||
android:name="android.content.SyncAdapter"
|
||||
android:resource="@xml/sync_calendars"/>
|
||||
</service>
|
||||
<service
|
||||
android:name=".syncadapter.TasksSyncAdapterService"
|
||||
android:exported="true"
|
||||
android:process=":sync"
|
||||
tools:ignore="ExportedService">
|
||||
<intent-filter>
|
||||
<action android:name="android.content.SyncAdapter"/>
|
||||
</intent-filter>
|
||||
|
||||
<meta-data
|
||||
android:name="android.content.SyncAdapter"
|
||||
android:resource="@xml/sync_tasks"/>
|
||||
</service>
|
||||
|
||||
<service
|
||||
android:name=".DavService"
|
||||
android:enabled="true">
|
||||
</service>
|
||||
<receiver
|
||||
android:name=".AccountsChangedReceiver"
|
||||
android:enabled="true"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.accounts.LOGIN_ACCOUNTS_CHANGED"/>
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<activity
|
||||
android:name=".ui.AccountsActivity"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/AppTheme.NoActionBar">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".ui.AboutActivity"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/AppTheme.NoActionBar"
|
||||
android:parentActivityName=".ui.AccountsActivity"/>
|
||||
|
||||
<activity
|
||||
android:name=".ui.AppSettingsActivity"
|
||||
android:label="@string/app_settings"
|
||||
android:parentActivityName=".ui.AccountsActivity"/>
|
||||
|
||||
<activity android:name=".ui.PermissionsActivity"
|
||||
android:label="@string/permissions_title"
|
||||
android:parentActivityName=".ui.AccountsActivity"/>
|
||||
|
||||
<activity
|
||||
android:name=".ui.setup.LoginActivity"
|
||||
android:label="@string/login_title"
|
||||
android:parentActivityName=".ui.AccountsActivity">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".ui.AccountActivity"
|
||||
android:parentActivityName=".ui.AccountsActivity">
|
||||
</activity>
|
||||
<activity android:name=".ui.AccountSettingsActivity"/>
|
||||
<activity android:name=".ui.CreateAddressBookActivity"
|
||||
android:label="@string/create_addressbook"/>
|
||||
<activity android:name=".ui.CreateCalendarActivity"
|
||||
android:label="@string/create_calendar"/>
|
||||
|
||||
<activity
|
||||
android:name=".ui.DebugInfoActivity"
|
||||
android:exported="true"
|
||||
android:label="@string/debug_info_title">
|
||||
</activity>
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
153
app/src/main/assets/apache2.html
Normal file
153
app/src/main/assets/apache2.html
Normal file
@@ -0,0 +1,153 @@
|
||||
<h3>Apache License, Version 2.0, January 2004</h3>
|
||||
<p><a href="http://www.apache.org/licenses/">http://www.apache.org/licenses/</a> </p>
|
||||
<p>TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION</p>
|
||||
<p><strong><a name="definitions">1. Definitions</a></strong>.</p>
|
||||
<p>"License" shall mean the terms and conditions for use, reproduction, and
|
||||
distribution as defined by Sections 1 through 9 of this document.</p>
|
||||
<p>"Licensor" shall mean the copyright owner or entity authorized by the
|
||||
copyright owner that is granting the License.</p>
|
||||
<p>"Legal Entity" shall mean the union of the acting entity and all other
|
||||
entities that control, are controlled by, or are under common control with
|
||||
that entity. For the purposes of this definition, "control" means (i) the
|
||||
power, direct or indirect, to cause the direction or management of such
|
||||
entity, whether by contract or otherwise, or (ii) ownership of fifty
|
||||
percent (50%) or more of the outstanding shares, or (iii) beneficial
|
||||
ownership of such entity.</p>
|
||||
<p>"You" (or "Your") shall mean an individual or Legal Entity exercising
|
||||
permissions granted by this License.</p>
|
||||
<p>"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation source,
|
||||
and configuration files.</p>
|
||||
<p>"Object" form shall mean any form resulting from mechanical transformation
|
||||
or translation of a Source form, including but not limited to compiled
|
||||
object code, generated documentation, and conversions to other media types.</p>
|
||||
<p>"Work" shall mean the work of authorship, whether in Source or Object form,
|
||||
made available under the License, as indicated by a copyright notice that
|
||||
is included in or attached to the work (an example is provided in the
|
||||
Appendix below).</p>
|
||||
<p>"Derivative Works" shall mean any work, whether in Source or Object form,
|
||||
that is based on (or derived from) the Work and for which the editorial
|
||||
revisions, annotations, elaborations, or other modifications represent, as
|
||||
a whole, an original work of authorship. For the purposes of this License,
|
||||
Derivative Works shall not include works that remain separable from, or
|
||||
merely link (or bind by name) to the interfaces of, the Work and Derivative
|
||||
Works thereof.</p>
|
||||
<p>"Contribution" shall mean any work of authorship, including the original
|
||||
version of the Work and any modifications or additions to that Work or
|
||||
Derivative Works thereof, that is intentionally submitted to Licensor for
|
||||
inclusion in the Work by the copyright owner or by an individual or Legal
|
||||
Entity authorized to submit on behalf of the copyright owner. For the
|
||||
purposes of this definition, "submitted" means any form of electronic,
|
||||
verbal, or written communication sent to the Licensor or its
|
||||
representatives, including but not limited to communication on electronic
|
||||
mailing lists, source code control systems, and issue tracking systems that
|
||||
are managed by, or on behalf of, the Licensor for the purpose of discussing
|
||||
and improving the Work, but excluding communication that is conspicuously
|
||||
marked or otherwise designated in writing by the copyright owner as "Not a
|
||||
Contribution."</p>
|
||||
<p>"Contributor" shall mean Licensor and any individual or Legal Entity on
|
||||
behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.</p>
|
||||
<p><strong><a name="copyright">2. Grant of Copyright License</a></strong>. Subject to the
|
||||
terms and conditions of this License, each Contributor hereby grants to You
|
||||
a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of, publicly
|
||||
display, publicly perform, sublicense, and distribute the Work and such
|
||||
Derivative Works in Source or Object form.</p>
|
||||
<p><strong><a name="patent">3. Grant of Patent License</a></strong>. Subject to the terms
|
||||
and conditions of this License, each Contributor hereby grants to You a
|
||||
perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made, use,
|
||||
offer to sell, sell, import, and otherwise transfer the Work, where such
|
||||
license applies only to those patent claims licensable by such Contributor
|
||||
that are necessarily infringed by their Contribution(s) alone or by
|
||||
combination of their Contribution(s) with the Work to which such
|
||||
Contribution(s) was submitted. If You institute patent litigation against
|
||||
any entity (including a cross-claim or counterclaim in a lawsuit) alleging
|
||||
that the Work or a Contribution incorporated within the Work constitutes
|
||||
direct or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate as of the
|
||||
date such litigation is filed.</p>
|
||||
<p><strong><a name="redistribution">4. Redistribution</a></strong>. You may reproduce and
|
||||
distribute copies of the Work or Derivative Works thereof in any medium,
|
||||
with or without modifications, and in Source or Object form, provided that
|
||||
You meet the following conditions:</p>
|
||||
|
||||
<p>a. You must give any other recipients of the Work or Derivative Works a
|
||||
copy of this License; and</p>
|
||||
|
||||
<p>b. You must cause any modified files to carry prominent notices stating
|
||||
that You changed the files; and</p>
|
||||
|
||||
<p>c. You must retain, in the Source form of any Derivative Works that You
|
||||
distribute, all copyright, patent, trademark, and attribution notices from
|
||||
the Source form of the Work, excluding those notices that do not pertain to
|
||||
any part of the Derivative Works; and</p>
|
||||
|
||||
<p>d. If the Work includes a "NOTICE" text file as part of its distribution,
|
||||
then any Derivative Works that You distribute must include a readable copy
|
||||
of the attribution notices contained within such NOTICE file, excluding
|
||||
those notices that do not pertain to any part of the Derivative Works, in
|
||||
at least one of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or documentation,
|
||||
if provided along with the Derivative Works; or, within a display generated
|
||||
by the Derivative Works, if and wherever such third-party notices normally
|
||||
appear. The contents of the NOTICE file are for informational purposes only
|
||||
and do not modify the License. You may add Your own attribution notices
|
||||
within Derivative Works that You distribute, alongside or as an addendum to
|
||||
the NOTICE text from the Work, provided that such additional attribution
|
||||
notices cannot be construed as modifying the License.
|
||||
<br/>
|
||||
<br/>
|
||||
You may add Your own copyright statement to Your modifications and may
|
||||
provide additional or different license terms and conditions for use,
|
||||
reproduction, or distribution of Your modifications, or for any such
|
||||
Derivative Works as a whole, provided Your use, reproduction, and
|
||||
distribution of the Work otherwise complies with the conditions stated in
|
||||
this License.
|
||||
</p>
|
||||
|
||||
<p><strong><a name="contributions">5. Submission of Contributions</a></strong>. Unless You
|
||||
explicitly state otherwise, any Contribution intentionally submitted for
|
||||
inclusion in the Work by You to the Licensor shall be under the terms and
|
||||
conditions of this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify the
|
||||
terms of any separate license agreement you may have executed with Licensor
|
||||
regarding such Contributions.</p>
|
||||
<p><strong><a name="trademarks">6. Trademarks</a></strong>. This License does not grant
|
||||
permission to use the trade names, trademarks, service marks, or product
|
||||
names of the Licensor, except as required for reasonable and customary use
|
||||
in describing the origin of the Work and reproducing the content of the
|
||||
NOTICE file.</p>
|
||||
<p><strong><a name="no-warranty">7. Disclaimer of Warranty</a></strong>. Unless required by
|
||||
applicable law or agreed to in writing, Licensor provides the Work (and
|
||||
each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT
|
||||
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including,
|
||||
without limitation, any warranties or conditions of TITLE,
|
||||
NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You
|
||||
are solely responsible for determining the appropriateness of using or
|
||||
redistributing the Work and assume any risks associated with Your exercise
|
||||
of permissions under this License.</p>
|
||||
<p><strong><a name="no-liability">8. Limitation of Liability</a></strong>. In no event and
|
||||
under no legal theory, whether in tort (including negligence), contract, or
|
||||
otherwise, unless required by applicable law (such as deliberate and
|
||||
grossly negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a result
|
||||
of this License or out of the use or inability to use the Work (including
|
||||
but not limited to damages for loss of goodwill, work stoppage, computer
|
||||
failure or malfunction, or any and all other commercial damages or losses),
|
||||
even if such Contributor has been advised of the possibility of such
|
||||
damages.</p>
|
||||
<p><strong><a name="additional">9. Accepting Warranty or Additional Liability</a></strong>.
|
||||
While redistributing the Work or Derivative Works thereof, You may choose
|
||||
to offer, and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this License.
|
||||
However, in accepting such obligations, You may act only on Your own behalf
|
||||
and on Your sole responsibility, not on behalf of any other Contributor,
|
||||
and only if You agree to indemnify, defend, and hold each Contributor
|
||||
harmless for any liability incurred by, or claims asserted against, such
|
||||
Contributor by reason of your accepting any such warranty or additional
|
||||
liability.</p>
|
||||
|
||||
<p>END OF TERMS AND CONDITIONS</p>
|
||||
28
app/src/main/assets/bsd-3clause.html
Normal file
28
app/src/main/assets/bsd-3clause.html
Normal file
@@ -0,0 +1,28 @@
|
||||
<h3>BSD License (3-clause)</h3>
|
||||
|
||||
<p>Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:</p>
|
||||
|
||||
<p>o Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.</p>
|
||||
|
||||
<p>o Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.</p>
|
||||
|
||||
<p>o Neither the name of Ben Fortuna nor the names of any other contributors
|
||||
may be used to endorse or promote products derived from this software
|
||||
without specific prior written permission.</p>
|
||||
|
||||
<p>THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.</p>
|
||||
23
app/src/main/assets/bsd.html
Normal file
23
app/src/main/assets/bsd.html
Normal file
@@ -0,0 +1,23 @@
|
||||
<h3>BSD License</h3>
|
||||
|
||||
<p>Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:</p>
|
||||
|
||||
<p>1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.</p>
|
||||
|
||||
<p>2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.</p>
|
||||
|
||||
<p>THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.</p>
|
||||
|
||||
628
app/src/main/assets/gpl-3.0-standalone.html
Normal file
628
app/src/main/assets/gpl-3.0-standalone.html
Normal file
@@ -0,0 +1,628 @@
|
||||
<h3 style="text-align: center;">GNU GENERAL PUBLIC LICENSE</h3>
|
||||
<p style="text-align: center;">Version 3, 29 June 2007</p>
|
||||
|
||||
<p>Copyright © 2007 Free Software Foundation, Inc.
|
||||
<<a href="http://fsf.org/">http://fsf.org/</a>></p><p>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.</p>
|
||||
|
||||
<h3><a name="preamble"></a>Preamble</h3>
|
||||
|
||||
<p>The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.</p>
|
||||
|
||||
<p>The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.</p>
|
||||
|
||||
<p>When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.</p>
|
||||
|
||||
<p>To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.</p>
|
||||
|
||||
<p>For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.</p>
|
||||
|
||||
<p>Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.</p>
|
||||
|
||||
<p>For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.</p>
|
||||
|
||||
<p>Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.</p>
|
||||
|
||||
<p>Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.</p>
|
||||
|
||||
<p>The precise terms and conditions for copying, distribution and
|
||||
modification follow.</p>
|
||||
|
||||
<h3><a name="terms"></a>TERMS AND CONDITIONS</h3>
|
||||
|
||||
<h4><a name="section0"></a>0. Definitions.</h4>
|
||||
|
||||
<p>“This License” refers to version 3 of the GNU General Public License.</p>
|
||||
|
||||
<p>“Copyright” also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.</p>
|
||||
|
||||
<p>“The Program” refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as “you”. “Licensees” and
|
||||
“recipients” may be individuals or organizations.</p>
|
||||
|
||||
<p>To “modify” a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a “modified version” of the
|
||||
earlier work or a work “based on” the earlier work.</p>
|
||||
|
||||
<p>A “covered work” means either the unmodified Program or a work based
|
||||
on the Program.</p>
|
||||
|
||||
<p>To “propagate” a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.</p>
|
||||
|
||||
<p>To “convey” a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.</p>
|
||||
|
||||
<p>An interactive user interface displays “Appropriate Legal Notices”
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.</p>
|
||||
|
||||
<h4><a name="section1"></a>1. Source Code.</h4>
|
||||
|
||||
<p>The “source code” for a work means the preferred form of the work
|
||||
for making modifications to it. “Object code” means any non-source
|
||||
form of a work.</p>
|
||||
|
||||
<p>A “Standard Interface” means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.</p>
|
||||
|
||||
<p>The “System Libraries” of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
“Major Component”, in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.</p>
|
||||
|
||||
<p>The “Corresponding Source” for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.</p>
|
||||
|
||||
<p>The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.</p>
|
||||
|
||||
<p>The Corresponding Source for a work in source code form is that
|
||||
same work.</p>
|
||||
|
||||
<h4><a name="section2"></a>2. Basic Permissions.</h4>
|
||||
|
||||
<p>All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.</p>
|
||||
|
||||
<p>You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.</p>
|
||||
|
||||
<p>Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.</p>
|
||||
|
||||
<h4><a name="section3"></a>3. Protecting Users' Legal Rights From Anti-Circumvention Law.</h4>
|
||||
|
||||
<p>No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.</p>
|
||||
|
||||
<p>When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.</p>
|
||||
|
||||
<h4><a name="section4"></a>4. Conveying Verbatim Copies.</h4>
|
||||
|
||||
<p>You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.</p>
|
||||
|
||||
<p>You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.</p>
|
||||
|
||||
<h4><a name="section5"></a>5. Conveying Modified Source Versions.</h4>
|
||||
|
||||
<p>You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:</p>
|
||||
|
||||
<ul>
|
||||
<li>a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.</li>
|
||||
|
||||
<li>b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
“keep intact all notices”.</li>
|
||||
|
||||
<li>c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.</li>
|
||||
|
||||
<li>d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.</li>
|
||||
</ul>
|
||||
|
||||
<p>A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
“aggregate” if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.</p>
|
||||
|
||||
<h4><a name="section6"></a>6. Conveying Non-Source Forms.</h4>
|
||||
|
||||
<p>You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:</p>
|
||||
|
||||
<ul>
|
||||
<li>a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.</li>
|
||||
|
||||
<li>b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.</li>
|
||||
|
||||
<li>c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.</li>
|
||||
|
||||
<li>d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.</li>
|
||||
|
||||
<li>e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.</li>
|
||||
</ul>
|
||||
|
||||
<p>A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.</p>
|
||||
|
||||
<p>A “User Product” is either (1) a “consumer product”, which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, “normally used” refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.</p>
|
||||
|
||||
<p>“Installation Information” for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.</p>
|
||||
|
||||
<p>If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).</p>
|
||||
|
||||
<p>The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.</p>
|
||||
|
||||
<p>Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.</p>
|
||||
|
||||
<h4><a name="section7"></a>7. Additional Terms.</h4>
|
||||
|
||||
<p>“Additional permissions” are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.</p>
|
||||
|
||||
<p>When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.</p>
|
||||
|
||||
<p>Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:</p>
|
||||
|
||||
<ul>
|
||||
<li>a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or</li>
|
||||
|
||||
<li>b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or</li>
|
||||
|
||||
<li>c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or</li>
|
||||
|
||||
<li>d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or</li>
|
||||
|
||||
<li>e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or</li>
|
||||
|
||||
<li>f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.</li>
|
||||
</ul>
|
||||
|
||||
<p>All other non-permissive additional terms are considered “further
|
||||
restrictions” within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.</p>
|
||||
|
||||
<p>If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.</p>
|
||||
|
||||
<p>Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.</p>
|
||||
|
||||
<h4><a name="section8"></a>8. Termination.</h4>
|
||||
|
||||
<p>You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).</p>
|
||||
|
||||
<p>However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.</p>
|
||||
|
||||
<p>Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.</p>
|
||||
|
||||
<p>Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.</p>
|
||||
|
||||
<h4><a name="section9"></a>9. Acceptance Not Required for Having Copies.</h4>
|
||||
|
||||
<p>You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.</p>
|
||||
|
||||
<h4><a name="section10"></a>10. Automatic Licensing of Downstream Recipients.</h4>
|
||||
|
||||
<p>Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.</p>
|
||||
|
||||
<p>An “entity transaction” is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.</p>
|
||||
|
||||
<p>You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.</p>
|
||||
|
||||
<h4><a name="section11"></a>11. Patents.</h4>
|
||||
|
||||
<p>A “contributor” is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's “contributor version”.</p>
|
||||
|
||||
<p>A contributor's “essential patent claims” are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, “control” includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.</p>
|
||||
|
||||
<p>Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.</p>
|
||||
|
||||
<p>In the following three paragraphs, a “patent license” is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To “grant” such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.</p>
|
||||
|
||||
<p>If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. “Knowingly relying” means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.</p>
|
||||
|
||||
<p>If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.</p>
|
||||
|
||||
<p>A patent license is “discriminatory” if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.</p>
|
||||
|
||||
<p>Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.</p>
|
||||
|
||||
<h4><a name="section12"></a>12. No Surrender of Others' Freedom.</h4>
|
||||
|
||||
<p>If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.</p>
|
||||
|
||||
<h4><a name="section13"></a>13. Use with the GNU Affero General Public License.</h4>
|
||||
|
||||
<p>Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.</p>
|
||||
|
||||
<h4><a name="section14"></a>14. Revised Versions of this License.</h4>
|
||||
|
||||
<p>The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.</p>
|
||||
|
||||
<p>Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License “or any later version” applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.</p>
|
||||
|
||||
<p>If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.</p>
|
||||
|
||||
<p>Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.</p>
|
||||
|
||||
<h4><a name="section15"></a>15. Disclaimer of Warranty.</h4>
|
||||
|
||||
<p>THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM “AS IS” WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.</p>
|
||||
|
||||
<h4><a name="section16"></a>16. Limitation of Liability.</h4>
|
||||
|
||||
<p>IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.</p>
|
||||
|
||||
<h4><a name="section17"></a>17. Interpretation of Sections 15 and 16.</h4>
|
||||
|
||||
<p>If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.</p>
|
||||
|
||||
<p>END OF TERMS AND CONDITIONS</p>
|
||||
7
app/src/main/assets/mit.html
Normal file
7
app/src/main/assets/mit.html
Normal file
@@ -0,0 +1,7 @@
|
||||
<h3>The MIT License (MIT)</h3>
|
||||
|
||||
<p>Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:</p>
|
||||
|
||||
<p>The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.</p>
|
||||
|
||||
<p>THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.</p>
|
||||
461
app/src/main/java/at/bitfire/davdroid/AccountSettings.java
Normal file
461
app/src/main/java/at/bitfire/davdroid/AccountSettings.java
Normal file
@@ -0,0 +1,461 @@
|
||||
/*
|
||||
* Copyright © 2013 – 2015 Ricki Hirner (bitfire web engineering).
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the GNU Public License v3.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.gnu.org/licenses/gpl.html
|
||||
*/
|
||||
package at.bitfire.davdroid;
|
||||
|
||||
import android.accounts.Account;
|
||||
import android.accounts.AccountManager;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.Notification;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.ContentProviderClient;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.PeriodicSync;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.provider.CalendarContract;
|
||||
import android.provider.ContactsContract;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.NotificationManagerCompat;
|
||||
import android.support.v7.app.NotificationCompat;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import at.bitfire.davdroid.model.ServiceDB;
|
||||
import at.bitfire.davdroid.model.ServiceDB.Collections;
|
||||
import at.bitfire.davdroid.model.ServiceDB.HomeSets;
|
||||
import at.bitfire.davdroid.model.ServiceDB.Services;
|
||||
import at.bitfire.davdroid.resource.LocalAddressBook;
|
||||
import at.bitfire.davdroid.resource.LocalCalendar;
|
||||
import at.bitfire.davdroid.resource.LocalTaskList;
|
||||
import at.bitfire.ical4android.CalendarStorageException;
|
||||
import at.bitfire.ical4android.TaskProvider;
|
||||
import at.bitfire.vcard4android.ContactsStorageException;
|
||||
import at.bitfire.vcard4android.GroupMethod;
|
||||
import lombok.Cleanup;
|
||||
import okhttp3.HttpUrl;
|
||||
|
||||
public class AccountSettings {
|
||||
private final static int CURRENT_VERSION = 4;
|
||||
private final static String
|
||||
KEY_SETTINGS_VERSION = "version",
|
||||
|
||||
KEY_USERNAME = "user_name",
|
||||
|
||||
KEY_WIFI_ONLY = "wifi_only", // sync on WiFi only (default: false)
|
||||
KEY_WIFI_ONLY_SSID = "wifi_only_ssid"; // restrict sync to specific WiFi SSID
|
||||
|
||||
/** Whether to use RFC 6868 for VCards
|
||||
* value = null (not existing) use RFC6868-style encoding (default value)
|
||||
* "0" don't use RFC 6868-style encoding
|
||||
*/
|
||||
private final static String KEY_VCARD_RFC6868 = "vcard_rfc6868";
|
||||
|
||||
/** Time range limitation to the past [in days]
|
||||
value = null default value (DEFAULT_TIME_RANGE_PAST_DAYS)
|
||||
< 0 (-1) no limit
|
||||
>= 0 entries more than n days in the past won't be synchronized
|
||||
*/
|
||||
private final static String KEY_TIME_RANGE_PAST_DAYS = "time_range_past_days";
|
||||
private final static int DEFAULT_TIME_RANGE_PAST_DAYS = 90;
|
||||
|
||||
/* Whether DAVdroid sets the local calendar color to the value from service DB at every sync
|
||||
value = null (not existing) true (default)
|
||||
"0" false */
|
||||
private final static String KEY_MANAGE_CALENDAR_COLORS = "manage_calendar_colors";
|
||||
|
||||
/** Contact group method:
|
||||
value = null (not existing) groups as separate VCards (default)
|
||||
"CATEGORIES" groups are per-contact CATEGORIES
|
||||
*/
|
||||
private final static String KEY_CONTACT_GROUP_METHOD = "contact_group_method";
|
||||
|
||||
public final static long SYNC_INTERVAL_MANUALLY = -1;
|
||||
|
||||
final Context context;
|
||||
final AccountManager accountManager;
|
||||
final Account account;
|
||||
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
public AccountSettings(@NonNull Context context, @NonNull Account account) throws InvalidAccountException {
|
||||
this.context = context;
|
||||
this.account = account;
|
||||
|
||||
accountManager = AccountManager.get(context);
|
||||
|
||||
synchronized(AccountSettings.class) {
|
||||
String versionStr = accountManager.getUserData(account, KEY_SETTINGS_VERSION);
|
||||
if (versionStr == null)
|
||||
throw new InvalidAccountException(account);
|
||||
|
||||
int version = 0;
|
||||
try {
|
||||
version = Integer.parseInt(versionStr);
|
||||
} catch (NumberFormatException ignored) {
|
||||
}
|
||||
App.log.info("Account " + account.name + " has version " + version + ", current version: " + CURRENT_VERSION);
|
||||
|
||||
if (version < CURRENT_VERSION) {
|
||||
Notification notify = new NotificationCompat.Builder(context)
|
||||
.setSmallIcon(R.drawable.ic_error_light)
|
||||
.setLargeIcon(App.getLauncherBitmap(context))
|
||||
.setContentTitle(context.getString(R.string.settings_version_update))
|
||||
.setContentText(context.getString(R.string.settings_version_update_settings_updated))
|
||||
.setSubText(context.getString(R.string.settings_version_update_install_hint))
|
||||
.setStyle(new NotificationCompat.BigTextStyle()
|
||||
.bigText(context.getString(R.string.settings_version_update_settings_updated)))
|
||||
.setCategory(NotificationCompat.CATEGORY_SYSTEM)
|
||||
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
||||
.setContentIntent(PendingIntent.getActivity(context, 0,
|
||||
new Intent(Intent.ACTION_VIEW, Constants.webUri.buildUpon().appendEncodedPath("faq/entry/davdroid-not-working-after-update/").build()),
|
||||
PendingIntent.FLAG_CANCEL_CURRENT))
|
||||
.setLocalOnly(true)
|
||||
.build();
|
||||
NotificationManagerCompat nm = NotificationManagerCompat.from(context);
|
||||
nm.notify(Constants.NOTIFICATION_ACCOUNT_SETTINGS_UPDATED, notify);
|
||||
|
||||
update(version);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static Bundle initialUserData(String userName) {
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putString(KEY_SETTINGS_VERSION, String.valueOf(CURRENT_VERSION));
|
||||
bundle.putString(KEY_USERNAME, userName);
|
||||
return bundle;
|
||||
}
|
||||
|
||||
|
||||
// authentication settings
|
||||
|
||||
public String username() { return accountManager.getUserData(account, KEY_USERNAME); }
|
||||
public void username(@NonNull String userName) { accountManager.setUserData(account, KEY_USERNAME, userName); }
|
||||
|
||||
public String password() { return accountManager.getPassword(account); }
|
||||
public void password(@NonNull String password) { accountManager.setPassword(account, password); }
|
||||
|
||||
|
||||
// sync. settings
|
||||
|
||||
public Long getSyncInterval(@NonNull String authority) {
|
||||
if (ContentResolver.getIsSyncable(account, authority) <= 0)
|
||||
return null;
|
||||
|
||||
if (ContentResolver.getSyncAutomatically(account, authority)) {
|
||||
List<PeriodicSync> syncs = ContentResolver.getPeriodicSyncs(account, authority);
|
||||
if (syncs.isEmpty())
|
||||
return SYNC_INTERVAL_MANUALLY;
|
||||
else
|
||||
return syncs.get(0).period;
|
||||
} else
|
||||
return SYNC_INTERVAL_MANUALLY;
|
||||
}
|
||||
|
||||
public void setSyncInterval(@NonNull String authority, long seconds) {
|
||||
if (seconds == SYNC_INTERVAL_MANUALLY) {
|
||||
ContentResolver.setSyncAutomatically(account, authority, false);
|
||||
} else {
|
||||
ContentResolver.setSyncAutomatically(account, authority, true);
|
||||
ContentResolver.addPeriodicSync(account, authority, new Bundle(), seconds);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean getSyncWifiOnly() {
|
||||
return accountManager.getUserData(account, KEY_WIFI_ONLY) != null;
|
||||
}
|
||||
|
||||
public void setSyncWiFiOnly(boolean wiFiOnly) {
|
||||
accountManager.setUserData(account, KEY_WIFI_ONLY, wiFiOnly ? "1" : null);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getSyncWifiOnlySSID() {
|
||||
return accountManager.getUserData(account, KEY_WIFI_ONLY_SSID);
|
||||
}
|
||||
|
||||
public void setSyncWifiOnlySSID(String ssid) {
|
||||
accountManager.setUserData(account, KEY_WIFI_ONLY_SSID, ssid);
|
||||
}
|
||||
|
||||
|
||||
// CardDAV settings
|
||||
|
||||
public boolean getVCardRFC6868() {
|
||||
if (BuildConfig.settingVCardRFC6868 != null)
|
||||
return BuildConfig.settingVCardRFC6868;
|
||||
|
||||
return accountManager.getUserData(account, KEY_VCARD_RFC6868) == null;
|
||||
}
|
||||
|
||||
public void setVCardRFC6868(boolean use) {
|
||||
if (BuildConfig.settingVCardRFC6868 == null)
|
||||
accountManager.setUserData(account, KEY_VCARD_RFC6868, use ? null : "0");
|
||||
else if (BuildConfig.settingVCardRFC6868 != use)
|
||||
throw new UnsupportedOperationException("Setting is read-only");
|
||||
}
|
||||
|
||||
|
||||
// CalDAV settings
|
||||
|
||||
@Nullable
|
||||
public Integer getTimeRangePastDays() {
|
||||
String strDays = accountManager.getUserData(account, KEY_TIME_RANGE_PAST_DAYS);
|
||||
if (strDays != null) {
|
||||
int days = Integer.valueOf(strDays);
|
||||
return days < 0 ? null : days;
|
||||
} else
|
||||
return DEFAULT_TIME_RANGE_PAST_DAYS;
|
||||
}
|
||||
|
||||
public void setTimeRangePastDays(@Nullable Integer days) {
|
||||
accountManager.setUserData(account, KEY_TIME_RANGE_PAST_DAYS, String.valueOf(days == null ? -1 : days));
|
||||
}
|
||||
|
||||
public boolean getManageCalendarColors() {
|
||||
return accountManager.getUserData(account, KEY_MANAGE_CALENDAR_COLORS) == null;
|
||||
}
|
||||
|
||||
public void setManageCalendarColors(boolean manage) {
|
||||
accountManager.setUserData(account, KEY_MANAGE_CALENDAR_COLORS, manage ? null : "0");
|
||||
}
|
||||
|
||||
|
||||
// CardDAV settings
|
||||
|
||||
@NonNull
|
||||
public GroupMethod getGroupMethod() {
|
||||
if (BuildConfig.settingContactGroupMethod != null)
|
||||
return BuildConfig.settingContactGroupMethod;
|
||||
|
||||
final String name = accountManager.getUserData(account, KEY_CONTACT_GROUP_METHOD);
|
||||
return name != null ?
|
||||
GroupMethod.valueOf(name) :
|
||||
GroupMethod.GROUP_VCARDS;
|
||||
}
|
||||
|
||||
public void setGroupMethod(@NonNull GroupMethod method) {
|
||||
if (BuildConfig.settingContactGroupMethod == null) {
|
||||
final String name = method == GroupMethod.GROUP_VCARDS ? null : method.name();
|
||||
accountManager.setUserData(account, KEY_CONTACT_GROUP_METHOD, name);
|
||||
} else if (BuildConfig.settingContactGroupMethod != method)
|
||||
throw new UnsupportedOperationException("Setting is read-only");
|
||||
}
|
||||
|
||||
|
||||
// update from previous account settings
|
||||
|
||||
private void update(int fromVersion) {
|
||||
for (int toVersion = fromVersion + 1; toVersion <= CURRENT_VERSION; toVersion++) {
|
||||
App.log.info("Updating account " + account.name + " from version " + fromVersion + " to " + toVersion);
|
||||
try {
|
||||
Method updateProc = getClass().getDeclaredMethod("update_" + fromVersion + "_" + toVersion);
|
||||
updateProc.invoke(this);
|
||||
accountManager.setUserData(account, KEY_SETTINGS_VERSION, String.valueOf(toVersion));
|
||||
} catch (Exception e) {
|
||||
App.log.log(Level.SEVERE, "Couldn't update account settings", e);
|
||||
}
|
||||
fromVersion = toVersion;
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "Recycle", "unused" })
|
||||
private void update_1_2() throws ContactsStorageException {
|
||||
/* - KEY_ADDRESSBOOK_URL ("addressbook_url"),
|
||||
- KEY_ADDRESSBOOK_CTAG ("addressbook_ctag"),
|
||||
- KEY_ADDRESSBOOK_VCARD_VERSION ("addressbook_vcard_version") are not used anymore (now stored in ContactsContract.SyncState)
|
||||
- KEY_LAST_ANDROID_VERSION ("last_android_version") has been added
|
||||
*/
|
||||
|
||||
// move previous address book info to ContactsContract.SyncState
|
||||
@Cleanup("release") ContentProviderClient provider = context.getContentResolver().acquireContentProviderClient(ContactsContract.AUTHORITY);
|
||||
if (provider == null)
|
||||
throw new ContactsStorageException("Couldn't access Contacts provider");
|
||||
|
||||
LocalAddressBook addr = new LocalAddressBook(account, provider);
|
||||
|
||||
// until now, ContactsContract.Settings.UNGROUPED_VISIBLE was not set explicitly
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(ContactsContract.Settings.UNGROUPED_VISIBLE, 1);
|
||||
addr.updateSettings(values);
|
||||
|
||||
String url = accountManager.getUserData(account, "addressbook_url");
|
||||
if (!TextUtils.isEmpty(url))
|
||||
addr.setURL(url);
|
||||
accountManager.setUserData(account, "addressbook_url", null);
|
||||
|
||||
String cTag = accountManager.getUserData(account, "addressbook_ctag");
|
||||
if (!TextUtils.isEmpty(cTag))
|
||||
addr.setCTag(cTag);
|
||||
accountManager.setUserData(account, "addressbook_ctag", null);
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "Recycle", "unused" })
|
||||
private void update_2_3() {
|
||||
// Don't show a warning for Android updates anymore
|
||||
accountManager.setUserData(account, "last_android_version", null);
|
||||
|
||||
Long serviceCardDAV = null, serviceCalDAV = null;
|
||||
|
||||
ServiceDB.OpenHelper dbHelper = new ServiceDB.OpenHelper(context);
|
||||
try {
|
||||
SQLiteDatabase db = dbHelper.getWritableDatabase();
|
||||
// we have to create the WebDAV Service database only from the old address book, calendar and task list URLs
|
||||
|
||||
// CardDAV: migrate address books
|
||||
ContentProviderClient client = context.getContentResolver().acquireContentProviderClient(ContactsContract.AUTHORITY);
|
||||
if (client != null)
|
||||
try {
|
||||
LocalAddressBook addrBook = new LocalAddressBook(account, client);
|
||||
String url = addrBook.getURL();
|
||||
if (url != null) {
|
||||
App.log.fine("Migrating address book " + url);
|
||||
|
||||
// insert CardDAV service
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(Services.ACCOUNT_NAME, account.name);
|
||||
values.put(Services.SERVICE, Services.SERVICE_CARDDAV);
|
||||
serviceCardDAV = db.insert(Services._TABLE, null, values);
|
||||
|
||||
// insert address book
|
||||
values.clear();
|
||||
values.put(Collections.SERVICE_ID, serviceCardDAV);
|
||||
values.put(Collections.URL, url);
|
||||
values.put(Collections.SYNC, 1);
|
||||
db.insert(Collections._TABLE, null, values);
|
||||
|
||||
// insert home set
|
||||
HttpUrl homeSet = HttpUrl.parse(url).resolve("../");
|
||||
values.clear();
|
||||
values.put(HomeSets.SERVICE_ID, serviceCardDAV);
|
||||
values.put(HomeSets.URL, homeSet.toString());
|
||||
db.insert(HomeSets._TABLE, null, values);
|
||||
}
|
||||
|
||||
} catch (ContactsStorageException e) {
|
||||
App.log.log(Level.SEVERE, "Couldn't migrate address book", e);
|
||||
} finally {
|
||||
client.release();
|
||||
}
|
||||
|
||||
// CalDAV: migrate calendars + task lists
|
||||
Set<String> collections = new HashSet<>();
|
||||
Set<HttpUrl> homeSets = new HashSet<>();
|
||||
|
||||
client = context.getContentResolver().acquireContentProviderClient(CalendarContract.AUTHORITY);
|
||||
if (client != null)
|
||||
try {
|
||||
LocalCalendar calendars[] = (LocalCalendar[])LocalCalendar.find(account, client, LocalCalendar.Factory.INSTANCE, null, null);
|
||||
for (LocalCalendar calendar : calendars) {
|
||||
String url = calendar.getName();
|
||||
App.log.fine("Migrating calendar " + url);
|
||||
collections.add(url);
|
||||
homeSets.add(HttpUrl.parse(url).resolve("../"));
|
||||
}
|
||||
} catch (CalendarStorageException e) {
|
||||
App.log.log(Level.SEVERE, "Couldn't migrate calendars", e);
|
||||
} finally {
|
||||
client.release();
|
||||
}
|
||||
|
||||
TaskProvider provider = LocalTaskList.acquireTaskProvider(context.getContentResolver());
|
||||
if (provider != null)
|
||||
try {
|
||||
LocalTaskList[] taskLists = (LocalTaskList[])LocalTaskList.find(account, provider, LocalTaskList.Factory.INSTANCE, null, null);
|
||||
for (LocalTaskList taskList : taskLists) {
|
||||
String url = taskList.getSyncId();
|
||||
App.log.fine("Migrating task list " + url);
|
||||
collections.add(url);
|
||||
homeSets.add(HttpUrl.parse(url).resolve("../"));
|
||||
}
|
||||
} catch (CalendarStorageException e) {
|
||||
App.log.log(Level.SEVERE, "Couldn't migrate task lists", e);
|
||||
} finally {
|
||||
provider.close();
|
||||
}
|
||||
|
||||
if (!collections.isEmpty()) {
|
||||
// insert CalDAV service
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(Services.ACCOUNT_NAME, account.name);
|
||||
values.put(Services.SERVICE, Services.SERVICE_CALDAV);
|
||||
serviceCalDAV = db.insert(Services._TABLE, null, values);
|
||||
|
||||
// insert collections
|
||||
for (String url : collections) {
|
||||
values.clear();
|
||||
values.put(Collections.SERVICE_ID, serviceCalDAV);
|
||||
values.put(Collections.URL, url);
|
||||
values.put(Collections.SYNC, 1);
|
||||
db.insert(Collections._TABLE, null, values);
|
||||
}
|
||||
|
||||
// insert home sets
|
||||
for (HttpUrl homeSet : homeSets) {
|
||||
values.clear();
|
||||
values.put(HomeSets.SERVICE_ID, serviceCalDAV);
|
||||
values.put(HomeSets.URL, homeSet.toString());
|
||||
db.insert(HomeSets._TABLE, null, values);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
dbHelper.close();
|
||||
}
|
||||
|
||||
// initiate service detection (refresh) to get display names, colors etc.
|
||||
Intent refresh = new Intent(context, DavService.class);
|
||||
refresh.setAction(DavService.ACTION_REFRESH_COLLECTIONS);
|
||||
if (serviceCardDAV != null) {
|
||||
refresh.putExtra(DavService.EXTRA_DAV_SERVICE_ID, serviceCardDAV);
|
||||
context.startService(refresh);
|
||||
}
|
||||
if (serviceCalDAV != null) {
|
||||
refresh.putExtra(DavService.EXTRA_DAV_SERVICE_ID, serviceCalDAV);
|
||||
context.startService(refresh);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "Recycle", "unused" })
|
||||
private void update_3_4() {
|
||||
setGroupMethod(GroupMethod.CATEGORIES);
|
||||
}
|
||||
|
||||
|
||||
public static class AppUpdatedReceiver extends BroadcastReceiver {
|
||||
|
||||
@Override
|
||||
@SuppressLint("UnsafeProtectedBroadcastReceiver,MissingPermission")
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
App.log.info("DAVdroid was updated, checking for AccountSettings version");
|
||||
|
||||
// peek into AccountSettings to initiate a possible migration
|
||||
AccountManager accountManager = AccountManager.get(context);
|
||||
for (Account account : accountManager.getAccountsByType(context.getString(R.string.account_type)))
|
||||
try {
|
||||
App.log.info("Checking account " + account.name);
|
||||
new AccountSettings(context, account);
|
||||
} catch (InvalidAccountException e) {
|
||||
App.log.log(Level.SEVERE, "Couldn't check for updated account settings", e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright © 2013 – 2016 Ricki Hirner (bitfire web engineering).
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the GNU Public License v3.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.gnu.org/licenses/gpl.html
|
||||
*/
|
||||
|
||||
package at.bitfire.davdroid;
|
||||
|
||||
import android.accounts.AccountManager;
|
||||
import android.accounts.OnAccountsUpdateListener;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
public class AccountsChangedReceiver extends BroadcastReceiver {
|
||||
|
||||
protected static final List<OnAccountsUpdateListener> listeners = new LinkedList<>();
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if (AccountManager.LOGIN_ACCOUNTS_CHANGED_ACTION.equals(intent.getAction())) {
|
||||
Intent serviceIntent = new Intent(context, DavService.class);
|
||||
serviceIntent.setAction(DavService.ACTION_ACCOUNTS_UPDATED);
|
||||
context.startService(serviceIntent);
|
||||
|
||||
for (OnAccountsUpdateListener listener : listeners)
|
||||
listener.onAccountsUpdated(null);
|
||||
}
|
||||
}
|
||||
|
||||
public static void registerListener(OnAccountsUpdateListener listener, boolean callImmediately) {
|
||||
listeners.add(listener);
|
||||
if (callImmediately)
|
||||
listener.onAccountsUpdated(null);
|
||||
}
|
||||
|
||||
public static void unregisterListener(OnAccountsUpdateListener listener) {
|
||||
listeners.remove(listener);
|
||||
}
|
||||
|
||||
}
|
||||
178
app/src/main/java/at/bitfire/davdroid/App.java
Normal file
178
app/src/main/java/at/bitfire/davdroid/App.java
Normal file
@@ -0,0 +1,178 @@
|
||||
/*
|
||||
* Copyright © 2013 – 2016 Ricki Hirner (bitfire web engineering).
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the GNU Public License v3.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.gnu.org/licenses/gpl.html
|
||||
*/
|
||||
|
||||
package at.bitfire.davdroid;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.Application;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Build;
|
||||
import android.os.Process;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.NotificationManagerCompat;
|
||||
import android.support.v7.app.NotificationCompat;
|
||||
import android.util.Log;
|
||||
|
||||
import org.apache.commons.lang3.time.DateFormatUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.logging.FileHandler;
|
||||
import java.util.logging.Handler;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.net.ssl.HostnameVerifier;
|
||||
|
||||
import at.bitfire.cert4android.CustomCertManager;
|
||||
import at.bitfire.davdroid.log.LogcatHandler;
|
||||
import at.bitfire.davdroid.log.PlainTextFormatter;
|
||||
import at.bitfire.davdroid.model.ServiceDB;
|
||||
import at.bitfire.davdroid.model.Settings;
|
||||
import lombok.Cleanup;
|
||||
import lombok.Getter;
|
||||
import okhttp3.internal.tls.OkHostnameVerifier;
|
||||
|
||||
public class App extends Application {
|
||||
public static final String
|
||||
FLAVOR_GOOGLE_PLAY = "gplay",
|
||||
FLAVOR_ICLOUD = "icloud",
|
||||
FLAVOR_STANDARD = "standard";
|
||||
|
||||
public static final String
|
||||
DISTRUST_SYSTEM_CERTIFICATES = "distrustSystemCerts",
|
||||
LOG_TO_EXTERNAL_STORAGE = "logToExternalStorage";
|
||||
|
||||
@Getter
|
||||
private CustomCertManager certManager;
|
||||
|
||||
@Getter
|
||||
private static SSLSocketFactoryCompat sslSocketFactoryCompat;
|
||||
|
||||
@Getter
|
||||
private static HostnameVerifier hostnameVerifier;
|
||||
|
||||
public final static Logger log = Logger.getLogger("davdroid");
|
||||
static {
|
||||
at.bitfire.dav4android.Constants.log = Logger.getLogger("davdroid.dav4android");
|
||||
at.bitfire.cert4android.Constants.log = Logger.getLogger("davdroid.cert4android");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
reinitCertManager();
|
||||
reinitLogger();
|
||||
}
|
||||
|
||||
public void reinitCertManager() {
|
||||
if (BuildConfig.customCerts) {
|
||||
if (certManager != null)
|
||||
certManager.close();
|
||||
|
||||
@Cleanup ServiceDB.OpenHelper dbHelper = new ServiceDB.OpenHelper(this);
|
||||
Settings settings = new Settings(dbHelper.getReadableDatabase());
|
||||
|
||||
certManager = new CustomCertManager(this, !settings.getBoolean(DISTRUST_SYSTEM_CERTIFICATES, false));
|
||||
sslSocketFactoryCompat = new SSLSocketFactoryCompat(certManager);
|
||||
hostnameVerifier = certManager.hostnameVerifier(OkHostnameVerifier.INSTANCE);
|
||||
}
|
||||
}
|
||||
|
||||
public void reinitLogger() {
|
||||
@Cleanup ServiceDB.OpenHelper dbHelper = new ServiceDB.OpenHelper(this);
|
||||
Settings settings = new Settings(dbHelper.getReadableDatabase());
|
||||
|
||||
boolean logToFile = settings.getBoolean(LOG_TO_EXTERNAL_STORAGE, false),
|
||||
logVerbose = logToFile || Log.isLoggable(log.getName(), Log.DEBUG);
|
||||
|
||||
// set logging level according to preferences
|
||||
final Logger rootLogger = Logger.getLogger("");
|
||||
rootLogger.setLevel(logVerbose ? Level.ALL : Level.INFO);
|
||||
|
||||
// remove all handlers and add our own logcat handler
|
||||
rootLogger.setUseParentHandlers(false);
|
||||
for (Handler handler : rootLogger.getHandlers())
|
||||
rootLogger.removeHandler(handler);
|
||||
rootLogger.addHandler(LogcatHandler.INSTANCE);
|
||||
|
||||
NotificationManagerCompat nm = NotificationManagerCompat.from(this);
|
||||
// log to external file according to preferences
|
||||
if (logToFile) {
|
||||
NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
|
||||
builder .setSmallIcon(R.drawable.ic_sd_storage_light)
|
||||
.setLargeIcon(getLauncherBitmap(this))
|
||||
.setContentTitle(getString(R.string.logging_davdroid_file_logging))
|
||||
.setLocalOnly(true);
|
||||
|
||||
File dir = getExternalFilesDir(null);
|
||||
if (dir != null)
|
||||
try {
|
||||
String fileName = new File(dir, "davdroid-" + Process.myPid() + "-" +
|
||||
DateFormatUtils.format(System.currentTimeMillis(), "yyyyMMdd-HHmmss") + ".txt").toString();
|
||||
log.info("Logging to " + fileName);
|
||||
|
||||
FileHandler fileHandler = new FileHandler(fileName);
|
||||
fileHandler.setFormatter(PlainTextFormatter.DEFAULT);
|
||||
log.addHandler(fileHandler);
|
||||
builder .setContentText(dir.getPath())
|
||||
.setSubText(getString(R.string.logging_to_external_storage_warning))
|
||||
.setCategory(NotificationCompat.CATEGORY_STATUS)
|
||||
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
||||
.setStyle(new NotificationCompat.BigTextStyle()
|
||||
.bigText(getString(R.string.logging_to_external_storage, dir.getPath())))
|
||||
.setOngoing(true);
|
||||
|
||||
} catch (IOException e) {
|
||||
log.log(Level.SEVERE, "Couldn't create external log file", e);
|
||||
|
||||
builder .setContentText(getString(R.string.logging_couldnt_create_file, e.getLocalizedMessage()))
|
||||
.setCategory(NotificationCompat.CATEGORY_ERROR);
|
||||
}
|
||||
else
|
||||
builder.setContentText(getString(R.string.logging_no_external_storage));
|
||||
|
||||
nm.notify(Constants.NOTIFICATION_EXTERNAL_FILE_LOGGING, builder.build());
|
||||
} else
|
||||
nm.cancel(Constants.NOTIFICATION_EXTERNAL_FILE_LOGGING);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
public static Bitmap getLauncherBitmap(@NonNull Context context) {
|
||||
Bitmap bitmapLogo = null;
|
||||
Drawable drawableLogo = android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP ?
|
||||
context.getDrawable(R.mipmap.ic_launcher) :
|
||||
context.getResources().getDrawable(R.mipmap.ic_launcher);
|
||||
if (drawableLogo instanceof BitmapDrawable)
|
||||
bitmapLogo = ((BitmapDrawable)drawableLogo).getBitmap();
|
||||
return bitmapLogo;
|
||||
}
|
||||
|
||||
|
||||
public static class ReinitSettingsReceiver extends BroadcastReceiver {
|
||||
|
||||
public static final String ACTION_REINIT_SETTINGS = "at.bitfire.davdroid.REINIT_SETTINGS";
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
log.info("Received broadcast: re-initializing settings (logger/cert manager)");
|
||||
|
||||
App app = (App)context.getApplicationContext();
|
||||
app.reinitLogger();
|
||||
app.reinitCertManager();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2014 Ricki Hirner (bitfire web engineering).
|
||||
/*
|
||||
* Copyright © 2013 – 2015 Ricki Hirner (bitfire web engineering).
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the GNU Public License v3.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.gnu.org/licenses/gpl.html
|
||||
******************************************************************************/
|
||||
*/
|
||||
package at.bitfire.davdroid;
|
||||
|
||||
import java.lang.reflect.Array;
|
||||
29
app/src/main/java/at/bitfire/davdroid/Constants.java
Normal file
29
app/src/main/java/at/bitfire/davdroid/Constants.java
Normal file
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright © 2013 – 2015 Ricki Hirner (bitfire web engineering).
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the GNU Public License v3.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.gnu.org/licenses/gpl.html
|
||||
*/
|
||||
package at.bitfire.davdroid;
|
||||
|
||||
import android.net.Uri;
|
||||
|
||||
public class Constants {
|
||||
|
||||
// notification IDs
|
||||
public final static int
|
||||
NOTIFICATION_ACCOUNT_SETTINGS_UPDATED = 0,
|
||||
NOTIFICATION_EXTERNAL_FILE_LOGGING = 1,
|
||||
NOTIFICATION_REFRESH_COLLECTIONS = 2,
|
||||
NOTIFICATION_CONTACTS_SYNC = 10,
|
||||
NOTIFICATION_CALENDAR_SYNC = 11,
|
||||
NOTIFICATION_TASK_SYNC = 12,
|
||||
NOTIFICATION_PERMISSIONS = 20,
|
||||
NOTIFICATION_SUBSCRIPTION = 21;
|
||||
|
||||
public static final Uri webUri = BuildConfig.FLAVOR == App.FLAVOR_ICLOUD ?
|
||||
Uri.parse("https://multisync.cloud/?pk_campaign=multisync-app") :
|
||||
Uri.parse("https://davdroid.bitfire.at/?pk_campaign=davdroid-app");
|
||||
|
||||
}
|
||||
428
app/src/main/java/at/bitfire/davdroid/DavService.java
Normal file
428
app/src/main/java/at/bitfire/davdroid/DavService.java
Normal file
@@ -0,0 +1,428 @@
|
||||
/*
|
||||
* Copyright © 2013 – 2016 Ricki Hirner (bitfire web engineering).
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the GNU Public License v3.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.gnu.org/licenses/gpl.html
|
||||
*/
|
||||
|
||||
package at.bitfire.davdroid;
|
||||
|
||||
import android.accounts.Account;
|
||||
import android.accounts.AccountManager;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Notification;
|
||||
import android.app.PendingIntent;
|
||||
import android.app.Service;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Intent;
|
||||
import android.database.Cursor;
|
||||
import android.database.DatabaseUtils;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.os.Binder;
|
||||
import android.os.IBinder;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.NotificationManagerCompat;
|
||||
import android.support.v7.app.NotificationCompat;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import org.apache.commons.collections4.iterators.IteratorChain;
|
||||
import org.apache.commons.collections4.iterators.SingletonIterator;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import at.bitfire.dav4android.DavResource;
|
||||
import at.bitfire.dav4android.UrlUtils;
|
||||
import at.bitfire.dav4android.exception.DavException;
|
||||
import at.bitfire.dav4android.exception.HttpException;
|
||||
import at.bitfire.dav4android.property.AddressbookHomeSet;
|
||||
import at.bitfire.dav4android.property.CalendarHomeSet;
|
||||
import at.bitfire.dav4android.property.CalendarProxyReadFor;
|
||||
import at.bitfire.dav4android.property.CalendarProxyWriteFor;
|
||||
import at.bitfire.dav4android.property.GroupMembership;
|
||||
import at.bitfire.davdroid.model.CollectionInfo;
|
||||
import at.bitfire.davdroid.model.ServiceDB.Collections;
|
||||
import at.bitfire.davdroid.model.ServiceDB.HomeSets;
|
||||
import at.bitfire.davdroid.model.ServiceDB.OpenHelper;
|
||||
import at.bitfire.davdroid.model.ServiceDB.Services;
|
||||
import at.bitfire.davdroid.ui.DebugInfoActivity;
|
||||
import lombok.Cleanup;
|
||||
import okhttp3.HttpUrl;
|
||||
import okhttp3.OkHttpClient;
|
||||
|
||||
public class DavService extends Service {
|
||||
|
||||
public static final String
|
||||
ACTION_ACCOUNTS_UPDATED = "accountsUpdated",
|
||||
ACTION_REFRESH_COLLECTIONS = "refreshCollections",
|
||||
EXTRA_DAV_SERVICE_ID = "davServiceID";
|
||||
|
||||
private final IBinder binder = new InfoBinder();
|
||||
|
||||
private final Set<Long> runningRefresh = new HashSet<>();
|
||||
private final List<WeakReference<RefreshingStatusListener>> refreshingStatusListeners = new LinkedList<>();
|
||||
|
||||
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
if (intent != null) {
|
||||
String action = intent.getAction();
|
||||
long id = intent.getLongExtra(EXTRA_DAV_SERVICE_ID, -1);
|
||||
|
||||
switch (action) {
|
||||
case ACTION_ACCOUNTS_UPDATED:
|
||||
cleanupAccounts();
|
||||
break;
|
||||
case ACTION_REFRESH_COLLECTIONS:
|
||||
if (runningRefresh.add(id)) {
|
||||
new Thread(new RefreshCollections(id)).start();
|
||||
for (WeakReference<RefreshingStatusListener> ref : refreshingStatusListeners) {
|
||||
RefreshingStatusListener listener = ref.get();
|
||||
if (listener != null)
|
||||
listener.onDavRefreshStatusChanged(id, true);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return START_NOT_STICKY;
|
||||
}
|
||||
|
||||
|
||||
/* BOUND SERVICE PART
|
||||
for communicating with the activities
|
||||
*/
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
return binder;
|
||||
}
|
||||
|
||||
public interface RefreshingStatusListener {
|
||||
void onDavRefreshStatusChanged(long id, boolean refreshing);
|
||||
}
|
||||
|
||||
public class InfoBinder extends Binder {
|
||||
public boolean isRefreshing(long id) {
|
||||
return runningRefresh.contains(id);
|
||||
}
|
||||
|
||||
public void addRefreshingStatusListener(@NonNull RefreshingStatusListener listener, boolean callImmediate) {
|
||||
refreshingStatusListeners.add(new WeakReference<>(listener));
|
||||
if (callImmediate)
|
||||
for (long id : runningRefresh)
|
||||
listener.onDavRefreshStatusChanged(id, true);
|
||||
}
|
||||
|
||||
public void removeRefreshingStatusListener(@NonNull RefreshingStatusListener listener) {
|
||||
for (Iterator<WeakReference<RefreshingStatusListener>> iterator = refreshingStatusListeners.iterator(); iterator.hasNext(); ) {
|
||||
RefreshingStatusListener item = iterator.next().get();
|
||||
if (listener.equals(item))
|
||||
iterator.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* ACTION RUNNABLES
|
||||
which actually do the work
|
||||
*/
|
||||
|
||||
@SuppressLint("MissingPermission")
|
||||
void cleanupAccounts() {
|
||||
App.log.info("Cleaning up orphaned accounts");
|
||||
|
||||
final OpenHelper dbHelper = new OpenHelper(this);
|
||||
try {
|
||||
SQLiteDatabase db = dbHelper.getWritableDatabase();
|
||||
|
||||
List<String> sqlAccountNames = new LinkedList<>();
|
||||
AccountManager am = AccountManager.get(this);
|
||||
for (Account account : am.getAccountsByType(getString(R.string.account_type)))
|
||||
sqlAccountNames.add(DatabaseUtils.sqlEscapeString(account.name));
|
||||
|
||||
if (sqlAccountNames.isEmpty())
|
||||
db.delete(Services._TABLE, null, null);
|
||||
else
|
||||
db.delete(Services._TABLE, Services.ACCOUNT_NAME + " NOT IN (" + TextUtils.join(",", sqlAccountNames) + ")", null);
|
||||
} finally {
|
||||
dbHelper.close();
|
||||
}
|
||||
}
|
||||
|
||||
private class RefreshCollections implements Runnable {
|
||||
final long service;
|
||||
final OpenHelper dbHelper;
|
||||
SQLiteDatabase db;
|
||||
|
||||
RefreshCollections(long davServiceId) {
|
||||
this.service = davServiceId;
|
||||
dbHelper = new OpenHelper(DavService.this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
Account account = null;
|
||||
|
||||
try {
|
||||
db = dbHelper.getWritableDatabase();
|
||||
|
||||
String serviceType = serviceType();
|
||||
App.log.info("Refreshing " + serviceType + " collections of service #" + service);
|
||||
|
||||
// get account
|
||||
account = account();
|
||||
|
||||
// create authenticating OkHttpClient (credentials taken from account settings)
|
||||
OkHttpClient httpClient = HttpClient.create(DavService.this, account);
|
||||
|
||||
// refresh home sets: principal
|
||||
Set<HttpUrl> homeSets = readHomeSets();
|
||||
HttpUrl principal = readPrincipal();
|
||||
if (principal != null) {
|
||||
App.log.fine("Querying principal for home sets");
|
||||
DavResource dav = new DavResource(httpClient, principal);
|
||||
queryHomeSets(serviceType, dav, homeSets);
|
||||
|
||||
// refresh home sets: calendar-proxy-read/write-for
|
||||
CalendarProxyReadFor proxyRead = (CalendarProxyReadFor)dav.properties.get(CalendarProxyReadFor.NAME);
|
||||
if (proxyRead != null)
|
||||
for (String href : proxyRead.principals) {
|
||||
App.log.fine("Principal is a read-only proxy for " + href + ", checking for home sets");
|
||||
queryHomeSets(serviceType, new DavResource(httpClient, dav.location.resolve(href)), homeSets);
|
||||
}
|
||||
CalendarProxyWriteFor proxyWrite = (CalendarProxyWriteFor)dav.properties.get(CalendarProxyWriteFor.NAME);
|
||||
if (proxyWrite != null)
|
||||
for (String href : proxyWrite.principals) {
|
||||
App.log.fine("Principal is a read-write proxy for " + href + ", checking for home sets");
|
||||
queryHomeSets(serviceType, new DavResource(httpClient, dav.location.resolve(href)), homeSets);
|
||||
}
|
||||
|
||||
// refresh home sets: direct group memberships
|
||||
GroupMembership groupMembership = (GroupMembership)dav.properties.get(GroupMembership.NAME);
|
||||
if (groupMembership != null)
|
||||
for (String href : groupMembership.hrefs) {
|
||||
App.log.fine("Principal is member of group " + href + ", checking for home sets");
|
||||
DavResource group = new DavResource(httpClient, dav.location.resolve(href));
|
||||
try {
|
||||
queryHomeSets(serviceType, group, homeSets);
|
||||
} catch(HttpException|DavException e) {
|
||||
App.log.log(Level.WARNING, "Couldn't query member group ", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// now refresh collections (taken from home sets)
|
||||
Map<HttpUrl, CollectionInfo> collections = readCollections();
|
||||
|
||||
// (remember selections before)
|
||||
Set<HttpUrl> selectedCollections = new HashSet<>();
|
||||
for (CollectionInfo info : collections.values())
|
||||
if (info.selected)
|
||||
selectedCollections.add(HttpUrl.parse(info.url));
|
||||
|
||||
for (Iterator<HttpUrl> itHomeSets = homeSets.iterator(); itHomeSets.hasNext(); ) {
|
||||
HttpUrl homeSet = itHomeSets.next();
|
||||
App.log.fine("Listing home set " + homeSet);
|
||||
|
||||
DavResource dav = new DavResource(httpClient, homeSet);
|
||||
try {
|
||||
dav.propfind(1, CollectionInfo.DAV_PROPERTIES);
|
||||
IteratorChain<DavResource> itCollections = new IteratorChain<>(dav.members.iterator(), new SingletonIterator(dav));
|
||||
while (itCollections.hasNext()) {
|
||||
DavResource member = itCollections.next();
|
||||
CollectionInfo info = CollectionInfo.fromDavResource(member);
|
||||
info.confirmed = true;
|
||||
App.log.log(Level.FINE, "Found collection", info);
|
||||
|
||||
if ((serviceType.equals(Services.SERVICE_CARDDAV) && info.type == CollectionInfo.Type.ADDRESS_BOOK) ||
|
||||
(serviceType.equals(Services.SERVICE_CALDAV) && info.type == CollectionInfo.Type.CALENDAR))
|
||||
collections.put(member.location, info);
|
||||
}
|
||||
} catch(HttpException e) {
|
||||
if (e.status == 403 || e.status == 404 || e.status == 410)
|
||||
// delete home set only if it was not accessible (40x)
|
||||
itHomeSets.remove();
|
||||
}
|
||||
}
|
||||
|
||||
// check/refresh unconfirmed collections
|
||||
for (Iterator<Map.Entry<HttpUrl, CollectionInfo>> iterator = collections.entrySet().iterator(); iterator.hasNext(); ) {
|
||||
Map.Entry<HttpUrl, CollectionInfo> entry = iterator.next();
|
||||
HttpUrl url = entry.getKey();
|
||||
CollectionInfo info = entry.getValue();
|
||||
|
||||
if (!info.confirmed)
|
||||
try {
|
||||
DavResource dav = new DavResource(httpClient, url);
|
||||
dav.propfind(0, CollectionInfo.DAV_PROPERTIES);
|
||||
info = CollectionInfo.fromDavResource(dav);
|
||||
info.confirmed = true;
|
||||
|
||||
// remove unusable collections
|
||||
if ((serviceType.equals(Services.SERVICE_CARDDAV) && info.type != CollectionInfo.Type.ADDRESS_BOOK) ||
|
||||
(serviceType.equals(Services.SERVICE_CALDAV) && info.type != CollectionInfo.Type.CALENDAR))
|
||||
iterator.remove();
|
||||
} catch(HttpException e) {
|
||||
if (e.status == 403 || e.status == 404 || e.status == 410)
|
||||
// delete collection only if it was not accessible (40x)
|
||||
iterator.remove();
|
||||
else
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
// restore selections
|
||||
for (HttpUrl url : selectedCollections) {
|
||||
CollectionInfo info = collections.get(url);
|
||||
if (info != null)
|
||||
info.selected = true;
|
||||
}
|
||||
|
||||
db.beginTransactionNonExclusive();
|
||||
try {
|
||||
saveHomeSets(homeSets);
|
||||
saveCollections(collections.values());
|
||||
db.setTransactionSuccessful();
|
||||
} finally {
|
||||
db.endTransaction();
|
||||
}
|
||||
|
||||
} catch(InvalidAccountException e) {
|
||||
App.log.log(Level.SEVERE, "Invalid account", e);
|
||||
} catch(IOException|HttpException|DavException e) {
|
||||
App.log.log(Level.SEVERE, "Couldn't refresh collection list", e);
|
||||
|
||||
Intent debugIntent = new Intent(DavService.this, DebugInfoActivity.class);
|
||||
debugIntent.putExtra(DebugInfoActivity.KEY_THROWABLE, e);
|
||||
if (account != null)
|
||||
debugIntent.putExtra(DebugInfoActivity.KEY_ACCOUNT, account);
|
||||
|
||||
NotificationManagerCompat nm = NotificationManagerCompat.from(DavService.this);
|
||||
Notification notify = new NotificationCompat.Builder(DavService.this)
|
||||
.setSmallIcon(R.drawable.ic_error_light)
|
||||
.setLargeIcon(App.getLauncherBitmap(DavService.this))
|
||||
.setContentTitle(getString(R.string.dav_service_refresh_failed))
|
||||
.setContentText(getString(R.string.dav_service_refresh_couldnt_refresh))
|
||||
.setContentIntent(PendingIntent.getActivity(DavService.this, 0, debugIntent, PendingIntent.FLAG_UPDATE_CURRENT))
|
||||
.build();
|
||||
nm.notify(Constants.NOTIFICATION_REFRESH_COLLECTIONS, notify);
|
||||
} finally {
|
||||
dbHelper.close();
|
||||
|
||||
runningRefresh.remove(service);
|
||||
for (WeakReference<RefreshingStatusListener> ref : refreshingStatusListeners) {
|
||||
RefreshingStatusListener listener = ref.get();
|
||||
if (listener != null)
|
||||
listener.onDavRefreshStatusChanged(service, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given URL defines home sets and adds them to the home set list.
|
||||
* @param serviceType CalDAV/CardDAV (calendar home set / addressbook home set)
|
||||
* @param dav DavResource to check
|
||||
* @param homeSets set where found home set URLs will be put into
|
||||
*/
|
||||
private void queryHomeSets(String serviceType, DavResource dav, Set<HttpUrl> homeSets) throws IOException, HttpException, DavException {
|
||||
if (Services.SERVICE_CARDDAV.equals(serviceType)) {
|
||||
dav.propfind(0, AddressbookHomeSet.NAME, GroupMembership.NAME);
|
||||
AddressbookHomeSet addressbookHomeSet = (AddressbookHomeSet)dav.properties.get(AddressbookHomeSet.NAME);
|
||||
if (addressbookHomeSet != null)
|
||||
for (String href : addressbookHomeSet.hrefs)
|
||||
homeSets.add(UrlUtils.withTrailingSlash(dav.location.resolve(href)));
|
||||
} else if (Services.SERVICE_CALDAV.equals(serviceType)) {
|
||||
dav.propfind(0, CalendarHomeSet.NAME, CalendarProxyReadFor.NAME, CalendarProxyWriteFor.NAME, GroupMembership.NAME);
|
||||
CalendarHomeSet calendarHomeSet = (CalendarHomeSet)dav.properties.get(CalendarHomeSet.NAME);
|
||||
if (calendarHomeSet != null)
|
||||
for (String href : calendarHomeSet.hrefs)
|
||||
homeSets.add(UrlUtils.withTrailingSlash(dav.location.resolve(href)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@NonNull
|
||||
private Account account() {
|
||||
@Cleanup Cursor cursor = db.query(Services._TABLE, new String[] { Services.ACCOUNT_NAME }, Services.ID + "=?", new String[] { String.valueOf(service) }, null, null, null);
|
||||
if (cursor.moveToNext()) {
|
||||
return new Account(cursor.getString(0), getString(R.string.account_type));
|
||||
} else
|
||||
throw new IllegalArgumentException("Service not found");
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private String serviceType() {
|
||||
@Cleanup Cursor cursor = db.query(Services._TABLE, new String[] { Services.SERVICE }, Services.ID + "=?", new String[] { String.valueOf(service) }, null, null, null);
|
||||
if (cursor.moveToNext())
|
||||
return cursor.getString(0);
|
||||
else
|
||||
throw new IllegalArgumentException("Service not found");
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private HttpUrl readPrincipal() {
|
||||
@Cleanup Cursor cursor = db.query(Services._TABLE, new String[] { Services.PRINCIPAL }, Services.ID + "=?", new String[] { String.valueOf(service) }, null, null, null);
|
||||
if (cursor.moveToNext()) {
|
||||
String principal = cursor.getString(0);
|
||||
if (principal != null)
|
||||
return HttpUrl.parse(cursor.getString(0));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private Set<HttpUrl> readHomeSets() {
|
||||
Set<HttpUrl> homeSets = new LinkedHashSet<>();
|
||||
@Cleanup Cursor cursor = db.query(HomeSets._TABLE, new String[] { HomeSets.URL }, HomeSets.SERVICE_ID + "=?", new String[] { String.valueOf(service) }, null, null, null);
|
||||
while (cursor.moveToNext())
|
||||
homeSets.add(HttpUrl.parse(cursor.getString(0)));
|
||||
return homeSets;
|
||||
}
|
||||
|
||||
private void saveHomeSets(Set<HttpUrl> homeSets) {
|
||||
db.delete(HomeSets._TABLE, HomeSets.SERVICE_ID + "=?", new String[] { String.valueOf(service) });
|
||||
for (HttpUrl homeSet : homeSets) {
|
||||
ContentValues values = new ContentValues(1);
|
||||
values.put(HomeSets.SERVICE_ID, service);
|
||||
values.put(HomeSets.URL, homeSet.toString());
|
||||
db.insertOrThrow(HomeSets._TABLE, null, values);
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private Map<HttpUrl, CollectionInfo> readCollections() {
|
||||
Map<HttpUrl, CollectionInfo> collections = new LinkedHashMap<>();
|
||||
@Cleanup Cursor cursor = db.query(Collections._TABLE, null, Collections.SERVICE_ID + "=?", new String[]{String.valueOf(service)}, null, null, null);
|
||||
while (cursor.moveToNext()) {
|
||||
ContentValues values = new ContentValues();
|
||||
DatabaseUtils.cursorRowToContentValues(cursor, values);
|
||||
collections.put(HttpUrl.parse(values.getAsString(Collections.URL)), CollectionInfo.fromDB(values));
|
||||
}
|
||||
return collections;
|
||||
}
|
||||
|
||||
private void saveCollections(Iterable<CollectionInfo> collections) {
|
||||
db.delete(Collections._TABLE, HomeSets.SERVICE_ID + "=?", new String[] { String.valueOf(service) });
|
||||
for (CollectionInfo collection : collections) {
|
||||
ContentValues values = collection.toDB();
|
||||
App.log.log(Level.FINE, "Saving collection", values);
|
||||
values.put(Collections.SERVICE_ID, service);
|
||||
db.insertWithOnConflict(Collections._TABLE, null, values, SQLiteDatabase.CONFLICT_REPLACE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
41
app/src/main/java/at/bitfire/davdroid/DavUtils.java
Normal file
41
app/src/main/java/at/bitfire/davdroid/DavUtils.java
Normal file
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright © 2013 – 2015 Ricki Hirner (bitfire web engineering).
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the GNU Public License v3.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.gnu.org/licenses/gpl.html
|
||||
*/
|
||||
|
||||
package at.bitfire.davdroid;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import okhttp3.HttpUrl;
|
||||
|
||||
public class DavUtils {
|
||||
|
||||
public static String ARGBtoCalDAVColor(int colorWithAlpha) {
|
||||
byte alpha = (byte)(colorWithAlpha >> 24);
|
||||
int color = colorWithAlpha & 0xFFFFFF;
|
||||
return String.format("#%06X%02X", color, alpha);
|
||||
}
|
||||
|
||||
public static String lastSegmentOfUrl(@NonNull String url) {
|
||||
// the list returned by HttpUrl.pathSegments() is unmodifiable, so we have to create a copy
|
||||
List<String> segments = new LinkedList<>(HttpUrl.parse(url).pathSegments());
|
||||
Collections.reverse(segments);
|
||||
|
||||
for (String segment : segments)
|
||||
if (!StringUtils.isEmpty(segment))
|
||||
return segment;
|
||||
|
||||
return "/";
|
||||
}
|
||||
|
||||
}
|
||||
141
app/src/main/java/at/bitfire/davdroid/HttpClient.java
Normal file
141
app/src/main/java/at/bitfire/davdroid/HttpClient.java
Normal file
@@ -0,0 +1,141 @@
|
||||
/*
|
||||
* Copyright © 2013 – 2015 Ricki Hirner (bitfire web engineering).
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the GNU Public License v3.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.gnu.org/licenses/gpl.html
|
||||
*/
|
||||
|
||||
package at.bitfire.davdroid;
|
||||
|
||||
import android.accounts.Account;
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import at.bitfire.dav4android.BasicDigestAuthHandler;
|
||||
import okhttp3.Interceptor;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
import okhttp3.logging.HttpLoggingInterceptor;
|
||||
|
||||
public class HttpClient {
|
||||
private static final OkHttpClient client = new OkHttpClient();
|
||||
private static final UserAgentInterceptor userAgentInterceptor = new UserAgentInterceptor();
|
||||
|
||||
private static final String userAgent;
|
||||
static {
|
||||
String date = new SimpleDateFormat("yyyy/MM/dd", Locale.US).format(new Date(BuildConfig.buildTime));
|
||||
userAgent = "DAVdroid/" + BuildConfig.VERSION_NAME + " (" + date + "; dav4android; okhttp3) Android/" + Build.VERSION.RELEASE;
|
||||
}
|
||||
|
||||
private HttpClient() {
|
||||
}
|
||||
|
||||
public static OkHttpClient create(@Nullable Context context, @NonNull Account account, @NonNull final Logger logger) throws InvalidAccountException {
|
||||
OkHttpClient.Builder builder = defaultBuilder(context, logger);
|
||||
|
||||
// use account settings for authentication
|
||||
AccountSettings settings = new AccountSettings(context, account);
|
||||
builder = addAuthentication(builder, null, settings.username(), settings.password());
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
public static OkHttpClient create(@NonNull Context context, @NonNull Logger logger) {
|
||||
return defaultBuilder(context, logger).build();
|
||||
}
|
||||
|
||||
public static OkHttpClient create(@NonNull Context context, @NonNull Account account) throws InvalidAccountException {
|
||||
return create(context, account, App.log);
|
||||
}
|
||||
|
||||
public static OkHttpClient create(@Nullable Context context) {
|
||||
return create(context, App.log);
|
||||
}
|
||||
|
||||
|
||||
private static OkHttpClient.Builder defaultBuilder(@Nullable Context context, @NonNull final Logger logger) {
|
||||
OkHttpClient.Builder builder = client.newBuilder();
|
||||
|
||||
// use MemorizingTrustManager to manage self-signed certificates
|
||||
if (context != null) {
|
||||
App app = (App)context.getApplicationContext();
|
||||
if (App.getSslSocketFactoryCompat() != null && app.getCertManager() != null)
|
||||
builder.sslSocketFactory(App.getSslSocketFactoryCompat(), app.getCertManager());
|
||||
if (App.getHostnameVerifier() != null)
|
||||
builder.hostnameVerifier(App.getHostnameVerifier());
|
||||
}
|
||||
|
||||
// set timeouts
|
||||
builder.connectTimeout(30, TimeUnit.SECONDS);
|
||||
builder.writeTimeout(30, TimeUnit.SECONDS);
|
||||
builder.readTimeout(120, TimeUnit.SECONDS);
|
||||
|
||||
// don't allow redirects, because it would break PROPFIND handling
|
||||
builder.followRedirects(false);
|
||||
|
||||
// add User-Agent to every request
|
||||
builder.addNetworkInterceptor(userAgentInterceptor);
|
||||
|
||||
// add cookie store for non-persistent cookies (some services like Horde use cookies for session tracking)
|
||||
builder.cookieJar(new MemoryCookieStore());
|
||||
|
||||
// add network logging, if requested
|
||||
if (logger.isLoggable(Level.FINEST)) {
|
||||
HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor(new HttpLoggingInterceptor.Logger() {
|
||||
@Override
|
||||
public void log(String message) {
|
||||
logger.finest(message);
|
||||
}
|
||||
});
|
||||
loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
|
||||
builder.addInterceptor(loggingInterceptor);
|
||||
}
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
private static OkHttpClient.Builder addAuthentication(@NonNull OkHttpClient.Builder builder, @Nullable String host, @NonNull String username, @NonNull String password) {
|
||||
BasicDigestAuthHandler authHandler = new BasicDigestAuthHandler(host, username, password);
|
||||
return builder
|
||||
.addNetworkInterceptor(authHandler)
|
||||
.authenticator(authHandler);
|
||||
}
|
||||
|
||||
public static OkHttpClient addAuthentication(@NonNull OkHttpClient client, @NonNull String username, @NonNull String password) {
|
||||
OkHttpClient.Builder builder = client.newBuilder();
|
||||
addAuthentication(builder, null, username, password);
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
public static OkHttpClient addAuthentication(@NonNull OkHttpClient client, @NonNull String host, @NonNull String username, @NonNull String password) {
|
||||
OkHttpClient.Builder builder = client.newBuilder();
|
||||
addAuthentication(builder, host, username, password);
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
|
||||
static class UserAgentInterceptor implements Interceptor {
|
||||
@Override
|
||||
public Response intercept(Chain chain) throws IOException {
|
||||
Locale locale = Locale.getDefault();
|
||||
Request request = chain.request().newBuilder()
|
||||
.header("User-Agent", userAgent)
|
||||
.header("Accept-Language", locale.getLanguage() + "-" + locale.getCountry() + ", " + locale.getLanguage() + ";q=0.7, *;q=0.5")
|
||||
.build();
|
||||
return chain.proceed(request);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* Copyright © 2013 – 2016 Ricki Hirner (bitfire web engineering).
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the GNU Public License v3.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.gnu.org/licenses/gpl.html
|
||||
*/
|
||||
|
||||
package at.bitfire.davdroid;
|
||||
|
||||
import android.accounts.Account;
|
||||
|
||||
public class InvalidAccountException extends Exception {
|
||||
|
||||
public InvalidAccountException(Account account) {
|
||||
super("Invalid account: " + account);
|
||||
}
|
||||
|
||||
}
|
||||
69
app/src/main/java/at/bitfire/davdroid/MemoryCookieStore.java
Normal file
69
app/src/main/java/at/bitfire/davdroid/MemoryCookieStore.java
Normal file
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright © 2013 – 2016 Ricki Hirner (bitfire web engineering).
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the GNU Public License v3.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.gnu.org/licenses/gpl.html
|
||||
*/
|
||||
|
||||
package at.bitfire.davdroid;
|
||||
|
||||
import org.apache.commons.collections4.MapIterator;
|
||||
import org.apache.commons.collections4.keyvalue.MultiKey;
|
||||
import org.apache.commons.collections4.map.HashedMap;
|
||||
import org.apache.commons.collections4.map.MultiKeyMap;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import okhttp3.Cookie;
|
||||
import okhttp3.CookieJar;
|
||||
import okhttp3.HttpUrl;
|
||||
|
||||
/**
|
||||
* Primitive cookie store that stores cookies in a (volatile) hash map.
|
||||
* Will be sufficient for session cookies.
|
||||
*/
|
||||
public class MemoryCookieStore implements CookieJar {
|
||||
|
||||
/**
|
||||
* Stored cookies. The multi-key consists of three parts: name, domain, and path.
|
||||
* This ensures that cookies can be overwritten. [RFC 6265 5.3 Storage Model]
|
||||
* Not thread-safe!
|
||||
*/
|
||||
protected final MultiKeyMap<String, Cookie> storage = MultiKeyMap.multiKeyMap(new HashedMap<MultiKey<? extends String>, Cookie>());
|
||||
|
||||
@Override
|
||||
public void saveFromResponse(HttpUrl url, List<Cookie> cookies) {
|
||||
synchronized(storage) {
|
||||
for (Cookie cookie : cookies)
|
||||
storage.put(cookie.name(), cookie.domain(), cookie.path(), cookie);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Cookie> loadForRequest(HttpUrl url) {
|
||||
List<Cookie> cookies = new LinkedList<>();
|
||||
|
||||
synchronized(storage) {
|
||||
MapIterator<MultiKey<? extends String>, Cookie> iter = storage.mapIterator();
|
||||
while (iter.hasNext()) {
|
||||
iter.next();
|
||||
Cookie cookie = iter.getValue();
|
||||
|
||||
// remove expired cookies
|
||||
if (cookie.expiresAt() <= System.currentTimeMillis()) {
|
||||
iter.remove();
|
||||
continue;
|
||||
}
|
||||
|
||||
// add applicable cookies
|
||||
if (cookie.matches(url))
|
||||
cookies.add(cookie);
|
||||
}
|
||||
}
|
||||
|
||||
return cookies;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
/*
|
||||
* Copyright © 2013 – 2015 Ricki Hirner (bitfire web engineering).
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the GNU Public License v3.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.gnu.org/licenses/gpl.html
|
||||
*/
|
||||
|
||||
package at.bitfire.davdroid;
|
||||
|
||||
import android.os.Build;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.Socket;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLSocket;
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
|
||||
import lombok.Cleanup;
|
||||
|
||||
public class SSLSocketFactoryCompat extends SSLSocketFactory {
|
||||
|
||||
private SSLSocketFactory delegate;
|
||||
|
||||
// Android 5.0+ (API level21) provides reasonable default settings
|
||||
// but it still allows SSLv3
|
||||
// https://developer.android.com/reference/javax/net/ssl/SSLSocket.html
|
||||
static String protocols[] = null, cipherSuites[] = null;
|
||||
static {
|
||||
try {
|
||||
@Cleanup SSLSocket socket = (SSLSocket)SSLSocketFactory.getDefault().createSocket();
|
||||
if (socket != null) {
|
||||
/* set reasonable protocol versions */
|
||||
// - enable all supported protocols (enables TLSv1.1 and TLSv1.2 on Android <5.0)
|
||||
// - remove all SSL versions (especially SSLv3) because they're insecure now
|
||||
List<String> protocols = new LinkedList<>();
|
||||
for (String protocol : socket.getSupportedProtocols())
|
||||
if (!protocol.toUpperCase(Locale.US).contains("SSL"))
|
||||
protocols.add(protocol);
|
||||
App.log.info("Setting allowed TLS protocols: " + TextUtils.join(", ", protocols));
|
||||
SSLSocketFactoryCompat.protocols = protocols.toArray(new String[protocols.size()]);
|
||||
|
||||
/* set up reasonable cipher suites */
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
|
||||
// choose known secure cipher suites
|
||||
List<String> allowedCiphers = Arrays.asList(
|
||||
// TLS 1.2
|
||||
"TLS_RSA_WITH_AES_256_GCM_SHA384",
|
||||
"TLS_RSA_WITH_AES_128_GCM_SHA256",
|
||||
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256",
|
||||
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
|
||||
"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
|
||||
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256",
|
||||
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
|
||||
// maximum interoperability
|
||||
"TLS_RSA_WITH_3DES_EDE_CBC_SHA",
|
||||
"TLS_RSA_WITH_AES_128_CBC_SHA",
|
||||
// additionally
|
||||
"TLS_RSA_WITH_AES_256_CBC_SHA",
|
||||
"TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA",
|
||||
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA",
|
||||
"TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA",
|
||||
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA");
|
||||
List<String> availableCiphers = Arrays.asList(socket.getSupportedCipherSuites());
|
||||
App.log.info("Available cipher suites: " + TextUtils.join(", ", availableCiphers));
|
||||
App.log.info("Cipher suites enabled by default: " + TextUtils.join(", ", socket.getEnabledCipherSuites()));
|
||||
|
||||
// take all allowed ciphers that are available and put them into preferredCiphers
|
||||
HashSet<String> preferredCiphers = new HashSet<>(allowedCiphers);
|
||||
preferredCiphers.retainAll(availableCiphers);
|
||||
|
||||
/* For maximum security, preferredCiphers should *replace* enabled ciphers (thus disabling
|
||||
* ciphers which are enabled by default, but have become unsecure), but I guess for
|
||||
* the security level of DAVdroid and maximum compatibility, disabling of insecure
|
||||
* ciphers should be a server-side task */
|
||||
|
||||
// add preferred ciphers to enabled ciphers
|
||||
HashSet<String> enabledCiphers = preferredCiphers;
|
||||
enabledCiphers.addAll(new HashSet<>(Arrays.asList(socket.getEnabledCipherSuites())));
|
||||
|
||||
App.log.info("Enabling (only) those TLS ciphers: " + TextUtils.join(", ", enabledCiphers));
|
||||
SSLSocketFactoryCompat.cipherSuites = enabledCiphers.toArray(new String[enabledCiphers.size()]);
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
App.log.severe("Couldn't determine default TLS settings");
|
||||
}
|
||||
}
|
||||
|
||||
public SSLSocketFactoryCompat(@NonNull X509TrustManager trustManager) {
|
||||
try {
|
||||
SSLContext sslContext = SSLContext.getInstance("TLS");
|
||||
sslContext.init(null, new X509TrustManager[] { trustManager }, null);
|
||||
delegate = sslContext.getSocketFactory();
|
||||
} catch (GeneralSecurityException e) {
|
||||
throw new AssertionError(); // The system has no TLS. Just give up.
|
||||
}
|
||||
}
|
||||
|
||||
private void upgradeTLS(SSLSocket ssl) {
|
||||
if (protocols != null)
|
||||
ssl.setEnabledProtocols(protocols);
|
||||
|
||||
if (cipherSuites != null)
|
||||
ssl.setEnabledCipherSuites(cipherSuites);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String[] getDefaultCipherSuites() {
|
||||
return cipherSuites;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getSupportedCipherSuites() {
|
||||
return cipherSuites;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException {
|
||||
Socket ssl = delegate.createSocket(s, host, port, autoClose);
|
||||
if (ssl instanceof SSLSocket)
|
||||
upgradeTLS((SSLSocket)ssl);
|
||||
return ssl;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket createSocket(String host, int port) throws IOException {
|
||||
Socket ssl = delegate.createSocket(host, port);
|
||||
if (ssl instanceof SSLSocket)
|
||||
upgradeTLS((SSLSocket)ssl);
|
||||
return ssl;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException {
|
||||
Socket ssl = delegate.createSocket(host, port, localHost, localPort);
|
||||
if (ssl instanceof SSLSocket)
|
||||
upgradeTLS((SSLSocket)ssl);
|
||||
return ssl;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket createSocket(InetAddress host, int port) throws IOException {
|
||||
Socket ssl = delegate.createSocket(host, port);
|
||||
if (ssl instanceof SSLSocket)
|
||||
upgradeTLS((SSLSocket)ssl);
|
||||
return ssl;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
|
||||
Socket ssl = delegate.createSocket(address, port, localAddress, localPort);
|
||||
if (ssl instanceof SSLSocket)
|
||||
upgradeTLS((SSLSocket)ssl);
|
||||
return ssl;
|
||||
}
|
||||
|
||||
}
|
||||
59
app/src/main/java/at/bitfire/davdroid/log/LogcatHandler.java
Normal file
59
app/src/main/java/at/bitfire/davdroid/log/LogcatHandler.java
Normal file
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright © 2013 – 2016 Ricki Hirner (bitfire web engineering).
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the GNU Public License v3.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.gnu.org/licenses/gpl.html
|
||||
*/
|
||||
|
||||
package at.bitfire.davdroid.log;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import org.apache.commons.lang3.math.NumberUtils;
|
||||
|
||||
import java.util.logging.Handler;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.LogRecord;
|
||||
|
||||
public class LogcatHandler extends Handler {
|
||||
private static final int MAX_LINE_LENGTH = 3000;
|
||||
public static final LogcatHandler INSTANCE = new LogcatHandler();
|
||||
|
||||
private LogcatHandler() {
|
||||
super();
|
||||
setFormatter(PlainTextFormatter.LOGCAT);
|
||||
setLevel(Level.ALL);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void publish(LogRecord r) {
|
||||
String text = getFormatter().format(r);
|
||||
int level = r.getLevel().intValue();
|
||||
|
||||
int end = text.length();
|
||||
for (int pos = 0; pos < end; pos += MAX_LINE_LENGTH) {
|
||||
String line = text.substring(pos, NumberUtils.min(pos + MAX_LINE_LENGTH, end));
|
||||
|
||||
if (level >= Level.SEVERE.intValue())
|
||||
Log.e(r.getLoggerName(), line);
|
||||
else if (level >= Level.WARNING.intValue())
|
||||
Log.w(r.getLoggerName(), line);
|
||||
else if (level >= Level.CONFIG.intValue())
|
||||
Log.i(r.getLoggerName(), line);
|
||||
else if (level >= Level.FINER.intValue())
|
||||
Log.d(r.getLoggerName(), line);
|
||||
else
|
||||
Log.v(r.getLoggerName(), line);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flush() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright © 2013 – 2016 Ricki Hirner (bitfire web engineering).
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the GNU Public License v3.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.gnu.org/licenses/gpl.html
|
||||
*/
|
||||
|
||||
package at.bitfire.davdroid.log;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.exception.ExceptionUtils;
|
||||
import org.apache.commons.lang3.time.DateFormatUtils;
|
||||
|
||||
import java.util.logging.Formatter;
|
||||
import java.util.logging.LogRecord;
|
||||
|
||||
public class PlainTextFormatter extends Formatter {
|
||||
public final static PlainTextFormatter
|
||||
LOGCAT = new PlainTextFormatter(true),
|
||||
DEFAULT = new PlainTextFormatter(false);
|
||||
|
||||
private final boolean logcat;
|
||||
|
||||
private PlainTextFormatter(boolean onLogcat) {
|
||||
this.logcat = onLogcat;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("ThrowableResultOfMethodCallIgnored")
|
||||
public String format(LogRecord r) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
if (!logcat)
|
||||
builder .append(DateFormatUtils.format(r.getMillis(), "yyyy-MM-dd HH:mm:ss"))
|
||||
.append(" ").append(r.getThreadID()).append(" ");
|
||||
|
||||
builder.append(String.format("[%s] %s", shortClassName(r.getSourceClassName()), r.getMessage()));
|
||||
|
||||
if (r.getThrown() != null)
|
||||
builder .append("\nEXCEPTION ")
|
||||
.append(ExceptionUtils.getStackTrace(r.getThrown()));
|
||||
|
||||
if (r.getParameters() != null) {
|
||||
int idx = 1;
|
||||
for (Object param : r.getParameters())
|
||||
builder.append("\n\tPARAMETER #").append(idx++).append(" = ").append(param);
|
||||
}
|
||||
|
||||
if (!logcat)
|
||||
builder.append("\n");
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
private String shortClassName(String className) {
|
||||
String s = StringUtils.replace(className, "at.bitfire.davdroid.", "");
|
||||
return StringUtils.replace(s, "at.bitfire.", "");
|
||||
}
|
||||
|
||||
}
|
||||
40
app/src/main/java/at/bitfire/davdroid/log/StringHandler.java
Normal file
40
app/src/main/java/at/bitfire/davdroid/log/StringHandler.java
Normal file
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright © 2013 – 2016 Ricki Hirner (bitfire web engineering).
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the GNU Public License v3.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.gnu.org/licenses/gpl.html
|
||||
*/
|
||||
|
||||
package at.bitfire.davdroid.log;
|
||||
|
||||
import java.util.logging.Handler;
|
||||
import java.util.logging.LogRecord;
|
||||
|
||||
public class StringHandler extends Handler {
|
||||
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
public StringHandler() {
|
||||
super();
|
||||
setFormatter(PlainTextFormatter.DEFAULT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void publish(LogRecord record) {
|
||||
builder.append(getFormatter().format(record));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flush() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
||||
163
app/src/main/java/at/bitfire/davdroid/model/CollectionInfo.java
Normal file
163
app/src/main/java/at/bitfire/davdroid/model/CollectionInfo.java
Normal file
@@ -0,0 +1,163 @@
|
||||
/*
|
||||
* Copyright © 2013 – 2016 Ricki Hirner (bitfire web engineering).
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the GNU Public License v3.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.gnu.org/licenses/gpl.html
|
||||
*/
|
||||
|
||||
package at.bitfire.davdroid.model;
|
||||
|
||||
import android.content.ContentValues;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import at.bitfire.dav4android.DavResource;
|
||||
import at.bitfire.dav4android.Property;
|
||||
import at.bitfire.dav4android.property.AddressbookDescription;
|
||||
import at.bitfire.dav4android.property.CalendarColor;
|
||||
import at.bitfire.dav4android.property.CalendarDescription;
|
||||
import at.bitfire.dav4android.property.CalendarTimezone;
|
||||
import at.bitfire.dav4android.property.CurrentUserPrivilegeSet;
|
||||
import at.bitfire.dav4android.property.DisplayName;
|
||||
import at.bitfire.dav4android.property.ResourceType;
|
||||
import at.bitfire.dav4android.property.SupportedAddressData;
|
||||
import at.bitfire.dav4android.property.SupportedCalendarComponentSet;
|
||||
import at.bitfire.davdroid.model.ServiceDB.Collections;
|
||||
import lombok.ToString;
|
||||
|
||||
@ToString
|
||||
public class CollectionInfo implements Serializable {
|
||||
public long id;
|
||||
public Long serviceID;
|
||||
|
||||
public enum Type {
|
||||
ADDRESS_BOOK,
|
||||
CALENDAR
|
||||
}
|
||||
public Type type;
|
||||
|
||||
public String url;
|
||||
|
||||
public boolean readOnly;
|
||||
public String displayName, description;
|
||||
public Integer color;
|
||||
|
||||
public String timeZone;
|
||||
public Boolean supportsVEVENT;
|
||||
public Boolean supportsVTODO;
|
||||
|
||||
public boolean selected;
|
||||
|
||||
// non-persistent properties
|
||||
public boolean confirmed;
|
||||
|
||||
|
||||
public static final Property.Name[] DAV_PROPERTIES = {
|
||||
ResourceType.NAME,
|
||||
CurrentUserPrivilegeSet.NAME,
|
||||
DisplayName.NAME,
|
||||
AddressbookDescription.NAME, SupportedAddressData.NAME,
|
||||
CalendarDescription.NAME, CalendarColor.NAME, SupportedCalendarComponentSet.NAME
|
||||
};
|
||||
|
||||
public static CollectionInfo fromDavResource(DavResource dav) {
|
||||
CollectionInfo info = new CollectionInfo();
|
||||
info.url = dav.location.toString();
|
||||
|
||||
ResourceType type = (ResourceType)dav.properties.get(ResourceType.NAME);
|
||||
if (type != null) {
|
||||
if (type.types.contains(ResourceType.ADDRESSBOOK))
|
||||
info.type = Type.ADDRESS_BOOK;
|
||||
else if (type.types.contains(ResourceType.CALENDAR))
|
||||
info.type = Type.CALENDAR;
|
||||
}
|
||||
|
||||
info.readOnly = false;
|
||||
CurrentUserPrivilegeSet privilegeSet = (CurrentUserPrivilegeSet)dav.properties.get(CurrentUserPrivilegeSet.NAME);
|
||||
if (privilegeSet != null)
|
||||
info.readOnly = !privilegeSet.mayWriteContent;
|
||||
|
||||
DisplayName displayName = (DisplayName)dav.properties.get(DisplayName.NAME);
|
||||
if (displayName != null && !StringUtils.isEmpty(displayName.displayName))
|
||||
info.displayName = displayName.displayName;
|
||||
|
||||
if (info.type == Type.ADDRESS_BOOK) {
|
||||
AddressbookDescription addressbookDescription = (AddressbookDescription)dav.properties.get(AddressbookDescription.NAME);
|
||||
if (addressbookDescription != null)
|
||||
info.description = addressbookDescription.description;
|
||||
|
||||
} else if (info.type == Type.CALENDAR) {
|
||||
CalendarDescription calendarDescription = (CalendarDescription)dav.properties.get(CalendarDescription.NAME);
|
||||
if (calendarDescription != null)
|
||||
info.description = calendarDescription.description;
|
||||
|
||||
CalendarColor calendarColor = (CalendarColor)dav.properties.get(CalendarColor.NAME);
|
||||
if (calendarColor != null)
|
||||
info.color = calendarColor.color;
|
||||
|
||||
CalendarTimezone timeZone = (CalendarTimezone)dav.properties.get(CalendarTimezone.NAME);
|
||||
if (timeZone != null)
|
||||
info.timeZone = timeZone.vTimeZone;
|
||||
|
||||
info.supportsVEVENT = info.supportsVTODO = true;
|
||||
SupportedCalendarComponentSet supportedCalendarComponentSet = (SupportedCalendarComponentSet)dav.properties.get(SupportedCalendarComponentSet.NAME);
|
||||
if (supportedCalendarComponentSet != null) {
|
||||
info.supportsVEVENT = supportedCalendarComponentSet.supportsEvents;
|
||||
info.supportsVTODO = supportedCalendarComponentSet.supportsTasks;
|
||||
}
|
||||
}
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
|
||||
public static CollectionInfo fromDB(ContentValues values) {
|
||||
CollectionInfo info = new CollectionInfo();
|
||||
info.id = values.getAsLong(Collections.ID);
|
||||
info.serviceID = values.getAsLong(Collections.SERVICE_ID);
|
||||
|
||||
info.url = values.getAsString(Collections.URL);
|
||||
info.readOnly = values.getAsInteger(Collections.READ_ONLY) != 0;
|
||||
info.displayName = values.getAsString(Collections.DISPLAY_NAME);
|
||||
info.description = values.getAsString(Collections.DESCRIPTION);
|
||||
|
||||
info.color = values.getAsInteger(Collections.COLOR);
|
||||
|
||||
info.timeZone = values.getAsString(Collections.TIME_ZONE);
|
||||
info.supportsVEVENT = getAsBooleanOrNull(values, Collections.SUPPORTS_VEVENT);
|
||||
info.supportsVTODO = getAsBooleanOrNull(values, Collections.SUPPORTS_VTODO);
|
||||
|
||||
info.selected = values.getAsInteger(Collections.SYNC) != 0;
|
||||
return info;
|
||||
}
|
||||
|
||||
public ContentValues toDB() {
|
||||
ContentValues values = new ContentValues();
|
||||
// Collections.SERVICE_ID is never changed
|
||||
|
||||
values.put(Collections.URL, url);
|
||||
values.put(Collections.READ_ONLY, readOnly ? 1 : 0);
|
||||
values.put(Collections.DISPLAY_NAME, displayName);
|
||||
values.put(Collections.DESCRIPTION, description);
|
||||
values.put(Collections.COLOR, color);
|
||||
|
||||
values.put(Collections.TIME_ZONE, timeZone);
|
||||
if (supportsVEVENT != null)
|
||||
values.put(Collections.SUPPORTS_VEVENT, supportsVEVENT ? 1 : 0);
|
||||
if (supportsVTODO != null)
|
||||
values.put(Collections.SUPPORTS_VTODO, supportsVTODO ? 1 : 0);
|
||||
|
||||
values.put(Collections.SYNC, selected ? 1 : 0);
|
||||
return values;
|
||||
}
|
||||
|
||||
|
||||
private static Boolean getAsBooleanOrNull(ContentValues values, String field) {
|
||||
Integer i = values.getAsInteger(field);
|
||||
return (i == null) ? null : (i != 0);
|
||||
}
|
||||
|
||||
}
|
||||
189
app/src/main/java/at/bitfire/davdroid/model/ServiceDB.java
Normal file
189
app/src/main/java/at/bitfire/davdroid/model/ServiceDB.java
Normal file
@@ -0,0 +1,189 @@
|
||||
/*
|
||||
* Copyright © 2013 – 2016 Ricki Hirner (bitfire web engineering).
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the GNU Public License v3.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.gnu.org/licenses/gpl.html
|
||||
*/
|
||||
|
||||
package at.bitfire.davdroid.model;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteException;
|
||||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
import android.os.Build;
|
||||
import android.support.annotation.RequiresApi;
|
||||
|
||||
import java.util.logging.Level;
|
||||
|
||||
import aQute.service.reporter.Messages;
|
||||
import at.bitfire.davdroid.App;
|
||||
import lombok.Cleanup;
|
||||
|
||||
public class ServiceDB {
|
||||
|
||||
public static class Settings {
|
||||
public static final String
|
||||
_TABLE = "settings",
|
||||
NAME = "setting",
|
||||
VALUE = "value";
|
||||
}
|
||||
|
||||
public static class Services {
|
||||
public static final String
|
||||
_TABLE = "services",
|
||||
ID = "_id",
|
||||
ACCOUNT_NAME = "accountName",
|
||||
SERVICE = "service",
|
||||
PRINCIPAL = "principal";
|
||||
|
||||
// allowed values for SERVICE column
|
||||
public static final String
|
||||
SERVICE_CALDAV = "caldav",
|
||||
SERVICE_CARDDAV = "carddav";
|
||||
}
|
||||
|
||||
public static class HomeSets {
|
||||
public static final String
|
||||
_TABLE = "homesets",
|
||||
ID = "_id",
|
||||
SERVICE_ID = "serviceID",
|
||||
URL = "url";
|
||||
}
|
||||
|
||||
public static class Collections {
|
||||
public static final String
|
||||
_TABLE = "collections",
|
||||
ID = "_id",
|
||||
SERVICE_ID = "serviceID",
|
||||
URL = "url",
|
||||
READ_ONLY = "readOnly",
|
||||
DISPLAY_NAME = "displayName",
|
||||
DESCRIPTION = "description",
|
||||
COLOR = "color",
|
||||
TIME_ZONE = "timezone",
|
||||
SUPPORTS_VEVENT = "supportsVEVENT",
|
||||
SUPPORTS_VTODO = "supportsVTODO",
|
||||
SYNC = "sync";
|
||||
}
|
||||
|
||||
|
||||
public static class OpenHelper extends SQLiteOpenHelper {
|
||||
private static final String DATABASE_NAME = "services.db";
|
||||
private static final int DATABASE_VERSION = 1;
|
||||
|
||||
public OpenHelper(Context context) {
|
||||
super(context, DATABASE_NAME, null, DATABASE_VERSION);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOpen(SQLiteDatabase db) {
|
||||
if (Build.VERSION.SDK_INT < 16)
|
||||
db.execSQL("PRAGMA foreign_keys=ON;");
|
||||
}
|
||||
|
||||
@Override
|
||||
@RequiresApi(16)
|
||||
public void onConfigure(SQLiteDatabase db) {
|
||||
setWriteAheadLoggingEnabled(true);
|
||||
db.setForeignKeyConstraintsEnabled(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(SQLiteDatabase db) {
|
||||
App.log.info("Creating database " + db.getPath());
|
||||
|
||||
db.execSQL("CREATE TABLE " + Settings._TABLE + "(" +
|
||||
Settings.NAME + " TEXT NOT NULL," +
|
||||
Settings.VALUE + " TEXT NOT NULL" +
|
||||
")");
|
||||
db.execSQL("CREATE UNIQUE INDEX settings_name ON " + Settings._TABLE + " (" + Settings.NAME + ")");
|
||||
|
||||
db.execSQL("CREATE TABLE " + Services._TABLE + "(" +
|
||||
Services.ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
|
||||
Services.ACCOUNT_NAME + " TEXT NOT NULL," +
|
||||
Services.SERVICE + " TEXT NOT NULL," +
|
||||
Services.PRINCIPAL + " TEXT NULL" +
|
||||
")");
|
||||
db.execSQL("CREATE UNIQUE INDEX services_account ON " + Services._TABLE + " (" + Services.ACCOUNT_NAME + "," + Services.SERVICE + ")");
|
||||
|
||||
db.execSQL("CREATE TABLE " + HomeSets._TABLE + "(" +
|
||||
HomeSets.ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
|
||||
HomeSets.SERVICE_ID + " INTEGER NOT NULL REFERENCES " + Services._TABLE +" ON DELETE CASCADE," +
|
||||
HomeSets.URL + " TEXT NOT NULL" +
|
||||
")");
|
||||
db.execSQL("CREATE UNIQUE INDEX homesets_service_url ON " + HomeSets._TABLE + "(" + HomeSets.SERVICE_ID + "," + HomeSets.URL + ")");
|
||||
|
||||
db.execSQL("CREATE TABLE " + Collections._TABLE + "(" +
|
||||
Collections.ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
|
||||
Collections.SERVICE_ID + " INTEGER NOT NULL REFERENCES " + Services._TABLE +" ON DELETE CASCADE," +
|
||||
Collections.URL + " TEXT NOT NULL," +
|
||||
Collections.READ_ONLY + " INTEGER DEFAULT 0 NOT NULL," +
|
||||
Collections.DISPLAY_NAME + " TEXT NULL," +
|
||||
Collections.DESCRIPTION + " TEXT NULL," +
|
||||
Collections.COLOR + " INTEGER NULL," +
|
||||
Collections.TIME_ZONE + " TEXT NULL," +
|
||||
Collections.SUPPORTS_VEVENT + " INTEGER NULL," +
|
||||
Collections.SUPPORTS_VTODO + " INTEGER NULL," +
|
||||
Collections.SYNC + " INTEGER DEFAULT 0 NOT NULL" +
|
||||
")");
|
||||
db.execSQL("CREATE UNIQUE INDEX collections_service_url ON " + Collections._TABLE + "(" + Collections.SERVICE_ID + "," + Collections.URL + ")");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
||||
// no different versions yet
|
||||
}
|
||||
|
||||
|
||||
public void dump(StringBuilder sb) {
|
||||
SQLiteDatabase db = getReadableDatabase();
|
||||
db.beginTransactionNonExclusive();
|
||||
|
||||
// iterate through all tables
|
||||
@Cleanup Cursor cursorTables = db.query("sqlite_master", new String[] { "name" }, "type='table'", null, null, null, null);
|
||||
while (cursorTables.moveToNext()) {
|
||||
String table = cursorTables.getString(0);
|
||||
sb.append(table).append("\n");
|
||||
@Cleanup Cursor cursor = db.query(table, null, null, null, null, null, null);
|
||||
|
||||
// print columns
|
||||
int cols = cursor.getColumnCount();
|
||||
sb.append("\t| ");
|
||||
for (int i = 0; i < cols; i++) {
|
||||
sb.append(" ");
|
||||
sb.append(cursor.getColumnName(i));
|
||||
sb.append(" |");
|
||||
}
|
||||
sb.append("\n");
|
||||
|
||||
// print rows
|
||||
while (cursor.moveToNext()) {
|
||||
sb.append("\t| ");
|
||||
for (int i = 0; i < cols; i++) {
|
||||
sb.append(" ");
|
||||
try {
|
||||
String value = cursor.getString(i);
|
||||
if (value != null)
|
||||
sb.append(value
|
||||
.replace("\r", "<CR>")
|
||||
.replace("\n", "<LF>"));
|
||||
else
|
||||
sb.append("<null>");
|
||||
|
||||
} catch (SQLiteException e) {
|
||||
sb.append("<unprintable>");
|
||||
}
|
||||
sb.append(" |");
|
||||
}
|
||||
sb.append("\n");
|
||||
}
|
||||
sb.append("----------\n");
|
||||
}
|
||||
db.endTransaction();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
45
app/src/main/java/at/bitfire/davdroid/model/Settings.java
Normal file
45
app/src/main/java/at/bitfire/davdroid/model/Settings.java
Normal file
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright © 2013 – 2016 Ricki Hirner (bitfire web engineering).
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the GNU Public License v3.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.gnu.org/licenses/gpl.html
|
||||
*/
|
||||
|
||||
package at.bitfire.davdroid.model;
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
|
||||
import lombok.Cleanup;
|
||||
|
||||
public class Settings {
|
||||
|
||||
final SQLiteDatabase db;
|
||||
|
||||
public Settings(SQLiteDatabase db) {
|
||||
this.db = db;
|
||||
}
|
||||
|
||||
public boolean getBoolean(String name, boolean defaultValue) {
|
||||
@Cleanup Cursor cursor = db.query(ServiceDB.Settings._TABLE, new String[] { ServiceDB.Settings.VALUE },
|
||||
ServiceDB.Settings.NAME + "=?", new String[] { name }, null, null, null);
|
||||
if (cursor.moveToNext())
|
||||
return cursor.getInt(0) != 0;
|
||||
else
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
public void putBoolean(String name, boolean value) {
|
||||
ContentValues values = new ContentValues(2);
|
||||
values.put(ServiceDB.Settings.NAME, name);
|
||||
values.put(ServiceDB.Settings.VALUE, value ? 1 : 0);
|
||||
db.insertWithOnConflict(ServiceDB.Settings._TABLE, null, values, SQLiteDatabase.CONFLICT_REPLACE);
|
||||
}
|
||||
|
||||
public void remove(String name) {
|
||||
db.delete(ServiceDB.Settings._TABLE, ServiceDB.Settings.NAME + "=?", new String[] { name });
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright © 2013 – 2016 Ricki Hirner (bitfire web engineering).
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the GNU Public License v3.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.gnu.org/licenses/gpl.html
|
||||
*/
|
||||
|
||||
package at.bitfire.davdroid.model;
|
||||
|
||||
import android.provider.ContactsContract.RawContacts;
|
||||
|
||||
public class UnknownProperties {
|
||||
|
||||
public static final String CONTENT_ITEM_TYPE = "x.davdroid/unknown-properties";
|
||||
|
||||
public static final String
|
||||
MIMETYPE = RawContacts.Data.MIMETYPE,
|
||||
RAW_CONTACT_ID = RawContacts.Data.RAW_CONTACT_ID,
|
||||
UNKNOWN_PROPERTIES = RawContacts.Data.DATA1;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,229 @@
|
||||
/*
|
||||
* Copyright © 2013 – 2015 Ricki Hirner (bitfire web engineering).
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the GNU Public License v3.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.gnu.org/licenses/gpl.html
|
||||
*/
|
||||
package at.bitfire.davdroid.resource;
|
||||
|
||||
import android.accounts.Account;
|
||||
import android.content.ContentProviderClient;
|
||||
import android.content.ContentUris;
|
||||
import android.content.ContentValues;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcel;
|
||||
import android.os.RemoteException;
|
||||
import android.provider.ContactsContract.Groups;
|
||||
import android.provider.ContactsContract.RawContacts;
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import at.bitfire.vcard4android.AndroidAddressBook;
|
||||
import at.bitfire.vcard4android.AndroidContact;
|
||||
import at.bitfire.vcard4android.AndroidGroup;
|
||||
import at.bitfire.vcard4android.ContactsStorageException;
|
||||
import lombok.Cleanup;
|
||||
|
||||
|
||||
public class LocalAddressBook extends AndroidAddressBook implements LocalCollection {
|
||||
|
||||
protected static final String
|
||||
SYNC_STATE_CTAG = "ctag",
|
||||
SYNC_STATE_URL = "url";
|
||||
|
||||
private final Bundle syncState = new Bundle();
|
||||
|
||||
/**
|
||||
* Whether contact groups (LocalGroup resources) are included in query results for
|
||||
* {@link #getAll()}, {@link #getDeleted()}, {@link #getDirty()} and
|
||||
* {@link #getWithoutFileName()}.
|
||||
*/
|
||||
public boolean includeGroups = true;
|
||||
|
||||
|
||||
public LocalAddressBook(Account account, ContentProviderClient provider) {
|
||||
super(account, provider, LocalGroup.Factory.INSTANCE, LocalContact.Factory.INSTANCE);
|
||||
}
|
||||
|
||||
public LocalContact findContactByUID(String uid) throws ContactsStorageException, FileNotFoundException {
|
||||
LocalContact[] contacts = (LocalContact[])queryContacts(LocalContact.COLUMN_UID + "=?", new String[] { uid });
|
||||
if (contacts.length == 0)
|
||||
throw new FileNotFoundException();
|
||||
return contacts[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocalResource[] getAll() throws ContactsStorageException {
|
||||
List<LocalResource> all = new LinkedList<>();
|
||||
Collections.addAll(all, (LocalResource[])queryContacts(null, null));
|
||||
if (includeGroups)
|
||||
Collections.addAll(all, (LocalResource[])queryGroups(null, null));
|
||||
return all.toArray(new LocalResource[all.size()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of local contacts/groups which have been deleted locally. (DELETED != 0).
|
||||
*/
|
||||
@Override
|
||||
public LocalResource[] getDeleted() throws ContactsStorageException {
|
||||
List<LocalResource> deleted = new LinkedList<>();
|
||||
Collections.addAll(deleted, getDeletedContacts());
|
||||
if (includeGroups)
|
||||
Collections.addAll(deleted, getDeletedGroups());
|
||||
return deleted.toArray(new LocalResource[deleted.size()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of local contacts/groups which have been changed locally (DIRTY != 0).
|
||||
*/
|
||||
@Override
|
||||
public LocalResource[] getDirty() throws ContactsStorageException {
|
||||
List<LocalResource> dirty = new LinkedList<>();
|
||||
Collections.addAll(dirty, getDirtyContacts());
|
||||
if (includeGroups)
|
||||
Collections.addAll(dirty, getDirtyGroups());
|
||||
return dirty.toArray(new LocalResource[dirty.size()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of local contacts which don't have a file name yet.
|
||||
*/
|
||||
@Override
|
||||
public LocalResource[] getWithoutFileName() throws ContactsStorageException {
|
||||
List<LocalResource> nameless = new LinkedList<>();
|
||||
Collections.addAll(nameless, (LocalContact[])queryContacts(AndroidContact.COLUMN_FILENAME + " IS NULL", null));
|
||||
if (includeGroups)
|
||||
Collections.addAll(nameless, (LocalGroup[])queryGroups(AndroidGroup.COLUMN_FILENAME + " IS NULL", null));
|
||||
return nameless.toArray(new LocalResource[nameless.size()]);
|
||||
}
|
||||
|
||||
public void deleteAll() throws ContactsStorageException {
|
||||
try {
|
||||
provider.delete(syncAdapterURI(RawContacts.CONTENT_URI), null, null);
|
||||
provider.delete(syncAdapterURI(Groups.CONTENT_URI), null, null);
|
||||
} catch(RemoteException e) {
|
||||
throw new ContactsStorageException("Couldn't delete all local contacts and groups", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public LocalContact[] getDeletedContacts() throws ContactsStorageException {
|
||||
return (LocalContact[])queryContacts(RawContacts.DELETED + "!= 0", null);
|
||||
}
|
||||
|
||||
public LocalContact[] getDirtyContacts() throws ContactsStorageException {
|
||||
return (LocalContact[])queryContacts(RawContacts.DIRTY + "!= 0", null);
|
||||
}
|
||||
|
||||
public LocalGroup[] getDeletedGroups() throws ContactsStorageException {
|
||||
return (LocalGroup[])queryGroups(Groups.DELETED + "!= 0", null);
|
||||
}
|
||||
|
||||
public LocalGroup[] getDirtyGroups() throws ContactsStorageException {
|
||||
return (LocalGroup[])queryGroups(Groups.DIRTY + "!= 0", null);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Finds the first group with the given title. If there is no group with this
|
||||
* title, a new group is created.
|
||||
* @param title title of the group to look for
|
||||
* @return id of the group with given title
|
||||
* @throws ContactsStorageException on contact provider errors
|
||||
*/
|
||||
public long findOrCreateGroup(@NonNull String title) throws ContactsStorageException {
|
||||
try {
|
||||
@Cleanup Cursor cursor = provider.query(syncAdapterURI(Groups.CONTENT_URI),
|
||||
new String[] { Groups._ID },
|
||||
Groups.TITLE + "=?", new String[] { title },
|
||||
null);
|
||||
if (cursor != null && cursor.moveToNext())
|
||||
return cursor.getLong(0);
|
||||
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(Groups.TITLE, title);
|
||||
Uri uri = provider.insert(syncAdapterURI(Groups.CONTENT_URI), values);
|
||||
return ContentUris.parseId(uri);
|
||||
} catch(RemoteException e) {
|
||||
throw new ContactsStorageException("Couldn't find local contact group", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void removeEmptyGroups() throws ContactsStorageException {
|
||||
// find groups without members
|
||||
/** should be done using {@link Groups.SUMMARY_COUNT}, but it's not implemented in Android yet */
|
||||
for (LocalGroup group : (LocalGroup[])queryGroups(null, null))
|
||||
if (group.getMembers().length == 0)
|
||||
group.delete();
|
||||
}
|
||||
|
||||
public void removeGroups() throws ContactsStorageException {
|
||||
try {
|
||||
provider.delete(syncAdapterURI(Groups.CONTENT_URI), null, null);
|
||||
} catch(RemoteException e) {
|
||||
throw new ContactsStorageException("Couldn't remove all groups", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// SYNC STATE
|
||||
|
||||
@SuppressWarnings("ParcelClassLoader,Recycle")
|
||||
protected void readSyncState() throws ContactsStorageException {
|
||||
@Cleanup("recycle") Parcel parcel = Parcel.obtain();
|
||||
byte[] raw = getSyncState();
|
||||
syncState.clear();
|
||||
if (raw != null) {
|
||||
parcel.unmarshall(raw, 0, raw.length);
|
||||
parcel.setDataPosition(0);
|
||||
syncState.putAll(parcel.readBundle());
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("Recycle")
|
||||
protected void writeSyncState() throws ContactsStorageException {
|
||||
@Cleanup("recycle") Parcel parcel = Parcel.obtain();
|
||||
parcel.writeBundle(syncState);
|
||||
setSyncState(parcel.marshall());
|
||||
}
|
||||
|
||||
public String getURL() throws ContactsStorageException {
|
||||
synchronized (syncState) {
|
||||
readSyncState();
|
||||
return syncState.getString(SYNC_STATE_URL);
|
||||
}
|
||||
}
|
||||
|
||||
public void setURL(String url) throws ContactsStorageException {
|
||||
synchronized (syncState) {
|
||||
readSyncState();
|
||||
syncState.putString(SYNC_STATE_URL, url);
|
||||
writeSyncState();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCTag() throws ContactsStorageException {
|
||||
synchronized (syncState) {
|
||||
readSyncState();
|
||||
return syncState.getString(SYNC_STATE_CTAG);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCTag(String cTag) throws ContactsStorageException {
|
||||
synchronized (syncState) {
|
||||
readSyncState();
|
||||
syncState.putString(SYNC_STATE_CTAG, cTag);
|
||||
writeSyncState();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,254 @@
|
||||
/*
|
||||
* Copyright © 2013 – 2015 Ricki Hirner (bitfire web engineering).
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the GNU Public License v3.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.gnu.org/licenses/gpl.html
|
||||
*/
|
||||
|
||||
package at.bitfire.davdroid.resource;
|
||||
|
||||
import android.accounts.Account;
|
||||
import android.content.ContentProviderClient;
|
||||
import android.content.ContentProviderOperation;
|
||||
import android.content.ContentUris;
|
||||
import android.content.ContentValues;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.RemoteException;
|
||||
import android.provider.CalendarContract;
|
||||
import android.provider.CalendarContract.Calendars;
|
||||
import android.provider.CalendarContract.Events;
|
||||
import android.provider.CalendarContract.Reminders;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import net.fortuna.ical4j.model.component.VTimeZone;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import at.bitfire.davdroid.App;
|
||||
import at.bitfire.davdroid.DavUtils;
|
||||
import at.bitfire.davdroid.model.CollectionInfo;
|
||||
import at.bitfire.ical4android.AndroidCalendar;
|
||||
import at.bitfire.ical4android.AndroidCalendarFactory;
|
||||
import at.bitfire.ical4android.BatchOperation;
|
||||
import at.bitfire.ical4android.CalendarStorageException;
|
||||
import at.bitfire.ical4android.DateUtils;
|
||||
import at.bitfire.vcard4android.ContactsStorageException;
|
||||
import lombok.Cleanup;
|
||||
|
||||
public class LocalCalendar extends AndroidCalendar implements LocalCollection {
|
||||
|
||||
public static final int defaultColor = 0xFF8bc34a; // light green 500
|
||||
|
||||
public static final String COLUMN_CTAG = Calendars.CAL_SYNC1;
|
||||
|
||||
static String[] BASE_INFO_COLUMNS = new String[] {
|
||||
Events._ID,
|
||||
Events._SYNC_ID,
|
||||
LocalEvent.COLUMN_ETAG
|
||||
};
|
||||
|
||||
@Override
|
||||
protected String[] eventBaseInfoColumns() {
|
||||
return BASE_INFO_COLUMNS;
|
||||
}
|
||||
|
||||
|
||||
protected LocalCalendar(Account account, ContentProviderClient provider, long id) {
|
||||
super(account, provider, LocalEvent.Factory.INSTANCE, id);
|
||||
}
|
||||
|
||||
public static Uri create(@NonNull Account account, @NonNull ContentProviderClient provider, @NonNull CollectionInfo info) throws CalendarStorageException {
|
||||
ContentValues values = valuesFromCollectionInfo(info, true);
|
||||
|
||||
// ACCOUNT_NAME and ACCOUNT_TYPE are required (see docs)! If it's missing, other apps will crash.
|
||||
values.put(Calendars.ACCOUNT_NAME, account.name);
|
||||
values.put(Calendars.ACCOUNT_TYPE, account.type);
|
||||
values.put(Calendars.OWNER_ACCOUNT, account.name);
|
||||
|
||||
// flag as visible & synchronizable at creation, might be changed by user at any time
|
||||
values.put(Calendars.VISIBLE, 1);
|
||||
values.put(Calendars.SYNC_EVENTS, 1);
|
||||
|
||||
return create(account, provider, values);
|
||||
}
|
||||
|
||||
public void update(CollectionInfo info, boolean updateColor) throws CalendarStorageException {
|
||||
update(valuesFromCollectionInfo(info, updateColor));
|
||||
}
|
||||
|
||||
private static ContentValues valuesFromCollectionInfo(CollectionInfo info, boolean withColor) {
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(Calendars.NAME, info.url);
|
||||
values.put(Calendars.CALENDAR_DISPLAY_NAME, !TextUtils.isEmpty(info.displayName) ? info.displayName : DavUtils.lastSegmentOfUrl(info.url));
|
||||
|
||||
if (withColor)
|
||||
values.put(Calendars.CALENDAR_COLOR, info.color != null ? info.color : defaultColor);
|
||||
|
||||
if (info.readOnly)
|
||||
values.put(Calendars.CALENDAR_ACCESS_LEVEL, Calendars.CAL_ACCESS_READ);
|
||||
else {
|
||||
values.put(Calendars.CALENDAR_ACCESS_LEVEL, Calendars.CAL_ACCESS_OWNER);
|
||||
values.put(Calendars.CAN_MODIFY_TIME_ZONE, 1);
|
||||
values.put(Calendars.CAN_ORGANIZER_RESPOND, 1);
|
||||
}
|
||||
|
||||
if (!TextUtils.isEmpty(info.timeZone)) {
|
||||
VTimeZone timeZone = DateUtils.parseVTimeZone(info.timeZone);
|
||||
if (timeZone != null && timeZone.getTimeZoneId() != null)
|
||||
values.put(Calendars.CALENDAR_TIME_ZONE, DateUtils.findAndroidTimezoneID(timeZone.getTimeZoneId().getValue()));
|
||||
}
|
||||
values.put(Calendars.ALLOWED_REMINDERS, Reminders.METHOD_ALERT);
|
||||
values.put(Calendars.ALLOWED_AVAILABILITY, StringUtils.join(new int[] { Reminders.AVAILABILITY_TENTATIVE, Reminders.AVAILABILITY_FREE, Reminders.AVAILABILITY_BUSY }, ","));
|
||||
values.put(Calendars.ALLOWED_ATTENDEE_TYPES, StringUtils.join(new int[] { CalendarContract.Attendees.TYPE_OPTIONAL, CalendarContract.Attendees.TYPE_REQUIRED, CalendarContract.Attendees.TYPE_RESOURCE }, ", "));
|
||||
return values;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public LocalResource[] getAll() throws CalendarStorageException, ContactsStorageException {
|
||||
return (LocalEvent[])queryEvents(Events.ORIGINAL_ID + " IS NULL", null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocalEvent[] getDeleted() throws CalendarStorageException {
|
||||
return (LocalEvent[])queryEvents(Events.DELETED + "!=0 AND " + Events.ORIGINAL_ID + " IS NULL", null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocalEvent[] getWithoutFileName() throws CalendarStorageException {
|
||||
return (LocalEvent[])queryEvents(Events._SYNC_ID + " IS NULL AND " + Events.ORIGINAL_ID + " IS NULL", null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocalResource[] getDirty() throws CalendarStorageException, FileNotFoundException {
|
||||
List<LocalResource> dirty = new LinkedList<>();
|
||||
|
||||
// get dirty events which are required to have an increased SEQUENCE value
|
||||
for (LocalEvent event : (LocalEvent[])queryEvents(Events.DIRTY + "!=0 AND " + Events.ORIGINAL_ID + " IS NULL", null)) {
|
||||
if (event.getEvent().sequence == null) // sequence has not been assigned yet (i.e. this event was just locally created)
|
||||
event.getEvent().sequence = 0;
|
||||
else if (event.weAreOrganizer)
|
||||
event.getEvent().sequence++;
|
||||
dirty.add(event);
|
||||
}
|
||||
|
||||
return dirty.toArray(new LocalResource[dirty.size()]);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("Recycle")
|
||||
public String getCTag() throws CalendarStorageException {
|
||||
try {
|
||||
@Cleanup Cursor cursor = provider.query(calendarSyncURI(), new String[] { COLUMN_CTAG }, null, null, null);
|
||||
if (cursor != null && cursor.moveToNext())
|
||||
return cursor.getString(0);
|
||||
} catch (RemoteException e) {
|
||||
throw new CalendarStorageException("Couldn't read local (last known) CTag", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCTag(String cTag) throws CalendarStorageException, ContactsStorageException {
|
||||
try {
|
||||
ContentValues values = new ContentValues(1);
|
||||
values.put(COLUMN_CTAG, cTag);
|
||||
provider.update(calendarSyncURI(), values, null, null);
|
||||
} catch (RemoteException e) {
|
||||
throw new CalendarStorageException("Couldn't write local (last known) CTag", e);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("Recycle")
|
||||
public void processDirtyExceptions() throws CalendarStorageException {
|
||||
// process deleted exceptions
|
||||
App.log.info("Processing deleted exceptions");
|
||||
try {
|
||||
@Cleanup Cursor cursor = provider.query(
|
||||
syncAdapterURI(Events.CONTENT_URI),
|
||||
new String[] { Events._ID, Events.ORIGINAL_ID, LocalEvent.COLUMN_SEQUENCE },
|
||||
Events.DELETED + "!=0 AND " + Events.ORIGINAL_ID + " IS NOT NULL", null, null);
|
||||
while (cursor != null && cursor.moveToNext()) {
|
||||
App.log.fine("Found deleted exception, removing; then re-schuling original event");
|
||||
long id = cursor.getLong(0), // can't be null (by definition)
|
||||
originalID = cursor.getLong(1); // can't be null (by query)
|
||||
|
||||
// get original event's SEQUENCE
|
||||
@Cleanup Cursor cursor2 = provider.query(
|
||||
syncAdapterURI(ContentUris.withAppendedId(Events.CONTENT_URI, originalID)),
|
||||
new String[] { LocalEvent.COLUMN_SEQUENCE },
|
||||
null, null, null);
|
||||
int originalSequence = (cursor2 == null || cursor2.isNull(0)) ? 0 : cursor2.getInt(0);
|
||||
|
||||
BatchOperation batch = new BatchOperation(provider);
|
||||
// re-schedule original event and set it to DIRTY
|
||||
batch.enqueue(new BatchOperation.Operation(
|
||||
ContentProviderOperation.newUpdate(syncAdapterURI(ContentUris.withAppendedId(Events.CONTENT_URI, originalID)))
|
||||
.withValue(LocalEvent.COLUMN_SEQUENCE, originalSequence + 1)
|
||||
.withValue(Events.DIRTY, 1)
|
||||
));
|
||||
// remove exception
|
||||
batch.enqueue(new BatchOperation.Operation(
|
||||
ContentProviderOperation.newDelete(syncAdapterURI(ContentUris.withAppendedId(Events.CONTENT_URI, id)))
|
||||
));
|
||||
batch.commit();
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
throw new CalendarStorageException("Couldn't process locally modified exception", e);
|
||||
}
|
||||
|
||||
// process dirty exceptions
|
||||
App.log.info("Processing dirty exceptions");
|
||||
try {
|
||||
@Cleanup Cursor cursor = provider.query(
|
||||
syncAdapterURI(Events.CONTENT_URI),
|
||||
new String[] { Events._ID, Events.ORIGINAL_ID, LocalEvent.COLUMN_SEQUENCE },
|
||||
Events.DIRTY + "!=0 AND " + Events.ORIGINAL_ID + " IS NOT NULL", null, null);
|
||||
while (cursor != null && cursor.moveToNext()) {
|
||||
App.log.fine("Found dirty exception, increasing SEQUENCE to re-schedule");
|
||||
long id = cursor.getLong(0), // can't be null (by definition)
|
||||
originalID = cursor.getLong(1); // can't be null (by query)
|
||||
int sequence = cursor.isNull(2) ? 0 : cursor.getInt(2);
|
||||
|
||||
BatchOperation batch = new BatchOperation(provider);
|
||||
// original event to DIRTY
|
||||
batch.enqueue(new BatchOperation.Operation(
|
||||
ContentProviderOperation.newUpdate(syncAdapterURI(ContentUris.withAppendedId(Events.CONTENT_URI, originalID)))
|
||||
.withValue(Events.DIRTY, 1)
|
||||
));
|
||||
// increase SEQUENCE and set DIRTY to 0
|
||||
batch.enqueue(new BatchOperation.Operation(
|
||||
ContentProviderOperation.newUpdate(syncAdapterURI(ContentUris.withAppendedId(Events.CONTENT_URI, id)))
|
||||
.withValue(LocalEvent.COLUMN_SEQUENCE, sequence + 1)
|
||||
.withValue(Events.DIRTY, 0)
|
||||
));
|
||||
batch.commit();
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
throw new CalendarStorageException("Couldn't process locally modified exception", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static class Factory implements AndroidCalendarFactory {
|
||||
public static final Factory INSTANCE = new Factory();
|
||||
|
||||
@Override
|
||||
public AndroidCalendar newInstance(Account account, ContentProviderClient provider, long id) {
|
||||
return new LocalCalendar(account, provider, id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AndroidCalendar[] newArray(int size) {
|
||||
return new LocalCalendar[size];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright © 2013 – 2015 Ricki Hirner (bitfire web engineering).
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the GNU Public License v3.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.gnu.org/licenses/gpl.html
|
||||
*/
|
||||
|
||||
package at.bitfire.davdroid.resource;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
|
||||
import at.bitfire.ical4android.CalendarStorageException;
|
||||
import at.bitfire.vcard4android.ContactsStorageException;
|
||||
|
||||
public interface LocalCollection {
|
||||
|
||||
LocalResource[] getDeleted() throws CalendarStorageException, ContactsStorageException;
|
||||
LocalResource[] getWithoutFileName() throws CalendarStorageException, ContactsStorageException;
|
||||
LocalResource[] getDirty() throws CalendarStorageException, ContactsStorageException, FileNotFoundException;
|
||||
|
||||
LocalResource[] getAll() throws CalendarStorageException, ContactsStorageException;
|
||||
|
||||
String getCTag() throws CalendarStorageException, ContactsStorageException;
|
||||
void setCTag(String cTag) throws CalendarStorageException, ContactsStorageException;
|
||||
}
|
||||
196
app/src/main/java/at/bitfire/davdroid/resource/LocalContact.java
Normal file
196
app/src/main/java/at/bitfire/davdroid/resource/LocalContact.java
Normal file
@@ -0,0 +1,196 @@
|
||||
/*
|
||||
* Copyright © 2013 – 2015 Ricki Hirner (bitfire web engineering).
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the GNU Public License v3.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.gnu.org/licenses/gpl.html
|
||||
*/
|
||||
|
||||
package at.bitfire.davdroid.resource;
|
||||
|
||||
import android.content.ContentProviderOperation;
|
||||
import android.content.ContentValues;
|
||||
import android.os.RemoteException;
|
||||
import android.provider.ContactsContract;
|
||||
import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
|
||||
import android.provider.ContactsContract.RawContacts.Data;
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import at.bitfire.davdroid.BuildConfig;
|
||||
import at.bitfire.davdroid.model.UnknownProperties;
|
||||
import at.bitfire.vcard4android.AndroidAddressBook;
|
||||
import at.bitfire.vcard4android.AndroidContact;
|
||||
import at.bitfire.vcard4android.AndroidContactFactory;
|
||||
import at.bitfire.vcard4android.BatchOperation;
|
||||
import at.bitfire.vcard4android.CachedGroupMembership;
|
||||
import at.bitfire.vcard4android.Contact;
|
||||
import at.bitfire.vcard4android.ContactsStorageException;
|
||||
import ezvcard.Ezvcard;
|
||||
|
||||
public class LocalContact extends AndroidContact implements LocalResource {
|
||||
static {
|
||||
Contact.productID = "+//IDN bitfire.at//DAVdroid/" + BuildConfig.VERSION_NAME + " vcard4android ez-vcard/" + Ezvcard.VERSION;
|
||||
}
|
||||
|
||||
protected final Set<Long>
|
||||
cachedGroupMemberships = new HashSet<>(),
|
||||
groupMemberships = new HashSet<>();
|
||||
|
||||
|
||||
protected LocalContact(AndroidAddressBook addressBook, long id, String fileName, String eTag) {
|
||||
super(addressBook, id, fileName, eTag);
|
||||
}
|
||||
|
||||
public LocalContact(AndroidAddressBook addressBook, Contact contact, String fileName, String eTag) {
|
||||
super(addressBook, contact, fileName, eTag);
|
||||
}
|
||||
|
||||
public void clearDirty(String eTag) throws ContactsStorageException {
|
||||
try {
|
||||
ContentValues values = new ContentValues(1);
|
||||
values.put(ContactsContract.RawContacts.DIRTY, 0);
|
||||
values.put(COLUMN_ETAG, eTag);
|
||||
addressBook.provider.update(rawContactSyncURI(), values, null, null);
|
||||
|
||||
this.eTag = eTag;
|
||||
} catch (RemoteException e) {
|
||||
throw new ContactsStorageException("Couldn't clear dirty flag", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void updateFileNameAndUID(String uid) throws ContactsStorageException {
|
||||
try {
|
||||
String newFileName = uid + ".vcf";
|
||||
|
||||
ContentValues values = new ContentValues(2);
|
||||
values.put(COLUMN_FILENAME, newFileName);
|
||||
values.put(COLUMN_UID, uid);
|
||||
addressBook.provider.update(rawContactSyncURI(), values, null, null);
|
||||
|
||||
fileName = newFileName;
|
||||
} catch (RemoteException e) {
|
||||
throw new ContactsStorageException("Couldn't update UID", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void populateData(String mimeType, ContentValues row) {
|
||||
switch (mimeType) {
|
||||
case CachedGroupMembership.CONTENT_ITEM_TYPE:
|
||||
cachedGroupMemberships.add(row.getAsLong(CachedGroupMembership.GROUP_ID));
|
||||
break;
|
||||
case GroupMembership.CONTENT_ITEM_TYPE:
|
||||
groupMemberships.add(row.getAsLong(GroupMembership.GROUP_ROW_ID));
|
||||
break;
|
||||
case UnknownProperties.CONTENT_ITEM_TYPE:
|
||||
contact.unknownProperties = row.getAsString(UnknownProperties.UNKNOWN_PROPERTIES);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void insertDataRows(BatchOperation batch) throws ContactsStorageException {
|
||||
super.insertDataRows(batch);
|
||||
|
||||
if (contact.unknownProperties != null) {
|
||||
final BatchOperation.Operation op;
|
||||
final ContentProviderOperation.Builder builder = ContentProviderOperation.newInsert(dataSyncURI());
|
||||
if (id == null) {
|
||||
op = new BatchOperation.Operation(builder, UnknownProperties.RAW_CONTACT_ID, 0);
|
||||
} else {
|
||||
op = new BatchOperation.Operation(builder);
|
||||
builder.withValue(UnknownProperties.RAW_CONTACT_ID, id);
|
||||
}
|
||||
builder .withValue(UnknownProperties.MIMETYPE, UnknownProperties.CONTENT_ITEM_TYPE)
|
||||
.withValue(UnknownProperties.UNKNOWN_PROPERTIES, contact.unknownProperties);
|
||||
batch.enqueue(op);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
public void addToGroup(BatchOperation batch, long groupID) {
|
||||
assertID();
|
||||
batch.enqueue(new BatchOperation.Operation(
|
||||
ContentProviderOperation.newInsert(dataSyncURI())
|
||||
.withValue(GroupMembership.MIMETYPE, GroupMembership.CONTENT_ITEM_TYPE)
|
||||
.withValue(GroupMembership.RAW_CONTACT_ID, id)
|
||||
.withValue(GroupMembership.GROUP_ROW_ID, groupID)
|
||||
));
|
||||
|
||||
batch.enqueue(new BatchOperation.Operation(
|
||||
ContentProviderOperation.newInsert(dataSyncURI())
|
||||
.withValue(CachedGroupMembership.MIMETYPE, CachedGroupMembership.CONTENT_ITEM_TYPE)
|
||||
.withValue(CachedGroupMembership.RAW_CONTACT_ID, id)
|
||||
.withValue(CachedGroupMembership.GROUP_ID, groupID)
|
||||
.withYieldAllowed(true)
|
||||
));
|
||||
}
|
||||
|
||||
public void removeGroupMemberships(BatchOperation batch) {
|
||||
assertID();
|
||||
batch.enqueue(new BatchOperation.Operation(
|
||||
ContentProviderOperation.newDelete(dataSyncURI())
|
||||
.withSelection(
|
||||
Data.RAW_CONTACT_ID + "=? AND " + Data.MIMETYPE + " IN (?,?)",
|
||||
new String[] { String.valueOf(id), GroupMembership.CONTENT_ITEM_TYPE, CachedGroupMembership.CONTENT_ITEM_TYPE }
|
||||
)
|
||||
.withYieldAllowed(true)
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the IDs of all groups the contact was member of (cached memberships).
|
||||
* Cached memberships are kept in sync with memberships by DAVdroid and are used to determine
|
||||
* whether a membership has been deleted/added when a raw contact is dirty.
|
||||
* @return set of {@link GroupMembership#GROUP_ROW_ID} (may be empty)
|
||||
* @throws ContactsStorageException on contact provider errors
|
||||
* @throws FileNotFoundException if the current contact can't be found
|
||||
*/
|
||||
@NonNull
|
||||
public Set<Long> getCachedGroupMemberships() throws ContactsStorageException, FileNotFoundException {
|
||||
getContact();
|
||||
return cachedGroupMemberships;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the IDs of all groups the contact is member of.
|
||||
* @return set of {@link GroupMembership#GROUP_ROW_ID}s (may be empty)
|
||||
* @throws ContactsStorageException on contact provider errors
|
||||
* @throws FileNotFoundException if the current contact can't be found
|
||||
*/
|
||||
@NonNull
|
||||
public Set<Long> getGroupMemberships() throws ContactsStorageException, FileNotFoundException {
|
||||
getContact();
|
||||
return groupMemberships;
|
||||
}
|
||||
|
||||
|
||||
// factory
|
||||
|
||||
static class Factory extends AndroidContactFactory {
|
||||
static final Factory INSTANCE = new Factory();
|
||||
|
||||
@Override
|
||||
public LocalContact newInstance(AndroidAddressBook addressBook, long id, String fileName, String eTag) {
|
||||
return new LocalContact(addressBook, id, fileName, eTag);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocalContact newInstance(AndroidAddressBook addressBook, Contact contact, String fileName, String eTag) {
|
||||
return new LocalContact(addressBook, contact, fileName, eTag);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocalContact[] newArray(int size) {
|
||||
return new LocalContact[size];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
154
app/src/main/java/at/bitfire/davdroid/resource/LocalEvent.java
Normal file
154
app/src/main/java/at/bitfire/davdroid/resource/LocalEvent.java
Normal file
@@ -0,0 +1,154 @@
|
||||
/*
|
||||
* Copyright © 2013 – 2015 Ricki Hirner (bitfire web engineering).
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the GNU Public License v3.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.gnu.org/licenses/gpl.html
|
||||
*/
|
||||
|
||||
package at.bitfire.davdroid.resource;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.ContentProviderOperation;
|
||||
import android.content.ContentValues;
|
||||
import android.os.Build;
|
||||
import android.os.RemoteException;
|
||||
import android.provider.CalendarContract;
|
||||
import android.provider.CalendarContract.Events;
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import net.fortuna.ical4j.model.property.ProdId;
|
||||
|
||||
import at.bitfire.davdroid.BuildConfig;
|
||||
import at.bitfire.ical4android.AndroidCalendar;
|
||||
import at.bitfire.ical4android.AndroidEvent;
|
||||
import at.bitfire.ical4android.AndroidEventFactory;
|
||||
import at.bitfire.ical4android.CalendarStorageException;
|
||||
import at.bitfire.ical4android.Event;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@TargetApi(17)
|
||||
public class LocalEvent extends AndroidEvent implements LocalResource {
|
||||
static {
|
||||
Event.prodId = new ProdId("+//IDN bitfire.at//DAVdroid/" + BuildConfig.VERSION_NAME + " ical4android ical4j/2.x");
|
||||
}
|
||||
|
||||
static final String COLUMN_ETAG = CalendarContract.Events.SYNC_DATA1,
|
||||
COLUMN_UID = Build.VERSION.SDK_INT >= 17 ? Events.UID_2445 : Events.SYNC_DATA2,
|
||||
COLUMN_SEQUENCE = CalendarContract.Events.SYNC_DATA3;
|
||||
|
||||
@Getter protected String fileName;
|
||||
@Getter @Setter protected String eTag;
|
||||
|
||||
public boolean weAreOrganizer = true;
|
||||
|
||||
public LocalEvent(@NonNull AndroidCalendar calendar, Event event, String fileName, String eTag) {
|
||||
super(calendar, event);
|
||||
this.fileName = fileName;
|
||||
this.eTag = eTag;
|
||||
}
|
||||
|
||||
protected LocalEvent(@NonNull AndroidCalendar calendar, long id, ContentValues baseInfo) {
|
||||
super(calendar, id, baseInfo);
|
||||
if (baseInfo != null) {
|
||||
fileName = baseInfo.getAsString(Events._SYNC_ID);
|
||||
eTag = baseInfo.getAsString(COLUMN_ETAG);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* process LocalEvent-specific fields */
|
||||
|
||||
@Override
|
||||
protected void populateEvent(ContentValues values) {
|
||||
super.populateEvent(values);
|
||||
fileName = values.getAsString(Events._SYNC_ID);
|
||||
eTag = values.getAsString(COLUMN_ETAG);
|
||||
event.uid = values.getAsString(COLUMN_UID);
|
||||
|
||||
event.sequence = values.getAsInteger(COLUMN_SEQUENCE);
|
||||
if (Build.VERSION.SDK_INT >= 17)
|
||||
weAreOrganizer = values.getAsInteger(Events.IS_ORGANIZER) != 0;
|
||||
else {
|
||||
String organizer = values.getAsString(Events.ORGANIZER);
|
||||
weAreOrganizer = organizer == null || organizer.equals(calendar.account.name);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void buildEvent(Event recurrence, ContentProviderOperation.Builder builder) {
|
||||
super.buildEvent(recurrence, builder);
|
||||
|
||||
boolean buildException = recurrence != null;
|
||||
Event eventToBuild = buildException ? recurrence : event;
|
||||
|
||||
builder .withValue(COLUMN_UID, event.uid)
|
||||
.withValue(COLUMN_SEQUENCE, eventToBuild.sequence)
|
||||
.withValue(CalendarContract.Events.DIRTY, 0)
|
||||
.withValue(CalendarContract.Events.DELETED, 0);
|
||||
|
||||
if (buildException)
|
||||
builder.withValue(Events.ORIGINAL_SYNC_ID, fileName);
|
||||
else
|
||||
builder .withValue(Events._SYNC_ID, fileName)
|
||||
.withValue(COLUMN_ETAG, eTag);
|
||||
}
|
||||
|
||||
|
||||
/* custom queries */
|
||||
|
||||
public void updateFileNameAndUID(String uid) throws CalendarStorageException {
|
||||
try {
|
||||
String newFileName = uid + ".ics";
|
||||
|
||||
ContentValues values = new ContentValues(2);
|
||||
values.put(Events._SYNC_ID, newFileName);
|
||||
values.put(COLUMN_UID, uid);
|
||||
calendar.provider.update(eventSyncURI(), values, null, null);
|
||||
|
||||
fileName = newFileName;
|
||||
if (event != null)
|
||||
event.uid = uid;
|
||||
|
||||
} catch (RemoteException e) {
|
||||
throw new CalendarStorageException("Couldn't update UID", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearDirty(String eTag) throws CalendarStorageException {
|
||||
try {
|
||||
ContentValues values = new ContentValues(2);
|
||||
values.put(CalendarContract.Events.DIRTY, 0);
|
||||
values.put(COLUMN_ETAG, eTag);
|
||||
if (event != null)
|
||||
values.put(COLUMN_SEQUENCE, event.sequence);
|
||||
calendar.provider.update(eventSyncURI(), values, null, null);
|
||||
|
||||
this.eTag = eTag;
|
||||
} catch (RemoteException e) {
|
||||
throw new CalendarStorageException("Couldn't update UID", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static class Factory implements AndroidEventFactory {
|
||||
static final Factory INSTANCE = new Factory();
|
||||
|
||||
@Override
|
||||
public AndroidEvent newInstance(AndroidCalendar calendar, long id, ContentValues baseInfo) {
|
||||
return new LocalEvent(calendar, id, baseInfo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AndroidEvent newInstance(AndroidCalendar calendar, Event event) {
|
||||
return new LocalEvent(calendar, event, null, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AndroidEvent[] newArray(int size) {
|
||||
return new LocalEvent[size];
|
||||
}
|
||||
}
|
||||
}
|
||||
248
app/src/main/java/at/bitfire/davdroid/resource/LocalGroup.java
Normal file
248
app/src/main/java/at/bitfire/davdroid/resource/LocalGroup.java
Normal file
@@ -0,0 +1,248 @@
|
||||
/*
|
||||
* Copyright © 2013 – 2015 Ricki Hirner (bitfire web engineering).
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the GNU Public License v3.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.gnu.org/licenses/gpl.html
|
||||
*/
|
||||
|
||||
package at.bitfire.davdroid.resource;
|
||||
|
||||
import android.content.ContentProviderOperation;
|
||||
import android.content.ContentUris;
|
||||
import android.content.ContentValues;
|
||||
import android.database.Cursor;
|
||||
import android.os.Parcel;
|
||||
import android.os.RemoteException;
|
||||
import android.provider.ContactsContract;
|
||||
import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
|
||||
import android.provider.ContactsContract.Groups;
|
||||
import android.provider.ContactsContract.RawContacts;
|
||||
import android.provider.ContactsContract.RawContacts.Data;
|
||||
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import at.bitfire.dav4android.Constants;
|
||||
import at.bitfire.vcard4android.AndroidAddressBook;
|
||||
import at.bitfire.vcard4android.AndroidGroup;
|
||||
import at.bitfire.vcard4android.AndroidGroupFactory;
|
||||
import at.bitfire.vcard4android.BatchOperation;
|
||||
import at.bitfire.vcard4android.CachedGroupMembership;
|
||||
import at.bitfire.vcard4android.Contact;
|
||||
import at.bitfire.vcard4android.ContactsStorageException;
|
||||
import lombok.Cleanup;
|
||||
import lombok.ToString;
|
||||
|
||||
@ToString(callSuper=true)
|
||||
public class LocalGroup extends AndroidGroup implements LocalResource {
|
||||
/** marshalled list of member UIDs, as sent by server */
|
||||
public static final String COLUMN_PENDING_MEMBERS = Groups.SYNC3;
|
||||
|
||||
public LocalGroup(AndroidAddressBook addressBook, long id, String fileName, String eTag) {
|
||||
super(addressBook, id, fileName, eTag);
|
||||
}
|
||||
|
||||
public LocalGroup(AndroidAddressBook addressBook, Contact contact, String fileName, String eTag) {
|
||||
super(addressBook, contact, fileName, eTag);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void clearDirty(String eTag) throws ContactsStorageException {
|
||||
assertID();
|
||||
|
||||
ContentValues values = new ContentValues(2);
|
||||
values.put(Groups.DIRTY, 0);
|
||||
values.put(COLUMN_ETAG, this.eTag = eTag);
|
||||
update(values);
|
||||
|
||||
// update cached group memberships
|
||||
BatchOperation batch = new BatchOperation(addressBook.provider);
|
||||
|
||||
// delete cached group memberships
|
||||
batch.enqueue(new BatchOperation.Operation(
|
||||
ContentProviderOperation.newDelete(addressBook.syncAdapterURI(ContactsContract.Data.CONTENT_URI))
|
||||
.withSelection(
|
||||
CachedGroupMembership.MIMETYPE + "=? AND " + CachedGroupMembership.GROUP_ID + "=?",
|
||||
new String[] { CachedGroupMembership.CONTENT_ITEM_TYPE, String.valueOf(id) }
|
||||
)
|
||||
));
|
||||
|
||||
// insert updated cached group memberships
|
||||
for (long member : getMembers())
|
||||
batch.enqueue(new BatchOperation.Operation(
|
||||
ContentProviderOperation.newInsert(addressBook.syncAdapterURI(ContactsContract.Data.CONTENT_URI))
|
||||
.withValue(CachedGroupMembership.MIMETYPE, CachedGroupMembership.CONTENT_ITEM_TYPE)
|
||||
.withValue(CachedGroupMembership.RAW_CONTACT_ID, member)
|
||||
.withValue(CachedGroupMembership.GROUP_ID, id)
|
||||
.withYieldAllowed(true)
|
||||
));
|
||||
|
||||
batch.commit();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateFileNameAndUID(String uid) throws ContactsStorageException {
|
||||
String newFileName = uid + ".vcf";
|
||||
|
||||
ContentValues values = new ContentValues(2);
|
||||
values.put(COLUMN_FILENAME, newFileName);
|
||||
values.put(COLUMN_UID, uid);
|
||||
update(values);
|
||||
|
||||
fileName = newFileName;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ContentValues contentValues() {
|
||||
ContentValues values = super.contentValues();
|
||||
|
||||
@Cleanup("recycle") Parcel members = Parcel.obtain();
|
||||
members.writeStringList(contact.members);
|
||||
values.put(COLUMN_PENDING_MEMBERS, members.marshall());
|
||||
|
||||
return values;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Marks all members of the current group as dirty.
|
||||
*/
|
||||
public void markMembersDirty() throws ContactsStorageException {
|
||||
assertID();
|
||||
BatchOperation batch = new BatchOperation(addressBook.provider);
|
||||
|
||||
for (long member : getMembers())
|
||||
batch.enqueue(new BatchOperation.Operation(
|
||||
ContentProviderOperation.newUpdate(addressBook.syncAdapterURI(ContentUris.withAppendedId(RawContacts.CONTENT_URI, member)))
|
||||
.withValue(RawContacts.DIRTY, 1)
|
||||
.withYieldAllowed(true)
|
||||
));
|
||||
|
||||
batch.commit();
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes all groups with non-null {@link #COLUMN_PENDING_MEMBERS}: the pending memberships
|
||||
* are (if possible) applied, keeping cached memberships in sync.
|
||||
* @param addressBook address book to take groups from
|
||||
* @throws ContactsStorageException on contact provider errors
|
||||
*/
|
||||
public static void applyPendingMemberships(LocalAddressBook addressBook) throws ContactsStorageException {
|
||||
try {
|
||||
@Cleanup Cursor cursor = addressBook.provider.query(
|
||||
addressBook.syncAdapterURI(Groups.CONTENT_URI),
|
||||
new String[] { Groups._ID, COLUMN_PENDING_MEMBERS },
|
||||
COLUMN_PENDING_MEMBERS + " IS NOT NULL", new String[] {},
|
||||
null
|
||||
);
|
||||
|
||||
BatchOperation batch = new BatchOperation(addressBook.provider);
|
||||
while (cursor != null && cursor.moveToNext()) {
|
||||
long id = cursor.getLong(0);
|
||||
Constants.log.fine("Assigning members to group " + id);
|
||||
|
||||
// delete all memberships and cached memberships for this group
|
||||
batch.enqueue(new BatchOperation.Operation(
|
||||
ContentProviderOperation.newDelete(addressBook.syncAdapterURI(ContactsContract.Data.CONTENT_URI))
|
||||
.withSelection(
|
||||
"(" + GroupMembership.MIMETYPE + "=? AND " + GroupMembership.GROUP_ROW_ID + "=?) OR (" +
|
||||
CachedGroupMembership.MIMETYPE + "=? AND " + CachedGroupMembership.GROUP_ID + "=?)",
|
||||
new String[] { GroupMembership.CONTENT_ITEM_TYPE, String.valueOf(id), CachedGroupMembership.CONTENT_ITEM_TYPE, String.valueOf(id) })
|
||||
.withYieldAllowed(true)
|
||||
));
|
||||
|
||||
// extract list of member UIDs
|
||||
List<String> members = new LinkedList<>();
|
||||
byte[] raw = cursor.getBlob(1);
|
||||
@Cleanup("recycle") Parcel parcel = Parcel.obtain();
|
||||
parcel.unmarshall(raw, 0, raw.length);
|
||||
parcel.setDataPosition(0);
|
||||
parcel.readStringList(members);
|
||||
|
||||
// insert memberships
|
||||
for (String uid : members) {
|
||||
Constants.log.fine("Assigning member: " + uid);
|
||||
try {
|
||||
LocalContact member = addressBook.findContactByUID(uid);
|
||||
member.addToGroup(batch, id);
|
||||
} catch(FileNotFoundException e) {
|
||||
Constants.log.log(Level.WARNING, "Group member not found: " + uid, e);
|
||||
}
|
||||
}
|
||||
|
||||
// remove pending memberships
|
||||
batch.enqueue(new BatchOperation.Operation(
|
||||
ContentProviderOperation.newUpdate(addressBook.syncAdapterURI(ContentUris.withAppendedId(Groups.CONTENT_URI, id)))
|
||||
.withValue(COLUMN_PENDING_MEMBERS, null)
|
||||
.withYieldAllowed(true)
|
||||
));
|
||||
|
||||
batch.commit();
|
||||
}
|
||||
} catch(RemoteException e) {
|
||||
throw new ContactsStorageException("Couldn't get pending memberships", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// helpers
|
||||
|
||||
private void assertID() {
|
||||
if (id == null)
|
||||
throw new IllegalStateException("Group has not been saved yet");
|
||||
}
|
||||
|
||||
/**
|
||||
* Lists all members of this group.
|
||||
* @return list of all members' raw contact IDs
|
||||
* @throws ContactsStorageException on contact provider errors
|
||||
*/
|
||||
protected long[] getMembers() throws ContactsStorageException {
|
||||
assertID();
|
||||
List<Long> members = new LinkedList<>();
|
||||
try {
|
||||
@Cleanup Cursor cursor = addressBook.provider.query(
|
||||
addressBook.syncAdapterURI(ContactsContract.Data.CONTENT_URI),
|
||||
new String[] { Data.RAW_CONTACT_ID },
|
||||
GroupMembership.MIMETYPE + "=? AND " + GroupMembership.GROUP_ROW_ID + "=?",
|
||||
new String[] { GroupMembership.CONTENT_ITEM_TYPE, String.valueOf(id) },
|
||||
null
|
||||
);
|
||||
while (cursor != null && cursor.moveToNext())
|
||||
members.add(cursor.getLong(0));
|
||||
} catch(RemoteException e) {
|
||||
throw new ContactsStorageException("Couldn't list group members", e);
|
||||
}
|
||||
return ArrayUtils.toPrimitive(members.toArray(new Long[members.size()]));
|
||||
}
|
||||
|
||||
|
||||
// factory
|
||||
|
||||
static class Factory extends AndroidGroupFactory {
|
||||
static final Factory INSTANCE = new Factory();
|
||||
|
||||
@Override
|
||||
public LocalGroup newInstance(AndroidAddressBook addressBook, long id, String fileName, String eTag) {
|
||||
return new LocalGroup(addressBook, id, fileName, eTag);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocalGroup newInstance(AndroidAddressBook addressBook, Contact contact, String fileName, String eTag) {
|
||||
return new LocalGroup(addressBook, contact, fileName, eTag);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocalGroup[] newArray(int size) {
|
||||
return new LocalGroup[size];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright © 2013 – 2015 Ricki Hirner (bitfire web engineering).
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the GNU Public License v3.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.gnu.org/licenses/gpl.html
|
||||
*/
|
||||
|
||||
package at.bitfire.davdroid.resource;
|
||||
|
||||
import at.bitfire.ical4android.CalendarStorageException;
|
||||
import at.bitfire.vcard4android.ContactsStorageException;
|
||||
|
||||
public interface LocalResource {
|
||||
|
||||
Long getId();
|
||||
|
||||
String getFileName();
|
||||
String getETag();
|
||||
|
||||
int delete() throws CalendarStorageException, ContactsStorageException;
|
||||
|
||||
void updateFileNameAndUID(String uuid) throws CalendarStorageException, ContactsStorageException;
|
||||
void clearDirty(String eTag) throws CalendarStorageException, ContactsStorageException;
|
||||
|
||||
}
|
||||
138
app/src/main/java/at/bitfire/davdroid/resource/LocalTask.java
Normal file
138
app/src/main/java/at/bitfire/davdroid/resource/LocalTask.java
Normal file
@@ -0,0 +1,138 @@
|
||||
/*
|
||||
* Copyright © 2013 – 2015 Ricki Hirner (bitfire web engineering).
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the GNU Public License v3.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.gnu.org/licenses/gpl.html
|
||||
*/
|
||||
|
||||
package at.bitfire.davdroid.resource;
|
||||
|
||||
import android.content.ContentProviderOperation;
|
||||
import android.content.ContentValues;
|
||||
import android.os.RemoteException;
|
||||
import android.provider.CalendarContract.Events;
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import net.fortuna.ical4j.model.property.ProdId;
|
||||
|
||||
import org.dmfs.provider.tasks.TaskContract.Tasks;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.text.ParseException;
|
||||
|
||||
import at.bitfire.davdroid.BuildConfig;
|
||||
import at.bitfire.ical4android.AndroidTask;
|
||||
import at.bitfire.ical4android.AndroidTaskFactory;
|
||||
import at.bitfire.ical4android.AndroidTaskList;
|
||||
import at.bitfire.ical4android.CalendarStorageException;
|
||||
import at.bitfire.ical4android.Task;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
public class LocalTask extends AndroidTask implements LocalResource {
|
||||
static {
|
||||
Task.prodId = new ProdId("+//IDN bitfire.at//DAVdroid/" + BuildConfig.VERSION_NAME + " ical4android ical4j/2.x");
|
||||
}
|
||||
|
||||
static final String COLUMN_ETAG = Tasks.SYNC1,
|
||||
COLUMN_UID = Tasks.SYNC2,
|
||||
COLUMN_SEQUENCE = Tasks.SYNC3;
|
||||
|
||||
@Getter protected String fileName;
|
||||
@Getter @Setter protected String eTag;
|
||||
|
||||
public LocalTask(@NonNull AndroidTaskList taskList, Task task, String fileName, String eTag) {
|
||||
super(taskList, task);
|
||||
this.fileName = fileName;
|
||||
this.eTag = eTag;
|
||||
}
|
||||
|
||||
protected LocalTask(@NonNull AndroidTaskList taskList, long id, ContentValues baseInfo) {
|
||||
super(taskList, id);
|
||||
if (baseInfo != null) {
|
||||
fileName = baseInfo.getAsString(Events._SYNC_ID);
|
||||
eTag = baseInfo.getAsString(COLUMN_ETAG);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* process LocalTask-specific fields */
|
||||
|
||||
@Override
|
||||
protected void populateTask(ContentValues values) throws FileNotFoundException, RemoteException, ParseException {
|
||||
super.populateTask(values);
|
||||
|
||||
fileName = values.getAsString(Events._SYNC_ID);
|
||||
eTag = values.getAsString(COLUMN_ETAG);
|
||||
task.uid = values.getAsString(COLUMN_UID);
|
||||
|
||||
task.sequence = values.getAsInteger(COLUMN_SEQUENCE);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void buildTask(ContentProviderOperation.Builder builder, boolean update) {
|
||||
super.buildTask(builder, update);
|
||||
builder .withValue(Tasks._SYNC_ID, fileName)
|
||||
.withValue(COLUMN_UID, task.uid)
|
||||
.withValue(COLUMN_SEQUENCE, task.sequence)
|
||||
.withValue(COLUMN_ETAG, eTag);
|
||||
}
|
||||
|
||||
|
||||
/* custom queries */
|
||||
|
||||
public void updateFileNameAndUID(String uid) throws CalendarStorageException {
|
||||
try {
|
||||
String newFileName = uid + ".ics";
|
||||
|
||||
ContentValues values = new ContentValues(2);
|
||||
values.put(Tasks._SYNC_ID, newFileName);
|
||||
values.put(COLUMN_UID, uid);
|
||||
taskList.provider.client.update(taskSyncURI(), values, null, null);
|
||||
|
||||
fileName = newFileName;
|
||||
if (task != null)
|
||||
task.uid = uid;
|
||||
|
||||
} catch (RemoteException e) {
|
||||
throw new CalendarStorageException("Couldn't update UID", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearDirty(String eTag) throws CalendarStorageException {
|
||||
try {
|
||||
ContentValues values = new ContentValues(2);
|
||||
values.put(Tasks._DIRTY, 0);
|
||||
values.put(COLUMN_ETAG, eTag);
|
||||
if (task != null)
|
||||
values.put(COLUMN_SEQUENCE, task.sequence);
|
||||
taskList.provider.client.update(taskSyncURI(), values, null, null);
|
||||
|
||||
this.eTag = eTag;
|
||||
} catch (RemoteException e) {
|
||||
throw new CalendarStorageException("Couldn't update _DIRTY/ETag/SEQUENCE", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static class Factory implements AndroidTaskFactory {
|
||||
static final Factory INSTANCE = new Factory();
|
||||
|
||||
@Override
|
||||
public LocalTask newInstance(AndroidTaskList taskList, long id, ContentValues baseInfo) {
|
||||
return new LocalTask(taskList, id, baseInfo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocalTask newInstance(AndroidTaskList taskList, Task task) {
|
||||
return new LocalTask(taskList, task, null, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocalTask[] newArray(int size) {
|
||||
return new LocalTask[size];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
/*
|
||||
* Copyright © 2013 – 2015 Ricki Hirner (bitfire web engineering).
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the GNU Public License v3.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.gnu.org/licenses/gpl.html
|
||||
*/
|
||||
|
||||
package at.bitfire.davdroid.resource;
|
||||
|
||||
import android.accounts.Account;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.RemoteException;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import org.dmfs.provider.tasks.TaskContract.TaskLists;
|
||||
import org.dmfs.provider.tasks.TaskContract.Tasks;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
|
||||
import at.bitfire.davdroid.DavUtils;
|
||||
import at.bitfire.davdroid.model.CollectionInfo;
|
||||
import at.bitfire.ical4android.AndroidTaskList;
|
||||
import at.bitfire.ical4android.AndroidTaskListFactory;
|
||||
import at.bitfire.ical4android.CalendarStorageException;
|
||||
import at.bitfire.ical4android.TaskProvider;
|
||||
import lombok.Cleanup;
|
||||
|
||||
public class LocalTaskList extends AndroidTaskList implements LocalCollection {
|
||||
|
||||
public static final int defaultColor = 0xFFC3EA6E; // "DAVdroid green"
|
||||
|
||||
public static final String COLUMN_CTAG = TaskLists.SYNC_VERSION;
|
||||
|
||||
static String[] BASE_INFO_COLUMNS = new String[] {
|
||||
Tasks._ID,
|
||||
Tasks._SYNC_ID,
|
||||
LocalTask.COLUMN_ETAG
|
||||
};
|
||||
|
||||
// can be cached, because after installing OpenTasks, you have to re-install DAVdroid anyway
|
||||
private static Boolean tasksProviderAvailable;
|
||||
|
||||
|
||||
@Override
|
||||
protected String[] taskBaseInfoColumns() {
|
||||
return BASE_INFO_COLUMNS;
|
||||
}
|
||||
|
||||
|
||||
protected LocalTaskList(Account account, TaskProvider provider, long id) {
|
||||
super(account, provider, LocalTask.Factory.INSTANCE, id);
|
||||
}
|
||||
|
||||
public static Uri create(Account account, TaskProvider provider, CollectionInfo info) throws CalendarStorageException {
|
||||
ContentValues values = valuesFromCollectionInfo(info, true);
|
||||
values.put(TaskLists.OWNER, account.name);
|
||||
values.put(TaskLists.SYNC_ENABLED, 1);
|
||||
values.put(TaskLists.VISIBLE, 1);
|
||||
return create(account, provider, values);
|
||||
}
|
||||
|
||||
public void update(CollectionInfo info, boolean updateColor) throws CalendarStorageException {
|
||||
update(valuesFromCollectionInfo(info, updateColor));
|
||||
}
|
||||
|
||||
private static ContentValues valuesFromCollectionInfo(CollectionInfo info, boolean withColor) {
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(TaskLists._SYNC_ID, info.url);
|
||||
values.put(TaskLists.LIST_NAME, !TextUtils.isEmpty(info.displayName) ? info.displayName : DavUtils.lastSegmentOfUrl(info.url));
|
||||
|
||||
if (withColor)
|
||||
values.put(TaskLists.LIST_COLOR, info.color != null ? info.color : defaultColor);
|
||||
|
||||
return values;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public LocalTask[] getAll() throws CalendarStorageException {
|
||||
return (LocalTask[])queryTasks(null, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocalTask[] getDeleted() throws CalendarStorageException {
|
||||
return (LocalTask[])queryTasks(Tasks._DELETED + "!=0", null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocalTask[] getWithoutFileName() throws CalendarStorageException {
|
||||
return (LocalTask[])queryTasks(Tasks._SYNC_ID + " IS NULL", null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocalResource[] getDirty() throws CalendarStorageException, FileNotFoundException {
|
||||
LocalTask[] tasks = (LocalTask[])queryTasks(Tasks._DIRTY + "!=0", null);
|
||||
if (tasks != null)
|
||||
for (LocalTask task : tasks) {
|
||||
if (task.getTask().sequence == null) // sequence has not been assigned yet (i.e. this task was just locally created)
|
||||
task.getTask().sequence = 0;
|
||||
else
|
||||
task.getTask().sequence++;
|
||||
}
|
||||
return tasks;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("Recycle")
|
||||
public String getCTag() throws CalendarStorageException {
|
||||
try {
|
||||
@Cleanup Cursor cursor = provider.client.query(taskListSyncUri(), new String[] { COLUMN_CTAG }, null, null, null);
|
||||
if (cursor != null && cursor.moveToNext())
|
||||
return cursor.getString(0);
|
||||
} catch (RemoteException e) {
|
||||
throw new CalendarStorageException("Couldn't read local (last known) CTag", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCTag(String cTag) throws CalendarStorageException {
|
||||
try {
|
||||
ContentValues values = new ContentValues(1);
|
||||
values.put(COLUMN_CTAG, cTag);
|
||||
provider.client.update(taskListSyncUri(), values, null, null);
|
||||
} catch (RemoteException e) {
|
||||
throw new CalendarStorageException("Couldn't write local (last known) CTag", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// helpers
|
||||
|
||||
public static boolean tasksProviderAvailable(@NonNull Context context) {
|
||||
if (tasksProviderAvailable != null)
|
||||
return tasksProviderAvailable;
|
||||
else {
|
||||
if (Build.VERSION.SDK_INT >= 23)
|
||||
return context.getPackageManager().resolveContentProvider(TaskProvider.ProviderName.OpenTasks.authority, 0) != null;
|
||||
else {
|
||||
@Cleanup TaskProvider provider = TaskProvider.acquire(context.getContentResolver(), TaskProvider.ProviderName.OpenTasks);
|
||||
return tasksProviderAvailable = (provider != null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static class Factory implements AndroidTaskListFactory {
|
||||
public static final Factory INSTANCE = new Factory();
|
||||
|
||||
@Override
|
||||
public AndroidTaskList newInstance(Account account, TaskProvider provider, long id) {
|
||||
return new LocalTaskList(account, provider, id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AndroidTaskList[] newArray(int size) {
|
||||
return new LocalTaskList[size];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* Copyright © 2013 – 2015 Ricki Hirner (bitfire web engineering).
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the GNU Public License v3.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.gnu.org/licenses/gpl.html
|
||||
*/
|
||||
package at.bitfire.davdroid.syncadapter;
|
||||
|
||||
import android.accounts.AbstractAccountAuthenticator;
|
||||
import android.accounts.Account;
|
||||
import android.accounts.AccountAuthenticatorResponse;
|
||||
import android.accounts.AccountManager;
|
||||
import android.accounts.NetworkErrorException;
|
||||
import android.app.Service;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
|
||||
import at.bitfire.davdroid.ui.setup.LoginActivity;
|
||||
|
||||
public class AccountAuthenticatorService extends Service {
|
||||
|
||||
private AccountAuthenticator accountAuthenticator;
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
accountAuthenticator = new AccountAuthenticator(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
if (intent.getAction().equals(android.accounts.AccountManager.ACTION_AUTHENTICATOR_INTENT))
|
||||
return accountAuthenticator.getIBinder();
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
private static class AccountAuthenticator extends AbstractAccountAuthenticator {
|
||||
final Context context;
|
||||
|
||||
public AccountAuthenticator(Context context) {
|
||||
super(context);
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Bundle addAccount(AccountAuthenticatorResponse response, String accountType, String authTokenType,
|
||||
String[] requiredFeatures, Bundle options) throws NetworkErrorException {
|
||||
Intent intent = new Intent(context, LoginActivity.class);
|
||||
intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response);
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putParcelable(AccountManager.KEY_INTENT, intent);
|
||||
return bundle;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Bundle confirmCredentials(AccountAuthenticatorResponse response, Account account, Bundle options) throws NetworkErrorException {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Bundle editProperties(AccountAuthenticatorResponse response, String accountType) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Bundle getAuthToken(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options) throws NetworkErrorException {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAuthTokenLabel(String authTokenType) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Bundle hasFeatures(AccountAuthenticatorResponse response, Account account, String[] features) throws NetworkErrorException {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Bundle updateCredentials(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options) throws NetworkErrorException {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,235 @@
|
||||
/*
|
||||
* Copyright © 2013 – 2015 Ricki Hirner (bitfire web engineering).
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the GNU Public License v3.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.gnu.org/licenses/gpl.html
|
||||
*/
|
||||
|
||||
package at.bitfire.davdroid.syncadapter;
|
||||
|
||||
import android.accounts.Account;
|
||||
import android.content.Context;
|
||||
import android.content.SyncResult;
|
||||
import android.os.Bundle;
|
||||
|
||||
import org.apache.commons.codec.Charsets;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import at.bitfire.dav4android.DavCalendar;
|
||||
import at.bitfire.dav4android.DavResource;
|
||||
import at.bitfire.dav4android.exception.DavException;
|
||||
import at.bitfire.dav4android.exception.HttpException;
|
||||
import at.bitfire.dav4android.property.CalendarData;
|
||||
import at.bitfire.dav4android.property.GetCTag;
|
||||
import at.bitfire.dav4android.property.GetContentType;
|
||||
import at.bitfire.dav4android.property.GetETag;
|
||||
import at.bitfire.davdroid.AccountSettings;
|
||||
import at.bitfire.davdroid.App;
|
||||
import at.bitfire.davdroid.ArrayUtils;
|
||||
import at.bitfire.davdroid.Constants;
|
||||
import at.bitfire.davdroid.InvalidAccountException;
|
||||
import at.bitfire.davdroid.R;
|
||||
import at.bitfire.davdroid.resource.LocalCalendar;
|
||||
import at.bitfire.davdroid.resource.LocalEvent;
|
||||
import at.bitfire.davdroid.resource.LocalResource;
|
||||
import at.bitfire.ical4android.CalendarStorageException;
|
||||
import at.bitfire.ical4android.Event;
|
||||
import at.bitfire.ical4android.InvalidCalendarException;
|
||||
import at.bitfire.vcard4android.ContactsStorageException;
|
||||
import lombok.Cleanup;
|
||||
import okhttp3.HttpUrl;
|
||||
import okhttp3.MediaType;
|
||||
import okhttp3.RequestBody;
|
||||
import okhttp3.ResponseBody;
|
||||
|
||||
/**
|
||||
* Synchronization manager for CalDAV collections; handles events ({@code VEVENT}).
|
||||
*/
|
||||
public class CalendarSyncManager extends SyncManager {
|
||||
|
||||
protected static final int MAX_MULTIGET = 20;
|
||||
|
||||
|
||||
public CalendarSyncManager(Context context, Account account, AccountSettings settings, Bundle extras, String authority, SyncResult result, LocalCalendar calendar) throws InvalidAccountException {
|
||||
super(context, account, settings, extras, authority, result, "calendar/" + calendar.getId());
|
||||
localCollection = calendar;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int notificationId() {
|
||||
return Constants.NOTIFICATION_CALENDAR_SYNC;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getSyncErrorTitle() {
|
||||
return context.getString(R.string.sync_error_calendar, account.name);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void prepare() {
|
||||
collectionURL = HttpUrl.parse(localCalendar().getName());
|
||||
davCollection = new DavCalendar(httpClient, collectionURL);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void queryCapabilities() throws DavException, IOException, HttpException {
|
||||
davCollection.propfind(0, GetCTag.NAME);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void prepareDirty() throws CalendarStorageException, ContactsStorageException {
|
||||
super.prepareDirty();
|
||||
|
||||
localCalendar().processDirtyExceptions();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RequestBody prepareUpload(LocalResource resource) throws IOException, CalendarStorageException {
|
||||
LocalEvent local = (LocalEvent)resource;
|
||||
App.log.log(Level.FINE, "Preparing upload of event " + local.getFileName(), local.getEvent());
|
||||
|
||||
ByteArrayOutputStream os = new ByteArrayOutputStream();
|
||||
local.getEvent().write(os);
|
||||
|
||||
return RequestBody.create(
|
||||
DavCalendar.MIME_ICALENDAR,
|
||||
os.toByteArray()
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void listRemote() throws IOException, HttpException, DavException {
|
||||
// calculate time range limits
|
||||
Date limitStart = null;
|
||||
Integer pastDays = settings.getTimeRangePastDays();
|
||||
if (pastDays != null) {
|
||||
Calendar calendar = Calendar.getInstance();
|
||||
calendar.add(Calendar.DAY_OF_MONTH, -pastDays);
|
||||
limitStart = calendar.getTime();
|
||||
}
|
||||
|
||||
// fetch list of remote VEVENTs and build hash table to index file name
|
||||
davCalendar().calendarQuery("VEVENT", limitStart, null);
|
||||
|
||||
remoteResources = new HashMap<>(davCollection.members.size());
|
||||
for (DavResource iCal : davCollection.members) {
|
||||
String fileName = iCal.fileName();
|
||||
App.log.fine("Found remote VEVENT: " + fileName);
|
||||
remoteResources.put(fileName, iCal);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void downloadRemote() throws IOException, HttpException, DavException, CalendarStorageException {
|
||||
App.log.info("Downloading " + toDownload.size() + " events (" + MAX_MULTIGET + " at once)");
|
||||
|
||||
// download new/updated iCalendars from server
|
||||
for (DavResource[] bunch : ArrayUtils.partition(toDownload.toArray(new DavResource[toDownload.size()]), MAX_MULTIGET)) {
|
||||
if (Thread.interrupted())
|
||||
return;
|
||||
App.log.info("Downloading " + StringUtils.join(bunch, ", "));
|
||||
|
||||
if (bunch.length == 1) {
|
||||
// only one contact, use GET
|
||||
DavResource remote = bunch[0];
|
||||
|
||||
ResponseBody body = remote.get("text/calendar");
|
||||
|
||||
// CalDAV servers MUST return ETag on GET [https://tools.ietf.org/html/rfc4791#section-5.3.4]
|
||||
GetETag eTag = (GetETag)remote.properties.get(GetETag.NAME);
|
||||
if (eTag == null || StringUtils.isEmpty(eTag.eTag))
|
||||
throw new DavException("Received CalDAV GET response without ETag for " + remote.location);
|
||||
|
||||
Charset charset = Charsets.UTF_8;
|
||||
MediaType contentType = body.contentType();
|
||||
if (contentType != null)
|
||||
charset = contentType.charset(Charsets.UTF_8);
|
||||
|
||||
@Cleanup InputStream stream = body.byteStream();
|
||||
processVEvent(remote.fileName(), eTag.eTag, stream, charset);
|
||||
|
||||
} else {
|
||||
// multiple contacts, use multi-get
|
||||
List<HttpUrl> urls = new LinkedList<>();
|
||||
for (DavResource remote : bunch)
|
||||
urls.add(remote.location);
|
||||
davCalendar().multiget(urls.toArray(new HttpUrl[urls.size()]));
|
||||
|
||||
// process multiget results
|
||||
for (DavResource remote : davCollection.members) {
|
||||
String eTag;
|
||||
GetETag getETag = (GetETag)remote.properties.get(GetETag.NAME);
|
||||
if (getETag != null)
|
||||
eTag = getETag.eTag;
|
||||
else
|
||||
throw new DavException("Received multi-get response without ETag");
|
||||
|
||||
Charset charset = Charsets.UTF_8;
|
||||
GetContentType getContentType = (GetContentType)remote.properties.get(GetContentType.NAME);
|
||||
if (getContentType != null && getContentType.type != null) {
|
||||
MediaType type = MediaType.parse(getContentType.type);
|
||||
if (type != null)
|
||||
charset = type.charset(Charsets.UTF_8);
|
||||
}
|
||||
|
||||
CalendarData calendarData = (CalendarData)remote.properties.get(CalendarData.NAME);
|
||||
if (calendarData == null || calendarData.iCalendar == null)
|
||||
throw new DavException("Received multi-get response without address data");
|
||||
|
||||
@Cleanup InputStream stream = new ByteArrayInputStream(calendarData.iCalendar.getBytes());
|
||||
processVEvent(remote.fileName(), eTag, stream, charset);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// helpers
|
||||
|
||||
private LocalCalendar localCalendar() { return ((LocalCalendar)localCollection); }
|
||||
private DavCalendar davCalendar() { return (DavCalendar)davCollection; }
|
||||
|
||||
private void processVEvent(String fileName, String eTag, InputStream stream, Charset charset) throws IOException, CalendarStorageException {
|
||||
Event[] events;
|
||||
try {
|
||||
events = Event.fromStream(stream, charset);
|
||||
} catch (InvalidCalendarException e) {
|
||||
App.log.log(Level.SEVERE, "Received invalid iCalendar, ignoring", e);
|
||||
return;
|
||||
}
|
||||
|
||||
if (events.length == 1) {
|
||||
Event newData = events[0];
|
||||
|
||||
// delete local event, if it exists
|
||||
LocalEvent localEvent = (LocalEvent)localResources.get(fileName);
|
||||
if (localEvent != null) {
|
||||
App.log.info("Updating " + fileName + " in local calendar");
|
||||
localEvent.setETag(eTag);
|
||||
localEvent.update(newData);
|
||||
syncResult.stats.numUpdates++;
|
||||
} else {
|
||||
App.log.info("Adding " + fileName + " to local calendar");
|
||||
localEvent = new LocalEvent(localCalendar(), newData, fileName, eTag);
|
||||
localEvent.add();
|
||||
syncResult.stats.numInserts++;
|
||||
}
|
||||
} else
|
||||
App.log.severe("Received VCALENDAR with not exactly one VEVENT with UID, but without RECURRENCE-ID; ignoring " + fileName);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
/*
|
||||
* Copyright © 2013 – 2015 Ricki Hirner (bitfire web engineering).
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the GNU Public License v3.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.gnu.org/licenses/gpl.html
|
||||
*/
|
||||
package at.bitfire.davdroid.syncadapter;
|
||||
|
||||
import android.accounts.Account;
|
||||
import android.content.AbstractThreadedSyncAdapter;
|
||||
import android.content.ContentProviderClient;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.content.SyncResult;
|
||||
import android.database.Cursor;
|
||||
import android.database.DatabaseUtils;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
import android.os.Bundle;
|
||||
import android.provider.CalendarContract;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import at.bitfire.davdroid.AccountSettings;
|
||||
import at.bitfire.davdroid.App;
|
||||
import at.bitfire.davdroid.InvalidAccountException;
|
||||
import at.bitfire.davdroid.model.CollectionInfo;
|
||||
import at.bitfire.davdroid.model.ServiceDB;
|
||||
import at.bitfire.davdroid.model.ServiceDB.Collections;
|
||||
import at.bitfire.davdroid.model.ServiceDB.Services;
|
||||
import at.bitfire.davdroid.resource.LocalCalendar;
|
||||
import at.bitfire.ical4android.CalendarStorageException;
|
||||
import lombok.Cleanup;
|
||||
|
||||
public class CalendarsSyncAdapterService extends SyncAdapterService {
|
||||
|
||||
@Override
|
||||
protected AbstractThreadedSyncAdapter syncAdapter() {
|
||||
return new SyncAdapter(this);
|
||||
}
|
||||
|
||||
|
||||
private static class SyncAdapter extends SyncAdapterService.SyncAdapter {
|
||||
|
||||
public SyncAdapter(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sync(Account account, Bundle extras, String authority, ContentProviderClient provider, SyncResult syncResult) {
|
||||
try {
|
||||
AccountSettings settings = new AccountSettings(getContext(), account);
|
||||
if (!extras.containsKey(ContentResolver.SYNC_EXTRAS_MANUAL) && !checkSyncConditions(settings))
|
||||
return;
|
||||
|
||||
updateLocalCalendars(provider, account, settings);
|
||||
|
||||
for (LocalCalendar calendar : (LocalCalendar[])LocalCalendar.find(account, provider, LocalCalendar.Factory.INSTANCE, CalendarContract.Calendars.SYNC_EVENTS + "!=0", null)) {
|
||||
App.log.info("Synchronizing calendar #" + calendar.getId() + ", URL: " + calendar.getName());
|
||||
CalendarSyncManager syncManager = new CalendarSyncManager(getContext(), account, settings, extras, authority, syncResult, calendar);
|
||||
syncManager.performSync();
|
||||
}
|
||||
} catch (CalendarStorageException e) {
|
||||
App.log.log(Level.SEVERE, "Couldn't enumerate local calendars", e);
|
||||
syncResult.databaseError = true;
|
||||
} catch (InvalidAccountException e) {
|
||||
App.log.log(Level.SEVERE, "Couldn't get account settings", e);
|
||||
}
|
||||
|
||||
App.log.info("Calendar sync complete");
|
||||
}
|
||||
|
||||
private void updateLocalCalendars(ContentProviderClient provider, Account account, AccountSettings settings) throws CalendarStorageException {
|
||||
SQLiteOpenHelper dbHelper = new ServiceDB.OpenHelper(getContext());
|
||||
try {
|
||||
// enumerate remote and local calendars
|
||||
SQLiteDatabase db = dbHelper.getReadableDatabase();
|
||||
Long service = getService(db, account);
|
||||
Map<String, CollectionInfo> remote = remoteCalendars(db, service);
|
||||
|
||||
LocalCalendar[] local = (LocalCalendar[])LocalCalendar.find(account, provider, LocalCalendar.Factory.INSTANCE, null, null);
|
||||
|
||||
boolean updateColors = settings.getManageCalendarColors();
|
||||
|
||||
// delete obsolete local calendar
|
||||
for (LocalCalendar calendar : local) {
|
||||
String url = calendar.getName();
|
||||
if (!remote.containsKey(url)) {
|
||||
App.log.fine("Deleting obsolete local calendar " + url);
|
||||
calendar.delete();
|
||||
} else {
|
||||
// remote CollectionInfo found for this local collection, update data
|
||||
CollectionInfo info = remote.get(url);
|
||||
App.log.fine("Updating local calendar " + url + " with " + info);
|
||||
calendar.update(info, updateColors);
|
||||
// we already have a local calendar for this remote collection, don't take into consideration anymore
|
||||
remote.remove(url);
|
||||
}
|
||||
}
|
||||
|
||||
// create new local calendars
|
||||
for (String url : remote.keySet()) {
|
||||
CollectionInfo info = remote.get(url);
|
||||
App.log.info("Adding local calendar list " + info);
|
||||
LocalCalendar.create(account, provider, info);
|
||||
}
|
||||
} finally {
|
||||
dbHelper.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
Long getService(@NonNull SQLiteDatabase db, @NonNull Account account) {
|
||||
@Cleanup Cursor c = db.query(Services._TABLE, new String[] { Services.ID },
|
||||
Services.ACCOUNT_NAME + "=? AND " + Services.SERVICE + "=?", new String[] { account.name, Services.SERVICE_CALDAV }, null, null, null);
|
||||
if (c.moveToNext())
|
||||
return c.getLong(0);
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private Map<String, CollectionInfo> remoteCalendars(@NonNull SQLiteDatabase db, Long service) {
|
||||
Map<String, CollectionInfo> collections = new LinkedHashMap<>();
|
||||
if (service != null) {
|
||||
@Cleanup Cursor cursor = db.query(Collections._TABLE, null,
|
||||
Collections.SERVICE_ID + "=? AND " + Collections.SUPPORTS_VEVENT + "!=0 AND " + Collections.SYNC,
|
||||
new String[] { String.valueOf(service) }, null, null, null);
|
||||
while (cursor.moveToNext()) {
|
||||
ContentValues values = new ContentValues();
|
||||
DatabaseUtils.cursorRowToContentValues(cursor, values);
|
||||
CollectionInfo info = CollectionInfo.fromDB(values);
|
||||
collections.put(info.url, info);
|
||||
}
|
||||
}
|
||||
return collections;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* Copyright © 2013 – 2015 Ricki Hirner (bitfire web engineering).
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the GNU Public License v3.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.gnu.org/licenses/gpl.html
|
||||
*/
|
||||
package at.bitfire.davdroid.syncadapter;
|
||||
|
||||
import android.accounts.Account;
|
||||
import android.content.AbstractThreadedSyncAdapter;
|
||||
import android.content.ContentProviderClient;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.content.SyncResult;
|
||||
import android.database.Cursor;
|
||||
import android.database.DatabaseUtils;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import java.util.logging.Level;
|
||||
|
||||
import at.bitfire.davdroid.AccountSettings;
|
||||
import at.bitfire.davdroid.App;
|
||||
import at.bitfire.davdroid.InvalidAccountException;
|
||||
import at.bitfire.davdroid.model.CollectionInfo;
|
||||
import at.bitfire.davdroid.model.ServiceDB;
|
||||
import at.bitfire.davdroid.model.ServiceDB.Collections;
|
||||
import lombok.Cleanup;
|
||||
|
||||
public class ContactsSyncAdapterService extends SyncAdapterService {
|
||||
|
||||
@Override
|
||||
protected AbstractThreadedSyncAdapter syncAdapter() {
|
||||
return new ContactsSyncAdapter(this);
|
||||
}
|
||||
|
||||
|
||||
private static class ContactsSyncAdapter extends SyncAdapter {
|
||||
|
||||
public ContactsSyncAdapter(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sync(Account account, Bundle extras, String authority, ContentProviderClient provider, SyncResult syncResult) {
|
||||
SQLiteOpenHelper dbHelper = new ServiceDB.OpenHelper(getContext());
|
||||
try {
|
||||
AccountSettings settings = new AccountSettings(getContext(), account);
|
||||
if (!extras.containsKey(ContentResolver.SYNC_EXTRAS_MANUAL) && !checkSyncConditions(settings))
|
||||
return;
|
||||
|
||||
SQLiteDatabase db = dbHelper.getReadableDatabase();
|
||||
Long service = getService(db, account);
|
||||
if (service != null) {
|
||||
CollectionInfo remote = remoteAddressBook(db, service);
|
||||
if (remote != null)
|
||||
try {
|
||||
ContactsSyncManager syncManager = new ContactsSyncManager(getContext(), account, settings, extras, authority, provider, syncResult, remote);
|
||||
syncManager.performSync();
|
||||
} catch(InvalidAccountException e) {
|
||||
App.log.log(Level.SEVERE, "Couldn't get account settings", e);
|
||||
}
|
||||
else
|
||||
App.log.info("No address book collection selected for synchronization");
|
||||
} else
|
||||
App.log.info("No CardDAV service found in DB");
|
||||
} catch (InvalidAccountException e) {
|
||||
App.log.log(Level.SEVERE, "Couldn't get account settings", e);
|
||||
} finally {
|
||||
dbHelper.close();
|
||||
}
|
||||
|
||||
App.log.info("Address book sync complete");
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Long getService(@NonNull SQLiteDatabase db, @NonNull Account account) {
|
||||
@Cleanup Cursor c = db.query(ServiceDB.Services._TABLE, new String[] { ServiceDB.Services.ID },
|
||||
ServiceDB.Services.ACCOUNT_NAME + "=? AND " + ServiceDB.Services.SERVICE + "=?", new String[] { account.name, ServiceDB.Services.SERVICE_CARDDAV }, null, null, null);
|
||||
if (c.moveToNext())
|
||||
return c.getLong(0);
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private CollectionInfo remoteAddressBook(@NonNull SQLiteDatabase db, long service) {
|
||||
@Cleanup Cursor c = db.query(Collections._TABLE, null,
|
||||
Collections.SERVICE_ID + "=? AND " + Collections.SYNC, new String[] { String.valueOf(service) }, null, null, null);
|
||||
if (c.moveToNext()) {
|
||||
ContentValues values = new ContentValues();
|
||||
DatabaseUtils.cursorRowToContentValues(c, values);
|
||||
return CollectionInfo.fromDB(values);
|
||||
} else
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,518 @@
|
||||
/*
|
||||
* Copyright © 2013 – 2015 Ricki Hirner (bitfire web engineering).
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the GNU Public License v3.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.gnu.org/licenses/gpl.html
|
||||
*/
|
||||
|
||||
package at.bitfire.davdroid.syncadapter;
|
||||
|
||||
import android.accounts.Account;
|
||||
import android.content.ContentProviderClient;
|
||||
import android.content.ContentProviderOperation;
|
||||
import android.content.ContentUris;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.content.SyncResult;
|
||||
import android.database.Cursor;
|
||||
import android.os.Bundle;
|
||||
import android.os.RemoteException;
|
||||
import android.provider.ContactsContract;
|
||||
import android.provider.ContactsContract.Groups;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import org.apache.commons.codec.Charsets;
|
||||
import org.apache.commons.collections4.SetUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import at.bitfire.dav4android.DavAddressBook;
|
||||
import at.bitfire.dav4android.DavResource;
|
||||
import at.bitfire.dav4android.exception.DavException;
|
||||
import at.bitfire.dav4android.exception.HttpException;
|
||||
import at.bitfire.dav4android.property.AddressData;
|
||||
import at.bitfire.dav4android.property.GetCTag;
|
||||
import at.bitfire.dav4android.property.GetContentType;
|
||||
import at.bitfire.dav4android.property.GetETag;
|
||||
import at.bitfire.dav4android.property.ResourceType;
|
||||
import at.bitfire.dav4android.property.SupportedAddressData;
|
||||
import at.bitfire.davdroid.AccountSettings;
|
||||
import at.bitfire.davdroid.App;
|
||||
import at.bitfire.davdroid.ArrayUtils;
|
||||
import at.bitfire.davdroid.Constants;
|
||||
import at.bitfire.davdroid.HttpClient;
|
||||
import at.bitfire.davdroid.InvalidAccountException;
|
||||
import at.bitfire.davdroid.R;
|
||||
import at.bitfire.davdroid.model.CollectionInfo;
|
||||
import at.bitfire.davdroid.resource.LocalAddressBook;
|
||||
import at.bitfire.davdroid.resource.LocalContact;
|
||||
import at.bitfire.davdroid.resource.LocalGroup;
|
||||
import at.bitfire.davdroid.resource.LocalResource;
|
||||
import at.bitfire.ical4android.CalendarStorageException;
|
||||
import at.bitfire.vcard4android.BatchOperation;
|
||||
import at.bitfire.vcard4android.Contact;
|
||||
import at.bitfire.vcard4android.ContactsStorageException;
|
||||
import at.bitfire.vcard4android.GroupMethod;
|
||||
import ezvcard.VCardVersion;
|
||||
import ezvcard.util.IOUtils;
|
||||
import lombok.Cleanup;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import okhttp3.HttpUrl;
|
||||
import okhttp3.MediaType;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.RequestBody;
|
||||
import okhttp3.Response;
|
||||
import okhttp3.ResponseBody;
|
||||
|
||||
/**
|
||||
* <p>Synchronization manager for CardDAV collections; handles contacts and groups.</p>
|
||||
*
|
||||
* <p></p>Group handling differs according to the {@link #groupMethod}. There are two basic methods to
|
||||
* handle/manage groups:</p>
|
||||
* <ul>
|
||||
* <li>{@code CATEGORIES}: groups memberships are attached to each contact and represented as
|
||||
* "category". When a group is dirty or has been deleted, all its members have to be set to
|
||||
* dirty, too (because they have to be uploaded without the respective category). This
|
||||
* is done in {@link #prepareDirty()}. Empty groups can be deleted without further processing,
|
||||
* which is done in {@link #postProcess()} because groups may become empty after downloading
|
||||
* updated remoted contacts.</li>
|
||||
* <li>Groups as separate VCards: individual and group contacts (with a list of member UIDs) are
|
||||
* distinguished. When a local group is dirty, its members don't need to be set to dirty.
|
||||
* <ol>
|
||||
* <li>However, when a contact is dirty, it has
|
||||
* to be checked whether its group memberships have changed. In this case, the respective
|
||||
* groups have to be set to dirty. For instance, if contact A is in group G and H, and then
|
||||
* group membership of G is removed, the contact will be set to dirty because of the changed
|
||||
* {@link android.provider.ContactsContract.CommonDataKinds.GroupMembership}. DAVdroid will
|
||||
* then have to check whether the group memberships have actually changed, and if so,
|
||||
* all affected groups have to be set to dirty. To detect changes in group memberships,
|
||||
* DAVdroid always mirrors all {@link android.provider.ContactsContract.CommonDataKinds.GroupMembership}
|
||||
* data rows in respective {@link at.bitfire.vcard4android.CachedGroupMembership} rows.
|
||||
* If the cached group memberships are not the same as the current group member ships, the
|
||||
* difference set (in our example G, because its in the cached memberships, but not in the
|
||||
* actual ones) is marked as dirty. This is done in {@link #prepareDirty()}.</li>
|
||||
* <li>When downloading remote contacts, groups (+ member information) may be received
|
||||
* by the actual members. Thus, the member lists have to be cached until all VCards
|
||||
* are received. This is done by caching the member UIDs of each group in
|
||||
* {@link LocalGroup#COLUMN_PENDING_MEMBERS}. In {@link #postProcess()},
|
||||
* these "pending memberships" are assigned to the actual contacs and then cleaned up.</li>
|
||||
* </ol>
|
||||
* </ul>
|
||||
*/
|
||||
public class ContactsSyncManager extends SyncManager {
|
||||
protected static final int MAX_MULTIGET = 10;
|
||||
|
||||
final private ContentProviderClient provider;
|
||||
final private CollectionInfo remote;
|
||||
|
||||
private boolean hasVCard4;
|
||||
private GroupMethod groupMethod;
|
||||
|
||||
|
||||
public ContactsSyncManager(Context context, Account account, AccountSettings settings, Bundle extras, String authority, ContentProviderClient provider, SyncResult result, CollectionInfo remote) throws InvalidAccountException {
|
||||
super(context, account, settings, extras, authority, result, "addressBook");
|
||||
this.provider = provider;
|
||||
this.remote = remote;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int notificationId() {
|
||||
return Constants.NOTIFICATION_CONTACTS_SYNC;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getSyncErrorTitle() {
|
||||
return context.getString(R.string.sync_error_contacts, account.name);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void prepare() throws ContactsStorageException {
|
||||
// prepare local address book
|
||||
localCollection = new LocalAddressBook(account, provider);
|
||||
LocalAddressBook localAddressBook = localAddressBook();
|
||||
|
||||
String url = remote.url;
|
||||
String lastUrl = localAddressBook.getURL();
|
||||
if (!url.equals(lastUrl)) {
|
||||
App.log.info("Selected address book has changed from " + lastUrl + " to " + url + ", deleting all local contacts");
|
||||
localAddressBook.deleteAll();
|
||||
}
|
||||
|
||||
// set up Contacts Provider Settings
|
||||
ContentValues values = new ContentValues(2);
|
||||
values.put(ContactsContract.Settings.SHOULD_SYNC, 1);
|
||||
values.put(ContactsContract.Settings.UNGROUPED_VISIBLE, 1);
|
||||
localAddressBook.updateSettings(values);
|
||||
|
||||
collectionURL = HttpUrl.parse(url);
|
||||
davCollection = new DavAddressBook(httpClient, collectionURL);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void queryCapabilities() throws DavException, IOException, HttpException {
|
||||
// prepare remote address book
|
||||
davCollection.propfind(0, SupportedAddressData.NAME, GetCTag.NAME);
|
||||
SupportedAddressData supportedAddressData = (SupportedAddressData)davCollection.properties.get(SupportedAddressData.NAME);
|
||||
hasVCard4 = supportedAddressData != null && supportedAddressData.hasVCard4();
|
||||
App.log.info("Server advertises VCard/4 support: " + hasVCard4);
|
||||
|
||||
groupMethod = settings.getGroupMethod();
|
||||
App.log.info("Contact group method: " + groupMethod);
|
||||
|
||||
localAddressBook().includeGroups = groupMethod == GroupMethod.GROUP_VCARDS;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void prepareDirty() throws CalendarStorageException, ContactsStorageException {
|
||||
super.prepareDirty();
|
||||
|
||||
LocalAddressBook addressBook = localAddressBook();
|
||||
|
||||
if (groupMethod == GroupMethod.CATEGORIES) {
|
||||
/* groups memberships are represented as contact CATEGORIES */
|
||||
|
||||
// groups with DELETED=1: set all members to dirty, then remove group
|
||||
for (LocalGroup group : addressBook.getDeletedGroups()) {
|
||||
App.log.fine("Finally removing group " + group);
|
||||
// useless because Android deletes group memberships as soon as a group is set to DELETED:
|
||||
// group.markMembersDirty();
|
||||
group.delete();
|
||||
}
|
||||
|
||||
// groups with DIRTY=1: mark all memberships as dirty, then clean DIRTY flag of group
|
||||
for (LocalGroup group : addressBook.getDirtyGroups()) {
|
||||
App.log.fine("Marking members of modified group " + group + " as dirty");
|
||||
group.markMembersDirty();
|
||||
group.clearDirty(null);
|
||||
}
|
||||
} else {
|
||||
/* groups as separate VCards: there are group contacts and individual contacts */
|
||||
|
||||
// mark groups with changed members as dirty
|
||||
BatchOperation batch = new BatchOperation(addressBook.provider);
|
||||
for (LocalContact contact : addressBook.getDirtyContacts())
|
||||
try {
|
||||
App.log.fine("Looking for changed group memberships of contact " + contact.getFileName());
|
||||
Set<Long> cachedGroups = contact.getCachedGroupMemberships(),
|
||||
currentGroups = contact.getGroupMemberships();
|
||||
for (Long groupID : SetUtils.disjunction(cachedGroups, currentGroups)) {
|
||||
App.log.fine("Marking group as dirty: " + groupID);
|
||||
batch.enqueue(new BatchOperation.Operation(
|
||||
ContentProviderOperation.newUpdate(addressBook.syncAdapterURI(ContentUris.withAppendedId(Groups.CONTENT_URI, groupID)))
|
||||
.withValue(Groups.DIRTY, 1)
|
||||
.withYieldAllowed(true)
|
||||
));
|
||||
}
|
||||
} catch(FileNotFoundException ignored) {
|
||||
}
|
||||
batch.commit();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RequestBody prepareUpload(@NonNull LocalResource resource) throws IOException, ContactsStorageException {
|
||||
final Contact contact;
|
||||
if (resource instanceof LocalContact) {
|
||||
LocalContact local = ((LocalContact)resource);
|
||||
contact = local.getContact();
|
||||
|
||||
if (groupMethod == GroupMethod.CATEGORIES) {
|
||||
// add groups as CATEGORIES
|
||||
for (long groupID : local.getGroupMemberships()) {
|
||||
try {
|
||||
@Cleanup Cursor c = provider.query(
|
||||
localAddressBook().syncAdapterURI(ContentUris.withAppendedId(Groups.CONTENT_URI, groupID)),
|
||||
new String[] { Groups.TITLE },
|
||||
null, null,
|
||||
null
|
||||
);
|
||||
if (c != null && c.moveToNext()) {
|
||||
String title = c.getString(0);
|
||||
if (!TextUtils.isEmpty(title))
|
||||
contact.categories.add(title);
|
||||
}
|
||||
} catch(RemoteException e) {
|
||||
throw new ContactsStorageException("Couldn't find group for adding CATEGORIES", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (resource instanceof LocalGroup)
|
||||
contact = ((LocalGroup)resource).getContact();
|
||||
else
|
||||
throw new IllegalArgumentException("Argument must be LocalContact or LocalGroup");
|
||||
|
||||
App.log.log(Level.FINE, "Preparing upload of VCard " + resource.getFileName(), contact);
|
||||
|
||||
ByteArrayOutputStream os = new ByteArrayOutputStream();
|
||||
contact.write(hasVCard4 ? VCardVersion.V4_0 : VCardVersion.V3_0, groupMethod, settings.getVCardRFC6868(), os);
|
||||
|
||||
return RequestBody.create(
|
||||
hasVCard4 ? DavAddressBook.MIME_VCARD4 : DavAddressBook.MIME_VCARD3_UTF8,
|
||||
os.toByteArray()
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void listRemote() throws IOException, HttpException, DavException {
|
||||
// fetch list of remote VCards and build hash table to index file name
|
||||
davAddressBook().propfind(1, ResourceType.NAME, GetETag.NAME);
|
||||
|
||||
remoteResources = new HashMap<>(davCollection.members.size());
|
||||
for (DavResource vCard : davCollection.members) {
|
||||
// ignore member collections
|
||||
ResourceType type = (ResourceType)vCard.properties.get(ResourceType.NAME);
|
||||
if (type != null && type.types.contains(ResourceType.COLLECTION))
|
||||
continue;
|
||||
|
||||
String fileName = vCard.fileName();
|
||||
App.log.fine("Found remote VCard: " + fileName);
|
||||
remoteResources.put(fileName, vCard);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void downloadRemote() throws IOException, HttpException, DavException, ContactsStorageException {
|
||||
App.log.info("Downloading " + toDownload.size() + " contacts (" + MAX_MULTIGET + " at once)");
|
||||
|
||||
// prepare downloader which may be used to download external resource like contact photos
|
||||
Contact.Downloader downloader = new ResourceDownloader(collectionURL);
|
||||
|
||||
// download new/updated VCards from server
|
||||
for (DavResource[] bunch : ArrayUtils.partition(toDownload.toArray(new DavResource[toDownload.size()]), MAX_MULTIGET)) {
|
||||
if (Thread.interrupted())
|
||||
return;
|
||||
|
||||
App.log.info("Downloading " + StringUtils.join(bunch, ", "));
|
||||
|
||||
if (bunch.length == 1) {
|
||||
// only one contact, use GET
|
||||
DavResource remote = bunch[0];
|
||||
|
||||
ResponseBody body = remote.get("text/vcard;version=4.0, text/vcard;charset=utf-8;q=0.8, text/vcard;q=0.5");
|
||||
|
||||
// CardDAV servers MUST return ETag on GET [https://tools.ietf.org/html/rfc6352#section-6.3.2.3]
|
||||
GetETag eTag = (GetETag)remote.properties.get(GetETag.NAME);
|
||||
if (eTag == null || StringUtils.isEmpty(eTag.eTag))
|
||||
throw new DavException("Received CardDAV GET response without ETag for " + remote.location);
|
||||
|
||||
Charset charset = Charsets.UTF_8;
|
||||
MediaType contentType = body.contentType();
|
||||
if (contentType != null)
|
||||
charset = contentType.charset(Charsets.UTF_8);
|
||||
|
||||
@Cleanup InputStream stream = body.byteStream();
|
||||
processVCard(remote.fileName(), eTag.eTag, stream, charset, downloader);
|
||||
|
||||
} else {
|
||||
// multiple contacts, use multi-get
|
||||
List<HttpUrl> urls = new LinkedList<>();
|
||||
for (DavResource remote : bunch)
|
||||
urls.add(remote.location);
|
||||
davAddressBook().multiget(urls.toArray(new HttpUrl[urls.size()]), hasVCard4);
|
||||
|
||||
// process multiget results
|
||||
for (DavResource remote : davCollection.members) {
|
||||
String eTag;
|
||||
GetETag getETag = (GetETag)remote.properties.get(GetETag.NAME);
|
||||
if (getETag != null)
|
||||
eTag = getETag.eTag;
|
||||
else
|
||||
throw new DavException("Received multi-get response without ETag");
|
||||
|
||||
Charset charset = Charsets.UTF_8;
|
||||
GetContentType getContentType = (GetContentType)remote.properties.get(GetContentType.NAME);
|
||||
if (getContentType != null && getContentType.type != null) {
|
||||
MediaType type = MediaType.parse(getContentType.type);
|
||||
if (type != null)
|
||||
charset = type.charset(Charsets.UTF_8);
|
||||
}
|
||||
|
||||
AddressData addressData = (AddressData)remote.properties.get(AddressData.NAME);
|
||||
if (addressData == null || addressData.vCard == null)
|
||||
throw new DavException("Received multi-get response without address data");
|
||||
|
||||
@Cleanup InputStream stream = new ByteArrayInputStream(addressData.vCard.getBytes());
|
||||
processVCard(remote.fileName(), eTag, stream, charset, downloader);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void postProcess() throws CalendarStorageException, ContactsStorageException {
|
||||
if (groupMethod == GroupMethod.CATEGORIES) {
|
||||
/* VCard3 group handling: groups memberships are represented as contact CATEGORIES */
|
||||
|
||||
// remove empty groups
|
||||
App.log.info("Removing empty groups");
|
||||
localAddressBook().removeEmptyGroups();
|
||||
|
||||
} else {
|
||||
/* VCard4 group handling: there are group contacts and individual contacts */
|
||||
App.log.info("Assigning memberships of downloaded contact groups");
|
||||
LocalGroup.applyPendingMemberships(localAddressBook());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void saveSyncState() throws CalendarStorageException, ContactsStorageException {
|
||||
super.saveSyncState();
|
||||
((LocalAddressBook)localCollection).setURL(remote.url);
|
||||
}
|
||||
|
||||
|
||||
// helpers
|
||||
|
||||
private DavAddressBook davAddressBook() { return (DavAddressBook)davCollection; }
|
||||
private LocalAddressBook localAddressBook() { return (LocalAddressBook)localCollection; }
|
||||
|
||||
private void processVCard(String fileName, String eTag, InputStream stream, Charset charset, Contact.Downloader downloader) throws IOException, ContactsStorageException {
|
||||
App.log.info("Processing CardDAV resource " + fileName);
|
||||
Contact[] contacts = Contact.fromStream(stream, charset, downloader);
|
||||
if (contacts.length == 0) {
|
||||
App.log.warning("Received VCard without data, ignoring");
|
||||
return;
|
||||
} else if (contacts.length > 1)
|
||||
App.log.warning("Received multiple VCards, using first one");
|
||||
|
||||
final Contact newData = contacts[0];
|
||||
|
||||
if (groupMethod == GroupMethod.CATEGORIES && newData.group) {
|
||||
groupMethod = GroupMethod.GROUP_VCARDS;
|
||||
App.log.warning("Received group VCard although group method is CATEGORIES. Deleting all groups; new group method: " + groupMethod);
|
||||
localAddressBook().removeGroups();
|
||||
settings.setGroupMethod(groupMethod);
|
||||
}
|
||||
|
||||
// update local contact, if it exists
|
||||
LocalResource local = localResources.get(fileName);
|
||||
if (local != null) {
|
||||
App.log.log(Level.INFO, "Updating " + fileName + " in local address book", newData);
|
||||
|
||||
if (local instanceof LocalGroup && newData.group) {
|
||||
// update group
|
||||
LocalGroup group = (LocalGroup)local;
|
||||
group.eTag = eTag;
|
||||
group.updateFromServer(newData);
|
||||
syncResult.stats.numUpdates++;
|
||||
|
||||
} else if (local instanceof LocalContact && !newData.group) {
|
||||
// update contact
|
||||
LocalContact contact = (LocalContact)local;
|
||||
contact.eTag = eTag;
|
||||
contact.update(newData);
|
||||
syncResult.stats.numUpdates++;
|
||||
|
||||
} else {
|
||||
// group has become an individual contact or vice versa
|
||||
try {
|
||||
local.delete();
|
||||
local = null;
|
||||
} catch(CalendarStorageException e) {
|
||||
// CalendarStorageException is not used by LocalGroup and LocalContact
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (local == null) {
|
||||
if (newData.group) {
|
||||
App.log.log(Level.INFO, "Creating local group", newData);
|
||||
LocalGroup group = new LocalGroup(localAddressBook(), newData, fileName, eTag);
|
||||
group.create();
|
||||
|
||||
local = group;
|
||||
} else {
|
||||
App.log.log(Level.INFO, "Creating local contact", newData);
|
||||
LocalContact contact = new LocalContact(localAddressBook(), newData, fileName, eTag);
|
||||
contact.create();
|
||||
|
||||
local = contact;
|
||||
}
|
||||
syncResult.stats.numInserts++;
|
||||
}
|
||||
|
||||
if (groupMethod == GroupMethod.CATEGORIES && local instanceof LocalContact) {
|
||||
// VCard3: update group memberships from CATEGORIES
|
||||
LocalContact contact = (LocalContact)local;
|
||||
|
||||
BatchOperation batch = new BatchOperation(provider);
|
||||
contact.removeGroupMemberships(batch);
|
||||
|
||||
for (String category : contact.getContact().categories) {
|
||||
long groupID = localAddressBook().findOrCreateGroup(category);
|
||||
contact.addToGroup(batch, groupID);
|
||||
}
|
||||
|
||||
batch.commit();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// downloader helper class
|
||||
|
||||
@RequiredArgsConstructor
|
||||
private class ResourceDownloader implements Contact.Downloader {
|
||||
final HttpUrl baseUrl;
|
||||
|
||||
@Override
|
||||
public byte[] download(String url, String accepts) {
|
||||
HttpUrl httpUrl = HttpUrl.parse(url);
|
||||
|
||||
if (httpUrl == null) {
|
||||
App.log.log(Level.SEVERE, "Invalid external resource URL", url);
|
||||
return null;
|
||||
}
|
||||
|
||||
String host = httpUrl.host();
|
||||
if (host == null) {
|
||||
App.log.log(Level.SEVERE, "External resource URL doesn't specify a host name", url);
|
||||
return null;
|
||||
}
|
||||
|
||||
OkHttpClient resourceClient = HttpClient.create(context);
|
||||
|
||||
// authenticate only against a certain host, and only upon request
|
||||
resourceClient = HttpClient.addAuthentication(resourceClient, baseUrl.host(), settings.username(), settings.password());
|
||||
|
||||
// allow redirects
|
||||
resourceClient = resourceClient.newBuilder()
|
||||
.followRedirects(true)
|
||||
.build();
|
||||
|
||||
try {
|
||||
Response response = resourceClient.newCall(new Request.Builder()
|
||||
.get()
|
||||
.url(httpUrl)
|
||||
.build()).execute();
|
||||
|
||||
ResponseBody body = response.body();
|
||||
if (body != null) {
|
||||
@Cleanup InputStream stream = body.byteStream();
|
||||
if (response.isSuccessful() && stream != null) {
|
||||
return IOUtils.toByteArray(stream);
|
||||
} else
|
||||
App.log.severe("Couldn't download external resource");
|
||||
}
|
||||
} catch(IOException e) {
|
||||
App.log.log(Level.SEVERE, "Couldn't download external resource", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package at.bitfire.davdroid.syncadapter;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
interface ISyncPlugin {
|
||||
|
||||
boolean beforeSync(@NonNull Context context);
|
||||
void afterSync(@NonNull Context context);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
/*
|
||||
* Copyright © 2013 – 2016 Ricki Hirner (bitfire web engineering).
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the GNU Public License v3.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.gnu.org/licenses/gpl.html
|
||||
*/
|
||||
|
||||
package at.bitfire.davdroid.syncadapter;
|
||||
|
||||
import android.accounts.Account;
|
||||
import android.app.Notification;
|
||||
import android.app.PendingIntent;
|
||||
import android.app.Service;
|
||||
import android.content.AbstractThreadedSyncAdapter;
|
||||
import android.content.ContentProviderClient;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SyncResult;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.NetworkInfo;
|
||||
import android.net.wifi.WifiInfo;
|
||||
import android.net.wifi.WifiManager;
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.NotificationCompat;
|
||||
import android.support.v4.app.NotificationManagerCompat;
|
||||
|
||||
import org.apache.commons.collections4.IteratorUtils;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.ServiceLoader;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import at.bitfire.davdroid.AccountSettings;
|
||||
import at.bitfire.davdroid.App;
|
||||
import at.bitfire.davdroid.Constants;
|
||||
import at.bitfire.davdroid.R;
|
||||
import at.bitfire.davdroid.ui.PermissionsActivity;
|
||||
|
||||
//import com.android.vending.billing.IInAppBillingService;
|
||||
|
||||
public abstract class SyncAdapterService extends Service {
|
||||
|
||||
abstract protected AbstractThreadedSyncAdapter syncAdapter();
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
return syncAdapter().getSyncAdapterBinder();
|
||||
}
|
||||
|
||||
|
||||
public static abstract class SyncAdapter extends AbstractThreadedSyncAdapter {
|
||||
|
||||
private static final ServiceLoader<ISyncPlugin> syncPluginLoader = ServiceLoader.load(ISyncPlugin.class);
|
||||
private final List<ISyncPlugin> syncPlugins = IteratorUtils.toList(syncPluginLoader.iterator());
|
||||
|
||||
|
||||
public SyncAdapter(Context context) {
|
||||
super(context, false);
|
||||
|
||||
for (ISyncPlugin plugin : syncPlugins)
|
||||
App.log.info("Registered sync plugin: " + plugin.getClass().getName());
|
||||
}
|
||||
|
||||
abstract void sync(Account account, Bundle extras, String authority, ContentProviderClient provider, SyncResult syncResult);
|
||||
|
||||
@Override
|
||||
public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient provider, SyncResult syncResult) {
|
||||
App.log.info("Sync for " + authority + " has been initiated");
|
||||
|
||||
// required for dav4android (ServiceLoader)
|
||||
final Context context = getContext();
|
||||
Thread.currentThread().setContextClassLoader(context.getClassLoader());
|
||||
|
||||
boolean runSync = true;
|
||||
for (ISyncPlugin plugin : syncPlugins)
|
||||
if (!plugin.beforeSync(context))
|
||||
runSync = false;
|
||||
|
||||
if (runSync)
|
||||
sync(account, extras, authority, provider, syncResult);
|
||||
|
||||
for (ISyncPlugin plugin : syncPlugins)
|
||||
plugin.afterSync(context);
|
||||
|
||||
App.log.info("Sync for " + authority + " complete");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSecurityException(Account account, Bundle extras, String authority, SyncResult syncResult) {
|
||||
App.log.log(Level.WARNING, "Security exception when opening content provider for " + authority);
|
||||
syncResult.databaseError = true;
|
||||
|
||||
Intent intent = new Intent(getContext(), PermissionsActivity.class);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
|
||||
Notification notify = new NotificationCompat.Builder(getContext())
|
||||
.setSmallIcon(R.drawable.ic_error_light)
|
||||
.setLargeIcon(App.getLauncherBitmap(getContext()))
|
||||
.setContentTitle(getContext().getString(R.string.sync_error_permissions))
|
||||
.setContentText(getContext().getString(R.string.sync_error_permissions_text))
|
||||
.setContentIntent(PendingIntent.getActivity(getContext(), 0, intent, PendingIntent.FLAG_CANCEL_CURRENT))
|
||||
.setCategory(NotificationCompat.CATEGORY_ERROR)
|
||||
.build();
|
||||
NotificationManagerCompat nm = NotificationManagerCompat.from(getContext());
|
||||
nm.notify(Constants.NOTIFICATION_PERMISSIONS, notify);
|
||||
}
|
||||
|
||||
protected boolean checkSyncConditions(@NonNull AccountSettings settings) {
|
||||
if (settings.getSyncWifiOnly()) {
|
||||
ConnectivityManager cm = (ConnectivityManager)getContext().getSystemService(CONNECTIVITY_SERVICE);
|
||||
NetworkInfo network = cm.getActiveNetworkInfo();
|
||||
if (network == null) {
|
||||
App.log.info("No network available, stopping");
|
||||
return false;
|
||||
}
|
||||
if (network.getType() != ConnectivityManager.TYPE_WIFI || !network.isConnected()) {
|
||||
App.log.info("Not on connected WiFi, stopping");
|
||||
return false;
|
||||
}
|
||||
|
||||
String onlySSID = settings.getSyncWifiOnlySSID();
|
||||
if (onlySSID != null) {
|
||||
onlySSID = "\"" + onlySSID + "\"";
|
||||
WifiManager wifi = (WifiManager)getContext().getSystemService(WIFI_SERVICE);
|
||||
WifiInfo info = wifi.getConnectionInfo();
|
||||
if (info == null || !onlySSID.equals(info.getSSID())) {
|
||||
App.log.info("Connected to wrong WiFi network (" + info.getSSID() + ", required: " + onlySSID + "), ignoring");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,446 @@
|
||||
/*
|
||||
* Copyright © 2013 – 2015 Ricki Hirner (bitfire web engineering).
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the GNU Public License v3.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.gnu.org/licenses/gpl.html
|
||||
*/
|
||||
package at.bitfire.davdroid.syncadapter;
|
||||
|
||||
import android.accounts.Account;
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SyncResult;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.NotificationManagerCompat;
|
||||
import android.support.v7.app.NotificationCompat;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import at.bitfire.dav4android.DavResource;
|
||||
import at.bitfire.dav4android.exception.ConflictException;
|
||||
import at.bitfire.dav4android.exception.DavException;
|
||||
import at.bitfire.dav4android.exception.HttpException;
|
||||
import at.bitfire.dav4android.exception.PreconditionFailedException;
|
||||
import at.bitfire.dav4android.exception.ServiceUnavailableException;
|
||||
import at.bitfire.dav4android.exception.UnauthorizedException;
|
||||
import at.bitfire.dav4android.property.GetCTag;
|
||||
import at.bitfire.dav4android.property.GetETag;
|
||||
import at.bitfire.davdroid.AccountSettings;
|
||||
import at.bitfire.davdroid.App;
|
||||
import at.bitfire.davdroid.HttpClient;
|
||||
import at.bitfire.davdroid.InvalidAccountException;
|
||||
import at.bitfire.davdroid.R;
|
||||
import at.bitfire.davdroid.resource.LocalCollection;
|
||||
import at.bitfire.davdroid.resource.LocalResource;
|
||||
import at.bitfire.davdroid.ui.AccountSettingsActivity;
|
||||
import at.bitfire.davdroid.ui.DebugInfoActivity;
|
||||
import at.bitfire.ical4android.CalendarStorageException;
|
||||
import at.bitfire.vcard4android.ContactsStorageException;
|
||||
import okhttp3.HttpUrl;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.RequestBody;
|
||||
|
||||
abstract public class SyncManager {
|
||||
|
||||
protected final int SYNC_PHASE_PREPARE = 0,
|
||||
SYNC_PHASE_QUERY_CAPABILITIES = 1,
|
||||
SYNC_PHASE_PROCESS_LOCALLY_DELETED = 2,
|
||||
SYNC_PHASE_PREPARE_DIRTY = 3,
|
||||
SYNC_PHASE_UPLOAD_DIRTY = 4,
|
||||
SYNC_PHASE_CHECK_SYNC_STATE = 5,
|
||||
SYNC_PHASE_LIST_LOCAL = 6,
|
||||
SYNC_PHASE_LIST_REMOTE = 7,
|
||||
SYNC_PHASE_COMPARE_LOCAL_REMOTE = 8,
|
||||
SYNC_PHASE_DOWNLOAD_REMOTE = 9,
|
||||
SYNC_PHASE_POST_PROCESSING = 10,
|
||||
SYNC_PHASE_SAVE_SYNC_STATE = 11;
|
||||
|
||||
protected final NotificationManagerCompat notificationManager;
|
||||
protected final String uniqueCollectionId;
|
||||
|
||||
protected final Context context;
|
||||
protected final Account account;
|
||||
protected final Bundle extras;
|
||||
protected final String authority;
|
||||
protected final SyncResult syncResult;
|
||||
|
||||
protected final AccountSettings settings;
|
||||
protected LocalCollection localCollection;
|
||||
|
||||
protected OkHttpClient httpClient;
|
||||
protected HttpUrl collectionURL;
|
||||
protected DavResource davCollection;
|
||||
|
||||
|
||||
/** remote CTag at the time of {@link #listRemote()} */
|
||||
protected String remoteCTag = null;
|
||||
|
||||
/** sync-able resources in the local collection, as enumerated by {@link #listLocal()} */
|
||||
protected Map<String, LocalResource> localResources;
|
||||
|
||||
/** sync-able resources in the remote collection, as enumerated by {@link #listRemote()} */
|
||||
protected Map<String, DavResource> remoteResources;
|
||||
|
||||
/** resources which have changed on the server, as determined by {@link #compareLocalRemote()} */
|
||||
protected Set<DavResource> toDownload;
|
||||
|
||||
|
||||
|
||||
public SyncManager(Context context, Account account, AccountSettings settings, Bundle extras, String authority, SyncResult syncResult, String uniqueCollectionId) throws InvalidAccountException {
|
||||
this.context = context;
|
||||
this.account = account;
|
||||
this.settings = settings;
|
||||
this.extras = extras;
|
||||
this.authority = authority;
|
||||
this.syncResult = syncResult;
|
||||
|
||||
// create HttpClient with given logger
|
||||
httpClient = HttpClient.create(context, account);
|
||||
|
||||
// dismiss previous error notifications
|
||||
this.uniqueCollectionId = uniqueCollectionId;
|
||||
notificationManager = NotificationManagerCompat.from(context);
|
||||
notificationManager.cancel(uniqueCollectionId, notificationId());
|
||||
}
|
||||
|
||||
protected abstract int notificationId();
|
||||
protected abstract String getSyncErrorTitle();
|
||||
|
||||
@TargetApi(21)
|
||||
public void performSync() {
|
||||
int syncPhase = SYNC_PHASE_PREPARE;
|
||||
try {
|
||||
App.log.info("Preparing synchronization");
|
||||
prepare();
|
||||
|
||||
if (Thread.interrupted())
|
||||
return;
|
||||
syncPhase = SYNC_PHASE_QUERY_CAPABILITIES;
|
||||
App.log.info("Querying capabilities");
|
||||
queryCapabilities();
|
||||
|
||||
syncPhase = SYNC_PHASE_PROCESS_LOCALLY_DELETED;
|
||||
App.log.info("Processing locally deleted entries");
|
||||
processLocallyDeleted();
|
||||
|
||||
if (Thread.interrupted())
|
||||
return;
|
||||
syncPhase = SYNC_PHASE_PREPARE_DIRTY;
|
||||
App.log.info("Locally preparing dirty entries");
|
||||
prepareDirty();
|
||||
|
||||
syncPhase = SYNC_PHASE_UPLOAD_DIRTY;
|
||||
App.log.info("Uploading dirty entries");
|
||||
uploadDirty();
|
||||
|
||||
syncPhase = SYNC_PHASE_CHECK_SYNC_STATE;
|
||||
App.log.info("Checking sync state");
|
||||
if (checkSyncState()) {
|
||||
syncPhase = SYNC_PHASE_LIST_LOCAL;
|
||||
App.log.info("Listing local entries");
|
||||
listLocal();
|
||||
|
||||
if (Thread.interrupted())
|
||||
return;
|
||||
syncPhase = SYNC_PHASE_LIST_REMOTE;
|
||||
App.log.info("Listing remote entries");
|
||||
listRemote();
|
||||
|
||||
if (Thread.interrupted())
|
||||
return;
|
||||
syncPhase = SYNC_PHASE_COMPARE_LOCAL_REMOTE;
|
||||
App.log.info("Comparing local/remote entries");
|
||||
compareLocalRemote();
|
||||
|
||||
syncPhase = SYNC_PHASE_DOWNLOAD_REMOTE;
|
||||
App.log.info("Downloading remote entries");
|
||||
downloadRemote();
|
||||
|
||||
syncPhase = SYNC_PHASE_POST_PROCESSING;
|
||||
App.log.info("Post-processing");
|
||||
postProcess();
|
||||
|
||||
syncPhase = SYNC_PHASE_SAVE_SYNC_STATE;
|
||||
App.log.info("Saving sync state");
|
||||
saveSyncState();
|
||||
} else
|
||||
App.log.info("Remote collection didn't change, skipping remote sync");
|
||||
|
||||
} catch (IOException|ServiceUnavailableException e) {
|
||||
App.log.log(Level.WARNING, "I/O exception during sync, trying again later", e);
|
||||
syncResult.stats.numIoExceptions++;
|
||||
|
||||
if (e instanceof ServiceUnavailableException) {
|
||||
Date retryAfter = ((ServiceUnavailableException) e).retryAfter;
|
||||
if (retryAfter != null) {
|
||||
// how many seconds to wait? getTime() returns ms, so divide by 1000
|
||||
syncResult.delayUntil = (retryAfter.getTime() - new Date().getTime()) / 1000;
|
||||
}
|
||||
}
|
||||
|
||||
} catch(Exception|OutOfMemoryError e) {
|
||||
final int messageString;
|
||||
|
||||
if (e instanceof UnauthorizedException) {
|
||||
App.log.log(Level.SEVERE, "Not authorized anymore", e);
|
||||
messageString = R.string.sync_error_unauthorized;
|
||||
syncResult.stats.numAuthExceptions++;
|
||||
} else if (e instanceof HttpException || e instanceof DavException) {
|
||||
App.log.log(Level.SEVERE, "HTTP/DAV Exception during sync", e);
|
||||
messageString = R.string.sync_error_http_dav;
|
||||
syncResult.stats.numParseExceptions++;
|
||||
} else if (e instanceof CalendarStorageException || e instanceof ContactsStorageException) {
|
||||
App.log.log(Level.SEVERE, "Couldn't access local storage", e);
|
||||
messageString = R.string.sync_error_local_storage;
|
||||
syncResult.databaseError = true;
|
||||
} else {
|
||||
App.log.log(Level.SEVERE, "Unknown sync error", e);
|
||||
messageString = R.string.sync_error;
|
||||
syncResult.stats.numParseExceptions++;
|
||||
}
|
||||
|
||||
final Intent detailsIntent;
|
||||
if (e instanceof UnauthorizedException) {
|
||||
detailsIntent = new Intent(context, AccountSettingsActivity.class);
|
||||
detailsIntent.putExtra(AccountSettingsActivity.EXTRA_ACCOUNT, account);
|
||||
} else {
|
||||
detailsIntent = new Intent(context, DebugInfoActivity.class);
|
||||
detailsIntent.putExtra(DebugInfoActivity.KEY_THROWABLE, e);
|
||||
detailsIntent.putExtra(DebugInfoActivity.KEY_ACCOUNT, account);
|
||||
detailsIntent.putExtra(DebugInfoActivity.KEY_AUTHORITY, authority);
|
||||
detailsIntent.putExtra(DebugInfoActivity.KEY_PHASE, syncPhase);
|
||||
}
|
||||
|
||||
// to make the PendingIntent unique
|
||||
detailsIntent.setData(Uri.parse("uri://" + getClass().getName() + "/" + uniqueCollectionId));
|
||||
|
||||
NotificationCompat.Builder builder = new NotificationCompat.Builder(context);
|
||||
builder .setSmallIcon(R.drawable.ic_error_light)
|
||||
.setLargeIcon(App.getLauncherBitmap(context))
|
||||
.setContentTitle(getSyncErrorTitle())
|
||||
.setContentIntent(PendingIntent.getActivity(context, 0, detailsIntent, PendingIntent.FLAG_CANCEL_CURRENT))
|
||||
.setCategory(NotificationCompat.CATEGORY_ERROR);
|
||||
|
||||
try {
|
||||
String[] phases = context.getResources().getStringArray(R.array.sync_error_phases);
|
||||
String message = context.getString(messageString, phases[syncPhase]);
|
||||
builder.setContentText(message);
|
||||
} catch (IndexOutOfBoundsException ex) {
|
||||
// should never happen
|
||||
}
|
||||
|
||||
notificationManager.notify(uniqueCollectionId, notificationId(), builder.build());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
abstract protected void prepare() throws ContactsStorageException;
|
||||
|
||||
abstract protected void queryCapabilities() throws IOException, HttpException, DavException, CalendarStorageException, ContactsStorageException;
|
||||
|
||||
/**
|
||||
* Process locally deleted entries (DELETE them on the server as well).
|
||||
* Checks Thread.interrupted() before each request to allow quick sync cancellation.
|
||||
*/
|
||||
protected void processLocallyDeleted() throws CalendarStorageException, ContactsStorageException {
|
||||
// Remove locally deleted entries from server (if they have a name, i.e. if they were uploaded before),
|
||||
// but only if they don't have changed on the server. Then finally remove them from the local address book.
|
||||
LocalResource[] localList = localCollection.getDeleted();
|
||||
for (LocalResource local : localList) {
|
||||
if (Thread.interrupted())
|
||||
return;
|
||||
|
||||
final String fileName = local.getFileName();
|
||||
if (!TextUtils.isEmpty(fileName)) {
|
||||
App.log.info(fileName + " has been deleted locally -> deleting from server");
|
||||
try {
|
||||
new DavResource(httpClient, collectionURL.newBuilder().addPathSegment(fileName).build())
|
||||
.delete(local.getETag());
|
||||
} catch (IOException|HttpException e) {
|
||||
App.log.warning("Couldn't delete " + fileName + " from server; ignoring (may be downloaded again)");
|
||||
}
|
||||
} else
|
||||
App.log.info("Removing local record #" + local.getId() + " which has been deleted locally and was never uploaded");
|
||||
local.delete();
|
||||
syncResult.stats.numDeletes++;
|
||||
}
|
||||
}
|
||||
|
||||
protected void prepareDirty() throws CalendarStorageException, ContactsStorageException {
|
||||
// assign file names and UIDs to new contacts so that we can use the file name as an index
|
||||
App.log.info("Looking for contacts/groups without file name");
|
||||
for (LocalResource local : localCollection.getWithoutFileName()) {
|
||||
String uuid = UUID.randomUUID().toString();
|
||||
App.log.fine("Found local record #" + local.getId() + " without file name; assigning file name/UID based on " + uuid);
|
||||
local.updateFileNameAndUID(uuid);
|
||||
}
|
||||
}
|
||||
|
||||
abstract protected RequestBody prepareUpload(LocalResource resource) throws IOException, CalendarStorageException, ContactsStorageException;
|
||||
|
||||
/**
|
||||
* Uploads dirty records to the server, using a PUT request for each record.
|
||||
* Checks Thread.interrupted() before each request to allow quick sync cancellation.
|
||||
*/
|
||||
protected void uploadDirty() throws IOException, HttpException, CalendarStorageException, ContactsStorageException {
|
||||
// upload dirty contacts
|
||||
for (LocalResource local : localCollection.getDirty()) {
|
||||
if (Thread.interrupted())
|
||||
return;
|
||||
|
||||
final String fileName = local.getFileName();
|
||||
|
||||
DavResource remote = new DavResource(httpClient, collectionURL.newBuilder().addPathSegment(fileName).build());
|
||||
|
||||
// generate entity to upload (VCard, iCal, whatever)
|
||||
RequestBody body = prepareUpload(local);
|
||||
|
||||
try {
|
||||
if (local.getETag() == null) {
|
||||
App.log.info("Uploading new record " + fileName);
|
||||
remote.put(body, null, true);
|
||||
} else {
|
||||
App.log.info("Uploading locally modified record " + fileName);
|
||||
remote.put(body, local.getETag(), false);
|
||||
}
|
||||
} catch (ConflictException|PreconditionFailedException e) {
|
||||
// we can't interact with the user to resolve the conflict, so we treat 409 like 412
|
||||
App.log.log(Level.INFO, "Resource has been modified on the server before upload, ignoring", e);
|
||||
}
|
||||
|
||||
String eTag = null;
|
||||
GetETag newETag = (GetETag) remote.properties.get(GetETag.NAME);
|
||||
if (newETag != null) {
|
||||
eTag = newETag.eTag;
|
||||
App.log.fine("Received new ETag=" + eTag + " after uploading");
|
||||
} else
|
||||
App.log.fine("Didn't receive new ETag after uploading, setting to null");
|
||||
|
||||
local.clearDirty(eTag);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the current sync state (e.g. CTag) and whether synchronization from remote is required.
|
||||
* @return <ul>
|
||||
* <li><code>true</code> if the remote collection has changed, i.e. synchronization from remote is required</li>
|
||||
* <li><code>false</code> if the remote collection hasn't changed</li>
|
||||
* </ul>
|
||||
*/
|
||||
protected boolean checkSyncState() throws CalendarStorageException, ContactsStorageException {
|
||||
// check CTag (ignore on manual sync)
|
||||
GetCTag getCTag = (GetCTag)davCollection.properties.get(GetCTag.NAME);
|
||||
if (getCTag != null)
|
||||
remoteCTag = getCTag.cTag;
|
||||
|
||||
String localCTag = null;
|
||||
if (extras.containsKey(ContentResolver.SYNC_EXTRAS_MANUAL))
|
||||
App.log.info("Manual sync, ignoring CTag");
|
||||
else
|
||||
localCTag = localCollection.getCTag();
|
||||
|
||||
if (remoteCTag != null && remoteCTag.equals(localCTag)) {
|
||||
App.log.info("Remote collection didn't change (CTag=" + remoteCTag + "), no need to query children");
|
||||
return false;
|
||||
} else
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lists all local resources which should be taken into account for synchronization into {@link #localResources}.
|
||||
*/
|
||||
protected void listLocal() throws CalendarStorageException, ContactsStorageException {
|
||||
// fetch list of local contacts and build hash table to index file name
|
||||
LocalResource[] localList = localCollection.getAll();
|
||||
localResources = new HashMap<>(localList.length);
|
||||
for (LocalResource resource : localList) {
|
||||
App.log.fine("Found local resource: " + resource.getFileName());
|
||||
localResources.put(resource.getFileName(), resource);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Lists all members of the remote collection which should be taken into account for synchronization into {@link #remoteResources}.
|
||||
*/
|
||||
abstract protected void listRemote() throws IOException, HttpException, DavException;
|
||||
|
||||
/**
|
||||
* Compares {@link #localResources} and {@link #remoteResources} by file name and ETag:
|
||||
* <ul>
|
||||
* <li>Local resources which are not available in the remote collection (anymore) will be removed.</li>
|
||||
* <li>Resources whose remote ETag has changed will be added into {@link #toDownload}</li>
|
||||
* </ul>
|
||||
*/
|
||||
protected void compareLocalRemote() throws IOException, HttpException, DavException, CalendarStorageException, ContactsStorageException {
|
||||
/* check which contacts
|
||||
1. are not present anymore remotely -> delete immediately on local side
|
||||
2. updated remotely -> add to downloadNames
|
||||
3. added remotely -> add to downloadNames
|
||||
*/
|
||||
toDownload = new HashSet<>();
|
||||
for (String localName : localResources.keySet()) {
|
||||
DavResource remote = remoteResources.get(localName);
|
||||
if (remote == null) {
|
||||
App.log.info(localName + " is not on server anymore, deleting");
|
||||
localResources.get(localName).delete();
|
||||
syncResult.stats.numDeletes++;
|
||||
} else {
|
||||
// contact is still on server, check whether it has been updated remotely
|
||||
GetETag getETag = (GetETag)remote.properties.get(GetETag.NAME);
|
||||
if (getETag == null || getETag.eTag == null)
|
||||
throw new DavException("Server didn't provide ETag");
|
||||
String localETag = localResources.get(localName).getETag(),
|
||||
remoteETag = getETag.eTag;
|
||||
if (remoteETag.equals(localETag))
|
||||
syncResult.stats.numSkippedEntries++;
|
||||
else {
|
||||
App.log.info(localName + " has been changed on server (current ETag=" + remoteETag + ", last known ETag=" + localETag + ")");
|
||||
toDownload.add(remote);
|
||||
}
|
||||
|
||||
// remote entry has been seen, remove from list
|
||||
remoteResources.remove(localName);
|
||||
}
|
||||
}
|
||||
|
||||
// add all unseen (= remotely added) remote contacts
|
||||
if (!remoteResources.isEmpty()) {
|
||||
App.log.info("New resources have been found on the server: " + TextUtils.join(", ", remoteResources.keySet()));
|
||||
toDownload.addAll(remoteResources.values());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Downloads the remote resources in {@link #toDownload} and stores them locally.
|
||||
* Must check Thread.interrupted() periodically to allow quick sync cancellation.
|
||||
*/
|
||||
abstract protected void downloadRemote() throws IOException, HttpException, DavException, ContactsStorageException, CalendarStorageException;
|
||||
|
||||
/**
|
||||
* For post-processing of entries, for instance assigning groups.
|
||||
*/
|
||||
protected void postProcess() throws CalendarStorageException, ContactsStorageException {
|
||||
}
|
||||
|
||||
protected void saveSyncState() throws CalendarStorageException, ContactsStorageException {
|
||||
/* Save sync state (CTag). It doesn't matter if it has changed during the sync process
|
||||
(for instance, because another client has uploaded changes), because this will simply
|
||||
cause all remote entries to be listed at the next sync. */
|
||||
App.log.info("Saving CTag=" + remoteCTag);
|
||||
localCollection.setCTag(remoteCTag);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
/*
|
||||
* Copyright © 2013 – 2015 Ricki Hirner (bitfire web engineering).
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the GNU Public License v3.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.gnu.org/licenses/gpl.html
|
||||
*/
|
||||
package at.bitfire.davdroid.syncadapter;
|
||||
|
||||
import android.accounts.Account;
|
||||
import android.content.AbstractThreadedSyncAdapter;
|
||||
import android.content.ContentProviderClient;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.content.SyncResult;
|
||||
import android.database.Cursor;
|
||||
import android.database.DatabaseUtils;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import org.dmfs.provider.tasks.TaskContract;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import at.bitfire.davdroid.AccountSettings;
|
||||
import at.bitfire.davdroid.App;
|
||||
import at.bitfire.davdroid.InvalidAccountException;
|
||||
import at.bitfire.davdroid.model.CollectionInfo;
|
||||
import at.bitfire.davdroid.model.ServiceDB;
|
||||
import at.bitfire.davdroid.model.ServiceDB.Collections;
|
||||
import at.bitfire.davdroid.model.ServiceDB.Services;
|
||||
import at.bitfire.davdroid.resource.LocalTaskList;
|
||||
import at.bitfire.ical4android.CalendarStorageException;
|
||||
import at.bitfire.ical4android.TaskProvider;
|
||||
import lombok.Cleanup;
|
||||
|
||||
/**
|
||||
* Synchronization manager for CalDAV collections; handles tasks ({@code VTODO}).
|
||||
*/
|
||||
public class TasksSyncAdapterService extends SyncAdapterService {
|
||||
|
||||
@Override
|
||||
protected AbstractThreadedSyncAdapter syncAdapter() {
|
||||
return new SyncAdapter(this);
|
||||
}
|
||||
|
||||
|
||||
private static class SyncAdapter extends SyncAdapterService.SyncAdapter {
|
||||
|
||||
public SyncAdapter(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sync(Account account, Bundle extras, String authority, ContentProviderClient providerClient, SyncResult syncResult) {
|
||||
try {
|
||||
@Cleanup TaskProvider provider = TaskProvider.acquire(getContext().getContentResolver(), TaskProvider.ProviderName.OpenTasks);
|
||||
if (provider == null)
|
||||
throw new CalendarStorageException("Couldn't access OpenTasks provider");
|
||||
|
||||
AccountSettings settings = new AccountSettings(getContext(), account);
|
||||
if (!extras.containsKey(ContentResolver.SYNC_EXTRAS_MANUAL) && !checkSyncConditions(settings))
|
||||
return;
|
||||
|
||||
updateLocalTaskLists(provider, account, settings);
|
||||
|
||||
for (LocalTaskList taskList : (LocalTaskList[])LocalTaskList.find(account, provider, LocalTaskList.Factory.INSTANCE, TaskContract.TaskLists.SYNC_ENABLED + "!=0", null)) {
|
||||
App.log.info("Synchronizing task list #" + taskList.getId() + " [" + taskList.getSyncId() + "]");
|
||||
TasksSyncManager syncManager = new TasksSyncManager(getContext(), account, settings, extras, authority, provider, syncResult, taskList);
|
||||
syncManager.performSync();
|
||||
}
|
||||
} catch (CalendarStorageException e) {
|
||||
App.log.log(Level.SEVERE, "Couldn't enumerate local task lists", e);
|
||||
} catch (InvalidAccountException e) {
|
||||
App.log.log(Level.SEVERE, "Couldn't get account settings", e);
|
||||
}
|
||||
|
||||
App.log.info("Task sync complete");
|
||||
}
|
||||
|
||||
private void updateLocalTaskLists(TaskProvider provider, Account account, AccountSettings settings) throws CalendarStorageException {
|
||||
SQLiteOpenHelper dbHelper = new ServiceDB.OpenHelper(getContext());
|
||||
try {
|
||||
// enumerate remote and local task lists
|
||||
SQLiteDatabase db = dbHelper.getReadableDatabase();
|
||||
Long service = getService(db, account);
|
||||
Map<String, CollectionInfo> remote = remoteTaskLists(db, service);
|
||||
LocalTaskList[] local = (LocalTaskList[])LocalTaskList.find(account, provider, LocalTaskList.Factory.INSTANCE, null, null);
|
||||
|
||||
boolean updateColors = settings.getManageCalendarColors();
|
||||
|
||||
// delete obsolete local task lists
|
||||
for (LocalTaskList list : local) {
|
||||
String url = list.getSyncId();
|
||||
if (!remote.containsKey(url)) {
|
||||
App.log.fine("Deleting obsolete local task list" + url);
|
||||
list.delete();
|
||||
} else {
|
||||
// remote CollectionInfo found for this local collection, update data
|
||||
CollectionInfo info = remote.get(url);
|
||||
App.log.fine("Updating local task list " + url + " with " + info);
|
||||
list.update(info, updateColors);
|
||||
// we already have a local task list for this remote collection, don't take into consideration anymore
|
||||
remote.remove(url);
|
||||
}
|
||||
}
|
||||
|
||||
// create new local task lists
|
||||
for (String url : remote.keySet()) {
|
||||
CollectionInfo info = remote.get(url);
|
||||
App.log.info("Adding local task list " + info);
|
||||
LocalTaskList.create(account, provider, info);
|
||||
}
|
||||
} finally {
|
||||
dbHelper.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
Long getService(@NonNull SQLiteDatabase db, @NonNull Account account) {
|
||||
@Cleanup Cursor c = db.query(Services._TABLE, new String[] { Services.ID },
|
||||
Services.ACCOUNT_NAME + "=? AND " + Services.SERVICE + "=?", new String[] { account.name, Services.SERVICE_CALDAV }, null, null, null);
|
||||
if (c.moveToNext())
|
||||
return c.getLong(0);
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private Map<String, CollectionInfo> remoteTaskLists(@NonNull SQLiteDatabase db, Long service) {
|
||||
Map<String, CollectionInfo> collections = new LinkedHashMap<>();
|
||||
if (service != null) {
|
||||
@Cleanup Cursor cursor = db.query(Collections._TABLE, null,
|
||||
Collections.SERVICE_ID + "=? AND " + Collections.SUPPORTS_VTODO + "!=0 AND " + Collections.SYNC,
|
||||
new String[] { String.valueOf(service) }, null, null, null);
|
||||
while (cursor.moveToNext()) {
|
||||
ContentValues values = new ContentValues();
|
||||
DatabaseUtils.cursorRowToContentValues(cursor, values);
|
||||
CollectionInfo info = CollectionInfo.fromDB(values);
|
||||
collections.put(info.url, info);
|
||||
}
|
||||
}
|
||||
return collections;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,217 @@
|
||||
/*
|
||||
* Copyright © 2013 – 2015 Ricki Hirner (bitfire web engineering).
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the GNU Public License v3.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.gnu.org/licenses/gpl.html
|
||||
*/
|
||||
|
||||
package at.bitfire.davdroid.syncadapter;
|
||||
|
||||
import android.accounts.Account;
|
||||
import android.content.Context;
|
||||
import android.content.SyncResult;
|
||||
import android.os.Bundle;
|
||||
|
||||
import org.apache.commons.codec.Charsets;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import at.bitfire.dav4android.DavCalendar;
|
||||
import at.bitfire.dav4android.DavResource;
|
||||
import at.bitfire.dav4android.exception.DavException;
|
||||
import at.bitfire.dav4android.exception.HttpException;
|
||||
import at.bitfire.dav4android.property.CalendarData;
|
||||
import at.bitfire.dav4android.property.GetCTag;
|
||||
import at.bitfire.dav4android.property.GetContentType;
|
||||
import at.bitfire.dav4android.property.GetETag;
|
||||
import at.bitfire.davdroid.AccountSettings;
|
||||
import at.bitfire.davdroid.App;
|
||||
import at.bitfire.davdroid.ArrayUtils;
|
||||
import at.bitfire.davdroid.Constants;
|
||||
import at.bitfire.davdroid.InvalidAccountException;
|
||||
import at.bitfire.davdroid.R;
|
||||
import at.bitfire.davdroid.resource.LocalResource;
|
||||
import at.bitfire.davdroid.resource.LocalTask;
|
||||
import at.bitfire.davdroid.resource.LocalTaskList;
|
||||
import at.bitfire.ical4android.CalendarStorageException;
|
||||
import at.bitfire.ical4android.InvalidCalendarException;
|
||||
import at.bitfire.ical4android.Task;
|
||||
import at.bitfire.ical4android.TaskProvider;
|
||||
import lombok.Cleanup;
|
||||
import okhttp3.HttpUrl;
|
||||
import okhttp3.MediaType;
|
||||
import okhttp3.RequestBody;
|
||||
import okhttp3.ResponseBody;
|
||||
|
||||
public class TasksSyncManager extends SyncManager {
|
||||
|
||||
protected static final int MAX_MULTIGET = 30;
|
||||
|
||||
final protected TaskProvider provider;
|
||||
|
||||
|
||||
public TasksSyncManager(Context context, Account account, AccountSettings settings, Bundle extras, String authority, TaskProvider provider, SyncResult result, LocalTaskList taskList) throws InvalidAccountException {
|
||||
super(context, account, settings, extras, authority, result, "taskList/" + taskList.getId());
|
||||
this.provider = provider;
|
||||
localCollection = taskList;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int notificationId() {
|
||||
return Constants.NOTIFICATION_TASK_SYNC;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getSyncErrorTitle() {
|
||||
return context.getString(R.string.sync_error_tasks, account.name);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void prepare() {
|
||||
collectionURL = HttpUrl.parse(localTaskList().getSyncId());
|
||||
davCollection = new DavCalendar(httpClient, collectionURL);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void queryCapabilities() throws DavException, IOException, HttpException {
|
||||
davCollection.propfind(0, GetCTag.NAME);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RequestBody prepareUpload(LocalResource resource) throws IOException, CalendarStorageException {
|
||||
LocalTask local = (LocalTask)resource;
|
||||
App.log.log(Level.FINE, "Preparing upload of task " + local.getFileName(), local.getTask() );
|
||||
|
||||
ByteArrayOutputStream os = new ByteArrayOutputStream();
|
||||
local.getTask().write(os);
|
||||
|
||||
return RequestBody.create(
|
||||
DavCalendar.MIME_ICALENDAR,
|
||||
os.toByteArray()
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void listRemote() throws IOException, HttpException, DavException {
|
||||
// fetch list of remote VTODOs and build hash table to index file name
|
||||
davCalendar().calendarQuery("VTODO", null, null);
|
||||
remoteResources = new HashMap<>(davCollection.members.size());
|
||||
for (DavResource vCard : davCollection.members) {
|
||||
String fileName = vCard.fileName();
|
||||
App.log.fine("Found remote VTODO: " + fileName);
|
||||
remoteResources.put(fileName, vCard);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void downloadRemote() throws IOException, HttpException, DavException, CalendarStorageException {
|
||||
App.log.info("Downloading " + toDownload.size() + " tasks (" + MAX_MULTIGET + " at once)");
|
||||
|
||||
// download new/updated iCalendars from server
|
||||
for (DavResource[] bunch : ArrayUtils.partition(toDownload.toArray(new DavResource[toDownload.size()]), MAX_MULTIGET)) {
|
||||
if (Thread.interrupted())
|
||||
return;
|
||||
|
||||
App.log.info("Downloading " + StringUtils.join(bunch, ", "));
|
||||
|
||||
if (bunch.length == 1) {
|
||||
// only one contact, use GET
|
||||
DavResource remote = bunch[0];
|
||||
|
||||
ResponseBody body = remote.get("text/calendar");
|
||||
|
||||
// CalDAV servers MUST return ETag on GET [https://tools.ietf.org/html/rfc4791#section-5.3.4]
|
||||
GetETag eTag = (GetETag)remote.properties.get(GetETag.NAME);
|
||||
if (eTag == null || StringUtils.isEmpty(eTag.eTag))
|
||||
throw new DavException("Received CalDAV GET response without ETag for " + remote.location);
|
||||
|
||||
Charset charset = Charsets.UTF_8;
|
||||
MediaType contentType = body.contentType();
|
||||
if (contentType != null)
|
||||
charset = contentType.charset(Charsets.UTF_8);
|
||||
|
||||
@Cleanup InputStream stream = body.byteStream();
|
||||
processVTodo(remote.fileName(), eTag.eTag, stream, charset);
|
||||
|
||||
} else {
|
||||
// multiple contacts, use multi-get
|
||||
List<HttpUrl> urls = new LinkedList<>();
|
||||
for (DavResource remote : bunch)
|
||||
urls.add(remote.location);
|
||||
davCalendar().multiget(urls.toArray(new HttpUrl[urls.size()]));
|
||||
|
||||
// process multiget results
|
||||
for (DavResource remote : davCollection.members) {
|
||||
String eTag;
|
||||
GetETag getETag = (GetETag)remote.properties.get(GetETag.NAME);
|
||||
if (getETag != null)
|
||||
eTag = getETag.eTag;
|
||||
else
|
||||
throw new DavException("Received multi-get response without ETag");
|
||||
|
||||
Charset charset = Charsets.UTF_8;
|
||||
GetContentType getContentType = (GetContentType)remote.properties.get(GetContentType.NAME);
|
||||
if (getContentType != null && getContentType.type != null) {
|
||||
MediaType type = MediaType.parse(getContentType.type);
|
||||
if (type != null)
|
||||
charset = type.charset(Charsets.UTF_8);
|
||||
}
|
||||
|
||||
CalendarData calendarData = (CalendarData)remote.properties.get(CalendarData.NAME);
|
||||
if (calendarData == null || calendarData.iCalendar == null)
|
||||
throw new DavException("Received multi-get response without address data");
|
||||
|
||||
@Cleanup InputStream stream = new ByteArrayInputStream(calendarData.iCalendar.getBytes());
|
||||
processVTodo(remote.fileName(), eTag, stream, charset);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// helpers
|
||||
|
||||
private LocalTaskList localTaskList() { return ((LocalTaskList)localCollection); }
|
||||
private DavCalendar davCalendar() { return (DavCalendar)davCollection; }
|
||||
|
||||
private void processVTodo(String fileName, String eTag, InputStream stream, Charset charset) throws IOException, CalendarStorageException {
|
||||
Task[] tasks;
|
||||
try {
|
||||
tasks = Task.fromStream(stream, charset);
|
||||
} catch (InvalidCalendarException e) {
|
||||
App.log.log(Level.SEVERE, "Received invalid iCalendar, ignoring", e);
|
||||
return;
|
||||
}
|
||||
|
||||
if (tasks.length == 1) {
|
||||
Task newData = tasks[0];
|
||||
|
||||
// update local task, if it exists
|
||||
LocalTask localTask = (LocalTask)localResources.get(fileName);
|
||||
if (localTask != null) {
|
||||
App.log.info("Updating " + fileName + " in local tasklist");
|
||||
localTask.setETag(eTag);
|
||||
localTask.update(newData);
|
||||
syncResult.stats.numUpdates++;
|
||||
} else {
|
||||
App.log.info("Adding " + fileName + " to local task list");
|
||||
localTask = new LocalTask(localTaskList(), newData, fileName, eTag);
|
||||
localTask.add();
|
||||
syncResult.stats.numInserts++;
|
||||
}
|
||||
} else
|
||||
App.log.severe("Received VCALENDAR with not exactly one VTODO; ignoring " + fileName);
|
||||
}
|
||||
|
||||
}
|
||||
226
app/src/main/java/at/bitfire/davdroid/ui/AboutActivity.java
Normal file
226
app/src/main/java/at/bitfire/davdroid/ui/AboutActivity.java
Normal file
@@ -0,0 +1,226 @@
|
||||
/*
|
||||
* Copyright © 2013 – 2016 Ricki Hirner (bitfire web engineering).
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the GNU Public License v3.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.gnu.org/licenses/gpl.html
|
||||
*/
|
||||
|
||||
package at.bitfire.davdroid.ui;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.design.widget.TabLayout;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.app.FragmentManager;
|
||||
import android.support.v4.app.FragmentPagerAdapter;
|
||||
import android.support.v4.app.LoaderManager;
|
||||
import android.support.v4.content.AsyncTaskLoader;
|
||||
import android.support.v4.content.Loader;
|
||||
import android.support.v4.view.ViewPager;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import android.text.Html;
|
||||
import android.text.Spanned;
|
||||
import android.text.util.Linkify;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.apache.commons.lang3.time.DateFormatUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import at.bitfire.davdroid.App;
|
||||
import at.bitfire.davdroid.BuildConfig;
|
||||
import at.bitfire.davdroid.R;
|
||||
import ezvcard.Ezvcard;
|
||||
import ezvcard.util.IOUtils;
|
||||
import lombok.Cleanup;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
public class AboutActivity extends AppCompatActivity {
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_about);
|
||||
|
||||
setSupportActionBar((Toolbar)findViewById(R.id.toolbar));
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
|
||||
ViewPager viewPager = (ViewPager)findViewById(R.id.viewpager);
|
||||
viewPager.setAdapter(new TabsAdapter(getSupportFragmentManager()));
|
||||
|
||||
TabLayout tabLayout = (TabLayout)findViewById(R.id.tabs);
|
||||
tabLayout.setupWithViewPager(viewPager);
|
||||
}
|
||||
|
||||
|
||||
@RequiredArgsConstructor
|
||||
private static class ComponentInfo {
|
||||
final String title, version, website, copyright;
|
||||
final Integer licenseInfo;
|
||||
final String licenseTextFile;
|
||||
}
|
||||
|
||||
private final static ComponentInfo components[] = {
|
||||
new ComponentInfo(
|
||||
null, BuildConfig.VERSION_NAME, "https://davdroid.bitfire.at",
|
||||
DateFormatUtils.format(BuildConfig.buildTime, "yyyy") + " Ricki Hirner, Bernhard Stockmann (bitfire web engineering)",
|
||||
null, null
|
||||
), new ComponentInfo(
|
||||
"AmbilWarna", null, "https://github.com/yukuku/ambilwarna",
|
||||
"Yuku", R.string.about_license_info_no_warranty, "apache2.html"
|
||||
), new ComponentInfo(
|
||||
"Apache Commons", null, "http://commons.apache.org/",
|
||||
"Apache Software Foundation", R.string.about_license_info_no_warranty, "apache2.html"
|
||||
), new ComponentInfo(
|
||||
"dnsjava", null, "http://dnsjava.org/",
|
||||
"Brian Wellington", R.string.about_license_info_no_warranty, "bsd.html"
|
||||
), new ComponentInfo(
|
||||
"ez-vcard", Ezvcard.VERSION, "https://github.com/mangstadt/ez-vcard",
|
||||
"Michael Angstadt", R.string.about_license_info_no_warranty, "bsd.html"
|
||||
), new ComponentInfo(
|
||||
"ical4j", "2.x", "https://ical4j.github.io/",
|
||||
"Ben Fortuna", R.string.about_license_info_no_warranty, "bsd-3clause.html"
|
||||
), new ComponentInfo(
|
||||
"OkHttp", null, "https://square.github.io/okhttp/",
|
||||
"Square, Inc.", R.string.about_license_info_no_warranty, "apache2.html"
|
||||
), new ComponentInfo(
|
||||
"Project Lombok", null, "https://projectlombok.org/",
|
||||
"The Project Lombok Authors", R.string.about_license_info_no_warranty, "mit.html"
|
||||
)
|
||||
};
|
||||
|
||||
|
||||
private class TabsAdapter extends FragmentPagerAdapter {
|
||||
public TabsAdapter(FragmentManager fm) {
|
||||
super(fm);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
return components.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence getPageTitle(int position) {
|
||||
return components[position].title != null ? components[position].title : getString(R.string.app_name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Fragment getItem(int position) {
|
||||
return ComponentFragment.instantiate(position);
|
||||
}
|
||||
}
|
||||
|
||||
public static class ComponentFragment extends Fragment implements LoaderManager.LoaderCallbacks<Spanned> {
|
||||
private static final String
|
||||
KEY_POSITION = "position",
|
||||
KEY_FILE_NAME = "fileName";
|
||||
|
||||
public static ComponentFragment instantiate(int position) {
|
||||
ComponentFragment frag = new ComponentFragment();
|
||||
Bundle args = new Bundle(1);
|
||||
args.putInt(KEY_POSITION, position);
|
||||
frag.setArguments(args);
|
||||
return frag;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
@SuppressLint("SetTextI18n")
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
ComponentInfo info = components[getArguments().getInt(KEY_POSITION)];
|
||||
|
||||
View v = inflater.inflate(R.layout.about_component, container, false);
|
||||
|
||||
TextView tv = (TextView)v.findViewById(R.id.title);
|
||||
tv.setText((info.title == null ? getString(R.string.app_name) : info.title) +
|
||||
(info.version != null ? (" " + info.version) : ""));
|
||||
|
||||
tv = (TextView)v.findViewById(R.id.website);
|
||||
tv.setAutoLinkMask(Linkify.WEB_URLS);
|
||||
tv.setText(info.website);
|
||||
|
||||
tv = (TextView)v.findViewById(R.id.copyright);
|
||||
tv.setText("© " + info.copyright);
|
||||
|
||||
tv = (TextView)v.findViewById(R.id.license_info);
|
||||
if (info.licenseInfo == null)
|
||||
tv.setVisibility(View.GONE);
|
||||
else
|
||||
tv.setText(info.licenseInfo);
|
||||
|
||||
// load and format license text
|
||||
if (info.licenseTextFile == null) {
|
||||
v.findViewById(R.id.license_header).setVisibility(View.GONE);
|
||||
v.findViewById(R.id.license_text).setVisibility(View.GONE);
|
||||
} else {
|
||||
Bundle args = new Bundle(1);
|
||||
args.putString(KEY_FILE_NAME, info.licenseTextFile);
|
||||
getLoaderManager().initLoader(0, args, this);
|
||||
}
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Loader<Spanned> onCreateLoader(int id, Bundle args) {
|
||||
return new LicenseLoader(getContext(), args.getString(KEY_FILE_NAME));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadFinished(Loader<Spanned> loader, Spanned license) {
|
||||
if (getView() != null) {
|
||||
TextView tv = (TextView)getView().findViewById(R.id.license_text);
|
||||
if (tv != null) {
|
||||
tv.setAutoLinkMask(Linkify.EMAIL_ADDRESSES | Linkify.WEB_URLS);
|
||||
tv.setText(license);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoaderReset(Loader<Spanned> loader) {
|
||||
}
|
||||
}
|
||||
|
||||
private static class LicenseLoader extends AsyncTaskLoader<Spanned> {
|
||||
final String fileName;
|
||||
Spanned content;
|
||||
|
||||
LicenseLoader(Context context, String fileName) {
|
||||
super(context);
|
||||
this.fileName = fileName;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStartLoading() {
|
||||
if (content == null)
|
||||
forceLoad();
|
||||
else
|
||||
deliverResult(content);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Spanned loadInBackground() {
|
||||
App.log.fine("Loading license file " + fileName);
|
||||
try {
|
||||
@Cleanup InputStream is = getContext().getResources().getAssets().open(fileName);
|
||||
byte[] raw = IOUtils.toByteArray(is);
|
||||
return content = Html.fromHtml(new String(raw));
|
||||
} catch (IOException e) {
|
||||
App.log.log(Level.SEVERE, "Couldn't read license file", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
598
app/src/main/java/at/bitfire/davdroid/ui/AccountActivity.java
Normal file
598
app/src/main/java/at/bitfire/davdroid/ui/AccountActivity.java
Normal file
@@ -0,0 +1,598 @@
|
||||
/*
|
||||
* Copyright © 2013 – 2016 Ricki Hirner (bitfire web engineering).
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the GNU Public License v3.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.gnu.org/licenses/gpl.html
|
||||
*/
|
||||
|
||||
package at.bitfire.davdroid.ui;
|
||||
|
||||
import android.accounts.Account;
|
||||
import android.accounts.AccountManager;
|
||||
import android.accounts.AccountManagerCallback;
|
||||
import android.accounts.AccountManagerFuture;
|
||||
import android.accounts.AuthenticatorException;
|
||||
import android.accounts.OperationCanceledException;
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.LoaderManager;
|
||||
import android.content.AsyncTaskLoader;
|
||||
import android.content.ComponentName;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.Loader;
|
||||
import android.content.ServiceConnection;
|
||||
import android.content.SyncStatusObserver;
|
||||
import android.database.Cursor;
|
||||
import android.database.DatabaseUtils;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
import android.provider.CalendarContract;
|
||||
import android.provider.ContactsContract;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.design.widget.Snackbar;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.support.v7.widget.CardView;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import android.text.TextUtils;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.AbsListView;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.ListView;
|
||||
import android.widget.PopupMenu;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.RadioButton;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.apache.commons.lang3.BooleanUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import at.bitfire.cert4android.CustomCertManager;
|
||||
import at.bitfire.davdroid.App;
|
||||
import at.bitfire.davdroid.DavService;
|
||||
import at.bitfire.davdroid.R;
|
||||
import at.bitfire.davdroid.model.CollectionInfo;
|
||||
import at.bitfire.davdroid.model.ServiceDB;
|
||||
import at.bitfire.davdroid.model.ServiceDB.Collections;
|
||||
import at.bitfire.davdroid.model.ServiceDB.OpenHelper;
|
||||
import at.bitfire.davdroid.model.ServiceDB.Services;
|
||||
import at.bitfire.ical4android.TaskProvider;
|
||||
import lombok.Cleanup;
|
||||
|
||||
import static android.content.ContentResolver.SYNC_OBSERVER_TYPE_ACTIVE;
|
||||
|
||||
public class AccountActivity extends AppCompatActivity implements Toolbar.OnMenuItemClickListener, PopupMenu.OnMenuItemClickListener, LoaderManager.LoaderCallbacks<AccountActivity.AccountInfo> {
|
||||
public static final String EXTRA_ACCOUNT = "account";
|
||||
|
||||
private Account account;
|
||||
private AccountInfo accountInfo;
|
||||
|
||||
ListView listCalDAV, listCardDAV;
|
||||
Toolbar tbCardDAV, tbCalDAV;
|
||||
|
||||
@Override
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
account = getIntent().getParcelableExtra(EXTRA_ACCOUNT);
|
||||
setTitle(account.name);
|
||||
|
||||
setContentView(R.layout.activity_account);
|
||||
|
||||
Drawable icMenu = Build.VERSION.SDK_INT >= 21 ? getDrawable(R.drawable.ic_menu_light) :
|
||||
getResources().getDrawable(R.drawable.ic_menu_light);
|
||||
|
||||
// CardDAV toolbar
|
||||
tbCardDAV = (Toolbar)findViewById(R.id.carddav_menu);
|
||||
tbCardDAV.setOverflowIcon(icMenu);
|
||||
tbCardDAV.inflateMenu(R.menu.carddav_actions);
|
||||
tbCardDAV.setOnMenuItemClickListener(this);
|
||||
|
||||
// CalDAV toolbar
|
||||
tbCalDAV = (Toolbar)findViewById(R.id.caldav_menu);
|
||||
tbCalDAV.setOverflowIcon(icMenu);
|
||||
tbCalDAV.inflateMenu(R.menu.caldav_actions);
|
||||
tbCalDAV.setOnMenuItemClickListener(this);
|
||||
|
||||
// load CardDAV/CalDAV collections
|
||||
getLoaderManager().initLoader(0, getIntent().getExtras(), this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
CustomCertManager certManager = ((App)getApplicationContext()).getCertManager();
|
||||
if (certManager != null)
|
||||
certManager.appInForeground = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
CustomCertManager certManager = ((App)getApplicationContext()).getCertManager();
|
||||
if (certManager != null)
|
||||
certManager.appInForeground = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
getMenuInflater().inflate(R.menu.activity_account, menu);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.sync_now:
|
||||
requestSync();
|
||||
break;
|
||||
case R.id.settings:
|
||||
Intent intent = new Intent(this, AccountSettingsActivity.class);
|
||||
intent.putExtra(AccountSettingsActivity.EXTRA_ACCOUNT, account);
|
||||
startActivity(intent);
|
||||
break;
|
||||
case R.id.delete_account:
|
||||
new AlertDialog.Builder(AccountActivity.this)
|
||||
.setIcon(R.drawable.ic_error_dark)
|
||||
.setTitle(R.string.account_delete_confirmation_title)
|
||||
.setMessage(R.string.account_delete_confirmation_text)
|
||||
.setNegativeButton(android.R.string.no, null)
|
||||
.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
deleteAccount();
|
||||
}
|
||||
})
|
||||
.show();
|
||||
break;
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onMenuItemClick(MenuItem item) {
|
||||
Intent intent;
|
||||
switch (item.getItemId()) {
|
||||
case R.id.refresh_address_books:
|
||||
if (accountInfo.carddav != null) {
|
||||
intent = new Intent(this, DavService.class);
|
||||
intent.setAction(DavService.ACTION_REFRESH_COLLECTIONS);
|
||||
intent.putExtra(DavService.EXTRA_DAV_SERVICE_ID, accountInfo.carddav.id);
|
||||
startService(intent);
|
||||
}
|
||||
break;
|
||||
case R.id.create_address_book:
|
||||
intent = new Intent(this, CreateAddressBookActivity.class);
|
||||
intent.putExtra(CreateAddressBookActivity.EXTRA_ACCOUNT, account);
|
||||
startActivity(intent);
|
||||
break;
|
||||
case R.id.refresh_calendars:
|
||||
if (accountInfo.caldav != null) {
|
||||
intent = new Intent(this, DavService.class);
|
||||
intent.setAction(DavService.ACTION_REFRESH_COLLECTIONS);
|
||||
intent.putExtra(DavService.EXTRA_DAV_SERVICE_ID, accountInfo.caldav.id);
|
||||
startService(intent);
|
||||
}
|
||||
break;
|
||||
case R.id.create_calendar:
|
||||
intent = new Intent(this, CreateCalendarActivity.class);
|
||||
intent.putExtra(CreateCalendarActivity.EXTRA_ACCOUNT, account);
|
||||
startActivity(intent);
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
private AdapterView.OnItemClickListener onItemClickListener = new AdapterView.OnItemClickListener() {
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||
final ListView list = (ListView)parent;
|
||||
final ArrayAdapter<CollectionInfo> adapter = (ArrayAdapter)list.getAdapter();
|
||||
final CollectionInfo info = adapter.getItem(position);
|
||||
|
||||
boolean nowChecked = !info.selected;
|
||||
|
||||
if (list.getChoiceMode() == AbsListView.CHOICE_MODE_SINGLE)
|
||||
// clear all other checked items
|
||||
for (int i = adapter.getCount()-1; i >= 0; i--)
|
||||
adapter.getItem(i).selected = false;
|
||||
|
||||
OpenHelper dbHelper = new OpenHelper(AccountActivity.this);
|
||||
try {
|
||||
SQLiteDatabase db = dbHelper.getWritableDatabase();
|
||||
db.beginTransactionNonExclusive();
|
||||
|
||||
if (list.getChoiceMode() == AbsListView.CHOICE_MODE_SINGLE) {
|
||||
// disable all other collections
|
||||
ContentValues values = new ContentValues(1);
|
||||
values.put(Collections.SYNC, 0);
|
||||
db.update(Collections._TABLE, values, Collections.SERVICE_ID + "=?", new String[] { String.valueOf(info.serviceID) });
|
||||
}
|
||||
|
||||
ContentValues values = new ContentValues(1);
|
||||
values.put(Collections.SYNC, nowChecked ? 1 : 0);
|
||||
db.update(Collections._TABLE, values, Collections.ID + "=?", new String[] { String.valueOf(info.id) });
|
||||
|
||||
db.setTransactionSuccessful();
|
||||
db.endTransaction();
|
||||
|
||||
info.selected = nowChecked;
|
||||
adapter.notifyDataSetChanged();
|
||||
} finally {
|
||||
dbHelper.close();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private AdapterView.OnItemLongClickListener onItemLongClickListener = new AdapterView.OnItemLongClickListener() {
|
||||
@Override
|
||||
public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
|
||||
final ListView list = (ListView)parent;
|
||||
final ArrayAdapter<CollectionInfo> adapter = (ArrayAdapter)list.getAdapter();
|
||||
final CollectionInfo info = adapter.getItem(position);
|
||||
|
||||
PopupMenu popup = new PopupMenu(AccountActivity.this, view);
|
||||
popup.inflate(R.menu.account_collection_operations);
|
||||
popup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
|
||||
@Override
|
||||
public boolean onMenuItemClick(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.delete_collection:
|
||||
DeleteCollectionFragment.ConfirmDeleteCollectionFragment.newInstance(account, info).show(getSupportFragmentManager(), null);
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
});
|
||||
popup.show();
|
||||
|
||||
// long click was handled
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/* LOADERS AND LOADED DATA */
|
||||
|
||||
protected static class AccountInfo {
|
||||
ServiceInfo carddav, caldav;
|
||||
|
||||
public static class ServiceInfo {
|
||||
long id;
|
||||
boolean refreshing;
|
||||
|
||||
boolean hasHomeSets;
|
||||
List<CollectionInfo> collections;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Loader<AccountInfo> onCreateLoader(int id, Bundle args) {
|
||||
return new AccountLoader(this, account);
|
||||
}
|
||||
|
||||
public void reload() {
|
||||
getLoaderManager().restartLoader(0, getIntent().getExtras(), this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadFinished(Loader<AccountInfo> loader, final AccountInfo info) {
|
||||
accountInfo = info;
|
||||
|
||||
if (accountInfo == null) {
|
||||
// account doesn't exist anymore
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
CardView card = (CardView)findViewById(R.id.carddav);
|
||||
if (info.carddav != null) {
|
||||
ProgressBar progress = (ProgressBar)findViewById(R.id.carddav_refreshing);
|
||||
progress.setVisibility(info.carddav.refreshing ? View.VISIBLE : View.GONE);
|
||||
|
||||
listCardDAV = (ListView)findViewById(R.id.address_books);
|
||||
listCardDAV.setEnabled(!info.carddav.refreshing);
|
||||
listCardDAV.setAlpha(info.carddav.refreshing ? 0.5f : 1);
|
||||
|
||||
tbCardDAV.getMenu().findItem(R.id.create_address_book).setEnabled(info.carddav.hasHomeSets);
|
||||
|
||||
AddressBookAdapter adapter = new AddressBookAdapter(this);
|
||||
adapter.addAll(info.carddav.collections);
|
||||
listCardDAV.setAdapter(adapter);
|
||||
listCardDAV.setOnItemClickListener(onItemClickListener);
|
||||
listCardDAV.setOnItemLongClickListener(onItemLongClickListener);
|
||||
} else
|
||||
card.setVisibility(View.GONE);
|
||||
|
||||
card = (CardView)findViewById(R.id.caldav);
|
||||
if (info.caldav != null) {
|
||||
ProgressBar progress = (ProgressBar)findViewById(R.id.caldav_refreshing);
|
||||
progress.setVisibility(info.caldav.refreshing ? View.VISIBLE : View.GONE);
|
||||
|
||||
listCalDAV = (ListView)findViewById(R.id.calendars);
|
||||
listCalDAV.setEnabled(!info.caldav.refreshing);
|
||||
listCalDAV.setAlpha(info.caldav.refreshing ? 0.5f : 1);
|
||||
|
||||
tbCalDAV.getMenu().findItem(R.id.create_calendar).setEnabled(info.caldav.hasHomeSets);
|
||||
|
||||
final CalendarAdapter adapter = new CalendarAdapter(this);
|
||||
adapter.addAll(info.caldav.collections);
|
||||
listCalDAV.setAdapter(adapter);
|
||||
listCalDAV.setOnItemClickListener(onItemClickListener);
|
||||
listCalDAV.setOnItemLongClickListener(onItemLongClickListener);
|
||||
} else
|
||||
card.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoaderReset(Loader<AccountInfo> loader) {
|
||||
if (listCardDAV != null)
|
||||
listCardDAV.setAdapter(null);
|
||||
|
||||
if (listCalDAV != null)
|
||||
listCalDAV.setAdapter(null);
|
||||
}
|
||||
|
||||
|
||||
private static class AccountLoader extends AsyncTaskLoader<AccountInfo> implements DavService.RefreshingStatusListener, ServiceConnection, SyncStatusObserver {
|
||||
private final Account account;
|
||||
private DavService.InfoBinder davService;
|
||||
private Object syncStatusListener;
|
||||
|
||||
public AccountLoader(Context context, Account account) {
|
||||
super(context);
|
||||
this.account = account;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStartLoading() {
|
||||
syncStatusListener = ContentResolver.addStatusChangeListener(SYNC_OBSERVER_TYPE_ACTIVE, this);
|
||||
|
||||
getContext().bindService(new Intent(getContext(), DavService.class), this, Context.BIND_AUTO_CREATE);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStopLoading() {
|
||||
davService.removeRefreshingStatusListener(this);
|
||||
getContext().unbindService(this);
|
||||
|
||||
if (syncStatusListener != null)
|
||||
ContentResolver.removeStatusChangeListener(syncStatusListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceConnected(ComponentName name, IBinder service) {
|
||||
davService = (DavService.InfoBinder)service;
|
||||
davService.addRefreshingStatusListener(this, false);
|
||||
|
||||
forceLoad();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceDisconnected(ComponentName name) {
|
||||
davService = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDavRefreshStatusChanged(long id, boolean refreshing) {
|
||||
forceLoad();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStatusChanged(int which) {
|
||||
forceLoad();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AccountInfo loadInBackground() {
|
||||
AccountInfo info = new AccountInfo();
|
||||
|
||||
@Cleanup OpenHelper dbHelper = new OpenHelper(getContext());
|
||||
SQLiteDatabase db = dbHelper.getReadableDatabase();
|
||||
|
||||
@Cleanup Cursor cursor = db.query(
|
||||
Services._TABLE,
|
||||
new String[] { Services.ID, Services.SERVICE },
|
||||
Services.ACCOUNT_NAME + "=?", new String[] { account.name },
|
||||
null, null, null);
|
||||
|
||||
if (cursor.getCount() == 0)
|
||||
// no services, account not useable
|
||||
return null;
|
||||
|
||||
while (cursor.moveToNext()) {
|
||||
long id = cursor.getLong(0);
|
||||
String service = cursor.getString(1);
|
||||
if (Services.SERVICE_CARDDAV.equals(service)) {
|
||||
info.carddav = new AccountInfo.ServiceInfo();
|
||||
info.carddav.id = id;
|
||||
info.carddav.refreshing = davService.isRefreshing(id) || ContentResolver.isSyncActive(account, ContactsContract.AUTHORITY);
|
||||
info.carddav.hasHomeSets = hasHomeSets(db, id);
|
||||
info.carddav.collections = readCollections(db, id);
|
||||
|
||||
} else if (Services.SERVICE_CALDAV.equals(service)) {
|
||||
info.caldav = new AccountInfo.ServiceInfo();
|
||||
info.caldav.id = id;
|
||||
info.caldav.refreshing = davService.isRefreshing(id) ||
|
||||
ContentResolver.isSyncActive(account, CalendarContract.AUTHORITY) ||
|
||||
ContentResolver.isSyncActive(account, TaskProvider.ProviderName.OpenTasks.authority);
|
||||
info.caldav.hasHomeSets = hasHomeSets(db, id);
|
||||
info.caldav.collections = readCollections(db, id);
|
||||
}
|
||||
}
|
||||
return info;
|
||||
}
|
||||
|
||||
private boolean hasHomeSets(@NonNull SQLiteDatabase db, long service) {
|
||||
@Cleanup Cursor cursor = db.query(ServiceDB.HomeSets._TABLE, null, ServiceDB.HomeSets.SERVICE_ID + "=?",
|
||||
new String[] { String.valueOf(service) }, null, null, null);
|
||||
return cursor.getCount() > 0;
|
||||
}
|
||||
|
||||
private List<CollectionInfo> readCollections(@NonNull SQLiteDatabase db, long service) {
|
||||
List<CollectionInfo> collections = new LinkedList<>();
|
||||
@Cleanup Cursor cursor = db.query(Collections._TABLE, null, Collections.SERVICE_ID + "=?",
|
||||
new String[] { String.valueOf(service) }, null, null, Collections.SUPPORTS_VEVENT + " DESC," + Collections.DISPLAY_NAME);
|
||||
while (cursor.moveToNext()) {
|
||||
ContentValues values = new ContentValues();
|
||||
DatabaseUtils.cursorRowToContentValues(cursor, values);
|
||||
collections.add(CollectionInfo.fromDB(values));
|
||||
}
|
||||
return collections;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/* LIST ADAPTERS */
|
||||
|
||||
public static class AddressBookAdapter extends ArrayAdapter<CollectionInfo> {
|
||||
public AddressBookAdapter(Context context) {
|
||||
super(context, R.layout.account_carddav_item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(int position, View v, ViewGroup parent) {
|
||||
if (v == null)
|
||||
v = LayoutInflater.from(getContext()).inflate(R.layout.account_carddav_item, parent, false);
|
||||
|
||||
final CollectionInfo info = getItem(position);
|
||||
|
||||
RadioButton checked = (RadioButton)v.findViewById(R.id.checked);
|
||||
checked.setChecked(info.selected);
|
||||
|
||||
TextView tv = (TextView)v.findViewById(R.id.title);
|
||||
tv.setText(TextUtils.isEmpty(info.displayName) ? info.url : info.displayName);
|
||||
|
||||
tv = (TextView)v.findViewById(R.id.description);
|
||||
if (TextUtils.isEmpty(info.description))
|
||||
tv.setVisibility(View.GONE);
|
||||
else {
|
||||
tv.setVisibility(View.VISIBLE);
|
||||
tv.setText(info.description);
|
||||
}
|
||||
|
||||
tv = (TextView)v.findViewById(R.id.read_only);
|
||||
tv.setVisibility(info.readOnly ? View.VISIBLE : View.GONE);
|
||||
|
||||
return v;
|
||||
}
|
||||
}
|
||||
|
||||
public static class CalendarAdapter extends ArrayAdapter<CollectionInfo> {
|
||||
public CalendarAdapter(Context context) {
|
||||
super(context, R.layout.account_caldav_item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(final int position, View v, ViewGroup parent) {
|
||||
if (v == null)
|
||||
v = LayoutInflater.from(getContext()).inflate(R.layout.account_caldav_item, parent, false);
|
||||
|
||||
final CollectionInfo info = getItem(position);
|
||||
|
||||
CheckBox checked = (CheckBox)v.findViewById(R.id.checked);
|
||||
checked.setChecked(info.selected);
|
||||
|
||||
View vColor = v.findViewById(R.id.color);
|
||||
if (info.color != null) {
|
||||
vColor.setVisibility(View.VISIBLE);
|
||||
vColor.setBackgroundColor(info.color);
|
||||
} else
|
||||
vColor.setVisibility(View.GONE);
|
||||
|
||||
TextView tv = (TextView)v.findViewById(R.id.title);
|
||||
tv.setText(TextUtils.isEmpty(info.displayName) ? info.url : info.displayName);
|
||||
|
||||
tv = (TextView)v.findViewById(R.id.description);
|
||||
if (TextUtils.isEmpty(info.description))
|
||||
tv.setVisibility(View.GONE);
|
||||
else {
|
||||
tv.setVisibility(View.VISIBLE);
|
||||
tv.setText(info.description);
|
||||
}
|
||||
|
||||
tv = (TextView)v.findViewById(R.id.read_only);
|
||||
tv.setVisibility(info.readOnly ? View.VISIBLE : View.GONE);
|
||||
|
||||
tv = (TextView)v.findViewById(R.id.events);
|
||||
tv.setVisibility(BooleanUtils.isTrue(info.supportsVEVENT) ? View.VISIBLE : View.GONE);
|
||||
|
||||
tv = (TextView)v.findViewById(R.id.tasks);
|
||||
tv.setVisibility(BooleanUtils.isTrue(info.supportsVTODO) ? View.VISIBLE : View.GONE);
|
||||
|
||||
return v;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* USER ACTIONS */
|
||||
|
||||
private void deleteAccount() {
|
||||
AccountManager accountManager = AccountManager.get(this);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 22)
|
||||
accountManager.removeAccount(account, this, new AccountManagerCallback<Bundle>() {
|
||||
@Override
|
||||
public void run(AccountManagerFuture<Bundle> future) {
|
||||
try {
|
||||
if (future.getResult().getBoolean(AccountManager.KEY_BOOLEAN_RESULT))
|
||||
finish();
|
||||
} catch(OperationCanceledException|IOException|AuthenticatorException e) {
|
||||
App.log.log(Level.SEVERE, "Couldn't remove account", e);
|
||||
}
|
||||
}
|
||||
}, null);
|
||||
else
|
||||
accountManager.removeAccount(account, new AccountManagerCallback<Boolean>() {
|
||||
@Override
|
||||
public void run(AccountManagerFuture<Boolean> future) {
|
||||
try {
|
||||
if (future.getResult())
|
||||
finish();
|
||||
} catch (OperationCanceledException|IOException|AuthenticatorException e) {
|
||||
App.log.log(Level.SEVERE, "Couldn't remove account", e);
|
||||
}
|
||||
}
|
||||
}, null);
|
||||
}
|
||||
|
||||
private void requestSync() {
|
||||
String authorities[] = {
|
||||
ContactsContract.AUTHORITY,
|
||||
CalendarContract.AUTHORITY,
|
||||
TaskProvider.ProviderName.OpenTasks.authority
|
||||
};
|
||||
|
||||
for (String authority : authorities) {
|
||||
Bundle extras = new Bundle();
|
||||
extras.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true); // manual sync
|
||||
extras.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true); // run immediately (don't queue)
|
||||
ContentResolver.requestSync(account, authority, extras);
|
||||
}
|
||||
|
||||
Snackbar.make(findViewById(R.id.parent), R.string.account_synchronizing_now, Snackbar.LENGTH_LONG).show();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
/*
|
||||
* Copyright © 2013 – 2016 Ricki Hirner (bitfire web engineering).
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the GNU Public License v3.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.gnu.org/licenses/gpl.html
|
||||
*/
|
||||
|
||||
package at.bitfire.davdroid.ui;
|
||||
|
||||
import android.accounts.Account;
|
||||
import android.accounts.AccountManager;
|
||||
import android.accounts.OnAccountsUpdateListener;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.ListFragment;
|
||||
import android.support.v4.app.LoaderManager;
|
||||
import android.support.v4.content.AsyncTaskLoader;
|
||||
import android.support.v4.content.Loader;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.AbsListView;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import at.bitfire.davdroid.AccountsChangedReceiver;
|
||||
import at.bitfire.davdroid.R;
|
||||
|
||||
public class AccountListFragment extends ListFragment implements LoaderManager.LoaderCallbacks<Account[]>, AdapterView.OnItemClickListener {
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
setListAdapter(new AccountListAdapter(getContext()));
|
||||
|
||||
return inflater.inflate(R.layout.account_list, container, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(View view, Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
getLoaderManager().initLoader(0, getArguments(), this);
|
||||
|
||||
ListView list = getListView();
|
||||
list.setOnItemClickListener(this);
|
||||
list.setChoiceMode(AbsListView.CHOICE_MODE_SINGLE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||
Account account = (Account)getListAdapter().getItem(position);
|
||||
|
||||
Intent intent = new Intent(getContext(), AccountActivity.class);
|
||||
intent.putExtra(AccountActivity.EXTRA_ACCOUNT, account);
|
||||
startActivity(intent);
|
||||
}
|
||||
|
||||
|
||||
// loader
|
||||
|
||||
@Override
|
||||
public Loader<Account[]> onCreateLoader(int id, Bundle args) {
|
||||
return new AccountLoader(getContext());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadFinished(Loader<Account[]> loader, Account[] accounts) {
|
||||
AccountListAdapter adapter = (AccountListAdapter)getListAdapter();
|
||||
adapter.clear();
|
||||
adapter.addAll(accounts);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoaderReset(Loader<Account[]> loader) {
|
||||
((AccountListAdapter)getListAdapter()).clear();
|
||||
}
|
||||
|
||||
private static class AccountLoader extends AsyncTaskLoader<Account[]> implements OnAccountsUpdateListener {
|
||||
private final AccountManager accountManager;
|
||||
|
||||
public AccountLoader(Context context) {
|
||||
super(context);
|
||||
accountManager = AccountManager.get(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStartLoading() {
|
||||
AccountsChangedReceiver.registerListener(this, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStopLoading() {
|
||||
AccountsChangedReceiver.unregisterListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAccountsUpdated(Account[] accounts) {
|
||||
forceLoad();
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressLint("MissingPermission")
|
||||
public Account[] loadInBackground() {
|
||||
return accountManager.getAccountsByType(getContext().getString(R.string.account_type));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// list adapter
|
||||
|
||||
static class AccountListAdapter extends ArrayAdapter<Account> {
|
||||
public AccountListAdapter(Context context) {
|
||||
super(context, R.layout.account_list_item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(int position, View v, ViewGroup parent) {
|
||||
if (v == null)
|
||||
v = LayoutInflater.from(getContext()).inflate(R.layout.account_list_item, parent, false);
|
||||
|
||||
Account account = getItem(position);
|
||||
|
||||
TextView tv = (TextView)v.findViewById(R.id.account_name);
|
||||
tv.setText(account.name);
|
||||
|
||||
return v;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,292 @@
|
||||
/*
|
||||
* Copyright © 2013 – 2016 Ricki Hirner (bitfire web engineering).
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the GNU Public License v3.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.gnu.org/licenses/gpl.html
|
||||
*/
|
||||
|
||||
package at.bitfire.davdroid.ui;
|
||||
|
||||
import android.accounts.Account;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.provider.CalendarContract;
|
||||
import android.provider.ContactsContract;
|
||||
import android.support.v4.app.NavUtils;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.support.v7.preference.EditTextPreference;
|
||||
import android.support.v7.preference.ListPreference;
|
||||
import android.support.v7.preference.Preference;
|
||||
import android.support.v7.preference.PreferenceFragmentCompat;
|
||||
import android.support.v7.preference.SwitchPreferenceCompat;
|
||||
import android.text.TextUtils;
|
||||
import android.view.MenuItem;
|
||||
|
||||
import java.util.logging.Level;
|
||||
|
||||
import at.bitfire.davdroid.AccountSettings;
|
||||
import at.bitfire.davdroid.App;
|
||||
import at.bitfire.davdroid.BuildConfig;
|
||||
import at.bitfire.davdroid.InvalidAccountException;
|
||||
import at.bitfire.davdroid.R;
|
||||
import at.bitfire.ical4android.TaskProvider;
|
||||
import at.bitfire.vcard4android.GroupMethod;
|
||||
|
||||
public class AccountSettingsActivity extends AppCompatActivity {
|
||||
public final static String EXTRA_ACCOUNT = "account";
|
||||
|
||||
private Account account;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
account = getIntent().getParcelableExtra(EXTRA_ACCOUNT);
|
||||
setTitle(getString(R.string.settings_title, account.name));
|
||||
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
|
||||
if (savedInstanceState == null)
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.replace(android.R.id.content, AccountSettingsFragment.instantiate(this, AccountSettingsFragment.class.getName(), getIntent().getExtras()))
|
||||
.commit();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
if (item.getItemId() == android.R.id.home) {
|
||||
Intent intent = new Intent(this, AccountActivity.class);
|
||||
intent.putExtra(AccountActivity.EXTRA_ACCOUNT, account);
|
||||
NavUtils.navigateUpTo(this, intent);
|
||||
return true;
|
||||
} else
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public static class AccountSettingsFragment extends PreferenceFragmentCompat {
|
||||
Account account;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
account = getArguments().getParcelable(EXTRA_ACCOUNT);
|
||||
refresh();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreatePreferences(Bundle bundle, String s) {
|
||||
addPreferencesFromResource(R.xml.settings_account);
|
||||
}
|
||||
|
||||
public void refresh() {
|
||||
final AccountSettings settings;
|
||||
|
||||
try {
|
||||
settings = new AccountSettings(getActivity(), account);
|
||||
} catch(InvalidAccountException e) {
|
||||
App.log.log(Level.INFO, "Account is invalid or doesn't exist (anymore)", e);
|
||||
getActivity().finish();
|
||||
return;
|
||||
}
|
||||
|
||||
// category: authentication
|
||||
final EditTextPreference prefUserName = (EditTextPreference)findPreference("username");
|
||||
prefUserName.setSummary(settings.username());
|
||||
prefUserName.setText(settings.username());
|
||||
prefUserName.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||
settings.username((String)newValue);
|
||||
refresh();
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
final EditTextPreference prefPassword = (EditTextPreference)findPreference("password");
|
||||
prefPassword.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||
settings.password((String)newValue);
|
||||
refresh();
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
// category: synchronization
|
||||
final ListPreference prefSyncContacts = (ListPreference)findPreference("sync_interval_contacts");
|
||||
final Long syncIntervalContacts = settings.getSyncInterval(ContactsContract.AUTHORITY);
|
||||
if (syncIntervalContacts != null) {
|
||||
prefSyncContacts.setValue(syncIntervalContacts.toString());
|
||||
if (syncIntervalContacts == AccountSettings.SYNC_INTERVAL_MANUALLY)
|
||||
prefSyncContacts.setSummary(R.string.settings_sync_summary_manually);
|
||||
else
|
||||
prefSyncContacts.setSummary(getString(R.string.settings_sync_summary_periodically, syncIntervalContacts / 60));
|
||||
prefSyncContacts.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||
settings.setSyncInterval(ContactsContract.AUTHORITY, Long.parseLong((String)newValue));
|
||||
refresh();
|
||||
return false;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
prefSyncContacts.setEnabled(false);
|
||||
prefSyncContacts.setSummary(R.string.settings_sync_summary_not_available);
|
||||
}
|
||||
|
||||
final ListPreference prefSyncCalendars = (ListPreference)findPreference("sync_interval_calendars");
|
||||
final Long syncIntervalCalendars = settings.getSyncInterval(CalendarContract.AUTHORITY);
|
||||
if (syncIntervalCalendars != null) {
|
||||
prefSyncCalendars.setValue(syncIntervalCalendars.toString());
|
||||
if (syncIntervalCalendars == AccountSettings.SYNC_INTERVAL_MANUALLY)
|
||||
prefSyncCalendars.setSummary(R.string.settings_sync_summary_manually);
|
||||
else
|
||||
prefSyncCalendars.setSummary(getString(R.string.settings_sync_summary_periodically, syncIntervalCalendars / 60));
|
||||
prefSyncCalendars.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||
settings.setSyncInterval(CalendarContract.AUTHORITY, Long.parseLong((String)newValue));
|
||||
refresh();
|
||||
return false;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
prefSyncCalendars.setEnabled(false);
|
||||
prefSyncCalendars.setSummary(R.string.settings_sync_summary_not_available);
|
||||
}
|
||||
|
||||
final ListPreference prefSyncTasks = (ListPreference)findPreference("sync_interval_tasks");
|
||||
final Long syncIntervalTasks = settings.getSyncInterval(TaskProvider.ProviderName.OpenTasks.authority);
|
||||
if (syncIntervalTasks != null) {
|
||||
prefSyncTasks.setValue(syncIntervalTasks.toString());
|
||||
if (syncIntervalTasks == AccountSettings.SYNC_INTERVAL_MANUALLY)
|
||||
prefSyncTasks.setSummary(R.string.settings_sync_summary_manually);
|
||||
else
|
||||
prefSyncTasks.setSummary(getString(R.string.settings_sync_summary_periodically, syncIntervalTasks / 60));
|
||||
prefSyncTasks.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||
settings.setSyncInterval(TaskProvider.ProviderName.OpenTasks.authority, Long.parseLong((String)newValue));
|
||||
refresh();
|
||||
return false;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
prefSyncTasks.setEnabled(false);
|
||||
prefSyncTasks.setSummary(R.string.settings_sync_summary_not_available);
|
||||
}
|
||||
|
||||
final SwitchPreferenceCompat prefWifiOnly = (SwitchPreferenceCompat)findPreference("sync_wifi_only");
|
||||
prefWifiOnly.setChecked(settings.getSyncWifiOnly());
|
||||
prefWifiOnly.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object wifiOnly) {
|
||||
settings.setSyncWiFiOnly((Boolean)wifiOnly);
|
||||
refresh();
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
final EditTextPreference prefWifiOnlySSID = (EditTextPreference)findPreference("sync_wifi_only_ssid");
|
||||
final String onlySSID = settings.getSyncWifiOnlySSID();
|
||||
prefWifiOnlySSID.setText(onlySSID);
|
||||
if (onlySSID != null)
|
||||
prefWifiOnlySSID.setSummary(getString(R.string.settings_sync_wifi_only_ssid_on, onlySSID));
|
||||
else
|
||||
prefWifiOnlySSID.setSummary(R.string.settings_sync_wifi_only_ssid_off);
|
||||
prefWifiOnlySSID.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||
String ssid = (String)newValue;
|
||||
settings.setSyncWifiOnlySSID(!TextUtils.isEmpty(ssid) ? ssid : null);
|
||||
refresh();
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
// category: CardDAV
|
||||
final SwitchPreferenceCompat prefRFC6868 = (SwitchPreferenceCompat)findPreference("vcard_rfc6868");
|
||||
if (syncIntervalContacts != null) {
|
||||
prefRFC6868.setChecked(settings.getVCardRFC6868());
|
||||
if (BuildConfig.settingVCardRFC6868 == null)
|
||||
prefRFC6868.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object o) {
|
||||
settings.setVCardRFC6868((Boolean)o);
|
||||
refresh();
|
||||
return false;
|
||||
}
|
||||
});
|
||||
else
|
||||
prefRFC6868.setEnabled(false);
|
||||
} else
|
||||
prefRFC6868.setEnabled(false);
|
||||
|
||||
final ListPreference prefGroupMethod = (ListPreference)findPreference("contact_group_method");
|
||||
if (syncIntervalContacts != null) {
|
||||
prefGroupMethod.setValue(settings.getGroupMethod().name());
|
||||
prefGroupMethod.setSummary(prefGroupMethod.getEntry());
|
||||
if (BuildConfig.settingContactGroupMethod == null)
|
||||
prefGroupMethod.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object o) {
|
||||
String name = (String)o;
|
||||
settings.setGroupMethod(GroupMethod.valueOf(name));
|
||||
refresh();
|
||||
return false;
|
||||
}
|
||||
});
|
||||
else
|
||||
prefGroupMethod.setEnabled(false);
|
||||
} else
|
||||
prefGroupMethod.setEnabled(false);
|
||||
|
||||
// category: CalDAV
|
||||
final EditTextPreference prefTimeRangePastDays = (EditTextPreference)findPreference("time_range_past_days");
|
||||
if (syncIntervalCalendars != null) {
|
||||
Integer pastDays = settings.getTimeRangePastDays();
|
||||
if (pastDays != null) {
|
||||
prefTimeRangePastDays.setText(pastDays.toString());
|
||||
prefTimeRangePastDays.setSummary(getResources().getQuantityString(R.plurals.settings_sync_time_range_past_days, pastDays, pastDays));
|
||||
} else {
|
||||
prefTimeRangePastDays.setText(null);
|
||||
prefTimeRangePastDays.setSummary(R.string.settings_sync_time_range_past_none);
|
||||
}
|
||||
prefTimeRangePastDays.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||
int days;
|
||||
try {
|
||||
days = Integer.parseInt((String)newValue);
|
||||
} catch(NumberFormatException ignored) {
|
||||
days = -1;
|
||||
}
|
||||
settings.setTimeRangePastDays(days < 0 ? null : days);
|
||||
refresh();
|
||||
return false;
|
||||
}
|
||||
});
|
||||
} else
|
||||
prefTimeRangePastDays.setEnabled(false);
|
||||
|
||||
final SwitchPreferenceCompat prefManageColors = (SwitchPreferenceCompat)findPreference("manage_calendar_colors");
|
||||
if (syncIntervalCalendars != null || syncIntervalTasks != null) {
|
||||
prefManageColors.setChecked(settings.getManageCalendarColors());
|
||||
prefManageColors.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||
settings.setManageCalendarColors((Boolean)newValue);
|
||||
refresh();
|
||||
return false;
|
||||
}
|
||||
});
|
||||
} else
|
||||
prefManageColors.setEnabled(false);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* Copyright © 2013 – 2016 Ricki Hirner (bitfire web engineering).
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the GNU Public License v3.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.gnu.org/licenses/gpl.html
|
||||
*/
|
||||
|
||||
package at.bitfire.davdroid.ui;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.design.widget.FloatingActionButton;
|
||||
import android.support.design.widget.NavigationView;
|
||||
import android.support.v4.app.FragmentTransaction;
|
||||
import android.support.v4.view.GravityCompat;
|
||||
import android.support.v4.widget.DrawerLayout;
|
||||
import android.support.v7.app.ActionBarDrawerToggle;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
|
||||
import java.util.ServiceLoader;
|
||||
|
||||
import at.bitfire.davdroid.R;
|
||||
import at.bitfire.davdroid.ui.setup.LoginActivity;
|
||||
|
||||
public class AccountsActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener {
|
||||
|
||||
private static final ServiceLoader<IAccountsDrawerHandler> serviceLoader = ServiceLoader.load(IAccountsDrawerHandler.class);
|
||||
private static final IAccountsDrawerHandler accountsDrawerHandler = serviceLoader.iterator().next();
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_accounts);
|
||||
|
||||
Toolbar toolbar = (Toolbar)findViewById(R.id.toolbar);
|
||||
setSupportActionBar(toolbar);
|
||||
|
||||
FloatingActionButton fab = (FloatingActionButton)findViewById(R.id.fab);
|
||||
fab.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
startActivity(new Intent(AccountsActivity.this, LoginActivity.class));
|
||||
}
|
||||
});
|
||||
|
||||
DrawerLayout drawer = (DrawerLayout)findViewById(R.id.drawer_layout);
|
||||
ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(
|
||||
this, drawer, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close);
|
||||
drawer.setDrawerListener(toggle);
|
||||
toggle.syncState();
|
||||
|
||||
NavigationView navigationView = (NavigationView)findViewById(R.id.nav_view);
|
||||
navigationView.setNavigationItemSelectedListener(this);
|
||||
navigationView.setItemIconTintList(null);
|
||||
|
||||
if (savedInstanceState == null && !getPackageName().equals(getCallingPackage())) {
|
||||
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
|
||||
for (StartupDialogFragment fragment : StartupDialogFragment.getStartupDialogs(this))
|
||||
ft.add(fragment, null);
|
||||
ft.commit();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
DrawerLayout drawer = (DrawerLayout)findViewById(R.id.drawer_layout);
|
||||
if (drawer.isDrawerOpen(GravityCompat.START))
|
||||
drawer.closeDrawer(GravityCompat.START);
|
||||
else
|
||||
super.onBackPressed();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onNavigationItemSelected(MenuItem item) {
|
||||
boolean processed = accountsDrawerHandler.onNavigationItemSelected(this, item);
|
||||
|
||||
DrawerLayout drawer = (DrawerLayout)findViewById(R.id.drawer_layout);
|
||||
drawer.closeDrawer(GravityCompat.START);
|
||||
|
||||
return processed;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
/*
|
||||
* Copyright © 2013 – 2016 Ricki Hirner (bitfire web engineering).
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the GNU Public License v3.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.gnu.org/licenses/gpl.html
|
||||
*/
|
||||
|
||||
package at.bitfire.davdroid.ui;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.design.widget.Snackbar;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.support.v7.preference.Preference;
|
||||
import android.support.v7.preference.PreferenceFragmentCompat;
|
||||
import android.support.v7.preference.SwitchPreferenceCompat;
|
||||
|
||||
import at.bitfire.davdroid.App;
|
||||
import at.bitfire.davdroid.R;
|
||||
import at.bitfire.davdroid.model.ServiceDB;
|
||||
import at.bitfire.davdroid.model.Settings;
|
||||
|
||||
public class AppSettingsActivity extends AppCompatActivity {
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
if (savedInstanceState == null) {
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.replace(android.R.id.content, new SettingsFragment())
|
||||
.commit();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static class SettingsFragment extends PreferenceFragmentCompat {
|
||||
ServiceDB.OpenHelper dbHelper;
|
||||
Settings settings;
|
||||
|
||||
Preference
|
||||
prefResetHints,
|
||||
prefResetCertificates;
|
||||
SwitchPreferenceCompat
|
||||
prefDistrustSystemCerts,
|
||||
prefLogToExternalStorage;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
dbHelper = new ServiceDB.OpenHelper(getContext());
|
||||
settings = new Settings(dbHelper.getReadableDatabase());
|
||||
|
||||
super.onCreate(savedInstanceState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
dbHelper.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreatePreferences(Bundle bundle, String s) {
|
||||
addPreferencesFromResource(R.xml.settings_app);
|
||||
|
||||
prefResetHints = findPreference("reset_hints");
|
||||
|
||||
prefDistrustSystemCerts = (SwitchPreferenceCompat)findPreference("distrust_system_certs");
|
||||
App app = (App)getContext().getApplicationContext();
|
||||
if (app.getCertManager() == null)
|
||||
prefDistrustSystemCerts.setVisible(false);
|
||||
else
|
||||
prefDistrustSystemCerts.setChecked(settings.getBoolean(App.DISTRUST_SYSTEM_CERTIFICATES, false));
|
||||
|
||||
prefResetCertificates = findPreference("reset_certificates");
|
||||
if (app.getCertManager() == null)
|
||||
prefResetCertificates.setVisible(false);
|
||||
|
||||
prefLogToExternalStorage = (SwitchPreferenceCompat)findPreference("log_to_external_storage");
|
||||
prefLogToExternalStorage.setChecked(settings.getBoolean(App.LOG_TO_EXTERNAL_STORAGE, false));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPreferenceTreeClick(Preference preference) {
|
||||
if (preference == prefResetHints)
|
||||
resetHints();
|
||||
else if (preference == prefDistrustSystemCerts)
|
||||
setDistrustSystemCerts(((SwitchPreferenceCompat)preference).isChecked());
|
||||
else if (preference == prefResetCertificates)
|
||||
resetCertificates();
|
||||
else if (preference == prefLogToExternalStorage)
|
||||
setExternalLogging(((SwitchPreferenceCompat)preference).isChecked());
|
||||
else
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
private void resetHints() {
|
||||
settings.remove(StartupDialogFragment.HINT_BATTERY_OPTIMIZATIONS);
|
||||
settings.remove(StartupDialogFragment.HINT_GOOGLE_PLAY_ACCOUNTS_REMOVED);
|
||||
settings.remove(StartupDialogFragment.HINT_OPENTASKS_NOT_INSTALLED);
|
||||
Snackbar.make(getView(), R.string.app_settings_reset_hints_success, Snackbar.LENGTH_LONG).show();
|
||||
}
|
||||
|
||||
private void setDistrustSystemCerts(boolean distrust) {
|
||||
settings.putBoolean(App.DISTRUST_SYSTEM_CERTIFICATES, distrust);
|
||||
|
||||
// re-initialize certificate manager
|
||||
App app = (App)getContext().getApplicationContext();
|
||||
app.reinitCertManager();
|
||||
|
||||
// reinitialize certificate manager of :sync process
|
||||
getContext().sendBroadcast(new Intent(App.ReinitSettingsReceiver.ACTION_REINIT_SETTINGS));
|
||||
}
|
||||
|
||||
private void resetCertificates() {
|
||||
((App)getContext().getApplicationContext()).getCertManager().resetCertificates();
|
||||
Snackbar.make(getView(), getString(R.string.app_settings_reset_certificates_success), Snackbar.LENGTH_LONG).show();
|
||||
}
|
||||
|
||||
private void setExternalLogging(boolean externalLogging) {
|
||||
settings.putBoolean(App.LOG_TO_EXTERNAL_STORAGE, externalLogging);
|
||||
|
||||
// reinitialize logger of default process
|
||||
App app = (App)getContext().getApplicationContext();
|
||||
app.reinitLogger();
|
||||
|
||||
// reinitialize logger of :sync process
|
||||
getContext().sendBroadcast(new Intent(App.ReinitSettingsReceiver.ACTION_REINIT_SETTINGS));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
/*
|
||||
* Copyright © 2013 – 2016 Ricki Hirner (bitfire web engineering).
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the GNU Public License v3.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.gnu.org/licenses/gpl.html
|
||||
*/
|
||||
|
||||
package at.bitfire.davdroid.ui;
|
||||
|
||||
import android.accounts.Account;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.LoaderManager;
|
||||
import android.support.v4.app.NavUtils;
|
||||
import android.support.v4.content.AsyncTaskLoader;
|
||||
import android.support.v4.content.Loader;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.text.TextUtils;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.EditText;
|
||||
import android.widget.Spinner;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import at.bitfire.davdroid.R;
|
||||
import at.bitfire.davdroid.model.CollectionInfo;
|
||||
import at.bitfire.davdroid.model.ServiceDB;
|
||||
import lombok.Cleanup;
|
||||
import okhttp3.HttpUrl;
|
||||
|
||||
public class CreateAddressBookActivity extends AppCompatActivity implements LoaderManager.LoaderCallbacks<CreateAddressBookActivity.AccountInfo> {
|
||||
public static final String EXTRA_ACCOUNT = "account";
|
||||
|
||||
protected Account account;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
account = getIntent().getParcelableExtra(EXTRA_ACCOUNT);
|
||||
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
setContentView(R.layout.activity_create_address_book);
|
||||
|
||||
getSupportLoaderManager().initLoader(0, getIntent().getExtras(), this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
getMenuInflater().inflate(R.menu.activity_create_collection, menu);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
if (item.getItemId() == android.R.id.home) {
|
||||
Intent intent = new Intent(this, AccountActivity.class);
|
||||
intent.putExtra(AccountActivity.EXTRA_ACCOUNT, account);
|
||||
NavUtils.navigateUpTo(this, intent);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void onCreateCollection(MenuItem item) {
|
||||
boolean ok = true;
|
||||
CollectionInfo info = new CollectionInfo();
|
||||
|
||||
Spinner spinner = (Spinner)findViewById(R.id.home_sets);
|
||||
String homeSet = (String)spinner.getSelectedItem();
|
||||
|
||||
EditText edit = (EditText)findViewById(R.id.display_name);
|
||||
info.displayName = edit.getText().toString();
|
||||
if (TextUtils.isEmpty(info.displayName)) {
|
||||
edit.setError(getString(R.string.create_collection_display_name_required));
|
||||
ok = false;
|
||||
}
|
||||
|
||||
edit = (EditText)findViewById(R.id.description);
|
||||
info.description = StringUtils.trimToNull(edit.getText().toString());
|
||||
|
||||
if (ok) {
|
||||
info.type = CollectionInfo.Type.ADDRESS_BOOK;
|
||||
info.url = HttpUrl.parse(homeSet).resolve(UUID.randomUUID().toString() + "/").toString();
|
||||
CreateCollectionFragment.newInstance(account, info).show(getSupportFragmentManager(), null);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Loader<AccountInfo> onCreateLoader(int id, Bundle args) {
|
||||
return new AccountInfoLoader(this, account);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadFinished(Loader<AccountInfo> loader, AccountInfo info) {
|
||||
if (info != null) {
|
||||
Spinner spinner = (Spinner)findViewById(R.id.home_sets);
|
||||
spinner.setAdapter(new ArrayAdapter<>(this, android.R.layout.simple_spinner_dropdown_item, info.homeSets));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoaderReset(Loader<AccountInfo> loader) {
|
||||
}
|
||||
|
||||
protected static class AccountInfo {
|
||||
List<String> homeSets = new LinkedList<>();
|
||||
}
|
||||
|
||||
protected static class AccountInfoLoader extends AsyncTaskLoader<AccountInfo> {
|
||||
private final Account account;
|
||||
private final ServiceDB.OpenHelper dbHelper;
|
||||
|
||||
public AccountInfoLoader(Context context, Account account) {
|
||||
super(context);
|
||||
this.account = account;
|
||||
dbHelper = new ServiceDB.OpenHelper(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStartLoading() {
|
||||
forceLoad();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AccountInfo loadInBackground() {
|
||||
final AccountInfo info = new AccountInfo();
|
||||
|
||||
// find DAV service and home sets
|
||||
SQLiteDatabase db = dbHelper.getReadableDatabase();
|
||||
try {
|
||||
@Cleanup Cursor cursorService = db.query(ServiceDB.Services._TABLE, new String[] { ServiceDB.Services.ID },
|
||||
ServiceDB.Services.ACCOUNT_NAME + "=? AND " + ServiceDB.Services.SERVICE + "=?",
|
||||
new String[] { account.name, ServiceDB.Services.SERVICE_CARDDAV }, null, null, null);
|
||||
if (!cursorService.moveToNext())
|
||||
return null;
|
||||
String strServiceID = cursorService.getString(0);
|
||||
|
||||
@Cleanup Cursor cursorHomeSets = db.query(ServiceDB.HomeSets._TABLE, new String[] { ServiceDB.HomeSets.URL },
|
||||
ServiceDB.HomeSets.SERVICE_ID + "=?", new String[] { strServiceID }, null, null, null);
|
||||
while (cursorHomeSets.moveToNext())
|
||||
info.homeSets.add(cursorHomeSets.getString(0));
|
||||
} finally {
|
||||
dbHelper.close();
|
||||
}
|
||||
|
||||
return info;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,222 @@
|
||||
/*
|
||||
* Copyright © 2013 – 2016 Ricki Hirner (bitfire web engineering).
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the GNU Public License v3.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.gnu.org/licenses/gpl.html
|
||||
*/
|
||||
|
||||
package at.bitfire.davdroid.ui;
|
||||
|
||||
import android.accounts.Account;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.LoaderManager;
|
||||
import android.support.v4.app.NavUtils;
|
||||
import android.support.v4.content.AsyncTaskLoader;
|
||||
import android.support.v4.content.Loader;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.text.TextUtils;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.EditText;
|
||||
import android.widget.RadioGroup;
|
||||
import android.widget.Spinner;
|
||||
|
||||
import net.fortuna.ical4j.model.Calendar;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.TimeZone;
|
||||
import java.util.UUID;
|
||||
|
||||
import at.bitfire.davdroid.R;
|
||||
import at.bitfire.davdroid.model.CollectionInfo;
|
||||
import at.bitfire.davdroid.model.ServiceDB;
|
||||
import at.bitfire.ical4android.DateUtils;
|
||||
import lombok.Cleanup;
|
||||
import okhttp3.HttpUrl;
|
||||
import yuku.ambilwarna.AmbilWarnaDialog;
|
||||
|
||||
public class CreateCalendarActivity extends AppCompatActivity implements LoaderManager.LoaderCallbacks<CreateCalendarActivity.AccountInfo> {
|
||||
public static final String EXTRA_ACCOUNT = "account";
|
||||
|
||||
protected Account account;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
account = getIntent().getExtras().getParcelable(EXTRA_ACCOUNT);
|
||||
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
|
||||
setContentView(R.layout.activity_create_calendar);
|
||||
final View colorSquare = findViewById(R.id.color);
|
||||
colorSquare.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
new AmbilWarnaDialog(CreateCalendarActivity.this, ((ColorDrawable)colorSquare.getBackground()).getColor(), true, new AmbilWarnaDialog.OnAmbilWarnaListener() {
|
||||
@Override
|
||||
public void onCancel(AmbilWarnaDialog dialog) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOk(AmbilWarnaDialog dialog, int color) {
|
||||
colorSquare.setBackgroundColor(color);
|
||||
}
|
||||
}).show();
|
||||
}
|
||||
});
|
||||
|
||||
getSupportLoaderManager().initLoader(0, null, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
getMenuInflater().inflate(R.menu.activity_create_collection, menu);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
if (item.getItemId() == android.R.id.home) {
|
||||
Intent intent = new Intent(this, AccountActivity.class);
|
||||
intent.putExtra(AccountActivity.EXTRA_ACCOUNT, account);
|
||||
NavUtils.navigateUpTo(this, intent);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void onCreateCollection(MenuItem item) {
|
||||
boolean ok = true;
|
||||
CollectionInfo info = new CollectionInfo();
|
||||
|
||||
Spinner spinner = (Spinner)findViewById(R.id.home_sets);
|
||||
String homeSet = (String)spinner.getSelectedItem();
|
||||
|
||||
EditText edit = (EditText)findViewById(R.id.display_name);
|
||||
info.displayName = edit.getText().toString();
|
||||
if (TextUtils.isEmpty(info.displayName)) {
|
||||
edit.setError(getString(R.string.create_collection_display_name_required));
|
||||
ok = false;
|
||||
}
|
||||
|
||||
edit = (EditText)findViewById(R.id.description);
|
||||
info.description = StringUtils.trimToNull(edit.getText().toString());
|
||||
|
||||
View view = findViewById(R.id.color);
|
||||
info.color = ((ColorDrawable)view.getBackground()).getColor();
|
||||
|
||||
spinner = (Spinner)findViewById(R.id.time_zone);
|
||||
net.fortuna.ical4j.model.TimeZone tz = DateUtils.tzRegistry.getTimeZone((String)spinner.getSelectedItem());
|
||||
if (tz != null) {
|
||||
Calendar cal = new Calendar();
|
||||
cal.getComponents().add(tz.getVTimeZone());
|
||||
info.timeZone = cal.toString();
|
||||
}
|
||||
|
||||
RadioGroup typeGroup = (RadioGroup)findViewById(R.id.type);
|
||||
switch (typeGroup.getCheckedRadioButtonId()) {
|
||||
case R.id.type_events:
|
||||
info.supportsVEVENT = true;
|
||||
break;
|
||||
case R.id.type_tasks:
|
||||
info.supportsVTODO = true;
|
||||
break;
|
||||
case R.id.type_events_and_tasks:
|
||||
info.supportsVEVENT = true;
|
||||
info.supportsVTODO = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (ok) {
|
||||
info.type = CollectionInfo.Type.CALENDAR;
|
||||
info.url = HttpUrl.parse(homeSet).resolve(UUID.randomUUID().toString() + "/").toString();
|
||||
CreateCollectionFragment.newInstance(account, info).show(getSupportFragmentManager(), null);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Loader<AccountInfo> onCreateLoader(int id, Bundle args) {
|
||||
return new AccountInfoLoader(this, account);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadFinished(Loader<AccountInfo> loader, AccountInfo info) {
|
||||
Spinner spinner = (Spinner)findViewById(R.id.time_zone);
|
||||
String[] timeZones = TimeZone.getAvailableIDs();
|
||||
spinner.setAdapter(new ArrayAdapter<>(this, android.R.layout.simple_spinner_dropdown_item, timeZones));
|
||||
// select system time zone
|
||||
String defaultTimeZone = TimeZone.getDefault().getID();
|
||||
for (int i = 0; i < timeZones.length; i++)
|
||||
if (timeZones[i].equals(defaultTimeZone)) {
|
||||
spinner.setSelection(i);
|
||||
break;
|
||||
}
|
||||
|
||||
if (info != null) {
|
||||
spinner = (Spinner)findViewById(R.id.home_sets);
|
||||
spinner.setAdapter(new ArrayAdapter<>(this, android.R.layout.simple_spinner_dropdown_item, info.homeSets));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoaderReset(Loader<AccountInfo> loader) {
|
||||
}
|
||||
|
||||
protected static class AccountInfo {
|
||||
List<String> homeSets = new LinkedList<>();
|
||||
}
|
||||
|
||||
protected static class AccountInfoLoader extends AsyncTaskLoader<AccountInfo> {
|
||||
private final Account account;
|
||||
private final ServiceDB.OpenHelper dbHelper;
|
||||
|
||||
public AccountInfoLoader(Context context, Account account) {
|
||||
super(context);
|
||||
this.account = account;
|
||||
dbHelper = new ServiceDB.OpenHelper(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStartLoading() {
|
||||
forceLoad();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AccountInfo loadInBackground() {
|
||||
final AccountInfo info = new AccountInfo();
|
||||
|
||||
// find DAV service and home sets
|
||||
SQLiteDatabase db = dbHelper.getReadableDatabase();
|
||||
try {
|
||||
@Cleanup Cursor cursorService = db.query(ServiceDB.Services._TABLE, new String[] { ServiceDB.Services.ID },
|
||||
ServiceDB.Services.ACCOUNT_NAME + "=? AND " + ServiceDB.Services.SERVICE + "=?",
|
||||
new String[] { account.name, ServiceDB.Services.SERVICE_CALDAV }, null, null, null);
|
||||
if (!cursorService.moveToNext())
|
||||
return null;
|
||||
String strServiceID = cursorService.getString(0);
|
||||
|
||||
@Cleanup Cursor cursorHomeSets = db.query(ServiceDB.HomeSets._TABLE, new String[] { ServiceDB.HomeSets.URL },
|
||||
ServiceDB.HomeSets.SERVICE_ID + "=?", new String[] { strServiceID }, null, null, null);
|
||||
while (cursorHomeSets.moveToNext())
|
||||
info.homeSets.add(cursorHomeSets.getString(0));
|
||||
} finally {
|
||||
dbHelper.close();
|
||||
}
|
||||
|
||||
return info;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,252 @@
|
||||
/*
|
||||
* Copyright © 2013 – 2016 Ricki Hirner (bitfire web engineering).
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the GNU Public License v3.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.gnu.org/licenses/gpl.html
|
||||
*/
|
||||
|
||||
package at.bitfire.davdroid.ui;
|
||||
|
||||
import android.accounts.Account;
|
||||
import android.app.Activity;
|
||||
import android.app.Dialog;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.app.DialogFragment;
|
||||
import android.support.v4.app.LoaderManager;
|
||||
import android.support.v4.content.AsyncTaskLoader;
|
||||
import android.support.v4.content.Loader;
|
||||
|
||||
import org.apache.commons.lang3.BooleanUtils;
|
||||
import org.xmlpull.v1.XmlSerializer;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.StringWriter;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import at.bitfire.dav4android.DavResource;
|
||||
import at.bitfire.dav4android.XmlUtils;
|
||||
import at.bitfire.dav4android.exception.HttpException;
|
||||
import at.bitfire.davdroid.App;
|
||||
import at.bitfire.davdroid.DavUtils;
|
||||
import at.bitfire.davdroid.HttpClient;
|
||||
import at.bitfire.davdroid.InvalidAccountException;
|
||||
import at.bitfire.davdroid.R;
|
||||
import at.bitfire.davdroid.model.CollectionInfo;
|
||||
import at.bitfire.davdroid.model.ServiceDB;
|
||||
import lombok.Cleanup;
|
||||
import okhttp3.HttpUrl;
|
||||
import okhttp3.OkHttpClient;
|
||||
|
||||
public class CreateCollectionFragment extends DialogFragment implements LoaderManager.LoaderCallbacks<Exception> {
|
||||
private static final String
|
||||
ARG_ACCOUNT = "account",
|
||||
ARG_COLLECTION_INFO = "collectionInfo";
|
||||
|
||||
protected Account account;
|
||||
protected CollectionInfo info;
|
||||
|
||||
public static CreateCollectionFragment newInstance(Account account, CollectionInfo info) {
|
||||
CreateCollectionFragment frag = new CreateCollectionFragment();
|
||||
Bundle args = new Bundle(2);
|
||||
args.putParcelable(ARG_ACCOUNT, account);
|
||||
args.putSerializable(ARG_COLLECTION_INFO, info);
|
||||
frag.setArguments(args);
|
||||
return frag;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
account = getArguments().getParcelable(ARG_ACCOUNT);
|
||||
info = (CollectionInfo)getArguments().getSerializable(ARG_COLLECTION_INFO);
|
||||
|
||||
getLoaderManager().initLoader(0, null, this);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
ProgressDialog progress = new ProgressDialog(getContext());
|
||||
progress.setTitle(R.string.create_collection_creating);
|
||||
progress.setMessage(getString(R.string.please_wait));
|
||||
progress.setIndeterminate(true);
|
||||
progress.setCanceledOnTouchOutside(false);
|
||||
setCancelable(false);
|
||||
return progress;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Loader<Exception> onCreateLoader(int id, Bundle args) {
|
||||
return new CreateCollectionLoader(getContext(), account, info);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadFinished(Loader<Exception> loader, Exception exception) {
|
||||
dismissAllowingStateLoss();
|
||||
|
||||
Activity parent = getActivity();
|
||||
if (parent != null) {
|
||||
if (exception != null)
|
||||
getFragmentManager().beginTransaction()
|
||||
.add(ExceptionInfoFragment.newInstance(exception, account), null)
|
||||
.commitAllowingStateLoss();
|
||||
else
|
||||
parent.finish();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoaderReset(Loader<Exception> loader) {
|
||||
}
|
||||
|
||||
|
||||
protected static class CreateCollectionLoader extends AsyncTaskLoader<Exception> {
|
||||
final Account account;
|
||||
final CollectionInfo info;
|
||||
|
||||
public CreateCollectionLoader(Context context, Account account, CollectionInfo info) {
|
||||
super(context);
|
||||
this.account = account;
|
||||
this.info = info;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStartLoading() {
|
||||
forceLoad();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Exception loadInBackground() {
|
||||
StringWriter writer = new StringWriter();
|
||||
try {
|
||||
XmlSerializer serializer = XmlUtils.newSerializer();
|
||||
serializer.setOutput(writer);
|
||||
serializer.startDocument("UTF-8", null);
|
||||
serializer.setPrefix("", XmlUtils.NS_WEBDAV);
|
||||
serializer.setPrefix("CAL", XmlUtils.NS_CALDAV);
|
||||
serializer.setPrefix("CARD", XmlUtils.NS_CARDDAV);
|
||||
|
||||
serializer.startTag(XmlUtils.NS_WEBDAV, "mkcol");
|
||||
serializer.startTag(XmlUtils.NS_WEBDAV, "set");
|
||||
serializer.startTag(XmlUtils.NS_WEBDAV, "prop");
|
||||
serializer.startTag(XmlUtils.NS_WEBDAV, "resourcetype");
|
||||
serializer.startTag(XmlUtils.NS_WEBDAV, "collection");
|
||||
serializer.endTag(XmlUtils.NS_WEBDAV, "collection");
|
||||
if (info.type == CollectionInfo.Type.ADDRESS_BOOK) {
|
||||
serializer.startTag(XmlUtils.NS_CARDDAV, "addressbook");
|
||||
serializer.endTag(XmlUtils.NS_CARDDAV, "addressbook");
|
||||
} else if (info.type == CollectionInfo.Type.CALENDAR) {
|
||||
serializer.startTag(XmlUtils.NS_CALDAV, "calendar");
|
||||
serializer.endTag(XmlUtils.NS_CALDAV, "calendar");
|
||||
}
|
||||
serializer.endTag(XmlUtils.NS_WEBDAV, "resourcetype");
|
||||
if (info.displayName != null) {
|
||||
serializer.startTag(XmlUtils.NS_WEBDAV, "displayname");
|
||||
serializer.text(info.displayName);
|
||||
serializer.endTag(XmlUtils.NS_WEBDAV, "displayname");
|
||||
}
|
||||
|
||||
// addressbook-specific properties
|
||||
if (info.type == CollectionInfo.Type.ADDRESS_BOOK) {
|
||||
if (info.description != null) {
|
||||
serializer.startTag(XmlUtils.NS_CARDDAV, "addressbook-description");
|
||||
serializer.text(info.description);
|
||||
serializer.endTag(XmlUtils.NS_CARDDAV, "addressbook-description");
|
||||
}
|
||||
}
|
||||
|
||||
// calendar-specific properties
|
||||
if (info.type == CollectionInfo.Type.CALENDAR) {
|
||||
if (info.description != null) {
|
||||
serializer.startTag(XmlUtils.NS_CALDAV, "calendar-description");
|
||||
serializer.text(info.description);
|
||||
serializer.endTag(XmlUtils.NS_CALDAV, "calendar-description");
|
||||
}
|
||||
|
||||
if (info.color != null) {
|
||||
serializer.startTag(XmlUtils.NS_APPLE_ICAL, "calendar-color");
|
||||
serializer.text(DavUtils.ARGBtoCalDAVColor(info.color));
|
||||
serializer.endTag(XmlUtils.NS_APPLE_ICAL, "calendar-color");
|
||||
}
|
||||
|
||||
if (info.timeZone != null) {
|
||||
serializer.startTag(XmlUtils.NS_CALDAV, "calendar-timezone");
|
||||
serializer.cdsect(info.timeZone);
|
||||
serializer.endTag(XmlUtils.NS_CALDAV, "calendar-timezone");
|
||||
}
|
||||
|
||||
serializer.startTag(XmlUtils.NS_CALDAV, "supported-calendar-component-set");
|
||||
if (BooleanUtils.isTrue(info.supportsVEVENT)) {
|
||||
serializer.startTag(XmlUtils.NS_CALDAV, "comp");
|
||||
serializer.attribute(null, "name", "VEVENT");
|
||||
serializer.endTag(XmlUtils.NS_CALDAV, "comp");
|
||||
}
|
||||
if (BooleanUtils.isTrue(info.supportsVTODO)) {
|
||||
serializer.startTag(XmlUtils.NS_CALDAV, "comp");
|
||||
serializer.attribute(null, "name", "VTODO");
|
||||
serializer.endTag(XmlUtils.NS_CALDAV, "comp");
|
||||
}
|
||||
serializer.endTag(XmlUtils.NS_CALDAV, "supported-calendar-component-set");
|
||||
}
|
||||
|
||||
serializer.endTag(XmlUtils.NS_WEBDAV, "prop");
|
||||
serializer.endTag(XmlUtils.NS_WEBDAV, "set");
|
||||
serializer.endTag(XmlUtils.NS_WEBDAV, "mkcol");
|
||||
serializer.endDocument();
|
||||
} catch (IOException e) {
|
||||
App.log.log(Level.SEVERE, "Couldn't assemble Extended MKCOL request", e);
|
||||
}
|
||||
|
||||
ServiceDB.OpenHelper dbHelper = new ServiceDB.OpenHelper(getContext());
|
||||
|
||||
try {
|
||||
OkHttpClient client = HttpClient.create(getContext(), account);
|
||||
DavResource collection = new DavResource(client, HttpUrl.parse(info.url));
|
||||
|
||||
// create collection on remote server
|
||||
collection.mkCol(writer.toString());
|
||||
|
||||
// now insert collection into database:
|
||||
SQLiteDatabase db = dbHelper.getWritableDatabase();
|
||||
|
||||
// 1. find service ID
|
||||
String serviceType;
|
||||
if (info.type == CollectionInfo.Type.ADDRESS_BOOK)
|
||||
serviceType = ServiceDB.Services.SERVICE_CARDDAV;
|
||||
else if (info.type == CollectionInfo.Type.CALENDAR)
|
||||
serviceType = ServiceDB.Services.SERVICE_CALDAV;
|
||||
else
|
||||
throw new IllegalArgumentException("Collection must be an address book or calendar");
|
||||
@Cleanup Cursor c = db.query(ServiceDB.Services._TABLE, new String[] { ServiceDB.Services.ID },
|
||||
ServiceDB.Services.ACCOUNT_NAME + "=? AND " + ServiceDB.Services.SERVICE + "=?",
|
||||
new String[] { account.name, serviceType }, null, null, null
|
||||
);
|
||||
if (!c.moveToNext())
|
||||
throw new IllegalStateException();
|
||||
long serviceID = c.getLong(0);
|
||||
|
||||
// 2. add collection to service
|
||||
ContentValues values = info.toDB();
|
||||
values.put(ServiceDB.Collections.SERVICE_ID, serviceID);
|
||||
db.insert(ServiceDB.Collections._TABLE, null, values);
|
||||
} catch(InvalidAccountException|IOException|HttpException|IllegalStateException e) {
|
||||
return e;
|
||||
} finally {
|
||||
dbHelper.close();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
257
app/src/main/java/at/bitfire/davdroid/ui/DebugInfoActivity.java
Normal file
257
app/src/main/java/at/bitfire/davdroid/ui/DebugInfoActivity.java
Normal file
@@ -0,0 +1,257 @@
|
||||
/*
|
||||
* Copyright © 2013 – 2015 Ricki Hirner (bitfire web engineering).
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the GNU Public License v3.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.gnu.org/licenses/gpl.html
|
||||
*/
|
||||
|
||||
package at.bitfire.davdroid.ui;
|
||||
|
||||
import android.accounts.Account;
|
||||
import android.accounts.AccountManager;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.LoaderManager;
|
||||
import android.content.AsyncTaskLoader;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.Loader;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.provider.CalendarContract;
|
||||
import android.provider.ContactsContract;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.text.TextUtils;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.apache.commons.lang3.exception.ExceptionUtils;
|
||||
import org.apache.commons.lang3.text.WordUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.util.Date;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import at.bitfire.dav4android.exception.HttpException;
|
||||
import at.bitfire.davdroid.AccountSettings;
|
||||
import at.bitfire.davdroid.App;
|
||||
import at.bitfire.davdroid.BuildConfig;
|
||||
import at.bitfire.davdroid.InvalidAccountException;
|
||||
import at.bitfire.davdroid.R;
|
||||
import at.bitfire.davdroid.model.ServiceDB;
|
||||
import lombok.Cleanup;
|
||||
|
||||
public class DebugInfoActivity extends AppCompatActivity implements LoaderManager.LoaderCallbacks<String> {
|
||||
public static final String
|
||||
KEY_THROWABLE = "throwable",
|
||||
KEY_LOGS = "logs",
|
||||
KEY_ACCOUNT = "account",
|
||||
KEY_AUTHORITY = "authority",
|
||||
KEY_PHASE = "phase";
|
||||
|
||||
private static final int MAX_INLINE_REPORT_LENGTH = 8000;
|
||||
|
||||
TextView tvReport;
|
||||
String report;
|
||||
|
||||
File reportFile;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setContentView(R.layout.activity_debug_info);
|
||||
tvReport = (TextView)findViewById(R.id.text_report);
|
||||
|
||||
getLoaderManager().initLoader(0, getIntent().getExtras(), this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
getMenuInflater().inflate(R.menu.activity_debug_info, menu);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public void onShare(MenuItem item) {
|
||||
if (!TextUtils.isEmpty(report)) {
|
||||
Intent sendIntent = new Intent();
|
||||
sendIntent.setAction(Intent.ACTION_SEND);
|
||||
sendIntent.setType("text/plain");
|
||||
sendIntent.putExtra(Intent.EXTRA_SUBJECT, "DAVdroid debug info");
|
||||
|
||||
boolean inline = false;
|
||||
|
||||
if (report.length() > MAX_INLINE_REPORT_LENGTH)
|
||||
// report is too long for inline text, send it as an attachment
|
||||
try {
|
||||
reportFile = File.createTempFile("davdroid-debug", ".txt", getExternalCacheDir());
|
||||
App.log.fine("Writing debug info to " + reportFile.getAbsolutePath());
|
||||
FileWriter writer = new FileWriter(reportFile);
|
||||
writer.write(report);
|
||||
writer.close();
|
||||
|
||||
sendIntent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(reportFile));
|
||||
} catch (IOException e) {
|
||||
// let's hope the report is < 1 MB (Android IPC limit)
|
||||
inline = true;
|
||||
}
|
||||
else
|
||||
inline = true;
|
||||
|
||||
if (inline)
|
||||
sendIntent.putExtra(Intent.EXTRA_TEXT, report);
|
||||
|
||||
startActivity(sendIntent);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Loader<String> onCreateLoader(int id, Bundle args) {
|
||||
return new ReportLoader(this, args);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadFinished(Loader<String> loader, String data) {
|
||||
if (data != null)
|
||||
tvReport.setText(report = data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoaderReset(Loader<String> loader) {
|
||||
}
|
||||
|
||||
|
||||
static class ReportLoader extends AsyncTaskLoader<String> {
|
||||
|
||||
final Bundle extras;
|
||||
|
||||
public ReportLoader(Context context, Bundle extras) {
|
||||
super(context);
|
||||
this.extras = extras;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStartLoading() {
|
||||
forceLoad();
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressLint("MissingPermission")
|
||||
public String loadInBackground() {
|
||||
Throwable throwable = null;
|
||||
String logs = null,
|
||||
authority = null;
|
||||
Account account = null;
|
||||
int phase = -1;
|
||||
|
||||
if (extras != null) {
|
||||
throwable = (Throwable)extras.getSerializable(KEY_THROWABLE);
|
||||
logs = extras.getString(KEY_LOGS);
|
||||
account = extras.getParcelable(KEY_ACCOUNT);
|
||||
authority = extras.getString(KEY_AUTHORITY);
|
||||
phase = extras.getInt(KEY_PHASE, -1);
|
||||
}
|
||||
|
||||
StringBuilder report = new StringBuilder();
|
||||
|
||||
// begin with most specific information
|
||||
|
||||
if (phase != -1)
|
||||
report.append("SYNCHRONIZATION INFO\nSynchronization phase: ").append(phase).append("\n");
|
||||
if (account != null)
|
||||
report.append("Account name: ").append(account.name).append("\n");
|
||||
if (authority != null)
|
||||
report.append("Authority: ").append(authority).append("\n");
|
||||
|
||||
if (throwable instanceof HttpException) {
|
||||
HttpException http = (HttpException)throwable;
|
||||
if (http.request != null)
|
||||
report.append("\nHTTP REQUEST:\n").append(http.request).append("\n\n");
|
||||
if (http.response != null)
|
||||
report.append("HTTP RESPONSE:\n").append(http.response).append("\n");
|
||||
}
|
||||
|
||||
if (throwable != null)
|
||||
report .append("\nEXCEPTION:\n")
|
||||
.append(ExceptionUtils.getStackTrace(throwable));
|
||||
|
||||
if (logs != null)
|
||||
report.append("\nLOGS:\n").append(logs).append("\n");
|
||||
|
||||
try {
|
||||
PackageManager pm = getContext().getPackageManager();
|
||||
String installedFrom = pm.getInstallerPackageName(BuildConfig.APPLICATION_ID);
|
||||
if (TextUtils.isEmpty(installedFrom))
|
||||
installedFrom = "APK (directly)";
|
||||
boolean workaroundInstalled = false;
|
||||
try {
|
||||
workaroundInstalled = pm.getPackageInfo("at.bitfire.davdroid.jbworkaround", 0) != null;
|
||||
} catch(PackageManager.NameNotFoundException ignored) {}
|
||||
report.append("\nSOFTWARE INFORMATION\n" +
|
||||
"Package: ").append(BuildConfig.APPLICATION_ID).append("\n" +
|
||||
"Version: ").append(BuildConfig.VERSION_NAME).append(" (").append(BuildConfig.VERSION_CODE).append(") ").append(new Date(BuildConfig.buildTime)).append("\n")
|
||||
.append("Installed from: ").append(installedFrom).append("\n")
|
||||
.append("JB Workaround installed: ").append(workaroundInstalled ? "yes" : "no").append("\n\n");
|
||||
} catch(Exception ex) {
|
||||
App.log.log(Level.SEVERE, "Couldn't get software information", ex);
|
||||
}
|
||||
|
||||
report.append(
|
||||
"CONFIGURATION\n" +
|
||||
"System-wide synchronization: ").append(ContentResolver.getMasterSyncAutomatically() ? "automatically" : "manually").append("\n");
|
||||
AccountManager accountManager = AccountManager.get(getContext());
|
||||
for (Account acct : accountManager.getAccountsByType(getContext().getString(R.string.account_type)))
|
||||
try {
|
||||
AccountSettings settings = new AccountSettings(getContext(), acct);
|
||||
report.append("Account: ").append(acct.name).append("\n" +
|
||||
" Address book sync. interval: ").append(syncStatus(settings, ContactsContract.AUTHORITY)).append("\n" +
|
||||
" Calendar sync. interval: ").append(syncStatus(settings, CalendarContract.AUTHORITY)).append("\n" +
|
||||
" OpenTasks sync. interval: ").append(syncStatus(settings, "org.dmfs.tasks")).append("\n" +
|
||||
" WiFi only: ").append(settings.getSyncWifiOnly());
|
||||
if (settings.getSyncWifiOnlySSID() != null)
|
||||
report.append(", SSID: ").append(settings.getSyncWifiOnlySSID());
|
||||
report.append("\n [CardDAV] Contact group method: ").append(settings.getGroupMethod())
|
||||
.append("\n RFC 6868 encoding: ").append(settings.getVCardRFC6868())
|
||||
.append("\n [CalDAV] Time range (past days): ").append(settings.getTimeRangePastDays())
|
||||
.append("\n Manage calendar colors: ").append(settings.getManageCalendarColors())
|
||||
.append("\n");
|
||||
} catch(InvalidAccountException e) {
|
||||
report.append(acct).append(" is invalid (unsupported settings version) or does not exist\n");
|
||||
}
|
||||
report.append("\n");
|
||||
|
||||
report.append("SQLITE DUMP\n");
|
||||
@Cleanup ServiceDB.OpenHelper dbHelper = new ServiceDB.OpenHelper(getContext());
|
||||
dbHelper.dump(report);
|
||||
report.append("\n");
|
||||
|
||||
try {
|
||||
report.append(
|
||||
"SYSTEM INFORMATION\n" +
|
||||
"Android version: ").append(Build.VERSION.RELEASE).append(" (").append(Build.DISPLAY).append(")\n" +
|
||||
"Device: ").append(WordUtils.capitalize(Build.MANUFACTURER)).append(" ").append(Build.MODEL).append(" (").append(Build.DEVICE).append(")\n\n"
|
||||
);
|
||||
} catch(Exception ex) {
|
||||
App.log.log(Level.SEVERE, "Couldn't get system details", ex);
|
||||
}
|
||||
|
||||
return report.toString();
|
||||
}
|
||||
|
||||
protected String syncStatus(AccountSettings settings, String authority) {
|
||||
Long interval = settings.getSyncInterval(authority);
|
||||
return interval != null ?
|
||||
(interval == AccountSettings.SYNC_INTERVAL_MANUALLY ? "manually" : interval/60 + " min") :
|
||||
"—";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
/*
|
||||
* Copyright © 2013 – 2016 Ricki Hirner (bitfire web engineering).
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the GNU Public License v3.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.gnu.org/licenses/gpl.html
|
||||
*/
|
||||
|
||||
package at.bitfire.davdroid.ui;
|
||||
|
||||
import android.accounts.Account;
|
||||
import android.app.Activity;
|
||||
import android.app.Dialog;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.app.DialogFragment;
|
||||
import android.support.v4.app.LoaderManager;
|
||||
import android.support.v4.content.AsyncTaskLoader;
|
||||
import android.support.v4.content.Loader;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import at.bitfire.dav4android.DavResource;
|
||||
import at.bitfire.dav4android.exception.HttpException;
|
||||
import at.bitfire.davdroid.HttpClient;
|
||||
import at.bitfire.davdroid.InvalidAccountException;
|
||||
import at.bitfire.davdroid.R;
|
||||
import at.bitfire.davdroid.model.CollectionInfo;
|
||||
import at.bitfire.davdroid.model.ServiceDB;
|
||||
import okhttp3.HttpUrl;
|
||||
import okhttp3.OkHttpClient;
|
||||
|
||||
public class DeleteCollectionFragment extends DialogFragment implements LoaderManager.LoaderCallbacks<Exception> {
|
||||
protected static final String
|
||||
ARG_ACCOUNT = "account",
|
||||
ARG_COLLECTION_INFO = "collectionInfo";
|
||||
|
||||
protected Account account;
|
||||
protected CollectionInfo collectionInfo;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
getLoaderManager().initLoader(0, getArguments(), this);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
ProgressDialog progress = new ProgressDialog(getContext());
|
||||
progress.setTitle(R.string.delete_collection_deleting_collection);
|
||||
progress.setMessage(getString(R.string.please_wait));
|
||||
progress.setIndeterminate(true);
|
||||
progress.setCanceledOnTouchOutside(false);
|
||||
setCancelable(false);
|
||||
return progress;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Loader<Exception> onCreateLoader(int id, Bundle args) {
|
||||
account = args.getParcelable(ARG_ACCOUNT);
|
||||
collectionInfo = (CollectionInfo)args.getSerializable(ARG_COLLECTION_INFO);
|
||||
return new DeleteCollectionLoader(getContext(), account, collectionInfo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadFinished(Loader loader, Exception exception) {
|
||||
dismissAllowingStateLoss();
|
||||
|
||||
if (exception != null)
|
||||
getFragmentManager().beginTransaction()
|
||||
.add(ExceptionInfoFragment.newInstance(exception, account), null)
|
||||
.commitAllowingStateLoss();
|
||||
else {
|
||||
Activity activity = getActivity();
|
||||
if (activity instanceof AccountActivity)
|
||||
((AccountActivity)activity).reload();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoaderReset(Loader loader) {
|
||||
}
|
||||
|
||||
|
||||
protected static class DeleteCollectionLoader extends AsyncTaskLoader<Exception> {
|
||||
final Account account;
|
||||
final CollectionInfo collectionInfo;
|
||||
final ServiceDB.OpenHelper dbHelper;
|
||||
|
||||
public DeleteCollectionLoader(Context context, Account account, CollectionInfo collectionInfo) {
|
||||
super(context);
|
||||
this.account = account;
|
||||
this.collectionInfo = collectionInfo;
|
||||
dbHelper = new ServiceDB.OpenHelper(getContext());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStartLoading() {
|
||||
forceLoad();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Exception loadInBackground() {
|
||||
try {
|
||||
OkHttpClient httpClient = HttpClient.create(getContext(), account);
|
||||
DavResource collection = new DavResource(httpClient, HttpUrl.parse(collectionInfo.url));
|
||||
|
||||
// delete collection from server
|
||||
collection.delete(null);
|
||||
|
||||
// delete collection locally
|
||||
SQLiteDatabase db = dbHelper.getWritableDatabase();
|
||||
db.delete(ServiceDB.Collections._TABLE, ServiceDB.Collections.ID + "=?", new String[] { String.valueOf(collectionInfo.id) });
|
||||
|
||||
return null;
|
||||
} catch (InvalidAccountException|IOException|HttpException e) {
|
||||
return e;
|
||||
} finally {
|
||||
dbHelper.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static class ConfirmDeleteCollectionFragment extends DialogFragment {
|
||||
|
||||
public static ConfirmDeleteCollectionFragment newInstance(Account account, CollectionInfo collectionInfo) {
|
||||
ConfirmDeleteCollectionFragment frag = new ConfirmDeleteCollectionFragment();
|
||||
Bundle args = new Bundle(2);
|
||||
args.putParcelable(ARG_ACCOUNT, account);
|
||||
args.putSerializable(ARG_COLLECTION_INFO, collectionInfo);
|
||||
frag.setArguments(args);
|
||||
return frag;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
CollectionInfo collectionInfo = (CollectionInfo)getArguments().getSerializable(ARG_COLLECTION_INFO);
|
||||
String name = TextUtils.isEmpty(collectionInfo.displayName) ? collectionInfo.url : collectionInfo.displayName;
|
||||
|
||||
return new AlertDialog.Builder(getContext())
|
||||
.setTitle(R.string.delete_collection_confirm_title)
|
||||
.setMessage(getString(R.string.delete_collection_confirm_warning, name))
|
||||
.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
DialogFragment frag = new DeleteCollectionFragment();
|
||||
frag.setArguments(getArguments());
|
||||
frag.show(getFragmentManager(), null);
|
||||
}
|
||||
})
|
||||
.setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
dismiss();
|
||||
}
|
||||
})
|
||||
.create();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright © 2013 – 2016 Ricki Hirner (bitfire web engineering).
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the GNU Public License v3.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.gnu.org/licenses/gpl.html
|
||||
*/
|
||||
|
||||
package at.bitfire.davdroid.ui;
|
||||
|
||||
import android.accounts.Account;
|
||||
import android.app.Dialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.app.DialogFragment;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import at.bitfire.dav4android.exception.HttpException;
|
||||
import at.bitfire.davdroid.R;
|
||||
|
||||
public class ExceptionInfoFragment extends DialogFragment {
|
||||
protected static final String
|
||||
ARG_ACCOUNT = "account",
|
||||
ARG_EXCEPTION = "exception";
|
||||
|
||||
public static ExceptionInfoFragment newInstance(@NonNull Exception exception, Account account) {
|
||||
ExceptionInfoFragment frag = new ExceptionInfoFragment();
|
||||
Bundle args = new Bundle(1);
|
||||
args.putSerializable(ARG_EXCEPTION, exception);
|
||||
args.putParcelable(ARG_ACCOUNT, account);
|
||||
frag.setArguments(args);
|
||||
return frag;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
Bundle args = getArguments();
|
||||
final Exception exception = (Exception)args.getSerializable(ARG_EXCEPTION);
|
||||
final Account account = args.getParcelable(ARG_ACCOUNT);
|
||||
|
||||
int title = R.string.exception;
|
||||
if (exception instanceof HttpException)
|
||||
title = R.string.exception_httpexception;
|
||||
else if (exception instanceof IOException)
|
||||
title = R.string.exception_ioexception;
|
||||
|
||||
Dialog dialog = new AlertDialog.Builder(getContext())
|
||||
.setIcon(R.drawable.ic_error_dark)
|
||||
.setTitle(title)
|
||||
.setMessage(exception.getClass().getCanonicalName() + "\n" + exception.getLocalizedMessage())
|
||||
.setNegativeButton(R.string.exception_show_details, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
Intent intent = new Intent(getContext(), DebugInfoActivity.class);
|
||||
intent.putExtra(DebugInfoActivity.KEY_THROWABLE, exception);
|
||||
if (account != null)
|
||||
intent.putExtra(DebugInfoActivity.KEY_ACCOUNT, account);
|
||||
startActivity(intent);
|
||||
}
|
||||
})
|
||||
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
}
|
||||
})
|
||||
.create();
|
||||
setCancelable(false);
|
||||
return dialog;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package at.bitfire.davdroid.ui;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.view.MenuItem;
|
||||
|
||||
public interface IAccountsDrawerHandler {
|
||||
|
||||
public boolean onNavigationItemSelected(@NonNull Activity activity, MenuItem item);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* Copyright © 2013 – 2016 Ricki Hirner (bitfire web engineering).
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the GNU Public License v3.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.gnu.org/licenses/gpl.html
|
||||
*/
|
||||
|
||||
package at.bitfire.davdroid.ui;
|
||||
|
||||
import android.Manifest;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.ActivityCompat;
|
||||
import android.support.v4.app.NotificationManagerCompat;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.view.View;
|
||||
|
||||
import at.bitfire.davdroid.Constants;
|
||||
import at.bitfire.davdroid.R;
|
||||
import at.bitfire.davdroid.resource.LocalTaskList;
|
||||
|
||||
public class PermissionsActivity extends AppCompatActivity {
|
||||
|
||||
public static final String
|
||||
PERMISSION_READ_TASKS = "org.dmfs.permission.READ_TASKS",
|
||||
PERMISSION_WRITE_TASKS = "org.dmfs.permission.WRITE_TASKS";
|
||||
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_permissions);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
refresh();
|
||||
}
|
||||
|
||||
protected void refresh() {
|
||||
boolean noCalendarPermissions =
|
||||
ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_CALENDAR) != PackageManager.PERMISSION_GRANTED ||
|
||||
ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_CALENDAR) != PackageManager.PERMISSION_GRANTED;
|
||||
findViewById(R.id.calendar_permissions).setVisibility(noCalendarPermissions ? View.VISIBLE : View.GONE);
|
||||
|
||||
boolean noContactsPermissions =
|
||||
ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED ||
|
||||
ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_CONTACTS) != PackageManager.PERMISSION_GRANTED;
|
||||
findViewById(R.id.contacts_permissions).setVisibility(noContactsPermissions ? View.VISIBLE : View.GONE);
|
||||
|
||||
boolean noTaskPermissions;
|
||||
if (LocalTaskList.tasksProviderAvailable(this)) {
|
||||
noTaskPermissions =
|
||||
ActivityCompat.checkSelfPermission(this, PERMISSION_READ_TASKS) != PackageManager.PERMISSION_GRANTED ||
|
||||
ActivityCompat.checkSelfPermission(this, PERMISSION_WRITE_TASKS) != PackageManager.PERMISSION_GRANTED;
|
||||
findViewById(R.id.opentasks_permissions).setVisibility(noTaskPermissions ? View.VISIBLE : View.GONE);
|
||||
} else {
|
||||
findViewById(R.id.opentasks_permissions).setVisibility(View.GONE);
|
||||
noTaskPermissions = false;
|
||||
}
|
||||
|
||||
if (!noCalendarPermissions && !noContactsPermissions && !noTaskPermissions) {
|
||||
NotificationManagerCompat nm = NotificationManagerCompat.from(this);
|
||||
nm.cancel(Constants.NOTIFICATION_PERMISSIONS);
|
||||
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
public void requestCalendarPermissions(View v) {
|
||||
ActivityCompat.requestPermissions(this, new String[] {
|
||||
Manifest.permission.READ_CALENDAR,
|
||||
Manifest.permission.WRITE_CALENDAR
|
||||
}, 0);
|
||||
}
|
||||
|
||||
public void requestContactsPermissions(View v) {
|
||||
ActivityCompat.requestPermissions(this, new String[] {
|
||||
Manifest.permission.READ_CONTACTS,
|
||||
Manifest.permission.WRITE_CONTACTS
|
||||
}, 0);
|
||||
}
|
||||
|
||||
public void requestOpenTasksPermissions(View v) {
|
||||
ActivityCompat.requestPermissions(this, new String[] {
|
||||
PERMISSION_READ_TASKS,
|
||||
PERMISSION_WRITE_TASKS
|
||||
}, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||
refresh();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,230 @@
|
||||
/*
|
||||
* Copyright © 2013 – 2015 Ricki Hirner (bitfire web engineering).
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the GNU Public License v3.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.gnu.org/licenses/gpl.html
|
||||
*/
|
||||
|
||||
package at.bitfire.davdroid.ui;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.PowerManager;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.app.DialogFragment;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import at.bitfire.davdroid.App;
|
||||
import at.bitfire.davdroid.BuildConfig;
|
||||
import at.bitfire.davdroid.Constants;
|
||||
import at.bitfire.davdroid.R;
|
||||
import at.bitfire.davdroid.model.ServiceDB;
|
||||
import at.bitfire.davdroid.model.Settings;
|
||||
import at.bitfire.davdroid.resource.LocalTaskList;
|
||||
import lombok.Cleanup;
|
||||
|
||||
public class StartupDialogFragment extends DialogFragment {
|
||||
public static final String
|
||||
HINT_BATTERY_OPTIMIZATIONS = "hint_BatteryOptimizations",
|
||||
HINT_GOOGLE_PLAY_ACCOUNTS_REMOVED = "hint_GooglePlayAccountsRemoved",
|
||||
HINT_OPENTASKS_NOT_INSTALLED = "hint_OpenTasksNotInstalled";
|
||||
|
||||
private static final String ARGS_MODE = "mode";
|
||||
|
||||
enum Mode {
|
||||
BATTERY_OPTIMIZATIONS,
|
||||
DEVELOPMENT_VERSION,
|
||||
GOOGLE_PLAY_ACCOUNTS_REMOVED,
|
||||
OPENTASKS_NOT_INSTALLED
|
||||
}
|
||||
|
||||
public static StartupDialogFragment[] getStartupDialogs(Context context) {
|
||||
List<StartupDialogFragment> dialogs = new LinkedList<>();
|
||||
|
||||
@Cleanup ServiceDB.OpenHelper dbHelper = new ServiceDB.OpenHelper(context);
|
||||
Settings settings = new Settings(dbHelper.getReadableDatabase());
|
||||
|
||||
if (BuildConfig.VERSION_NAME.contains("-alpha") || BuildConfig.VERSION_NAME.contains("-beta") || BuildConfig.VERSION_NAME.contains("-rc"))
|
||||
dialogs.add(StartupDialogFragment.instantiate(Mode.DEVELOPMENT_VERSION));
|
||||
else {
|
||||
// store-specific information
|
||||
if (BuildConfig.FLAVOR == App.FLAVOR_GOOGLE_PLAY) {
|
||||
// Play store
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP && // only on Android <5
|
||||
settings.getBoolean(HINT_GOOGLE_PLAY_ACCOUNTS_REMOVED, true)) // and only when "Don't show again" hasn't been clicked yet
|
||||
dialogs.add(StartupDialogFragment.instantiate(Mode.GOOGLE_PLAY_ACCOUNTS_REMOVED));
|
||||
}
|
||||
}
|
||||
|
||||
// battery optimization whitelisting
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && settings.getBoolean(HINT_BATTERY_OPTIMIZATIONS, true)) {
|
||||
PowerManager powerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
|
||||
if (!powerManager.isIgnoringBatteryOptimizations(BuildConfig.APPLICATION_ID))
|
||||
dialogs.add(StartupDialogFragment.instantiate(Mode.BATTERY_OPTIMIZATIONS));
|
||||
}
|
||||
|
||||
// OpenTasks information
|
||||
if (!LocalTaskList.tasksProviderAvailable(context) && settings.getBoolean(HINT_OPENTASKS_NOT_INSTALLED, true))
|
||||
dialogs.add(StartupDialogFragment.instantiate(Mode.OPENTASKS_NOT_INSTALLED));
|
||||
|
||||
Collections.reverse(dialogs);
|
||||
return dialogs.toArray(new StartupDialogFragment[dialogs.size()]);
|
||||
}
|
||||
|
||||
public static StartupDialogFragment instantiate(Mode mode) {
|
||||
StartupDialogFragment frag = new StartupDialogFragment();
|
||||
Bundle args = new Bundle(1);
|
||||
args.putString(ARGS_MODE, mode.name());
|
||||
frag.setArguments(args);
|
||||
return frag;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
@TargetApi(Build.VERSION_CODES.M)
|
||||
@SuppressLint("BatteryLife")
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
setCancelable(false);
|
||||
|
||||
Mode mode = Mode.valueOf(getArguments().getString(ARGS_MODE));
|
||||
switch (mode) {
|
||||
case BATTERY_OPTIMIZATIONS:
|
||||
return new AlertDialog.Builder(getActivity())
|
||||
.setTitle(R.string.startup_battery_optimization)
|
||||
.setMessage(R.string.startup_battery_optimization_message)
|
||||
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
}
|
||||
})
|
||||
.setNeutralButton(R.string.startup_battery_optimization_disable, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
Intent intent = new Intent(android.provider.Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS,
|
||||
Uri.parse("package:" + BuildConfig.APPLICATION_ID));
|
||||
getContext().startActivity(intent);
|
||||
}
|
||||
})
|
||||
.setNegativeButton(R.string.startup_dont_show_again, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
@Cleanup ServiceDB.OpenHelper dbHelper = new ServiceDB.OpenHelper(getContext());
|
||||
Settings settings = new Settings(dbHelper.getWritableDatabase());
|
||||
settings.putBoolean(HINT_BATTERY_OPTIMIZATIONS, false);
|
||||
}
|
||||
})
|
||||
.create();
|
||||
|
||||
case DEVELOPMENT_VERSION:
|
||||
return new AlertDialog.Builder(getActivity())
|
||||
.setIcon(R.mipmap.ic_launcher)
|
||||
.setTitle(R.string.startup_development_version)
|
||||
.setMessage(R.string.startup_development_version_message)
|
||||
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
}
|
||||
})
|
||||
.setNeutralButton(R.string.startup_development_version_give_feedback, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
startActivity(new Intent(Intent.ACTION_VIEW, Constants.webUri.buildUpon().appendEncodedPath("forums/").build()));
|
||||
}
|
||||
})
|
||||
.create();
|
||||
|
||||
case GOOGLE_PLAY_ACCOUNTS_REMOVED:
|
||||
Drawable icon = null;
|
||||
try {
|
||||
icon = getContext().getPackageManager().getApplicationIcon("com.android.vending").getCurrent();
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
App.log.log(Level.WARNING, "Can't load Play Store icon", e);
|
||||
}
|
||||
return new AlertDialog.Builder(getActivity())
|
||||
.setIcon(icon)
|
||||
.setTitle(R.string.startup_google_play_accounts_removed)
|
||||
.setMessage(R.string.startup_google_play_accounts_removed_message)
|
||||
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
}
|
||||
})
|
||||
.setNeutralButton(R.string.startup_google_play_accounts_removed_more_info, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW, Constants.webUri.buildUpon().appendEncodedPath("faq/").build());
|
||||
getContext().startActivity(intent);
|
||||
}
|
||||
})
|
||||
.setNegativeButton(R.string.startup_dont_show_again, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
@Cleanup ServiceDB.OpenHelper dbHelper = new ServiceDB.OpenHelper(getContext());
|
||||
Settings settings = new Settings(dbHelper.getWritableDatabase());
|
||||
settings.putBoolean(HINT_GOOGLE_PLAY_ACCOUNTS_REMOVED, false);
|
||||
}
|
||||
})
|
||||
.create();
|
||||
|
||||
case OPENTASKS_NOT_INSTALLED:
|
||||
StringBuilder builder = new StringBuilder(getString(R.string.startup_opentasks_not_installed_message));
|
||||
if (Build.VERSION.SDK_INT < 23)
|
||||
builder.append("\n\n").append(getString(R.string.startup_opentasks_reinstall_davdroid));
|
||||
return new AlertDialog.Builder(getActivity())
|
||||
.setIcon(R.drawable.ic_alarm_on_dark)
|
||||
.setTitle(R.string.startup_opentasks_not_installed)
|
||||
.setMessage(builder.toString())
|
||||
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
}
|
||||
})
|
||||
.setNeutralButton(R.string.startup_opentasks_not_installed_install, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=org.dmfs.tasks"));
|
||||
if (intent.resolveActivity(getContext().getPackageManager()) != null)
|
||||
getContext().startActivity(intent);
|
||||
else
|
||||
App.log.warning("No market app available, can't install OpenTasks");
|
||||
}
|
||||
})
|
||||
.setNegativeButton(R.string.startup_dont_show_again, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
@Cleanup ServiceDB.OpenHelper dbHelper = new ServiceDB.OpenHelper(getContext());
|
||||
Settings settings = new Settings(dbHelper.getWritableDatabase());
|
||||
settings.putBoolean(HINT_OPENTASKS_NOT_INSTALLED, false);
|
||||
}
|
||||
})
|
||||
.create();
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException(/* illegal mode argument */);
|
||||
}
|
||||
|
||||
private static String installedFrom(Context context) {
|
||||
try {
|
||||
return context.getPackageManager().getInstallerPackageName(context.getPackageName());
|
||||
} catch(IllegalArgumentException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user