Add CalDAVSettingsActivity

pull/645/head
Alex Baker 6 years ago
parent 2a54775927
commit bb0f99f35a

@ -2,7 +2,7 @@
"formatVersion": 1,
"database": {
"version": 55,
"identityHash": "298da0475e0b181e5298ea1e05302000",
"identityHash": "13c156cbb4c26b323967670f04502f53",
"entities": [
{
"tableName": "notification",
@ -690,7 +690,7 @@
},
{
"tableName": "caldav_account",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `uuid` TEXT, `name` TEXT, `color` INTEGER NOT NULL, `deleted` INTEGER NOT NULL, `ctag` TEXT, `url` TEXT, `username` TEXT)",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `uuid` TEXT, `name` TEXT, `color` INTEGER NOT NULL, `ctag` TEXT, `url` TEXT, `username` TEXT)",
"fields": [
{
"fieldPath": "id",
@ -716,12 +716,6 @@
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "deleted",
"columnName": "deleted",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "ctag",
"columnName": "ctag",
@ -809,7 +803,7 @@
],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"298da0475e0b181e5298ea1e05302000\")"
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"13c156cbb4c26b323967670f04502f53\")"
]
}
}

@ -1,32 +0,0 @@
package org.tasks.gtasks;
import com.todoroo.astrid.activity.TaskListFragment;
import javax.inject.Inject;
public class SyncAdapterHelper {
@Inject
public SyncAdapterHelper() {
}
public boolean shouldShowBackgroundSyncWarning() {
return false;
}
public void checkPlayServices(TaskListFragment taskListFragment) {
}
public boolean initiateManualSync() {
return false;
}
public boolean isEnabled() {
return false;
}
public void requestSynchronization() {
}
}

