1
0
mirror of https://github.com/etesync/android synced 2025-08-04 12:55:18 +00:00

Better group support

* change group methods to less specific values
* new account settings version: change group method to CATEGORIES for updated accounts
* change group method from CATEGORIES to GROUP_VCARDS automatically when a group VCard is received

GUI:
* AccountSettings: disable CalDAV/CardDAV options when the corresponding service is not available
* AccountSettings: new option to choose contact group method
* account setup: allow to choose contact group method at account creation
This commit is contained in:
Ricki Hirner 2016-06-19 18:52:56 +02:00
parent be2e15e463
commit 389af2b738
11 changed files with 184 additions and 98 deletions

View File

@ -50,7 +50,7 @@ import lombok.Cleanup;
import okhttp3.HttpUrl; import okhttp3.HttpUrl;
public class AccountSettings { public class AccountSettings {
private final static int CURRENT_VERSION = 3; private final static int CURRENT_VERSION = 4;
private final static String private final static String
KEY_SETTINGS_VERSION = "version", KEY_SETTINGS_VERSION = "version",
@ -80,10 +80,8 @@ public class AccountSettings {
private final static String KEY_MANAGE_CALENDAR_COLORS = "manage_calendar_colors"; private final static String KEY_MANAGE_CALENDAR_COLORS = "manage_calendar_colors";
/** Contact group method: /** Contact group method:
automatic VCard4 if server supports VCard 4, VCard3 otherwise (default value) value = null (not existing) groups as separate VCards (default)
VCard3 adds a contact's groups to its CATEGORIES / interprets a contact's CATEGORIES as groups "CATEGORIES" groups are per-contact CATEGORIES
VCard4 uses groups as defined in VCard 4 (KIND/MEMBER properties)
Apple uses Apple-proprietary X-ADDRESSBOOK-KIND/-MEMBER properties
*/ */
private final static String KEY_CONTACT_GROUP_METHOD = "contact_group_method"; private final static String KEY_CONTACT_GROUP_METHOD = "contact_group_method";
@ -240,11 +238,11 @@ public class AccountSettings {
final String name = accountManager.getUserData(account, KEY_CONTACT_GROUP_METHOD); final String name = accountManager.getUserData(account, KEY_CONTACT_GROUP_METHOD);
return name != null ? return name != null ?
GroupMethod.valueOf(name) : GroupMethod.valueOf(name) :
GroupMethod.AUTOMATIC; GroupMethod.GROUP_VCARDS;
} }
public void setGroupMethod(@NonNull GroupMethod method) { public void setGroupMethod(@NonNull GroupMethod method) {
final String name = GroupMethod.AUTOMATIC.equals(method) ? null : method.name(); final String name = method == GroupMethod.GROUP_VCARDS ? null : method.name();
accountManager.setUserData(account, KEY_CONTACT_GROUP_METHOD, name); accountManager.setUserData(account, KEY_CONTACT_GROUP_METHOD, name);
} }
@ -424,6 +422,11 @@ public class AccountSettings {
accountManager.setUserData(account, KEY_SETTINGS_VERSION, "3"); accountManager.setUserData(account, KEY_SETTINGS_VERSION, "3");
} }
@SuppressWarnings({ "Recycle", "unused" })
private void update_3_4() {
setGroupMethod(GroupMethod.CATEGORIES);
}
public static class AppUpdatedReceiver extends BroadcastReceiver { public static class AppUpdatedReceiver extends BroadcastReceiver {

View File

@ -164,6 +164,14 @@ public class LocalAddressBook extends AndroidAddressBook implements LocalCollect
group.delete(); 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 // SYNC STATE

View File

@ -19,6 +19,7 @@ import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
import android.provider.ContactsContract.Groups; import android.provider.ContactsContract.Groups;
import android.provider.ContactsContract.RawContacts; import android.provider.ContactsContract.RawContacts;
import android.provider.ContactsContract.RawContacts.Data; import android.provider.ContactsContract.RawContacts.Data;
import android.provider.ContactsContract.RawContactsEntity;
import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.ArrayUtils;

View File

@ -83,13 +83,13 @@ import okhttp3.ResponseBody;
* <p></p>Group handling differs according to the {@link #groupMethod}. There are two basic methods to * <p></p>Group handling differs according to the {@link #groupMethod}. There are two basic methods to
* handle/manage groups:</p> * handle/manage groups:</p>
* <ul> * <ul>
* <li>VCard3 {@code CATEGORIES}: groups memberships are attached to each contact and represented as * <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 * "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 * 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, * 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 * which is done in {@link #postProcess()} because groups may become empty after downloading
* updated remoted contacts.</li> * updated remoted contacts.</li>
* <li>VCard4-style: individual and group contacts (with a list of member UIDs) are * <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. * distinguished. When a local group is dirty, its members don't need to be set to dirty.
* <ol> * <ol>
* <li>However, when a contact is dirty, it has * <li>However, when a contact is dirty, it has
@ -171,11 +171,9 @@ public class ContactsSyncManager extends SyncManager {
App.log.info("Server advertises VCard/4 support: " + hasVCard4); App.log.info("Server advertises VCard/4 support: " + hasVCard4);
groupMethod = settings.getGroupMethod(); groupMethod = settings.getGroupMethod();
if (GroupMethod.AUTOMATIC.equals(groupMethod))
groupMethod = hasVCard4 ? GroupMethod.VCARD4 : GroupMethod.VCARD3_CATEGORIES;
App.log.info("Contact group method: " + groupMethod); App.log.info("Contact group method: " + groupMethod);
localAddressBook().includeGroups = !GroupMethod.VCARD3_CATEGORIES.equals(groupMethod); localAddressBook().includeGroups = groupMethod == GroupMethod.GROUP_VCARDS;
} }
@Override @Override
@ -184,13 +182,14 @@ public class ContactsSyncManager extends SyncManager {
LocalAddressBook addressBook = localAddressBook(); LocalAddressBook addressBook = localAddressBook();
if (GroupMethod.VCARD3_CATEGORIES.equals(groupMethod)) { if (groupMethod == GroupMethod.CATEGORIES) {
/* VCard3 group handling: groups memberships are represented as contact CATEGORIES */ /* groups memberships are represented as contact CATEGORIES */
// groups with DELETED=1: set all members to dirty, then remove group // groups with DELETED=1: set all members to dirty, then remove group
for (LocalGroup group : addressBook.getDeletedGroups()) { for (LocalGroup group : addressBook.getDeletedGroups()) {
App.log.fine("Removing group " + group + " and marking its members as dirty"); App.log.fine("Finally removing group " + group);
group.markMembersDirty(); // useless because Android deletes group memberships as soon as a group is set to DELETED:
// group.markMembersDirty();
group.delete(); group.delete();
} }
@ -201,7 +200,7 @@ public class ContactsSyncManager extends SyncManager {
group.clearDirty(null); group.clearDirty(null);
} }
} else { } else {
/* VCard4 group handling: there are group contacts and individual contacts */ /* groups as separate VCards: there are group contacts and individual contacts */
// mark groups with changed members as dirty // mark groups with changed members as dirty
BatchOperation batch = new BatchOperation(addressBook.provider); BatchOperation batch = new BatchOperation(addressBook.provider);
@ -231,8 +230,8 @@ public class ContactsSyncManager extends SyncManager {
LocalContact local = ((LocalContact)resource); LocalContact local = ((LocalContact)resource);
contact = local.getContact(); contact = local.getContact();
if (groupMethod == GroupMethod.VCARD3_CATEGORIES) { if (groupMethod == GroupMethod.CATEGORIES) {
// VCard3: add groups as CATEGORIES // add groups as CATEGORIES
for (long groupID : local.getGroupMemberships()) { for (long groupID : local.getGroupMemberships()) {
try { try {
@Cleanup Cursor c = provider.query( @Cleanup Cursor c = provider.query(
@ -362,7 +361,7 @@ public class ContactsSyncManager extends SyncManager {
@Override @Override
protected void postProcess() throws CalendarStorageException, ContactsStorageException { protected void postProcess() throws CalendarStorageException, ContactsStorageException {
if (groupMethod == GroupMethod.VCARD3_CATEGORIES) { if (groupMethod == GroupMethod.CATEGORIES) {
/* VCard3 group handling: groups memberships are represented as contact CATEGORIES */ /* VCard3 group handling: groups memberships are represented as contact CATEGORIES */
// remove empty groups // remove empty groups
@ -399,6 +398,13 @@ public class ContactsSyncManager extends SyncManager {
final Contact newData = contacts[0]; 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 // update local contact, if it exists
LocalResource local = localResources.get(fileName); LocalResource local = localResources.get(fileName);
if (local != null) { if (local != null) {
@ -446,7 +452,7 @@ public class ContactsSyncManager extends SyncManager {
syncResult.stats.numInserts++; syncResult.stats.numInserts++;
} }
if (groupMethod == GroupMethod.VCARD3_CATEGORIES && local instanceof LocalContact) { if (groupMethod == GroupMethod.CATEGORIES && local instanceof LocalContact) {
// VCard3: update group memberships from CATEGORIES // VCard3: update group memberships from CATEGORIES
LocalContact contact = (LocalContact)local; LocalContact contact = (LocalContact)local;

View File

@ -98,8 +98,9 @@ public class AccountSettingsActivity extends AppCompatActivity {
prefUserName.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { prefUserName.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override @Override
public boolean onPreferenceChange(Preference preference, Object newValue) { public boolean onPreferenceChange(Preference preference, Object newValue) {
settings.username((String) newValue); settings.username((String)newValue);
refresh(); return false; refresh();
return false;
} }
}); });
@ -107,8 +108,9 @@ public class AccountSettingsActivity extends AppCompatActivity {
prefPassword.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { prefPassword.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override @Override
public boolean onPreferenceChange(Preference preference, Object newValue) { public boolean onPreferenceChange(Preference preference, Object newValue) {
settings.password((String) newValue); settings.password((String)newValue);
refresh(); return false; refresh();
return false;
} }
}); });
@ -117,8 +119,9 @@ public class AccountSettingsActivity extends AppCompatActivity {
prefPreemptive.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { prefPreemptive.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override @Override
public boolean onPreferenceChange(Preference preference, Object newValue) { public boolean onPreferenceChange(Preference preference, Object newValue) {
settings.preemptiveAuth((Boolean) newValue); settings.preemptiveAuth((Boolean)newValue);
refresh(); return false; refresh();
return false;
} }
}); });
@ -134,8 +137,9 @@ public class AccountSettingsActivity extends AppCompatActivity {
prefSyncContacts.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { prefSyncContacts.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override @Override
public boolean onPreferenceChange(Preference preference, Object newValue) { public boolean onPreferenceChange(Preference preference, Object newValue) {
settings.setSyncInterval(ContactsContract.AUTHORITY, Long.parseLong((String) newValue)); settings.setSyncInterval(ContactsContract.AUTHORITY, Long.parseLong((String)newValue));
refresh(); return false; refresh();
return false;
} }
}); });
} else { } else {
@ -154,8 +158,9 @@ public class AccountSettingsActivity extends AppCompatActivity {
prefSyncCalendars.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { prefSyncCalendars.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override @Override
public boolean onPreferenceChange(Preference preference, Object newValue) { public boolean onPreferenceChange(Preference preference, Object newValue) {
settings.setSyncInterval(CalendarContract.AUTHORITY, Long.parseLong((String) newValue)); settings.setSyncInterval(CalendarContract.AUTHORITY, Long.parseLong((String)newValue));
refresh(); return false; refresh();
return false;
} }
}); });
} else { } else {
@ -174,8 +179,9 @@ public class AccountSettingsActivity extends AppCompatActivity {
prefSyncTasks.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { prefSyncTasks.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override @Override
public boolean onPreferenceChange(Preference preference, Object newValue) { public boolean onPreferenceChange(Preference preference, Object newValue) {
settings.setSyncInterval(TaskProvider.ProviderName.OpenTasks.authority, Long.parseLong((String) newValue)); settings.setSyncInterval(TaskProvider.ProviderName.OpenTasks.authority, Long.parseLong((String)newValue));
refresh(); return false; refresh();
return false;
} }
}); });
} else { } else {
@ -189,7 +195,8 @@ public class AccountSettingsActivity extends AppCompatActivity {
@Override @Override
public boolean onPreferenceChange(Preference preference, Object wifiOnly) { public boolean onPreferenceChange(Preference preference, Object wifiOnly) {
settings.setSyncWiFiOnly((Boolean)wifiOnly); settings.setSyncWiFiOnly((Boolean)wifiOnly);
refresh(); return false; refresh();
return false;
} }
}); });
@ -205,23 +212,45 @@ public class AccountSettingsActivity extends AppCompatActivity {
public boolean onPreferenceChange(Preference preference, Object newValue) { public boolean onPreferenceChange(Preference preference, Object newValue) {
String ssid = (String)newValue; String ssid = (String)newValue;
settings.setSyncWifiOnlySSID(!TextUtils.isEmpty(ssid) ? ssid : null); settings.setSyncWifiOnlySSID(!TextUtils.isEmpty(ssid) ? ssid : null);
refresh(); return false; refresh();
return false;
} }
}); });
// category: CardDAV // category: CardDAV
final SwitchPreferenceCompat prefRFC6868 = (SwitchPreferenceCompat)findPreference("vcard_rfc6868"); final SwitchPreferenceCompat prefRFC6868 = (SwitchPreferenceCompat)findPreference("vcard_rfc6868");
if (syncIntervalContacts != null) {
prefRFC6868.setChecked(settings.getVCardRFC6868()); prefRFC6868.setChecked(settings.getVCardRFC6868());
prefRFC6868.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { prefRFC6868.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override @Override
public boolean onPreferenceChange(Preference preference, Object o) { public boolean onPreferenceChange(Preference preference, Object o) {
settings.setVCardRFC6868((Boolean)o); settings.setVCardRFC6868((Boolean)o);
refresh(); return false; refresh();
return 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());
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);
// category: CalDAV // category: CalDAV
final EditTextPreference prefTimeRangePastDays = (EditTextPreference)findPreference("time_range_past_days"); final EditTextPreference prefTimeRangePastDays = (EditTextPreference)findPreference("time_range_past_days");
if (syncIntervalCalendars != null) {
Integer pastDays = settings.getTimeRangePastDays(); Integer pastDays = settings.getTimeRangePastDays();
if (pastDays != null) { if (pastDays != null) {
prefTimeRangePastDays.setText(pastDays.toString()); prefTimeRangePastDays.setText(pastDays.toString());
@ -240,33 +269,28 @@ public class AccountSettingsActivity extends AppCompatActivity {
days = -1; days = -1;
} }
settings.setTimeRangePastDays(days < 0 ? null : days); settings.setTimeRangePastDays(days < 0 ? null : days);
refresh(); return false; refresh();
return false;
} }
}); });
} else
prefTimeRangePastDays.setEnabled(false);
final SwitchPreferenceCompat prefManageColors = (SwitchPreferenceCompat)findPreference("manage_calendar_colors"); final SwitchPreferenceCompat prefManageColors = (SwitchPreferenceCompat)findPreference("manage_calendar_colors");
if (syncIntervalCalendars != null || syncIntervalTasks != null) {
prefManageColors.setChecked(settings.getManageCalendarColors()); prefManageColors.setChecked(settings.getManageCalendarColors());
prefManageColors.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { prefManageColors.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override @Override
public boolean onPreferenceChange(Preference preference, Object newValue) { public boolean onPreferenceChange(Preference preference, Object newValue) {
settings.setManageCalendarColors((Boolean)newValue); settings.setManageCalendarColors((Boolean)newValue);
refresh(); return false; refresh();
return false;
} }
}); });
} else
// category: CardDAV prefManageColors.setEnabled(false);
final ListPreference prefGroupMethod = (ListPreference)findPreference("contact_group_method");
prefGroupMethod.setValue(settings.getGroupMethod().name());
prefGroupMethod.setSummary(prefGroupMethod.getEntry());
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;
}
});
}
} }
}
} }

