DavService: refresh collections

pull/2/head
Ricki Hirner 8 years ago
parent af71ed8bc5
commit 89a516bfd1

@ -49,7 +49,6 @@
android:label="@string/app_name"
android:theme="@style/AppTheme"
tools:ignore="UnusedAttribute">
<service
android:name=".syncadapter.AccountAuthenticatorService"
android:exported="false">
@ -103,11 +102,18 @@
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"
@ -123,6 +129,8 @@
android:name=".ui.AccountActivity"
android:parentActivityName=".ui.AccountsActivity">
</activity>
<activity android:name=".ui.CreateAddressBookActivity">
</activity>
<activity
android:name=".ui.DebugInfoActivity"
android:exported="true"

@ -0,0 +1,24 @@
/*
* 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.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
public class AccountsChangedReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Intent serviceIntent = new Intent(context, DavService.class);
serviceIntent.setAction(DavService.ACTION_ACCOUNTS_UPDATED);
context.startService(serviceIntent);
}
}

@ -9,6 +9,7 @@
package at.bitfire.davdroid;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.app.Service;
import android.content.ContentValues;
import android.content.Intent;
@ -18,6 +19,7 @@ import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException;
import android.os.Binder;
import android.os.IBinder;
import android.text.TextUtils;
import java.io.IOException;
import java.util.HashSet;
@ -50,6 +52,7 @@ 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";
@ -65,6 +68,9 @@ public class DavService extends Service {
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();
@ -113,6 +119,27 @@ public class DavService extends Service {
which actually do the work
*/
void cleanupAccounts() {
Constants.log.info("[DavService] 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(Constants.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;
@ -127,7 +154,6 @@ public class DavService extends Service {
public void run() {
try {
db = dbHelper.getWritableDatabase();
db.beginTransactionNonExclusive();
String serviceType = serviceType();
Constants.log.info("[DavService {}] Refreshing {} collections", service, serviceType);
@ -155,7 +181,6 @@ public class DavService extends Service {
homeSets.add(UrlUtils.withTrailingSlash(dav.location.resolve(href)));
}
}
saveHomeSets(homeSets);
// refresh collections in home sets
Map<HttpUrl, CollectionInfo> collections = readCollections();
@ -203,13 +228,19 @@ public class DavService extends Service {
iterator.remove();
}
}
saveCollections(collections.values());
db.setTransactionSuccessful();
} catch (SQLiteException | IOException|HttpException|DavException e) {
try {
db.beginTransaction();
saveHomeSets(homeSets);
saveCollections(collections.values());
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
} catch (IOException|HttpException|DavException e) {
Constants.log.error("Couldn't refresh collection list", e);
} finally {
db.endTransaction();
dbHelper.close();
runningRefresh.remove(service);

@ -18,10 +18,11 @@ import at.bitfire.dav4android.property.CalendarDescription;
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 lombok.ToString;
import okhttp3.HttpUrl;
import at.bitfire.davdroid.model.ServiceDB.*;
import okhttp3.MediaType;
@ToString
public class CollectionInfo {
@ -39,6 +40,8 @@ public class CollectionInfo {
public String displayName, description;
public Integer color;
public Integer vCardVersion;
public Boolean supportsVEVENT;
public Boolean supportsVTODO;
@ -50,8 +53,8 @@ public class CollectionInfo {
ResourceType.NAME,
CurrentUserPrivilegeSet.NAME,
DisplayName.NAME,
AddressbookDescription.NAME, CalendarDescription.NAME,
CalendarColor.NAME, SupportedCalendarComponentSet.NAME
AddressbookDescription.NAME, SupportedAddressData.NAME,
CalendarDescription.NAME, CalendarColor.NAME, SupportedCalendarComponentSet.NAME
};
public static CollectionInfo fromDavResource(DavResource dav) {
@ -80,6 +83,15 @@ public class CollectionInfo {
if (addressbookDescription != null)
info.description = addressbookDescription.description;
SupportedAddressData addressData = (SupportedAddressData)dav.properties.get(SupportedAddressData.NAME);
if (addressData != null) {
boolean vCard4 = false;
for (MediaType contentType : addressData.types)
if ("text/vcard".equals(contentType.type()) && contentType.toString().contains("version=4.0"))
vCard4 = true;
info.vCardVersion = vCard4 ? 4 : 3;
}
} else if (info.type == Type.CALENDAR) {
CalendarDescription calendarDescription = (CalendarDescription)dav.properties.get(CalendarDescription.NAME);
if (calendarDescription != null)
@ -107,9 +119,13 @@ public class CollectionInfo {
info.url = values.getAsString(Collections.URL);
info.displayName = values.getAsString(Collections.DISPLAY_NAME);
info.description = values.getAsString(Collections.DESCRIPTION);
info.vCardVersion = values.getAsInteger(Collections.VCARD_VERSION);
info.color = values.getAsInteger(Collections.COLOR);
info.supportsVEVENT = values.getAsBoolean(Collections.SUPPORTS_VEVENT);
info.supportsVTODO = values.getAsBoolean(Collections.SUPPORTS_VTODO);
info.supportsVEVENT = booleanField(values, Collections.SUPPORTS_VEVENT);
info.supportsVTODO = booleanField(values, Collections.SUPPORTS_VTODO);
return info;
}
@ -118,10 +134,23 @@ public class CollectionInfo {
values.put(Collections.URL, url);
values.put(Collections.DISPLAY_NAME, displayName);
values.put(Collections.DESCRIPTION, description);
values.put(Collections.VCARD_VERSION, vCardVersion);
values.put(Collections.COLOR, color);
values.put(Collections.SUPPORTS_VEVENT, supportsVEVENT);
values.put(Collections.SUPPORTS_VTODO, supportsVTODO);
if (supportsVEVENT != null)
values.put(Collections.SUPPORTS_VEVENT, supportsVEVENT ? 1 : 0);
if (supportsVTODO != null)
values.put(Collections.SUPPORTS_VTODO, supportsVTODO ? 1 : 0);
return values;
}
private static Boolean booleanField(ContentValues values, String field) {
Integer i = values.getAsInteger(field);
if (i == null)
return null;
return i != 0;
}
}

@ -51,11 +51,12 @@ public class ServiceDB {
DISPLAY_NAME = "displayName",
DESCRIPTION = "description",
COLOR = "color",
VCARD_VERSION = "vCardVersion",
SUPPORTS_VEVENT = "supportsVEVENT",
SUPPORTS_VTODO = "supportsVTODO";
public static String[] _COLUMNS = new String[] {
ID, SERVICE_ID, URL, DISPLAY_NAME, DESCRIPTION, COLOR, SUPPORTS_VEVENT, SUPPORTS_VTODO
ID, SERVICE_ID, URL, DISPLAY_NAME, DESCRIPTION, COLOR, VCARD_VERSION, SUPPORTS_VEVENT, SUPPORTS_VTODO
};
}
@ -70,6 +71,7 @@ public class ServiceDB {
@Override
public void onOpen(SQLiteDatabase db) {
db.enableWriteAheadLogging();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN)
db.setForeignKeyConstraintsEnabled(true);
else
@ -103,6 +105,7 @@ public class ServiceDB {
Collections.DISPLAY_NAME + " TEXT NULL," +
Collections.DESCRIPTION + " TEXT NULL," +
Collections.COLOR + " INTEGER NULL," +
Collections.VCARD_VERSION + " INTEGER NULL," +
Collections.SUPPORTS_VEVENT + " INTEGER NULL," +
Collections.SUPPORTS_VTODO + " INTEGER NULL" +
")");

@ -20,6 +20,7 @@ import org.dmfs.provider.tasks.TaskContract.Tasks;
import java.io.FileNotFoundException;
import at.bitfire.davdroid.model.CollectionInfo;
import at.bitfire.ical4android.AndroidTaskList;
import at.bitfire.ical4android.AndroidTaskListFactory;
import at.bitfire.ical4android.CalendarStorageException;
@ -53,14 +54,10 @@ public class LocalTaskList extends AndroidTaskList implements LocalCollection {
super(account, provider, LocalTask.Factory.INSTANCE, id);
}
public static Uri create(Account account, ContentResolver resolver, ServerInfo.ResourceInfo info) throws CalendarStorageException {
TaskProvider provider = TaskProvider.acquire(resolver, TaskProvider.ProviderName.OpenTasks);
if (provider == null)
throw new CalendarStorageException("Couldn't access OpenTasks provider");
public static Uri create(Account account, TaskProvider provider, CollectionInfo info) throws CalendarStorageException {
ContentValues values = new ContentValues();
values.put(TaskLists._SYNC_ID, info.getUrl());
values.put(TaskLists.LIST_NAME, info.getTitle());
values.put(TaskLists._SYNC_ID, info.url);
values.put(TaskLists.LIST_NAME, info.displayName);
values.put(TaskLists.LIST_COLOR, info.color != null ? info.color : defaultColor);
values.put(TaskLists.OWNER, account.name);
values.put(TaskLists.SYNC_ENABLED, 1);

@ -65,7 +65,7 @@ public class AccountAuthenticatorService extends Service {
return null;
}
@Override
@Override
public Bundle getAuthToken(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options) throws NetworkErrorException {
return null;
}

@ -11,13 +11,24 @@ import android.accounts.Account;
import android.app.Service;
import android.content.AbstractThreadedSyncAdapter;
import android.content.ContentProviderClient;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.SyncResult;
import android.database.Cursor;
import android.database.DatabaseUtils;
import android.database.sqlite.SQLiteDatabase;
import android.os.Bundle;
import android.os.IBinder;
import java.util.*;
import at.bitfire.davdroid.Constants;
import at.bitfire.davdroid.model.CollectionInfo;
import at.bitfire.davdroid.model.ServiceDB;
import at.bitfire.davdroid.model.ServiceDB.*;
import at.bitfire.davdroid.model.ServiceDB.Collections;
import at.bitfire.davdroid.resource.LocalTask;
import at.bitfire.davdroid.resource.LocalTaskList;
import at.bitfire.ical4android.CalendarStorageException;
import at.bitfire.ical4android.TaskProvider;
@ -25,27 +36,34 @@ import lombok.Cleanup;
public class TasksSyncAdapterService extends Service {
private static SyncAdapter syncAdapter;
OpenHelper dbHelper;
@Override
public void onCreate() {
if (syncAdapter == null)
syncAdapter = new SyncAdapter(getApplicationContext());
dbHelper = new OpenHelper(this);
syncAdapter = new SyncAdapter(this, dbHelper);
}
@Override
public void onDestroy() {
syncAdapter = null;
}
@Override
public void onDestroy() {
dbHelper.close();
}
@Override
@Override
public IBinder onBind(Intent intent) {
return syncAdapter.getSyncAdapterBinder();
return syncAdapter.getSyncAdapterBinder();
}
private static class SyncAdapter extends AbstractThreadedSyncAdapter {
public SyncAdapter(Context context) {
final OpenHelper dbHelper;
final SQLiteDatabase db;
public SyncAdapter(Context context, OpenHelper dbHelper) {
super(context, false);
this.dbHelper = dbHelper;
db = dbHelper.getReadableDatabase();
}
@Override
@ -57,6 +75,8 @@ public class TasksSyncAdapterService extends Service {
if (provider == null)
throw new CalendarStorageException("Couldn't access OpenTasks provider");
syncLocalTaskLists(provider, account);
for (LocalTaskList taskList : (LocalTaskList[])LocalTaskList.find(account, provider, LocalTaskList.Factory.INSTANCE, null, null)) {
Constants.log.info("Synchronizing task list #" + taskList.getId() + ", URL: " + taskList.getSyncId());
TasksSyncManager syncManager = new TasksSyncManager(getContext(), account, extras, authority, provider, syncResult, taskList);
@ -68,6 +88,54 @@ public class TasksSyncAdapterService extends Service {
Constants.log.info("Task sync complete");
}
private void syncLocalTaskLists(TaskProvider provider, Account account) throws CalendarStorageException {
long service = getService(account);
// enumerate remote and local task lists
Map<String, CollectionInfo> remote = remoteTaskLists(service);
LocalTaskList[] local = (LocalTaskList[])LocalTaskList.find(account, provider, LocalTaskList.Factory.INSTANCE, null, null);
// delete obsolete local task lists
for (LocalTaskList list : local) {
String url = list.getSyncId();
Constants.log.debug("Checking local task list {} {}", list.getId(), url);
if (!remote.containsKey(url)) {
Constants.log.debug("Deleting local task list {}", url);
list.delete();
} else
// we already have a local task list for this remote collection
remote.remove(url);
}
// create new local task lists
for (String url : remote.keySet()) {
CollectionInfo info = remote.get(url);
Constants.log.info("Adding local task list {}", info);
LocalTaskList.create(account, provider, info);
}
}
long getService(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);
c.moveToNext();
return c.getLong(0);
}
private Map<String, CollectionInfo> remoteTaskLists(long service) {
Map<String, CollectionInfo> collections = new LinkedHashMap<>();
@Cleanup Cursor cursor = db.query(Collections._TABLE, Collections._COLUMNS,
Collections.SERVICE_ID + "=? AND " + Collections.SUPPORTS_VTODO + "!=0",
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;
}
}
}

@ -33,12 +33,17 @@ import android.os.IBinder;
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.ArrayAdapter;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.TextView;
import java.io.IOException;
import java.util.LinkedList;
@ -134,6 +139,9 @@ public class AccountActivity extends AppCompatActivity implements Toolbar.OnMenu
intent.putExtra(DavService.EXTRA_DAV_SERVICE_ID, accountInfo.carddav.id);
startService(intent);
break;
case R.id.create_address_book:
// TODO
break;
case R.id.refresh_calendars:
intent = new Intent(this, DavService.class);
intent.setAction(DavService.ACTION_REFRESH_COLLECTIONS);
@ -193,10 +201,9 @@ public class AccountActivity extends AppCompatActivity implements Toolbar.OnMenu
progress.setVisibility(info.carddav.refreshing ? View.VISIBLE : View.GONE);
ListView list = (ListView)findViewById(R.id.address_books);
List<String> names = new LinkedList<>();
for (CollectionInfo addrBook : info.carddav.collections)
names.add(addrBook.displayName != null ? addrBook.displayName : addrBook.url);
list.setAdapter(new ArrayAdapter<String>(this, android.R.layout.simple_list_item_checked, android.R.id.text1, names));
AddressBookAdapter adapter = new AddressBookAdapter(this);
adapter.addAll(info.carddav.collections);
list.setAdapter(adapter);
} else
card.setVisibility(View.GONE);
@ -206,10 +213,9 @@ public class AccountActivity extends AppCompatActivity implements Toolbar.OnMenu
progress.setVisibility(info.caldav.refreshing ? View.VISIBLE : View.GONE);
ListView list = (ListView)findViewById(R.id.calendars);
List<String> names = new LinkedList<>();
for (CollectionInfo calendar : info.caldav.collections)
names.add(calendar.displayName != null ? calendar.displayName : calendar.url);
list.setAdapter(new ArrayAdapter<String>(this, android.R.layout.simple_list_item_checked, android.R.id.text1, names));
CalendarAdapter adapter = new CalendarAdapter(this);
adapter.addAll(info.caldav.collections);
list.setAdapter(adapter);
} else
card.setVisibility(View.GONE);
}
@ -269,7 +275,6 @@ public class AccountActivity extends AppCompatActivity implements Toolbar.OnMenu
null, null, 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();
@ -303,6 +308,69 @@ public class AccountActivity extends AppCompatActivity implements Toolbar.OnMenu
}
/* LIST ADAPTERS */
public static class AddressBookAdapter extends ArrayAdapter<CollectionInfo> {
public AddressBookAdapter(Context context) {
super(context, R.layout.account_address_book_item);
}
@Override
public View getView(int position, View v, ViewGroup parent) {
if (v == null)
v = LayoutInflater.from(getContext()).inflate(R.layout.account_address_book_item, parent, false);
CollectionInfo info = getItem(position);
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);
}
return v;
}
}
public static class CalendarAdapter extends ArrayAdapter<CollectionInfo> {
public CalendarAdapter(Context context) {
super(context, R.layout.account_calendar_item);
}
@Override
public View getView(int position, View v, ViewGroup parent) {
if (v == null)
v = LayoutInflater.from(getContext()).inflate(R.layout.account_calendar_item, parent, false);
CollectionInfo info = getItem(position);
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.events);
tv.setVisibility(info.supportsVEVENT ? View.VISIBLE : View.GONE);
tv = (TextView)v.findViewById(R.id.tasks);
tv.setVisibility(info.supportsVTODO ? View.VISIBLE : View.GONE);
return v;
}
}
/* USER ACTIONS */
protected void deleteAccount() {

@ -125,34 +125,20 @@ public class AccountListFragment extends ListFragment implements OnAccountsUpdat
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View v = convertView;
if (v == null) {
LayoutInflater inflater = LayoutInflater.from(getContext());
v = inflater.inflate(R.layout.account_list_item, parent, false);
}
public View getView(int position, View v, ViewGroup parent) {
if (v == null)
v = LayoutInflater.from(getContext()).inflate(R.layout.account_list_item, parent, false);
AccountInfo info = getItem(position);
TextView tv = (TextView)v.findViewById(R.id.account_name);
tv.setText(info.account.name);
ProgressBar progressRefresh = (ProgressBar)v.findViewById(R.id.refreshing);
progressRefresh.setVisibility(info.isRefreshing ? View.VISIBLE : View.GONE);
Animation blink = new AlphaAnimation(0, 1);
blink.setDuration(400);
blink.setRepeatMode(Animation.REVERSE);
blink.setRepeatCount(Animation.INFINITE);
tv = (TextView)v.findViewById(R.id.carddav);
tv.setVisibility(info.cardDavService != null ? View.VISIBLE : View.GONE);
tv.setAnimation(info.isRefreshing ? blink : null);
tv = (TextView)v.findViewById(R.id.caldav);
tv.setVisibility(info.calDavService != null ? View.VISIBLE : View.GONE);
return v;
}

@ -10,10 +10,13 @@ package at.bitfire.davdroid.ui.setup;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Intent;
import android.database.sqlite.SQLiteDatabase;
import android.os.Bundle;
import android.provider.CalendarContract;
import android.provider.ContactsContract;
import android.support.design.widget.Snackbar;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
@ -30,6 +33,7 @@ import at.bitfire.davdroid.R;
import at.bitfire.davdroid.model.CollectionInfo;
import at.bitfire.davdroid.syncadapter.AccountSettings;
import at.bitfire.davdroid.model.ServiceDB.*;
import at.bitfire.ical4android.TaskProvider;
import lombok.Cleanup;
import okhttp3.HttpUrl;
@ -105,12 +109,22 @@ public class AccountDetailsFragment extends Fragment {
long id = insertService(db, accountName, Services.SERVICE_CARDDAV, config.cardDAV);
refreshIntent.putExtra(DavService.EXTRA_DAV_SERVICE_ID, id);
getActivity().startService(refreshIntent);
ContentResolver.setIsSyncable(account, ContactsContract.AUTHORITY, 1);
ContentResolver.setSyncAutomatically(account, ContactsContract.AUTHORITY, true);
}
if (config.calDAV != null) {
long id = insertService(db, accountName, Services.SERVICE_CALDAV, config.calDAV);
refreshIntent.putExtra(DavService.EXTRA_DAV_SERVICE_ID, id);
getActivity().startService(refreshIntent);
ContentResolver.setIsSyncable(account, CalendarContract.AUTHORITY, 1);
ContentResolver.setSyncAutomatically(account, CalendarContract.AUTHORITY, true);
// TODO check for tasks availability
ContentResolver.setIsSyncable(account, TaskProvider.ProviderName.OpenTasks.authority, 1);
ContentResolver.setSyncAutomatically(account, TaskProvider.ProviderName.OpenTasks.authority, true);
}
db.setTransactionSuccessful();

@ -0,0 +1,13 @@
<!--
~ 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:alpha="0.54" android:height="32dp"
android:viewportHeight="24.0" android:viewportWidth="24.0"
android:width="32dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M22,5.72l-4.6,-3.86 -1.29,1.53 4.6,3.86L22,5.72zM7.88,3.39L6.6,1.86 2,5.71l1.29,1.53 4.59,-3.85zM12,4c-4.97,0 -9,4.03 -9,9s4.02,9 9,9c4.97,0 9,-4.03 9,-9s-4.03,-9 -9,-9zm0,16c-3.87,0 -7,-3.13 -7,-7s3.13,-7 7,-7 7,3.13 7,7 -3.13,7 -7,7zm-1.46,-5.47L8.41,12.4l-1.06,1.06 3.18,3.18 6,-6 -1.06,-1.06 -4.93,4.95z"/>
</vector>

@ -0,0 +1,13 @@
<!--
~ 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:alpha="0.54" android:height="32dp"
android:viewportHeight="24.0" android:viewportWidth="24.0"
android:width="32dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M19,3h-1V1h-2v2H8V1H6v2H5c-1.11,0 -1.99,0.9 -1.99,2L3,19c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2V5c0,-1.1 -0.9,-2 -2,-2zm0,16H5V8h14v11zM7,10h5v5H7z"/>
</vector>

@ -0,0 +1,31 @@
<?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"
android:padding="8dp">
<TextView
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceLarge"
tools:text="My Address Book"/>
<TextView
android:id="@+id/description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceMedium"
tools:text="Address Book Description"/>
</LinearLayout>

@ -0,0 +1,51 @@
<?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="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="8dp"
android:gravity="center_vertical">
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceLarge"
tools:text="My Calendar"/>
<TextView
android:id="@+id/description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceMedium"
tools:text="Calendar Description"/>
</LinearLayout>
<TextView
android:id="@+id/events"
android:layout_width="32dp"
android:layout_height="32dp"
android:background="@drawable/ic_today_dark"/>
<TextView
android:id="@+id/tasks"
android:layout_width="32dp"
android:layout_height="32dp"
android:background="@drawable/ic_alarm_on_dark"/>
</LinearLayout>

@ -28,7 +28,7 @@
android:gravity="right|center_vertical">
<ProgressBar
android:id="@+id/refreshing"
android:id="@+id/synchronizing"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:indeterminate="true"

@ -0,0 +1,15 @@
<?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"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
</LinearLayout>
Loading…
Cancel
Save