@ -21,6 +21,7 @@ import org.tasks.activities.FilterSettingsActivity;
import org.tasks.activities.GoogleTaskListSettingsActivity;
import org.tasks.activities.TagSettingsActivity;
import org.tasks.activities.TimePickerActivity;
import org.tasks.caldav.CalDAVSettingsActivity;
import org.tasks.dashclock.DashClockSettings;
import org.tasks.files.FileExplore;
import org.tasks.files.MyFilePickerActivity;
@ -123,6 +124,8 @@ public interface ActivityComponent {
void inject(GoogleTaskListSettingsActivity googleTaskListSettingsActivity);
void inject(CalDAVSettingsActivity calDAVSettingsActivity);
void inject(TaskerCreateTaskActivity taskerCreateTaskActivity);
void inject(TaskListViewModel viewModel);

@ -2,6 +2,8 @@ package org.tasks.injection;
import org.tasks.activities.CalendarSelectionDialog;
import org.tasks.activities.RemoteListSupportPicker;
import org.tasks.caldav.DeleteAccountDialog;
import org.tasks.caldav.RenameAccountDialog;
import org.tasks.dialogs.AddAttachmentDialog;
import org.tasks.dialogs.ColorPickerDialog;
import org.tasks.dialogs.RecordAudioDialog;
@ -34,4 +36,8 @@ public interface DialogFragmentComponent {
void inject(CustomRecurrenceDialog customRecurrenceDialog);
void inject(RemoteListSupportPicker remoteListSupportPicker);
void inject(DeleteAccountDialog deleteAccountDialog);
void inject(RenameAccountDialog renameAccountDialog);
}

@ -0,0 +1,42 @@
package org.tasks.sync;
import android.content.ContentResolver;
import com.todoroo.astrid.activity.TaskListFragment;
import org.tasks.caldav.CaldavAccountManager;
import javax.inject.Inject;
public class SyncAdapters {
private CaldavAccountManager caldavAccountManager;
@Inject
public SyncAdapters(CaldavAccountManager caldavAccountManager) {
this.caldavAccountManager = caldavAccountManager;
}
public boolean initiateManualSync() {
return caldavAccountManager.initiateManualSync();
}
public void requestSynchronization() {
caldavAccountManager.requestSynchronization();
}
public boolean isGoogleTaskSyncEnabled() {
return false;
}
public void checkPlayServices(TaskListFragment taskListFragment) {
}
public boolean isMasterSyncEnabled() {
return ContentResolver.getMasterSyncAutomatically();
}
public boolean isSyncEnabled() {
return caldavAccountManager.getAccounts().size() > 0;
}
}

@ -21,6 +21,7 @@ import org.tasks.activities.FilterSettingsActivity;
import org.tasks.activities.GoogleTaskListSettingsActivity;
import org.tasks.activities.TagSettingsActivity;
import org.tasks.activities.TimePickerActivity;
import org.tasks.caldav.CalDAVSettingsActivity;
import org.tasks.dashclock.DashClockSettings;
import org.tasks.files.FileExplore;
import org.tasks.files.MyFilePickerActivity;
@ -123,6 +124,8 @@ public interface ActivityComponent {
void inject(GoogleTaskListSettingsActivity googleTaskListSettingsActivity);
void inject(CalDAVSettingsActivity calDAVSettingsActivity);
void inject(TaskerCreateTaskActivity taskerCreateTaskActivity);
void inject(TaskListViewModel viewModel);

@ -2,6 +2,8 @@ package org.tasks.injection;
import org.tasks.activities.CalendarSelectionDialog;
import org.tasks.activities.RemoteListSupportPicker;
import org.tasks.caldav.DeleteAccountDialog;
import org.tasks.caldav.RenameAccountDialog;
import org.tasks.dialogs.AddAttachmentDialog;
import org.tasks.dialogs.ColorPickerDialog;
import org.tasks.dialogs.RecordAudioDialog;
@ -34,4 +36,8 @@ public interface DialogFragmentComponent {
void inject(CustomRecurrenceDialog customRecurrenceDialog);
void inject(RemoteListSupportPicker remoteListSupportPicker);
void inject(DeleteAccountDialog deleteAccountDialog);
void inject(RenameAccountDialog renameAccountDialog);
}

@ -0,0 +1,42 @@
package org.tasks.sync;
import android.content.ContentResolver;
import com.todoroo.astrid.activity.TaskListFragment;
import org.tasks.caldav.CaldavAccountManager;
import javax.inject.Inject;
public class SyncAdapters {
private CaldavAccountManager caldavAccountManager;
@Inject
public SyncAdapters(CaldavAccountManager caldavAccountManager) {
this.caldavAccountManager = caldavAccountManager;
}
public boolean initiateManualSync() {
return caldavAccountManager.initiateManualSync();
}
public void requestSynchronization() {
caldavAccountManager.requestSynchronization();
}
public boolean isGoogleTaskSyncEnabled() {
return false;
}
public void checkPlayServices(TaskListFragment taskListFragment) {
}
public boolean isMasterSyncEnabled() {
return ContentResolver.getMasterSyncAutomatically();
}
public boolean isSyncEnabled() {
return caldavAccountManager.getAccounts().size() > 0;
}
}

@ -16,19 +16,10 @@
<uses-feature android:name="android.hardware.location" android:required="false" />
<uses-feature android:name="android.hardware.location.gps" android:required="false" />
<!-- **************** -->
<!-- google task sync -->
<!-- **************** -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
<uses-permission android:name="android.permission.USE_CREDENTIALS" />
<uses-permission android:name="android.permission.MANAGE_ACCOUNTS" />
<uses-permission android:name="com.google.android.googleapps.permission.GOOGLE_AUTH" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.READ_SYNC_STATS"/>
<uses-permission android:name="android.permission.READ_SYNC_SETTINGS"/>
<uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS"/>
<application tools:ignore="GoogleAppIndexingWarning">
@ -93,7 +84,7 @@
</intent-filter>
<meta-data
android:name="android.content.SyncAdapter"
android:resource="@xml/syncadapter" />
android:resource="@xml/sync_adapter_gtask" />
</service>
</application>

@ -25,6 +25,7 @@ import org.tasks.activities.FilterSettingsActivity;
import org.tasks.activities.GoogleTaskListSettingsActivity;
import org.tasks.activities.TagSettingsActivity;
import org.tasks.activities.TimePickerActivity;
import org.tasks.caldav.CalDAVSettingsActivity;
import org.tasks.dashclock.DashClockSettings;
import org.tasks.files.FileExplore;
import org.tasks.files.MyFilePickerActivity;
@ -132,6 +133,8 @@ public interface ActivityComponent {
void inject(GoogleTaskListSettingsActivity googleTaskListSettingsActivity);
void inject(CalDAVSettingsActivity calDAVSettingsActivity);
void inject(TaskerCreateTaskActivity taskerCreateTaskActivity);
void inject(TaskListViewModel taskListViewModel);

@ -2,6 +2,8 @@ package org.tasks.injection;
import org.tasks.activities.CalendarSelectionDialog;
import org.tasks.activities.RemoteListSupportPicker;
import org.tasks.caldav.DeleteAccountDialog;
import org.tasks.caldav.RenameAccountDialog;
import org.tasks.dialogs.AddAttachmentDialog;
import org.tasks.dialogs.ColorPickerDialog;
import org.tasks.dialogs.RecordAudioDialog;
@ -44,4 +46,8 @@ public interface DialogFragmentComponent {
void inject(RenameListDialog renameListDialog);
void inject(CustomRecurrenceDialog customRecurrenceDialog);
void inject(DeleteAccountDialog deleteAccountDialog);
void inject(RenameAccountDialog renameAccountDialog);
}

@ -0,0 +1,51 @@
package org.tasks.sync;
import android.content.ContentResolver;
import com.todoroo.astrid.activity.TaskListFragment;
import org.tasks.caldav.CaldavAccountManager;
import org.tasks.gtasks.GtaskSyncAdapterHelper;
import javax.inject.Inject;
public class SyncAdapters {
private final GtaskSyncAdapterHelper gtaskSyncAdapterHelper;
private final CaldavAccountManager caldavAccountManager;
@Inject
public SyncAdapters(GtaskSyncAdapterHelper gtaskSyncAdapterHelper, CaldavAccountManager caldavAccountManager) {
this.gtaskSyncAdapterHelper = gtaskSyncAdapterHelper;
this.caldavAccountManager = caldavAccountManager;
}
public void requestSynchronization() {
gtaskSyncAdapterHelper.requestSynchronization();
caldavAccountManager.requestSynchronization();
}
public boolean initiateManualSync() {
return gtaskSyncAdapterHelper.initiateManualSync() | caldavAccountManager.initiateManualSync();
}
public boolean isMasterSyncEnabled() {
return ContentResolver.getMasterSyncAutomatically();
}
public boolean isSyncEnabled() {
return isGoogleTaskSyncEnabled() || isCaldavSyncEnabled();
}
public boolean isGoogleTaskSyncEnabled() {
return gtaskSyncAdapterHelper.isSyncEnabled();
}
public boolean isCaldavSyncEnabled() {
return caldavAccountManager.getAccounts().size() > 0;
}
public void checkPlayServices(TaskListFragment taskListFragment) {
gtaskSyncAdapterHelper.checkPlayServices(taskListFragment);
}
}

@ -53,6 +53,19 @@
<!-- *********************************** -->
<uses-permission android:name="android.permission.WAKE_LOCK" />
<!-- *************************** -->
<!-- caldav and google task sync -->
<!-- *************************** -->
<uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS" />
<uses-permission android:name="android.permission.READ_SYNC_SETTINGS"/>
<uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS"/>
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
<uses-permission android:name="android.permission.MANAGE_ACCOUNTS" />
<uses-permission android:name="android.permission.READ_SYNC_STATS"/>
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.USE_CREDENTIALS" />
<!-- ============================================== Exported Permissions = -->
<!-- for v2 tasks provider -->
@ -297,6 +310,8 @@
<activity android:name=".activities.FilterSettingsActivity" />
<activity android:name=".caldav.CalDAVSettingsActivity" />
<activity
android:name=".activities.CalendarSelectionActivity"
android:theme="@style/TranslucentDialog" />
@ -508,6 +523,18 @@
</intent-filter>
</receiver>
<service
android:name=".caldav.CalDAVAccountAuthenticatorService"
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>
</application>
</manifest>

@ -28,7 +28,6 @@ import com.todoroo.astrid.data.Task;
import com.todoroo.astrid.gtasks.GtasksListService;
import com.todoroo.astrid.gtasks.GtasksSubtaskListFragment;
import com.todoroo.astrid.repeats.RepeatControlSet;
import com.todoroo.astrid.service.TaskCreator;
import com.todoroo.astrid.subtasks.SubtasksHelper;
import com.todoroo.astrid.subtasks.SubtasksListFragment;
import com.todoroo.astrid.subtasks.SubtasksTagListFragment;
@ -48,13 +47,13 @@ import org.tasks.data.TagDataDao;
import org.tasks.dialogs.SortDialog;
import org.tasks.fragments.CommentBarFragment;
import org.tasks.gtasks.RemoteListSelectionHandler;
import org.tasks.gtasks.GtaskSyncAdapterHelper;
import org.tasks.injection.ActivityComponent;
import org.tasks.injection.InjectingAppCompatActivity;
import org.tasks.intents.TaskIntents;
import org.tasks.preferences.DefaultFilterProvider;
import org.tasks.preferences.Preferences;
import org.tasks.receivers.RepeatConfirmationReceiver;
import org.tasks.sync.SyncAdapters;
import org.tasks.tasklist.GtasksListFragment;
import org.tasks.tasklist.TagListFragment;
import org.tasks.themes.Theme;
@ -97,9 +96,8 @@ public class TaskListActivity extends InjectingAppCompatActivity implements
@Inject TagDataDao tagDataDao;
@Inject Theme theme;
@Inject ThemeCache themeCache;
@Inject GtaskSyncAdapterHelper gtaskSyncAdapterHelper;
@Inject SyncAdapters syncAdapters;
@Inject Tracker tracker;
@Inject TaskCreator taskCreator;
@Inject TaskDao taskDao;
@Inject CaldavDao caldavDao;
@Inject LocalBroadcastManager localBroadcastManager;
@ -274,7 +272,7 @@ public class TaskListActivity extends InjectingAppCompatActivity implements
localBroadcastManager.registerRepeatReceiver(repeatConfirmationReceiver);
gtaskSyncAdapterHelper.checkPlayServices(getTaskListFragment());
syncAdapters.checkPlayServices(getTaskListFragment());
}
public void restart() {

@ -50,12 +50,12 @@ import org.tasks.analytics.Tracker;
import org.tasks.analytics.Tracking;
import org.tasks.dialogs.DialogBuilder;
import org.tasks.dialogs.SortDialog;
import org.tasks.gtasks.GtaskSyncAdapterHelper;
import org.tasks.injection.ForActivity;
import org.tasks.injection.FragmentComponent;
import org.tasks.injection.InjectingFragment;
import org.tasks.preferences.Device;
import org.tasks.preferences.Preferences;
import org.tasks.sync.SyncAdapters;
import org.tasks.tasklist.TaskListRecyclerAdapter;
import org.tasks.tasklist.ViewHolderFactory;
import org.tasks.ui.CheckBoxes;
@ -97,7 +97,7 @@ public class TaskListFragment extends InjectingFragment implements
// --- instance variables
@Inject GtaskSyncAdapterHelper gtaskSyncAdapterHelper;
@Inject SyncAdapters syncAdapters;
@Inject TaskDeleter taskDeleter;
@Inject TaskDuplicator taskDuplicator;
@Inject @ForActivity Context context;
@ -135,7 +135,7 @@ public class TaskListFragment extends InjectingFragment implements
@Override
public void onRefresh() {
if (!gtaskSyncAdapterHelper.initiateManualSync()) {
if (!syncAdapters.initiateManualSync()) {
refresh();
}
}
@ -477,7 +477,7 @@ public class TaskListFragment extends InjectingFragment implements
for (Task task : tasks) {
onTaskCreated(task.getUuid());
}
gtaskSyncAdapterHelper.requestSynchronization();
syncAdapters.requestSynchronization();
}
public void onTaskCreated(String uuid) {

@ -32,6 +32,7 @@ import com.todoroo.astrid.gtasks.GtasksPreferenceService;
import org.tasks.R;
import org.tasks.activities.GoogleTaskListSettingsActivity;
import org.tasks.activities.TagSettingsActivity;
import org.tasks.caldav.CalDAVSettingsActivity;
import org.tasks.filters.FilterCounter;
import org.tasks.filters.FilterProvider;
import org.tasks.filters.NavigationDrawerAction;
@ -319,6 +320,14 @@ public class FilterAdapter extends ArrayAdapter<FilterListItem> {
addSubMenu(R.string.CalDAV, filterProvider.getCalDAVFilters(), false);
if (navigationDrawer) {
add(new NavigationDrawerAction(
activity.getResources().getString(R.string.add_account),
R.drawable.ic_add_24dp,
new Intent(activity, CalDAVSettingsActivity.class),
NavigationDrawerFragment.REQUEST_NEW_CALDAV_ACCOUNT));
}
if (navigationDrawer) {
add(new NavigationDrawerSeparator());

@ -13,7 +13,6 @@ import android.support.annotation.NonNull;
import com.todoroo.astrid.api.CaldavFilter;
import com.todoroo.astrid.api.Filter;
import com.todoroo.astrid.api.GtasksFilter;
import com.todoroo.astrid.gtasks.GtasksPreferenceService;
import org.tasks.R;
import org.tasks.activities.CalendarSelectionActivity;
@ -21,7 +20,6 @@ import org.tasks.analytics.Tracker;
import org.tasks.analytics.Tracking;
import org.tasks.calendars.AndroidCalendar;
import org.tasks.calendars.CalendarProvider;
import org.tasks.gtasks.GtaskSyncAdapterHelper;
import org.tasks.gtasks.RemoteListSelectionHandler;
import org.tasks.injection.ActivityComponent;
import org.tasks.injection.InjectingPreferenceActivity;
@ -29,6 +27,7 @@ import org.tasks.preferences.ActivityPermissionRequestor;
import org.tasks.preferences.DefaultFilterProvider;
import org.tasks.preferences.PermissionRequestor;
import org.tasks.preferences.Preferences;
import org.tasks.sync.SyncAdapters;
import javax.inject.Inject;
@ -45,8 +44,7 @@ public class DefaultsPreferences extends InjectingPreferenceActivity implements
@Inject CalendarProvider calendarProvider;
@Inject ActivityPermissionRequestor permissionRequester;
@Inject Tracker tracker;
@Inject GtasksPreferenceService gtasksPreferenceService;
@Inject GtaskSyncAdapterHelper gtaskSyncAdapterHelper;
@Inject SyncAdapters syncAdapters;
@Inject DefaultFilterProvider defaultFilterProvider;
private Preference defaultCalendarPref;
@ -69,7 +67,7 @@ public class DefaultsPreferences extends InjectingPreferenceActivity implements
? getString(R.string.dont_add_to_calendar)
: defaultCalendarName);
if (gtaskSyncAdapterHelper.isEnabled()) {
if (syncAdapters.isSyncEnabled()) {
findPreference(R.string.p_default_remote_list).setOnPreferenceClickListener(preference -> {
newRemoteListNativePicker(defaultFilterProvider.getDefaultRemoteList())
.show(getFragmentManager(), FRAG_TAG_REMOTE_LIST_SELECTION);

@ -166,6 +166,9 @@ public abstract class TaskDao {
@android.arch.persistence.room.Query("SELECT tasks.* FROM tasks INNER JOIN google_tasks ON google_tasks.task = tasks._id WHERE google_tasks.list_id = :googleTaskList")
public abstract List<Task> getGoogleTasks(String googleTaskList);
@android.arch.persistence.room.Query("SELECT tasks.* FROM tasks INNER JOIN caldav_tasks ON caldav_tasks.task = tasks._id WHERE caldav_tasks.account = :caldavAccount")
public abstract List<Task> getCaldavTasks(String caldavAccount);
// --- save
/**

@ -0,0 +1,65 @@
package org.tasks.caldav;
import android.accounts.AccountManager;
import android.content.ContentResolver;
import android.os.Bundle;
import java.util.concurrent.TimeUnit;
public class Account {
private static final String AUTHORITY = "org.tasks";
public static final String EXTRA_UUID = "uuid";
private AccountManager accountManager;
private android.accounts.Account account;
public Account(AccountManager accountManager, android.accounts.Account account) {
this.accountManager = accountManager;
this.account = account;
}
public String getName() {
return account.name;
}
public String getUuid() {
return accountManager.getUserData(account, EXTRA_UUID);
}
String getPassword() {
return accountManager.getPassword(account);
}
public android.accounts.Account getAccount() {
return account;
}
void setPassword(String password) {
accountManager.setPassword(account, password);
}
public void setUuid(String uuid) {
accountManager.setUserData(account, EXTRA_UUID, uuid);
}
boolean isBackgroundSyncEnabled() {
return ContentResolver.getSyncAutomatically(account, AUTHORITY);
}
void setSynchronizationEnabled(boolean enabled) {
ContentResolver.setSyncAutomatically(account, AUTHORITY, enabled);
if (enabled) {
ContentResolver.addPeriodicSync(account, AUTHORITY, Bundle.EMPTY, TimeUnit.HOURS.toSeconds(1));
} else {
ContentResolver.removePeriodicSync(account, AUTHORITY, Bundle.EMPTY);
}
}
@Override
public String toString() {
return "Account{" +
"account=" + account +
'}';
}
}

@ -0,0 +1,59 @@
package org.tasks.caldav;
import android.accounts.AbstractAccountAuthenticator;
import android.accounts.Account;
import android.accounts.AccountAuthenticatorResponse;
import android.accounts.AccountManager;
import android.accounts.NetworkErrorException;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
public class CalDAVAccountAuthenticator extends AbstractAccountAuthenticator {
private final Context context;
public CalDAVAccountAuthenticator(Context context) {
super(context);
this.context = context;
}
@Override
public Bundle editProperties(AccountAuthenticatorResponse response, String accountType) {
return null;
}
@Override
public Bundle addAccount(AccountAuthenticatorResponse response, String accountType, String authTokenType, String[] requiredFeatures, Bundle options) throws NetworkErrorException {
Intent intent = new Intent(context, CalDAVSettingsActivity.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 getAuthToken(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options) throws NetworkErrorException {
return null;
}
@Override
public String getAuthTokenLabel(String authTokenType) {
return null;
}
@Override
public Bundle updateCredentials(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options) throws NetworkErrorException {
return null;
}
@Override
public Bundle hasFeatures(AccountAuthenticatorResponse response, Account account, String[] features) throws NetworkErrorException {
return null;
}
}

@ -0,0 +1,25 @@
package org.tasks.caldav;
import android.accounts.AccountManager;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.support.annotation.Nullable;
public class CalDAVAccountAuthenticatorService extends Service {
private CalDAVAccountAuthenticator authenticator;
@Override
public void onCreate() {
authenticator = new CalDAVAccountAuthenticator(this);
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return AccountManager.ACTION_AUTHENTICATOR_INTENT.equals(intent.getAction())
? authenticator.getIBinder()
: null;
}
}

@ -0,0 +1,464 @@
package org.tasks.caldav;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.support.design.widget.Snackbar;
import android.support.design.widget.TextInputEditText;
import android.support.design.widget.TextInputLayout;
import android.support.v4.content.ContextCompat;
import android.support.v7.widget.Toolbar;
import android.text.InputType;
import android.view.MenuItem;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
import android.widget.CheckBox;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import com.todoroo.astrid.activity.TaskListActivity;
import com.todoroo.astrid.api.CaldavFilter;
import com.todoroo.astrid.helper.UUIDHelper;
import org.tasks.R;
import org.tasks.activities.ColorPickerActivity;
import org.tasks.analytics.Tracker;
import org.tasks.analytics.Tracking;
import org.tasks.data.CaldavAccount;
import org.tasks.data.CaldavDao;
import org.tasks.dialogs.ColorPickerDialog;
import org.tasks.dialogs.DialogBuilder;
import org.tasks.injection.ActivityComponent;
import org.tasks.injection.ThemedInjectingAppCompatActivity;
import org.tasks.preferences.Preferences;
import org.tasks.sync.SyncAdapters;
import org.tasks.themes.ThemeCache;
import org.tasks.themes.ThemeColor;
import java.net.IDN;
import java.net.URI;
import java.net.URISyntaxException;
import javax.inject.Inject;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
import butterknife.OnFocusChange;
import butterknife.OnTextChanged;
import timber.log.Timber;
import static android.text.TextUtils.isEmpty;
import static org.tasks.caldav.DeleteAccountDialog.newDeleteAccountDialog;
import static org.tasks.caldav.RenameAccountDialog.newRenameAccountDialog;
import static org.tasks.time.DateTimeUtils.currentTimeMillis;
public class CalDAVSettingsActivity extends ThemedInjectingAppCompatActivity
implements Toolbar.OnMenuItemClickListener, DeleteAccountDialog.DeleteAccountDialogCallback,
RenameAccountDialog.RenameAccountDialogCallback {
private static final String EXTRA_SELECTED_THEME = "extra_selected_theme";
private static final String FRAG_TAG_RENAME_ACCOUNT = "frag_tag_rename_account";
private static final String FRAG_TAG_DELETE_ACCOUNT = "frag_tag_delete_account";
private static final int REQUEST_COLOR_PICKER = 10109;
public static final String EXTRA_CALDAV_DATA = "caldavData"; //$NON-NLS-1$
public static final String EXTRA_CALDAV_UUID = "uuid"; //$NON-NLS-1$
public static final String ACTION_RELOAD = "accountRenamed";
public static final String ACTION_DELETED = "accountDeleted";
private CaldavAccount caldavAccount;
private Account localAccount;
private int selectedTheme;
@Inject DialogBuilder dialogBuilder;
@Inject Preferences preferences;
@Inject ThemeCache themeCache;
@Inject ThemeColor themeColor;
@Inject Tracker tracker;
@Inject CaldavDao caldavDao;
@Inject CaldavAccountManager caldavAccountManager;
@Inject SyncAdapters syncAdapters;
@BindView(R.id.root_layout) LinearLayout root;
@BindView(R.id.name) TextInputEditText name;
@BindView(R.id.url) TextInputEditText url;
@BindView(R.id.user) TextInputEditText user;
@BindView(R.id.password) TextInputEditText password;
@BindView(R.id.name_layout) TextInputLayout nameLayout;
@BindView(R.id.url_layout) TextInputLayout urlLayout;
@BindView(R.id.user_layout) TextInputLayout userLayout;
@BindView(R.id.password_layout) TextInputLayout passwordLayout;
@BindView(R.id.color) TextInputEditText color;
@BindView(R.id.toolbar) Toolbar toolbar;
@BindView(R.id.background_sync) CheckBox backgroundSync;
@BindView(R.id.master_sync_warning) TextView masterSyncWarning;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_caldav_settings);
ButterKnife.bind(this);
caldavAccount = getIntent().getParcelableExtra(EXTRA_CALDAV_DATA);
if (caldavAccount != null) {
localAccount = caldavAccountManager.getAccount(caldavAccount.getUuid());
}
if (savedInstanceState == null) {
if (caldavAccount != null) {
selectedTheme = caldavAccount.getColor();
name.setText(caldavAccount.getName());
url.setText(caldavAccount.getUrl());
user.setText(caldavAccount.getUsername());
}
backgroundSync.setChecked(localAccount == null || localAccount.isBackgroundSyncEnabled());
} else {
selectedTheme = savedInstanceState.getInt(EXTRA_SELECTED_THEME);
}
final boolean backButtonSavesTask = preferences.backButtonSavesTask();
toolbar.setTitle(caldavAccount == null ? getString(R.string.add_account) : caldavAccount.getName());
toolbar.setNavigationIcon(ContextCompat.getDrawable(this,
backButtonSavesTask ? R.drawable.ic_close_24dp : R.drawable.ic_save_24dp));
toolbar.setNavigationOnClickListener(v -> {
if (backButtonSavesTask) {
discard();
} else {
save();
}
});
toolbar.inflateMenu(R.menu.menu_tag_settings);
toolbar.setOnMenuItemClickListener(this);
toolbar.showOverflowMenu();
color.setInputType(InputType.TYPE_NULL);
if (caldavAccount == null) {
toolbar.getMenu().findItem(R.id.delete).setVisible(false);
name.requestFocus();
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
imm.showSoftInput(name, InputMethodManager.SHOW_IMPLICIT);
}
updateTheme();
}
@OnTextChanged(R.id.name)
void onNameChanged(CharSequence text) {
nameLayout.setError(null);
}
@OnTextChanged(R.id.url)
void onUrlChanged(CharSequence text) {
urlLayout.setError(null);
}
@OnTextChanged(R.id.user)
void onUserChanged(CharSequence text) {
userLayout.setError(null);
}
@OnTextChanged(R.id.password)
void onPasswordChanged(CharSequence text) {
passwordLayout.setError(null);
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt(EXTRA_SELECTED_THEME, selectedTheme);
}
@Override
protected void onResume() {
super.onResume();
masterSyncWarning.setVisibility(syncAdapters.isMasterSyncEnabled() ? View.GONE : View.VISIBLE);
}
@OnFocusChange(R.id.color)
void onFocusChange(boolean focused) {
if (focused) {
color.clearFocus();
showThemePicker();
}
}
@OnClick(R.id.color)
protected void showThemePicker() {
Intent intent = new Intent(CalDAVSettingsActivity.this, ColorPickerActivity.class);
intent.putExtra(ColorPickerActivity.EXTRA_PALETTE, ColorPickerDialog.ColorPalette.COLORS);
intent.putExtra(ColorPickerActivity.EXTRA_SHOW_NONE, true);
startActivityForResult(intent, REQUEST_COLOR_PICKER);
}
@Override
public void inject(ActivityComponent component) {
component.inject(this);
}
private String getNewName() {
return name.getText().toString().trim();
}
private String getNewURL() {
return url.getText().toString().trim();
}
private String getNewUsername() {
return user.getText().toString().trim();
}
private String getNewPassword() {
String input = password.getText().toString().trim();
return localAccount == null || !isEmpty(input) ? input : localAccount.getPassword();
}
private boolean clashes(String newName) {
CaldavAccount existing = caldavDao.getAccountByName(newName);
return caldavAccount != null && existing != null && caldavAccount.getId() != existing.getId();
}
private void save() {
String newName = getNewName();
String username = getNewUsername();
String url = getNewURL();
String password = getNewPassword();
boolean failed = false;
if (isEmpty(newName)) {
nameLayout.setError(getString(R.string.name_cannot_be_empty));
failed = true;
} else if (clashes(newName)) {
nameLayout.setError(getString(R.string.tag_already_exists));
failed = true;
}
if (isEmpty(url)) {
urlLayout.setError(getString(R.string.url_required));
failed = true;
} else {
Uri baseURL = Uri.parse(url);
String scheme = baseURL.getScheme();
if ("https".equalsIgnoreCase(scheme) || "http".equalsIgnoreCase(scheme)) {
String host = baseURL.getHost();
if (isEmpty(host)) {
urlLayout.setError(getString(R.string.url_host_name_required));
failed = true;
} else {
try {
host = IDN.toASCII(host);
} catch(Exception e) {
Timber.e(e.getMessage(), e);
}
String path = baseURL.getEncodedPath();
int port = baseURL.getPort();
try {
new URI(scheme, null, host, port, path, null, null);
} catch(URISyntaxException e) {
urlLayout.setError(e.getLocalizedMessage());
failed = true;
}
}
} else {
urlLayout.setError(getString(R.string.url_invalid_scheme));
failed = true;
}
}
if (isEmpty(username)) {
userLayout.setError(getString(R.string.username_required));
failed = true;
}
if (localAccount == null && isEmpty(password)) {
passwordLayout.setError(getString(R.string.password_required));
failed = true;
}
if (failed) {
return;
}
if (caldavAccount == null) {
CaldavAccount newAccount = new CaldavAccount(newName, UUIDHelper.newUUID());
newAccount.setColor(selectedTheme);
newAccount.setUrl(url);
newAccount.setUsername(username);
if (caldavAccountManager.addAccount(newAccount, password)) {
Account account = caldavAccountManager.getAccount(newAccount.getUuid());
if (account == null) {
showErrorSnackbar();
} else {
newAccount.setId(caldavDao.insert(newAccount));
setResult(RESULT_OK, new Intent().putExtra(TaskListActivity.OPEN_FILTER, new CaldavFilter(newAccount)));
finish();
}
} else {
showErrorSnackbar();
}
} else if (hasChanges()) {
if (localAccount == null) {
if (!caldavAccountManager.addAccount(caldavAccount, password)) {
localAccount = caldavAccountManager.getAccount(caldavAccount.getUuid());
if (localAccount == null) {
showErrorSnackbar();
return;
}
}
} else if (!newName.equals(localAccount.getName())) {
newRenameAccountDialog(caldavAccount.getUuid(), newName)
.show(getSupportFragmentManager(), FRAG_TAG_RENAME_ACCOUNT);
return;
}
caldavAccount.setName(newName);
caldavAccount.setColor(selectedTheme);
caldavAccount.setUrl(url);
caldavAccount.setUsername(username);
caldavDao.update(caldavAccount);
if (!isEmpty(password)) {
localAccount.setPassword(password);
}
localAccount.setSynchronizationEnabled(backgroundSync.isChecked());
setResult(RESULT_OK, new Intent(ACTION_RELOAD).putExtra(TaskListActivity.OPEN_FILTER, new CaldavFilter(caldavAccount)));
finish();
} else {
finish();
}
}
private void showErrorSnackbar() {
Snackbar snackbar = Snackbar.make(root, getString(R.string.error_adding_account), 8000)
.setActionTextColor(ContextCompat.getColor(this, R.color.snackbar_text_color));
snackbar.getView().setBackgroundColor(ContextCompat.getColor(this, R.color.snackbar_background));
snackbar.show();
}
private boolean hasChanges() {
if (caldavAccount == null) {
return selectedTheme >= 0 || !isEmpty(getNewName()) ||
!isEmpty(getNewPassword()) || !isEmpty(getNewURL()) ||
!isEmpty(getNewUsername()) || !backgroundSync.isChecked();
}
return localAccount == null ||
selectedTheme != caldavAccount.getColor() ||
!getNewName().equals(caldavAccount.getName()) ||
!getNewURL().equals(caldavAccount.getUrl()) ||
!getNewUsername().equals(caldavAccount.getUsername()) ||
!getNewPassword().equals(localAccount.getPassword()) ||
backgroundSync.isChecked() != localAccount.isBackgroundSyncEnabled();
}
@Override
public void finish() {
InputMethodManager imm = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(name.getWindowToken(), 0);
super.finish();
}
@Override
public void onBackPressed() {
if (preferences.backButtonSavesTask()) {
save();
} else {
discard();
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_COLOR_PICKER) {
if (resultCode == RESULT_OK) {
int index = data.getIntExtra(ColorPickerActivity.EXTRA_THEME_INDEX, 0);
tracker.reportEvent(Tracking.Events.SET_TAG_COLOR, Integer.toString(index));
selectedTheme = index;
updateTheme();
}
} else {
super.onActivityResult(requestCode, resultCode, data);
}
}
private void deleteAccount() {
dialogBuilder.newMessageDialog(R.string.delete_tag_confirmation, caldavAccount.getName())
.setPositiveButton(R.string.delete, (dialog, which) -> {
if (localAccount == null) {
onListDeleted();
} else {
newDeleteAccountDialog(localAccount.getUuid())
.show(getSupportFragmentManager(), FRAG_TAG_DELETE_ACCOUNT);
}
})
.setNegativeButton(android.R.string.cancel, null)
.show();
}
private void discard() {
if (!hasChanges()) {
finish();
} else {
dialogBuilder.newMessageDialog(R.string.discard_changes)
.setPositiveButton(R.string.discard, (dialog, which) -> finish())
.setNegativeButton(android.R.string.cancel, null)
.show();
}
}
private void updateTheme() {
ThemeColor themeColor;
if (selectedTheme < 0) {
themeColor = this.themeColor;
color.setText(R.string.none);
} else {
themeColor = themeCache.getThemeColor(selectedTheme);
color.setText(themeColor.getName());
}
themeColor.apply(toolbar);
themeColor.applyToStatusBar(this);
}
@Override
public boolean onMenuItemClick(MenuItem item) {
switch (item.getItemId()) {
case R.id.delete:
deleteAccount();
break;
}
return super.onOptionsItemSelected(item);
}
@Override
public void onListDeleted() {
if (caldavAccount != null) {
caldavAccountManager.deleteAccount(caldavAccount);
setResult(RESULT_OK, new Intent(ACTION_DELETED).putExtra(EXTRA_CALDAV_UUID, caldavAccount.getUuid()));
}
finish();
}
@Override
public void onListRenamed() {
localAccount = caldavAccountManager.getAccount(caldavAccount.getUuid());
save();
}
@Override
public void renameFailed() {
nameLayout.setError(getString(R.string.error_renaming_account));
}
@Override
public void deleteAccountFailed() {
Toast.makeText(this, R.string.error_deleting_account, Toast.LENGTH_LONG).show();
}
}

@ -0,0 +1,164 @@
package org.tasks.caldav;
import android.accounts.AuthenticatorException;
import android.accounts.OperationCanceledException;
import android.content.ContentResolver;
import android.content.Context;
import android.os.Bundle;
import com.google.common.base.Optional;
import com.google.common.base.Strings;
import com.todoroo.astrid.dao.TaskDao;
import com.todoroo.astrid.data.Task;
import com.todoroo.astrid.service.TaskDeleter;
import org.tasks.LocalBroadcastManager;
import org.tasks.data.CaldavAccount;
import org.tasks.data.CaldavDao;
import org.tasks.injection.ApplicationScope;
import org.tasks.injection.ForApplication;
import org.tasks.preferences.PermissionChecker;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.inject.Inject;
import timber.log.Timber;
import static com.google.common.collect.Iterables.tryFind;
import static org.tasks.caldav.Account.EXTRA_UUID;
@ApplicationScope
public class CaldavAccountManager {
private static final String AUTHORITY = "org.tasks";
private static final String ACCOUNT_TYPE = "org.tasks.caldav";
private final PermissionChecker permissionChecker;
private final android.accounts.AccountManager accountManager;
private final LocalBroadcastManager localBroadcastManager;
private final TaskDeleter taskDeleter;
private final CaldavDao caldavDao;
private final TaskDao taskDao;
@Inject
public CaldavAccountManager(@ForApplication Context context, PermissionChecker permissionChecker,
CaldavDao caldavDao, TaskDeleter taskDeleter,
LocalBroadcastManager localBroadcastManager, TaskDao taskDao) {
this.permissionChecker = permissionChecker;
this.caldavDao = caldavDao;
this.taskDao = taskDao;
this.taskDeleter = taskDeleter;
this.localBroadcastManager = localBroadcastManager;
accountManager = android.accounts.AccountManager.get(context);
syncAccountList();
}
public String getUuid(android.accounts.Account account) {
return accountManager.getUserData(account, EXTRA_UUID);
}
public Account getAccount(String uuid) {
for (Account account : getAccounts()) {
if (uuid.equals(account.getUuid())) {
return account;
}
}
return null;
}
public List<Account> getAccounts() {
if (!permissionChecker.canAccessAccounts()) {
return Collections.emptyList();
}
List<Account> accounts = new ArrayList<>();
for (android.accounts.Account account : accountManager.getAccountsByType(ACCOUNT_TYPE)) {
accounts.add(new Account(accountManager, account));
}
return accounts;
}
boolean removeAccount(Account account) {
return removeAccount(account.getAccount());
}
boolean removeAccount(android.accounts.Account account) {
try {
return accountManager
.removeAccount(account, null, null)
.getResult();
} catch (OperationCanceledException | IOException | AuthenticatorException e) {
Timber.e(e.getMessage(), e);
}
return false;
}
boolean addAccount(CaldavAccount caldavAccount, String password) {
Timber.d("Adding %s", caldavAccount);
android.accounts.Account account = new android.accounts.Account(caldavAccount.getName(), ACCOUNT_TYPE);
Bundle userdata = new Bundle();
userdata.putString(EXTRA_UUID, caldavAccount.getUuid());
return accountManager.addAccountExplicitly(account, password, userdata);
}
private void createAccount(Account account) {
Timber.d("Adding %s", account);
String uuid = account.getUuid();
if (!Strings.isNullOrEmpty(uuid)) {
caldavDao.insert(new CaldavAccount(account.getName(), uuid));
}
}
private void syncAccountList() {
List<CaldavAccount> oldAccountList = caldavDao.getAllOrderedByName();
List<Account> newAccountList = getAccounts();
for (CaldavAccount local : oldAccountList) {
Optional<Account> match = tryFind(newAccountList, remote -> local.getUuid().equals(remote.getUuid()));
if (match.isPresent()) {
Timber.d("found %s", match.get());
} else {
addAccount(local, null);
}
}
for (Account remote : newAccountList) {
Optional<CaldavAccount> match = tryFind(oldAccountList, local -> remote.getUuid().equals(local.getUuid()));
if (match.isPresent()) {
Timber.d("found %s", match.get());
} else {
createAccount(remote);
}
}
}
void deleteAccount(CaldavAccount account) {
String uuid = account.getUuid();
for (Task task : taskDao.getCaldavTasks(uuid)) {
taskDeleter.markDeleted(task);
}
caldavDao.deleteTasksForAccount(uuid);
caldavDao.delete(account);
localBroadcastManager.broadcastRefreshList();
}
public boolean initiateManualSync() {
for (org.tasks.caldav.Account account : getAccounts()) {
Bundle extras = new Bundle();
extras.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);
extras.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true);
ContentResolver.requestSync(account.getAccount(), AUTHORITY, extras);
}
return true;
}
public void requestSynchronization() {
for (org.tasks.caldav.Account account : getAccounts()) {
ContentResolver.requestSync(account.getAccount(), AUTHORITY, new Bundle());
}
}
}

@ -1,15 +1,22 @@
package org.tasks.caldav;
import android.content.Intent;
import android.os.Bundle;
import android.support.v7.widget.Toolbar;
import android.view.MenuItem;
import com.todoroo.astrid.activity.TaskListActivity;
import com.todoroo.astrid.activity.TaskListFragment;
import com.todoroo.astrid.api.CaldavFilter;
import com.todoroo.astrid.api.Filter;
import org.tasks.R;
import org.tasks.data.CaldavAccount;
import org.tasks.injection.FragmentComponent;
import static android.app.Activity.RESULT_OK;
import static org.tasks.caldav.CalDAVSettingsActivity.EXTRA_CALDAV_DATA;
public class CaldavListFragment extends TaskListFragment {
public static TaskListFragment newCaldavListFragment(CaldavFilter filter, CaldavAccount account) {
@ -20,6 +27,7 @@ public class CaldavListFragment extends TaskListFragment {
}
private static final String EXTRA_CALDAV_ACCOUNT = "extra_caldav_account";
private static final int REQUEST_ACCOUNT_SETTINGS = 10101;
protected CaldavAccount account;
@ -37,7 +45,39 @@ public class CaldavListFragment extends TaskListFragment {
super.inflateMenu(toolbar);
toolbar.inflateMenu(R.menu.menu_caldav_list_fragment);
}
@Override
public boolean onMenuItemClick(MenuItem item) {
switch(item.getItemId()) {
case R.id.menu_caldav_list_fragment:
Intent intent = new Intent(getActivity(), CalDAVSettingsActivity.class);
intent.putExtra(EXTRA_CALDAV_DATA, account);
startActivityForResult(intent, REQUEST_ACCOUNT_SETTINGS);
return true;
default:
return super.onMenuItemClick(item);
}
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_ACCOUNT_SETTINGS) {
if (resultCode == RESULT_OK) {
TaskListActivity activity = (TaskListActivity) getActivity();
String action = data.getAction();
if (CalDAVSettingsActivity.ACTION_DELETED.equals(action)) {
activity.onFilterItemClicked(null);
} else if (CalDAVSettingsActivity.ACTION_RELOAD.equals(action)) {
activity.getIntent().putExtra(TaskListActivity.OPEN_FILTER,
(Filter) data.getParcelableExtra(TaskListActivity.OPEN_FILTER));
activity.recreate();
}
}
} else {
super.onActivityResult(requestCode, resultCode, data);
}
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);

@ -0,0 +1,93 @@
package org.tasks.caldav;
import android.app.Activity;
import android.app.Dialog;
import android.app.ProgressDialog;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import org.tasks.R;
import org.tasks.dialogs.DialogBuilder;
import org.tasks.injection.DialogFragmentComponent;
import org.tasks.injection.InjectingDialogFragment;
import javax.inject.Inject;
public class DeleteAccountDialog extends InjectingDialogFragment {
public static DeleteAccountDialog newDeleteAccountDialog(String uuid) {
DeleteAccountDialog dialog = new DeleteAccountDialog();
Bundle args = new Bundle();
args.putString(EXTRA_UUID, uuid);
dialog.setArguments(args);
return dialog;
}
public interface DeleteAccountDialogCallback {
void onListDeleted();
void deleteAccountFailed();
}
private static final String EXTRA_UUID = "extra_uuid";
@Inject DialogBuilder dialogBuilder;
@Inject CaldavAccountManager caldavAccountManager;
private DeleteAccountDialogCallback callback;
private String uuid;
private ProgressDialog dialog;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
Bundle arguments = getArguments();
uuid = arguments.getString(EXTRA_UUID);
dialog = dialogBuilder.newProgressDialog(R.string.deleting_list);
execute();
}
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
return dialog;
}
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
callback = (DeleteAccountDialogCallback) activity;
}
@Override
protected void inject(DialogFragmentComponent component) {
component.inject(this);
}
private void execute() {
new AsyncTask<Void, Void, Boolean>() {
@Override
protected Boolean doInBackground(Void... voids) {
Account account = caldavAccountManager.getAccount(uuid);
return account == null || caldavAccountManager.removeAccount(account);
}
@Override
protected void onPostExecute(Boolean result) {
if (dialog.isShowing()) {
dialog.dismiss();
}
if (result) {
callback.onListDeleted();
} else {
callback.deleteAccountFailed();
}
}
}.execute();
}
}

@ -0,0 +1,112 @@
package org.tasks.caldav;
import android.app.Activity;
import android.app.Dialog;
import android.app.ProgressDialog;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import com.todoroo.astrid.helper.UUIDHelper;
import org.tasks.R;
import org.tasks.data.CaldavAccount;
import org.tasks.data.CaldavDao;
import org.tasks.dialogs.DialogBuilder;
import org.tasks.injection.DialogFragmentComponent;
import org.tasks.injection.InjectingDialogFragment;
import javax.inject.Inject;
import static org.tasks.time.DateTimeUtils.currentTimeMillis;
public class RenameAccountDialog extends InjectingDialogFragment {
public static RenameAccountDialog newRenameAccountDialog(String uuid, String name) {
RenameAccountDialog dialog = new RenameAccountDialog();
Bundle args = new Bundle();
args.putString(EXTRA_NAME, name);
args.putString(EXTRA_UUID, uuid);
dialog.setArguments(args);
return dialog;
}
public interface RenameAccountDialogCallback {
void onListRenamed();
void renameFailed();
}
private static final String EXTRA_NAME = "extra_name";
private static final String EXTRA_UUID = "extra_uuid";
@Inject DialogBuilder dialogBuilder;
@Inject CaldavAccountManager caldavAccountManager;
@Inject CaldavDao caldavDao;
private RenameAccountDialogCallback callback;
private String name;
private String uuid;
private ProgressDialog dialog;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
Bundle arguments = getArguments();
name = arguments.getString(EXTRA_NAME);
uuid = arguments.getString(EXTRA_UUID);
dialog = dialogBuilder.newProgressDialog(R.string.renaming_list);
execute();
}
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
return dialog;
}
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
callback = (RenameAccountDialogCallback) activity;
}
@Override
protected void inject(DialogFragmentComponent component) {
component.inject(this);
}
private void execute() {
new AsyncTask<Void, Void, Boolean>() {
@Override
protected Boolean doInBackground(Void... voids) {
CaldavAccount caldavAccount = caldavDao.getAccount(uuid);
caldavAccount.setName(name);
Account old = caldavAccountManager.getAccount(uuid);
if (!caldavAccountManager.addAccount(caldavAccount, old.getPassword())) {
return false;
}
caldavDao.update(caldavAccount);
old.setUuid(null);
caldavAccountManager.removeAccount(old);
return true;
}
@Override
protected void onPostExecute(Boolean result) {
if (dialog.isShowing()) {
dialog.dismiss();
}
if (result) {
callback.onListRenamed();
} else {
callback.renameFailed();
}
}
}.execute();
}
}

@ -26,29 +26,31 @@ public final class CaldavAccount implements Parcelable {
@ColumnInfo(name = "color")
private int color = -1;
@ColumnInfo(name = "deleted")
private long deleted;
@ColumnInfo(name = "ctag")
private String ctag;
@ColumnInfo(name = "url")
private String url;
private String url = "";
@ColumnInfo(name = "username")
private String username;
private String username = "";
public CaldavAccount() {
}
@Ignore
public CaldavAccount(String name, String uuid) {
this.name = name;
this.uuid = uuid;
}
@Ignore
public CaldavAccount(Parcel source) {
id = source.readLong();
uuid = source.readString();
name = source.readString();
color = source.readInt();
deleted = source.readLong();
ctag = source.readString();
url = source.readString();
username = source.readString();
@ -86,14 +88,6 @@ public final class CaldavAccount implements Parcelable {
this.color = color;
}
public long getDeleted() {
return deleted;
}
public void setDeleted(long deleted) {
this.deleted = deleted;
}
public String getCtag() {
return ctag;
}
@ -118,10 +112,6 @@ public final class CaldavAccount implements Parcelable {
this.username = username;
}
public boolean isDeleted() {
return deleted > 0;
}
public static Parcelable.Creator<CaldavAccount> CREATOR = new Parcelable.Creator<CaldavAccount>() {
@Override
public CaldavAccount createFromParcel(Parcel source) {
@ -145,7 +135,6 @@ public final class CaldavAccount implements Parcelable {
dest.writeString(uuid);
dest.writeString(name);
dest.writeInt(color);
dest.writeLong(deleted);
dest.writeString(ctag);
dest.writeString(url);
dest.writeString(username);
@ -158,7 +147,6 @@ public final class CaldavAccount implements Parcelable {
", uuid='" + uuid + '\'' +
", name='" + name + '\'' +
", color=" + color +
", deleted=" + deleted +
", ctag='" + ctag + '\'' +
", url='" + url + '\'' +
", username='" + username + '\'' +

@ -17,18 +17,15 @@ public interface CaldavDao {
@Query("SELECT * FROM caldav_account WHERE uuid = :uuid LIMIT 1")
CaldavAccount getByUuid(String uuid);
@Query("SELECT * FROM caldav_account WHERE deleted = 0 ORDER BY UPPER(name) ASC")
@Query("SELECT * FROM caldav_account ORDER BY UPPER(name) ASC")
List<CaldavAccount> getAllOrderedByName();
@Insert
void insert(CaldavAccount caldavAccount);
long insert(CaldavAccount caldavAccount);
@Update
void update(CaldavAccount caldavAccount);
@Query("UPDATE caldav_account SET deleted = (strftime('%s','now')*1000) WHERE uuid = :uuid")
void markAccountDeleted(String uuid);
@Insert
void insert(CaldavTask caldavTask);
@ -55,4 +52,13 @@ public interface CaldavDao {
@Query("SELECT * FROM caldav_account")
List<CaldavAccount> getAccounts();
@Delete
void delete(CaldavAccount caldavAccount);
@Query("SELECT * FROM caldav_account WHERE uuid = :uuid LIMIT 1")
CaldavAccount getAccount(String uuid);
@Query("DELETE FROM caldav_tasks WHERE account = :uuid")
void deleteTasksForAccount(String uuid);
}

@ -21,8 +21,8 @@ import org.tasks.data.GoogleTask;
import org.tasks.data.GoogleTaskList;
import org.tasks.data.Tag;
import org.tasks.data.TagData;
import org.tasks.gtasks.GtaskSyncAdapterHelper;
import org.tasks.injection.ForApplication;
import org.tasks.sync.SyncAdapters;
import java.util.HashMap;
import java.util.List;
@ -47,15 +47,15 @@ public class FilterCriteriaProvider {
private final TagService tagService;
private final GtasksListService gtasksListService;
private final Resources r;
private final GtaskSyncAdapterHelper gtaskSyncAdapterHelper;
private final SyncAdapters syncAdapters;
@Inject
public FilterCriteriaProvider(@ForApplication Context context, TagService tagService,
GtasksListService gtasksListService, GtaskSyncAdapterHelper gtaskSyncAdapterHelper) {
GtasksListService gtasksListService, SyncAdapters syncAdapters) {
this.context = context;
this.tagService = tagService;
this.gtasksListService = gtasksListService;
this.gtaskSyncAdapterHelper = gtaskSyncAdapterHelper;
this.syncAdapters = syncAdapters;
r = context.getResources();
}
@ -68,7 +68,7 @@ public class FilterCriteriaProvider {
result.add(getDueDateFilter());
result.add(getImportanceFilter());
result.add(getTaskTitleContainsFilter());
if (gtaskSyncAdapterHelper.isEnabled()) {
if (syncAdapters.isGoogleTaskSyncEnabled()) {
result.add(getGtasksFilterCriteria());
}

@ -16,8 +16,8 @@ import com.todoroo.astrid.ui.ReminderControlSet;
import org.tasks.BuildConfig;
import org.tasks.R;
import org.tasks.gtasks.GtaskSyncAdapterHelper;
import org.tasks.preferences.Preferences;
import org.tasks.sync.SyncAdapters;
import org.tasks.ui.CalendarControlSet;
import org.tasks.ui.DeadlineControlSet;
import org.tasks.ui.DescriptionControlSet;
@ -72,11 +72,11 @@ public class TaskEditControlSetFragmentManager {
private final Map<String, Integer> controlSetFragments = new LinkedHashMap<>();
private final List<String> displayOrder;
private final GtaskSyncAdapterHelper gtaskSyncAdapterHelper;
private final SyncAdapters syncAdapters;
private int numRows;
public TaskEditControlSetFragmentManager(Activity activity, Preferences preferences, GtaskSyncAdapterHelper gtaskSyncAdapterHelper) {
this.gtaskSyncAdapterHelper = gtaskSyncAdapterHelper;
public TaskEditControlSetFragmentManager(Activity activity, Preferences preferences, SyncAdapters syncAdapters) {
this.syncAdapters = syncAdapters;
displayOrder = BeastModePreferences.constructOrderedControlList(preferences, activity);
displayOrder.add(0, activity.getString(EditTitleControlSet.TAG));
displayOrder.add(1, activity.getString(CommentBarFragment.TAG));
@ -151,7 +151,7 @@ public class TaskEditControlSetFragmentManager {
case CommentBarFragment.TAG:
return new CommentBarFragment();
case RemoteListFragment.TAG:
return gtaskSyncAdapterHelper.isEnabled()
return syncAdapters.isSyncEnabled()
? new RemoteListFragment()
: null;
default:

@ -5,8 +5,8 @@ import android.content.Context;
import org.tasks.R;
import org.tasks.fragments.TaskEditControlSetFragmentManager;
import org.tasks.gtasks.GtaskSyncAdapterHelper;
import org.tasks.preferences.Preferences;
import org.tasks.sync.SyncAdapters;
import org.tasks.themes.ThemeAccent;
import org.tasks.themes.ThemeBase;
import org.tasks.themes.ThemeCache;
@ -55,7 +55,7 @@ public class ActivityModule {
@Provides
@ActivityScope
public TaskEditControlSetFragmentManager getTaskEditControlSetFragmentManager(Preferences preferences, GtaskSyncAdapterHelper gtaskSyncAdapterHelper) {
return new TaskEditControlSetFragmentManager(activity, preferences, gtaskSyncAdapterHelper);
public TaskEditControlSetFragmentManager getTaskEditControlSetFragmentManager(Preferences preferences, SyncAdapters syncAdapters) {
return new TaskEditControlSetFragmentManager(activity, preferences, syncAdapters);
}
}

@ -40,6 +40,7 @@ public class NavigationDrawerFragment extends InjectingFragment {
public static final int REQUEST_NEW_LIST = 4;
public static final int ACTIVITY_REQUEST_NEW_FILTER = 5;
public static final int REQUEST_NEW_GTASK_LIST = 6;
public static final int REQUEST_NEW_CALDAV_ACCOUNT = 7;
private final RefreshReceiver refreshReceiver = new RefreshReceiver();
@ -82,7 +83,8 @@ public class NavigationDrawerFragment extends InjectingFragment {
}
} else if (requestCode == REQUEST_NEW_LIST ||
requestCode == ACTIVITY_REQUEST_NEW_FILTER ||
requestCode == REQUEST_NEW_GTASK_LIST) {
requestCode == REQUEST_NEW_GTASK_LIST ||
requestCode == REQUEST_NEW_CALDAV_ACCOUNT) {
if (resultCode == RESULT_OK && data != null) {
Filter newList = data.getParcelableExtra(TaskListActivity.OPEN_FILTER);
if (newList != null) {

@ -0,0 +1,133 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/root_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:background="?attr/asContentBackground"
android:orientation="vertical">
<include layout="@layout/toolbar" />
<ScrollView
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:descendantFocusability="beforeDescendants"
android:focusableInTouchMode="true"
android:orientation="vertical">
<android.support.design.widget.TextInputLayout
android:id="@+id/name_layout"
style="@style/TagSettingsRow">
<android.support.design.widget.TextInputEditText
android:id="@+id/name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/name"
android:imeOptions="flagNoExtractUi"
android:inputType="textCapSentences|textFilter"
android:maxLines="1"
android:textColor="?attr/asTextColor" />
</android.support.design.widget.TextInputLayout>
<android.support.design.widget.TextInputLayout
android:id="@+id/url_layout"
style="@style/TagSettingsRow">
<android.support.design.widget.TextInputEditText
android:id="@+id/url"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/url"
android:textColor="?attr/asTextColor" />
</android.support.design.widget.TextInputLayout>
<android.support.design.widget.TextInputLayout
android:id="@+id/user_layout"
style="@style/TagSettingsRow">
<android.support.design.widget.TextInputEditText
android:id="@+id/user"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/user"
android:textColor="?attr/asTextColor" />
</android.support.design.widget.TextInputLayout>
<android.support.design.widget.TextInputLayout
android:id="@+id/password_layout"
style="@style/TagSettingsRow"
app:passwordToggleEnabled="true">
<android.support.design.widget.TextInputEditText
android:id="@+id/password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/password"
android:textColor="?attr/asTextColor"
android:inputType="textPassword"/>
</android.support.design.widget.TextInputLayout>
<android.support.design.widget.TextInputLayout style="@style/TagSettingsRow">
<android.support.design.widget.TextInputEditText
android:id="@+id/color"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/color"
android:textColor="?attr/asTextColor" />
</android.support.design.widget.TextInputLayout>
<RelativeLayout
style="@style/TagSettingsRow"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<CheckBox
android:id="@+id/background_sync"
android:checked="true"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/background_sync_label"
android:layout_alignParentStart="true"
android:layout_alignParentLeft="true"
android:paddingTop="3dp"
android:paddingLeft="4dp"
android:paddingStart="4dp"
android:textSize="@dimen/settings_text_size"
android:layout_toLeftOf="@id/background_sync"
android:layout_toStartOf="@id/background_sync"
android:text="@string/sync_SPr_interval_title"
android:layout_width="match_parent"
android:textColor="?attr/asTextColor"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/master_sync_warning"
android:paddingLeft="4dp"
android:paddingStart="4dp"
android:textColor="?attr/asTextColor"
android:layout_below="@id/background_sync_label"
android:text="@string/master_sync_warning"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</RelativeLayout>
</LinearLayout>
</ScrollView>
</LinearLayout>

@ -11,6 +11,7 @@
<dimen name="task_edit_double_padding_top_bottom">18dp</dimen>
<dimen name="task_edit_drawable_padding_top_bottom">17dp</dimen>
<dimen name="task_edit_text_size">16sp</dimen>
<dimen name="settings_text_size">18sp</dimen>
<item name="tag_characters" format="float" type="dimen">12</item>

@ -287,6 +287,7 @@
<string name="debug">Debug</string>
<string name="p_start_of_week">start_of_week</string>
<string name="p_use_native_datetime_pickers">use_native_datetime_pickers</string>
<string name="account_type_caldav">org.tasks.caldav</string>
<string name="gtask_background_sync">gtask_background_sync</string>
<string name="p_bundle_notifications">bundle_notifications</string>
<string name="p_strict_mode">strict_mode</string>

@ -756,6 +756,11 @@ File %1$s contained %2$s.\n\n
<string name="send_anonymous_statistics_summary">Send anonymous usage statistics and crash reports to help improve Tasks. No personal data will be collected.</string>
<string name="tag_already_exists">Tag already exists</string>
<string name="name_cannot_be_empty">Name cannot be empty</string>
<string name="username_required">Username required</string>
<string name="password_required">Password required</string>
<string name="url_required">URL required</string>
<string name="url_host_name_required">Host name required</string>
<string name="url_invalid_scheme">Must begin with http(s)://</string>
<string name="no_title">(No title)</string>
<string name="back_button_saves_task">Back button saves task</string>
<string name="default_list">Default list</string>
@ -832,6 +837,13 @@ File %1$s contained %2$s.\n\n
<string name="use_locale_default">Use locale default</string>
<string name="use_native_datetime_pickers">Use native date and time pickers</string>
<string name="CalDAV">CalDAV</string>
<string name="add_account">Add account</string>
<string name="user">User</string>
<string name="password">Password</string>
<string name="url">URL</string>
<string name="error_adding_account">Error adding account</string>
<string name="error_renaming_account">Error renaming account</string>
<string name="error_deleting_account">Error deleting account</string>
<string name="account_settings">Account settings</string>
<string name="notification_channel_settings">Manage notifications</string>
<string name="battery_optimization_settings">Manage battery optimizations</string>

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<account-authenticator xmlns:android="http://schemas.android.com/apk/res/android"
android:accountType="@string/account_type_caldav"
android:icon="@mipmap/ic_launcher"
android:label="@string/CalDAV"
android:smallIcon="@mipmap/ic_launcher" />

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<sync-adapter xmlns:android="http://schemas.android.com/apk/res/android"
android:accountType="@string/account_type_caldav"
android:contentAuthority="org.tasks"
android:supportsUploading="true"
android:isAlwaysSyncable="true"
android:userVisible="false" />
Loading…
Cancel
Save