View File

@ -25,6 +25,7 @@ import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.Button; import android.widget.Button;
import android.widget.EditText; import android.widget.EditText;
import android.widget.Spinner;
import java.net.URI; import java.net.URI;
import java.util.logging.Level; import java.util.logging.Level;
@ -42,6 +43,7 @@ import at.bitfire.davdroid.model.ServiceDB.OpenHelper;
import at.bitfire.davdroid.model.ServiceDB.Services; import at.bitfire.davdroid.model.ServiceDB.Services;
import at.bitfire.davdroid.resource.LocalTaskList; import at.bitfire.davdroid.resource.LocalTaskList;
import at.bitfire.ical4android.TaskProvider; import at.bitfire.ical4android.TaskProvider;
import at.bitfire.vcard4android.GroupMethod;
import lombok.Cleanup; import lombok.Cleanup;
public class AccountDetailsFragment extends Fragment { public class AccountDetailsFragment extends Fragment {
@ -49,6 +51,9 @@ public class AccountDetailsFragment extends Fragment {
private static final String KEY_CONFIG = "config"; private static final String KEY_CONFIG = "config";
private static final int DEFAULT_SYNC_INTERVAL = 4 * 3600; // 4 hours private static final int DEFAULT_SYNC_INTERVAL = 4 * 3600; // 4 hours
Spinner spnrGroupMethod;
public static AccountDetailsFragment newInstance(DavResourceFinder.Configuration config) { public static AccountDetailsFragment newInstance(DavResourceFinder.Configuration config) {
AccountDetailsFragment frag = new AccountDetailsFragment(); AccountDetailsFragment frag = new AccountDetailsFragment();
Bundle args = new Bundle(1); Bundle args = new Bundle(1);
@ -57,6 +62,7 @@ public class AccountDetailsFragment extends Fragment {
return frag; return frag;
} }
@Override @Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
final View v = inflater.inflate(R.layout.login_account_details, container, false); final View v = inflater.inflate(R.layout.login_account_details, container, false);
@ -74,6 +80,10 @@ public class AccountDetailsFragment extends Fragment {
final EditText editName = (EditText)v.findViewById(R.id.account_name); final EditText editName = (EditText)v.findViewById(R.id.account_name);
editName.setText(config.userName); editName.setText(config.userName);
// CardDAV-specific
v.findViewById(R.id.carddav).setVisibility(config.cardDAV != null ? View.VISIBLE : View.GONE);
spnrGroupMethod = (Spinner)v.findViewById(R.id.contact_group_method);
Button btnCreate = (Button)v.findViewById(R.id.create_account); Button btnCreate = (Button)v.findViewById(R.id.create_account);
btnCreate.setOnClickListener(new View.OnClickListener() { btnCreate.setOnClickListener(new View.OnClickListener() {
@Override @Override
@ -114,25 +124,39 @@ public class AccountDetailsFragment extends Fragment {
Intent refreshIntent = new Intent(getActivity(), DavService.class); Intent refreshIntent = new Intent(getActivity(), DavService.class);
refreshIntent.setAction(DavService.ACTION_REFRESH_COLLECTIONS); refreshIntent.setAction(DavService.ACTION_REFRESH_COLLECTIONS);
db.beginTransactionNonExclusive();
if (config.cardDAV != null) { if (config.cardDAV != null) {
// insert CardDAV service
long id = insertService(db, accountName, Services.SERVICE_CARDDAV, config.cardDAV); long id = insertService(db, accountName, Services.SERVICE_CARDDAV, config.cardDAV);
// start CardDAV service detection (refresh collections)
refreshIntent.putExtra(DavService.EXTRA_DAV_SERVICE_ID, id); refreshIntent.putExtra(DavService.EXTRA_DAV_SERVICE_ID, id);
getActivity().startService(refreshIntent); getActivity().startService(refreshIntent);
// initial CardDAV account settings
int idx = spnrGroupMethod.getSelectedItemPosition();
String groupMethodName = getResources().getStringArray(R.array.settings_contact_group_method_values)[idx];
settings.setGroupMethod(GroupMethod.valueOf(groupMethodName));
// enable contact sync
ContentResolver.setIsSyncable(account, ContactsContract.AUTHORITY, 1); ContentResolver.setIsSyncable(account, ContactsContract.AUTHORITY, 1);
settings.setSyncInterval(ContactsContract.AUTHORITY, DEFAULT_SYNC_INTERVAL); settings.setSyncInterval(ContactsContract.AUTHORITY, DEFAULT_SYNC_INTERVAL);
} else } else
// disable contact sync when CardDAV is not available
ContentResolver.setIsSyncable(account, ContactsContract.AUTHORITY, 0); ContentResolver.setIsSyncable(account, ContactsContract.AUTHORITY, 0);
if (config.calDAV != null) { if (config.calDAV != null) {
// insert CalDAV service
long id = insertService(db, accountName, Services.SERVICE_CALDAV, config.calDAV); long id = insertService(db, accountName, Services.SERVICE_CALDAV, config.calDAV);
// start CalDAV service detection (refresh collections)
refreshIntent.putExtra(DavService.EXTRA_DAV_SERVICE_ID, id); refreshIntent.putExtra(DavService.EXTRA_DAV_SERVICE_ID, id);
getActivity().startService(refreshIntent); getActivity().startService(refreshIntent);
// enable calendar sync
ContentResolver.setIsSyncable(account, CalendarContract.AUTHORITY, 1); ContentResolver.setIsSyncable(account, CalendarContract.AUTHORITY, 1);
settings.setSyncInterval(CalendarContract.AUTHORITY, DEFAULT_SYNC_INTERVAL); settings.setSyncInterval(CalendarContract.AUTHORITY, DEFAULT_SYNC_INTERVAL);
// enable task sync, if possible
if (Build.VERSION.SDK_INT >= 23 || LocalTaskList.tasksProviderAvailable(getContext())) { if (Build.VERSION.SDK_INT >= 23 || LocalTaskList.tasksProviderAvailable(getContext())) {
ContentResolver.setIsSyncable(account, TaskProvider.ProviderName.OpenTasks.authority, 1); ContentResolver.setIsSyncable(account, TaskProvider.ProviderName.OpenTasks.authority, 1);
settings.setSyncInterval(TaskProvider.ProviderName.OpenTasks.authority, DEFAULT_SYNC_INTERVAL); settings.setSyncInterval(TaskProvider.ProviderName.OpenTasks.authority, DEFAULT_SYNC_INTERVAL);
@ -141,15 +165,13 @@ public class AccountDetailsFragment extends Fragment {
// because otherwise, there will be a non-catchable SecurityException as soon as OpenTasks is installed // because otherwise, there will be a non-catchable SecurityException as soon as OpenTasks is installed
ContentResolver.setIsSyncable(account, TaskProvider.ProviderName.OpenTasks.authority, 0); ContentResolver.setIsSyncable(account, TaskProvider.ProviderName.OpenTasks.authority, 0);
} else { } else {
// disable calendar and task sync when CalDAV is not available
ContentResolver.setIsSyncable(account, CalendarContract.AUTHORITY, 0); ContentResolver.setIsSyncable(account, CalendarContract.AUTHORITY, 0);
ContentResolver.setIsSyncable(account, TaskProvider.ProviderName.OpenTasks.authority, 0); ContentResolver.setIsSyncable(account, TaskProvider.ProviderName.OpenTasks.authority, 0);
} }
db.setTransactionSuccessful();
} catch(InvalidAccountException e) { } catch(InvalidAccountException e) {
App.log.log(Level.SEVERE, "Couldn't access account settings", e); App.log.log(Level.SEVERE, "Couldn't access account settings", e);
} finally {
db.endTransaction();
} }
return true; return true;

View File

@ -87,6 +87,7 @@ public class LoginCredentialsFragment extends Fragment implements CompoundButton
boolean loginByEmail = buttonView == radioUseEmail; boolean loginByEmail = buttonView == radioUseEmail;
emailDetails.setVisibility(loginByEmail ? View.VISIBLE : View.GONE); emailDetails.setVisibility(loginByEmail ? View.VISIBLE : View.GONE);
urlDetails.setVisibility(loginByEmail ? View.GONE : View.VISIBLE); urlDetails.setVisibility(loginByEmail ? View.GONE : View.VISIBLE);
(loginByEmail ? editEmailAddress : editBaseURL).requestFocus();
} }
} }

View File

@ -38,11 +38,32 @@
android:inputType="textEmailAddress"/> android:inputType="textEmailAddress"/>
<TextView <TextView
android:id="@+id/account_email_hint"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="8dp" android:layout_marginTop="8dp"
android:text="@string/login_account_name_info"/> android:text="@string/login_account_name_info"/>
<LinearLayout
android:id="@+id/carddav"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="@string/login_account_contact_group_method"/>
<Spinner
android:id="@+id/contact_group_method"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:entries="@array/settings_contact_group_method_entries"/>
</LinearLayout>
</LinearLayout> </LinearLayout>
</ScrollView> </ScrollView>

View File

@ -126,6 +126,7 @@
<string name="login_create_account">Create account</string> <string name="login_create_account">Create account</string>
<string name="login_account_name">Account name</string> <string name="login_account_name">Account name</string>
<string name="login_account_name_info">Use your email address as account name because Android will use the account name as ORGANIZER field for events you create. You can\'t have two accounts with the same name.</string> <string name="login_account_name_info">Use your email address as account name because Android will use the account name as ORGANIZER field for events you create. You can\'t have two accounts with the same name.</string>
<string name="login_account_contact_group_method">Contact group method:</string>
<string name="login_account_name_required">Account name required</string> <string name="login_account_name_required">Account name required</string>
<string name="login_account_not_created">Account could not be created</string> <string name="login_account_not_created">Account could not be created</string>
@ -182,16 +183,12 @@
<string name="settings_carddav">CardDAV</string> <string name="settings_carddav">CardDAV</string>
<string name="settings_contact_group_method">Contact group method</string> <string name="settings_contact_group_method">Contact group method</string>
<string-array name="settings_contact_group_method_values"> <string-array name="settings_contact_group_method_values">
<item>AUTOMATIC</item> <item>GROUP_VCARDS</item>
<item>VCARD3_CATEGORIES</item> <item>CATEGORIES</item>
<item>VCARD4</item>
<item>X_ADDRESSBOOK_SERVER</item>
</string-array> </string-array>
<string-array name="settings_contact_group_method_entries"> <string-array name="settings_contact_group_method_entries">
<item>Automatic (VCard3/VCard4)</item> <item>Groups are separate VCards</item>
<item>VCard3 only (CATEGORIES)</item> <item>Groups are per-contact categories</item>
<item>VCard4 only (KIND/MEMBER)</item>
<item>Apple (X-ADDRESSBOOK-SERVER)</item>
</string-array> </string-array>
<string name="settings_rfc6868_for_vcards">Use RFC6868 for VCards</string> <string name="settings_rfc6868_for_vcards">Use RFC6868 for VCards</string>
<string name="settings_rfc6868_for_vcards_on">Double quotes can be used in parameter values</string> <string name="settings_rfc6868_for_vcards_on">Double quotes can be used in parameter values</string>

3
scripts/fetch-db.sh Executable file
View File

@ -0,0 +1,3 @@
#!/bin/sh
cd ~/tmp
adb pull /data/data/com.android.providers.contacts/databases/contacts2.db

@ -1 +1 @@
Subproject commit 38e24e8a19df339618ec33afb294a3e4a61b3cbe Subproject commit 02eae2c067c8fca4a9cf9f0e324af9bb4b91d3d0