Indicate google task and caldav sync errors

pull/685/merge
Alex Baker 6 years ago
parent 07f9d72219
commit a496040222

@ -2,7 +2,7 @@
"formatVersion": 1, "formatVersion": 1,
"database": { "database": {
"version": 59, "version": 59,
"identityHash": "1320507f7cf294e38cdd2d22d1ce4496", "identityHash": "d4c55b9b8440776e4eb5fdfc9bddaa76",
"entities": [ "entities": [
{ {
"tableName": "notification", "tableName": "notification",
@ -820,7 +820,7 @@
}, },
{ {
"tableName": "caldav_account", "tableName": "caldav_account",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `uuid` TEXT, `name` TEXT, `url` TEXT, `username` TEXT, `password` TEXT)", "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `uuid` TEXT, `name` TEXT, `url` TEXT, `username` TEXT, `password` TEXT, `error` TEXT)",
"fields": [ "fields": [
{ {
"fieldPath": "id", "fieldPath": "id",
@ -857,6 +857,12 @@
"columnName": "password", "columnName": "password",
"affinity": "TEXT", "affinity": "TEXT",
"notNull": false "notNull": false
},
{
"fieldPath": "error",
"columnName": "error",
"affinity": "TEXT",
"notNull": false
} }
], ],
"primaryKey": { "primaryKey": {
@ -870,7 +876,7 @@
}, },
{ {
"tableName": "google_task_accounts", "tableName": "google_task_accounts",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `account` TEXT)", "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `account` TEXT, `error` TEXT)",
"fields": [ "fields": [
{ {
"fieldPath": "id", "fieldPath": "id",
@ -883,6 +889,12 @@
"columnName": "account", "columnName": "account",
"affinity": "TEXT", "affinity": "TEXT",
"notNull": false "notNull": false
},
{
"fieldPath": "error",
"columnName": "error",
"affinity": "TEXT",
"notNull": false
} }
], ],
"primaryKey": { "primaryKey": {
@ -897,7 +909,7 @@
], ],
"setupQueries": [ "setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", "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, \"1320507f7cf294e38cdd2d22d1ce4496\")" "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"d4c55b9b8440776e4eb5fdfc9bddaa76\")"
] ]
} }
} }

@ -6,6 +6,7 @@
package com.todoroo.astrid.adapter; package com.todoroo.astrid.adapter;
import static android.support.v4.content.ContextCompat.getColor; import static android.support.v4.content.ContextCompat.getColor;
import static com.google.common.base.Strings.isNullOrEmpty;
import static com.todoroo.andlib.utility.AndroidUtilities.preLollipop; import static com.todoroo.andlib.utility.AndroidUtilities.preLollipop;
import static org.tasks.caldav.CaldavCalendarSettingsActivity.EXTRA_CALDAV_ACCOUNT; import static org.tasks.caldav.CaldavCalendarSettingsActivity.EXTRA_CALDAV_ACCOUNT;
@ -48,6 +49,7 @@ import org.tasks.filters.NavigationDrawerSeparator;
import org.tasks.filters.NavigationDrawerSubheader; import org.tasks.filters.NavigationDrawerSubheader;
import org.tasks.locale.Locale; import org.tasks.locale.Locale;
import org.tasks.preferences.BasicPreferences; import org.tasks.preferences.BasicPreferences;
import org.tasks.sync.SynchronizationPreferences;
import org.tasks.themes.Theme; import org.tasks.themes.Theme;
import org.tasks.themes.ThemeCache; import org.tasks.themes.ThemeCache;
import org.tasks.ui.NavigationDrawerFragment; import org.tasks.ui.NavigationDrawerFragment;
@ -161,6 +163,9 @@ public class FilterAdapter extends ArrayAdapter<FilterListItem> {
case SUBHEADER: case SUBHEADER:
convertView = inflater.inflate(R.layout.filter_adapter_subheader, parent, false); convertView = inflater.inflate(R.layout.filter_adapter_subheader, parent, false);
viewHolder.name = convertView.findViewById(R.id.subheader_text); viewHolder.name = convertView.findViewById(R.id.subheader_text);
viewHolder.icon = convertView.findViewById(R.id.subheader_icon);
viewHolder.icon.setOnClickListener(
v -> activity.startActivity(new Intent(activity, SynchronizationPreferences.class)));
break; break;
} }
viewHolder.view = convertView; viewHolder.view = convertView;
@ -224,20 +229,21 @@ public class FilterAdapter extends ArrayAdapter<FilterListItem> {
return getView(position, convertView, parent); return getView(position, convertView, parent);
} }
private void addSubMenu(final int titleResource, List<Filter> filters, boolean hideIfEmpty) { private void addSubMenu(
addSubMenu(activity.getResources().getString(titleResource), filters, hideIfEmpty); final int titleResource, boolean error, List<Filter> filters, boolean hideIfEmpty) {
addSubMenu(activity.getResources().getString(titleResource), error, filters, hideIfEmpty);
} }
/* ====================================================================== /* ======================================================================
* ============================================================= receiver * ============================================================= receiver
* ====================================================================== */ * ====================================================================== */
private void addSubMenu(String title, List<Filter> filters, boolean hideIfEmpty) { private void addSubMenu(String title, boolean error, List<Filter> filters, boolean hideIfEmpty) {
if (hideIfEmpty && filters.isEmpty()) { if (hideIfEmpty && filters.isEmpty()) {
return; return;
} }
add(new NavigationDrawerSubheader(title)); add(new NavigationDrawerSubheader(title, error));
for (FilterListItem filterListItem : filters) { for (FilterListItem filterListItem : filters) {
add(filterListItem); add(filterListItem);
@ -258,11 +264,13 @@ public class FilterAdapter extends ArrayAdapter<FilterListItem> {
add(item); add(item);
for (Pair<GoogleTaskAccount, List<Filter>> filters : filterProvider.getGoogleTaskFilters()) { for (Pair<GoogleTaskAccount, List<Filter>> filters : filterProvider.getGoogleTaskFilters()) {
addSubMenu(filters.first.getAccount(), filters.second, true); GoogleTaskAccount account = filters.first;
addSubMenu(account.getAccount(), !isNullOrEmpty(account.getError()), filters.second, true);
} }
for (Pair<CaldavAccount, List<Filter>> filters : filterProvider.getCaldavFilters()) { for (Pair<CaldavAccount, List<Filter>> filters : filterProvider.getCaldavFilters()) {
addSubMenu(filters.first.getName(), filters.second, true); CaldavAccount account = filters.first;
addSubMenu(account.getName(), !isNullOrEmpty(account.getError()), filters.second, true);
} }
notifyDataSetChanged(); notifyDataSetChanged();
@ -273,7 +281,7 @@ public class FilterAdapter extends ArrayAdapter<FilterListItem> {
add(filterProvider.getMyTasksFilter()); add(filterProvider.getMyTasksFilter());
addSubMenu(R.string.filters, filterProvider.getFilters(), false); addSubMenu(R.string.filters, false, filterProvider.getFilters(), false);
if (navigationDrawer) { if (navigationDrawer) {
add( add(
@ -284,7 +292,7 @@ public class FilterAdapter extends ArrayAdapter<FilterListItem> {
NavigationDrawerFragment.ACTIVITY_REQUEST_NEW_FILTER)); NavigationDrawerFragment.ACTIVITY_REQUEST_NEW_FILTER));
} }
addSubMenu(R.string.tags, filterProvider.getTags(), false); addSubMenu(R.string.tags, false, filterProvider.getTags(), false);
if (navigationDrawer) { if (navigationDrawer) {
add( add(
@ -297,7 +305,11 @@ public class FilterAdapter extends ArrayAdapter<FilterListItem> {
for (Pair<GoogleTaskAccount, List<Filter>> filters : filterProvider.getGoogleTaskFilters()) { for (Pair<GoogleTaskAccount, List<Filter>> filters : filterProvider.getGoogleTaskFilters()) {
GoogleTaskAccount account = filters.first; GoogleTaskAccount account = filters.first;
addSubMenu(account.getAccount(), filters.second, !navigationDrawer); addSubMenu(
account.getAccount(),
!isNullOrEmpty(account.getError()),
filters.second,
!navigationDrawer);
if (navigationDrawer) { if (navigationDrawer) {
add( add(
@ -312,7 +324,8 @@ public class FilterAdapter extends ArrayAdapter<FilterListItem> {
for (Pair<CaldavAccount, List<Filter>> filters : filterProvider.getCaldavFilters()) { for (Pair<CaldavAccount, List<Filter>> filters : filterProvider.getCaldavFilters()) {
CaldavAccount account = filters.first; CaldavAccount account = filters.first;
addSubMenu(account.getName(), filters.second, !navigationDrawer); addSubMenu(
account.getName(), !isNullOrEmpty(account.getError()), filters.second, !navigationDrawer);
if (navigationDrawer) { if (navigationDrawer) {
add( add(
@ -382,12 +395,13 @@ public class FilterAdapter extends ArrayAdapter<FilterListItem> {
} }
private void populateHeader(ViewHolder viewHolder) { private void populateHeader(ViewHolder viewHolder) {
FilterListItem filter = viewHolder.item; NavigationDrawerSubheader filter = (NavigationDrawerSubheader) viewHolder.item;
if (filter == null) { if (filter == null) {
return; return;
} }
viewHolder.name.setText(filter.listingTitle); viewHolder.name.setText(filter.listingTitle);
viewHolder.icon.setVisibility(filter.error ? View.VISIBLE : View.GONE);
} }
/* ====================================================================== /* ======================================================================

@ -65,12 +65,16 @@ public class GtasksLoginActivity extends InjectingAppCompatActivity {
new AuthResultHandler() { new AuthResultHandler() {
@Override @Override
public void authenticationSuccessful(String accountName) { public void authenticationSuccessful(String accountName) {
if (googleTaskListDao.getAccount(accountName) == null) { GoogleTaskAccount account = googleTaskListDao.getAccount(accountName);
GoogleTaskAccount googleTaskAccount = new GoogleTaskAccount(); if (account == null) {
googleTaskAccount.setAccount(accountName); account = new GoogleTaskAccount();
googleTaskListDao.insert(googleTaskAccount); account.setAccount(accountName);
setResult(RESULT_OK); googleTaskListDao.insert(account);
} else {
account.setError("");
googleTaskListDao.update(account);
} }
setResult(RESULT_OK);
finish(); finish();
DialogUtilities.dismissDialog(GtasksLoginActivity.this, pd); DialogUtilities.dismissDialog(GtasksLoginActivity.this, pd);
} }

@ -306,6 +306,7 @@ public class CaldavAccountSettingsActivity extends ThemedInjectingAppCompatActiv
caldavAccount.setName(getNewName()); caldavAccount.setName(getNewName());
caldavAccount.setUrl(principal); caldavAccount.setUrl(principal);
caldavAccount.setUsername(getNewUsername()); caldavAccount.setUsername(getNewUsername());
caldavAccount.setError("");
if (passwordChanged()) { if (passwordChanged()) {
caldavAccount.setPassword(encryption.encrypt(getNewPassword())); caldavAccount.setPassword(encryption.encrypt(getNewPassword()));
} }

@ -7,7 +7,6 @@ import static at.bitfire.dav4android.XmlUtils.NS_WEBDAV;
import static java.util.Arrays.asList; import static java.util.Arrays.asList;
import at.bitfire.dav4android.BasicDigestAuthHandler; import at.bitfire.dav4android.BasicDigestAuthHandler;
import at.bitfire.dav4android.DavCalendar;
import at.bitfire.dav4android.DavResource; import at.bitfire.dav4android.DavResource;
import at.bitfire.dav4android.PropertyCollection; import at.bitfire.dav4android.PropertyCollection;
import at.bitfire.dav4android.XmlUtils; import at.bitfire.dav4android.XmlUtils;
@ -136,14 +135,9 @@ class CaldavClient {
.observeOn(AndroidSchedulers.mainThread()); .observeOn(AndroidSchedulers.mainThread());
} }
public List<DavResource> getCalendars() { public List<DavResource> getCalendars() throws IOException, HttpException, DavException {
try { davResource.propfind(
davResource.propfind( 1, ResourceType.NAME, DisplayName.NAME, SupportedCalendarComponentSet.NAME, GetCTag.NAME);
1, ResourceType.NAME, DisplayName.NAME, SupportedCalendarComponentSet.NAME, GetCTag.NAME);
} catch (IOException | HttpException | DavException e) {
Timber.e(e);
return null;
}
List<DavResource> urls = new ArrayList<>(); List<DavResource> urls = new ArrayList<>();
for (DavResource member : davResource.getMembers()) { for (DavResource member : davResource.getMembers()) {
PropertyCollection properties = member.getProperties(); PropertyCollection properties = member.getProperties();

@ -48,6 +48,7 @@ import okhttp3.RequestBody;
import okhttp3.ResponseBody; import okhttp3.ResponseBody;
import org.tasks.BuildConfig; import org.tasks.BuildConfig;
import org.tasks.LocalBroadcastManager; import org.tasks.LocalBroadcastManager;
import org.tasks.R;
import org.tasks.data.CaldavAccount; import org.tasks.data.CaldavAccount;
import org.tasks.data.CaldavCalendar; import org.tasks.data.CaldavCalendar;
import org.tasks.data.CaldavDao; import org.tasks.data.CaldavDao;
@ -94,12 +95,21 @@ public class CaldavSynchronizer {
Thread.currentThread().setContextClassLoader(context.getClassLoader()); Thread.currentThread().setContextClassLoader(context.getClassLoader());
for (CaldavAccount account : caldavDao.getAccounts()) { for (CaldavAccount account : caldavDao.getAccounts()) {
if (isNullOrEmpty(account.getPassword())) { if (isNullOrEmpty(account.getPassword())) {
account.setError(context.getString(R.string.password_required));
caldavDao.update(account);
localBroadcastManager.broadcastRefreshList();
Timber.e("Missing password for %s", account); Timber.e("Missing password for %s", account);
continue; continue;
} }
CaldavClient caldavClient = new CaldavClient(account, encryption); CaldavClient caldavClient = new CaldavClient(account, encryption);
List<DavResource> resources = caldavClient.getCalendars(); List<DavResource> resources;
if (resources == null) { try {
resources = caldavClient.getCalendars();
} catch (IOException | DavException | HttpException e) {
account.setError(e.getMessage());
caldavDao.update(account);
localBroadcastManager.broadcastRefreshList();
Timber.e(e);
continue; continue;
} }
Set<String> urls = newHashSet(transform(resources, c -> c.getLocation().toString())); Set<String> urls = newHashSet(transform(resources, c -> c.getLocation().toString()));
@ -109,7 +119,6 @@ public class CaldavSynchronizer {
taskDeleter.markDeleted(caldavDao.getTasksByCalendar(deleted.getUuid())); taskDeleter.markDeleted(caldavDao.getTasksByCalendar(deleted.getUuid()));
caldavDao.deleteTasksForCalendar(deleted.getUuid()); caldavDao.deleteTasksForCalendar(deleted.getUuid());
caldavDao.delete(deleted); caldavDao.delete(deleted);
localBroadcastManager.broadcastRefreshList();
} }
for (DavResource resource : resources) { for (DavResource resource : resources) {
String url = resource.getLocation().toString(); String url = resource.getLocation().toString();
@ -122,10 +131,12 @@ public class CaldavSynchronizer {
calendar.setUrl(url); calendar.setUrl(url);
calendar.setUuid(UUIDHelper.newUUID()); calendar.setUuid(UUIDHelper.newUUID());
calendar.setId(caldavDao.insert(calendar)); calendar.setId(caldavDao.insert(calendar));
localBroadcastManager.broadcastRefreshList();
} }
sync(calendar, resource); sync(calendar, resource);
} }
account.setError("");
caldavDao.update(account);
localBroadcastManager.broadcastRefreshList();
} }
} }

@ -8,7 +8,6 @@ import android.arch.persistence.room.Ignore;
import android.arch.persistence.room.PrimaryKey; import android.arch.persistence.room.PrimaryKey;
import android.os.Parcel; import android.os.Parcel;
import android.os.Parcelable; import android.os.Parcelable;
import java.util.Arrays;
@Entity(tableName = "caldav_account") @Entity(tableName = "caldav_account")
public class CaldavAccount implements Parcelable { public class CaldavAccount implements Parcelable {
@ -46,6 +45,9 @@ public class CaldavAccount implements Parcelable {
@ColumnInfo(name = "password") @ColumnInfo(name = "password")
private transient String password = ""; private transient String password = "";
@ColumnInfo(name = "error")
private transient String error = "";
public CaldavAccount() {} public CaldavAccount() {}
@Ignore @Ignore
@ -56,6 +58,7 @@ public class CaldavAccount implements Parcelable {
url = source.readString(); url = source.readString();
username = source.readString(); username = source.readString();
password = source.readString(); password = source.readString();
error = source.readString();
} }
public long getId() { public long getId() {
@ -106,16 +109,38 @@ public class CaldavAccount implements Parcelable {
this.password = password; this.password = password;
} }
public String getError() {
return error;
}
public void setError(String error) {
this.error = error;
}
@Override @Override
public String toString() { public String toString() {
return "CaldavAccount{" + return "CaldavAccount{"
"id=" + id + + "id="
", uuid='" + uuid + '\'' + + id
", name='" + name + '\'' + + ", uuid='"
", url='" + url + '\'' + + uuid
", username='" + username + '\'' + + '\''
", password='" + password + '\'' + + ", name='"
'}'; + name
+ '\''
+ ", url='"
+ url
+ '\''
+ ", username='"
+ username
+ '\''
+ ", password='"
+ password
+ '\''
+ ", error='"
+ error
+ '\''
+ '}';
} }
@Override @Override
@ -144,7 +169,10 @@ public class CaldavAccount implements Parcelable {
if (username != null ? !username.equals(that.username) : that.username != null) { if (username != null ? !username.equals(that.username) : that.username != null) {
return false; return false;
} }
return password != null ? password.equals(that.password) : that.password == null; if (password != null ? !password.equals(that.password) : that.password != null) {
return false;
}
return error != null ? error.equals(that.error) : that.error == null;
} }
@Override @Override
@ -155,6 +183,7 @@ public class CaldavAccount implements Parcelable {
result = 31 * result + (url != null ? url.hashCode() : 0); result = 31 * result + (url != null ? url.hashCode() : 0);
result = 31 * result + (username != null ? username.hashCode() : 0); result = 31 * result + (username != null ? username.hashCode() : 0);
result = 31 * result + (password != null ? password.hashCode() : 0); result = 31 * result + (password != null ? password.hashCode() : 0);
result = 31 * result + (error != null ? error.hashCode() : 0);
return result; return result;
} }
@ -171,5 +200,6 @@ public class CaldavAccount implements Parcelable {
dest.writeString(url); dest.writeString(url);
dest.writeString(username); dest.writeString(username);
dest.writeString(password); dest.writeString(password);
dest.writeString(error);
} }
} }

@ -16,12 +16,16 @@ public class GoogleTaskAccount implements Parcelable {
@ColumnInfo(name = "account") @ColumnInfo(name = "account")
private String account; private String account;
@ColumnInfo(name = "error")
private transient String error = "";
public GoogleTaskAccount() {} public GoogleTaskAccount() {}
@Ignore @Ignore
public GoogleTaskAccount(Parcel source) { public GoogleTaskAccount(Parcel source) {
id = source.readLong(); id = source.readLong();
account = source.readString(); account = source.readString();
error = source.readString();
} }
@Ignore @Ignore
@ -45,6 +49,14 @@ public class GoogleTaskAccount implements Parcelable {
this.account = account; this.account = account;
} }
public String getError() {
return error;
}
public void setError(String error) {
this.error = error;
}
@Override @Override
public boolean equals(Object o) { public boolean equals(Object o) {
if (this == o) { if (this == o) {
@ -59,19 +71,32 @@ public class GoogleTaskAccount implements Parcelable {
if (id != that.id) { if (id != that.id) {
return false; return false;
} }
return account != null ? account.equals(that.account) : that.account == null; if (account != null ? !account.equals(that.account) : that.account != null) {
return false;
}
return error != null ? error.equals(that.error) : that.error == null;
} }
@Override @Override
public int hashCode() { public int hashCode() {
int result = (int) (id ^ (id >>> 32)); int result = (int) (id ^ (id >>> 32));
result = 31 * result + (account != null ? account.hashCode() : 0); result = 31 * result + (account != null ? account.hashCode() : 0);
result = 31 * result + (error != null ? error.hashCode() : 0);
return result; return result;
} }
@Override @Override
public String toString() { public String toString() {
return "GoogleTaskAccount{" + "id=" + id + ", account='" + account + '\'' + '}'; return "GoogleTaskAccount{"
+ "id="
+ id
+ ", account='"
+ account
+ '\''
+ ", error='"
+ error
+ '\''
+ '}';
} }
public static final Creator<GoogleTaskAccount> CREATOR = public static final Creator<GoogleTaskAccount> CREATOR =
@ -96,5 +121,6 @@ public class GoogleTaskAccount implements Parcelable {
public void writeToParcel(Parcel dest, int flags) { public void writeToParcel(Parcel dest, int flags) {
dest.writeLong(id); dest.writeLong(id);
dest.writeString(account); dest.writeString(account);
dest.writeString(error);
} }
} }

@ -51,4 +51,7 @@ public interface GoogleTaskDao {
@Query("SELECT DISTINCT list_id FROM google_tasks WHERE deleted = 0 AND task IN (:tasks)") @Query("SELECT DISTINCT list_id FROM google_tasks WHERE deleted = 0 AND task IN (:tasks)")
List<String> getLists(List<Long> tasks); List<String> getLists(List<Long> tasks);
@Query("SELECT task FROM google_tasks WHERE deleted = 0 AND list_id = :listId")
List<Long> getActiveTasks(String listId);
} }

@ -1,6 +1,7 @@
package org.tasks.data; package org.tasks.data;
import android.arch.persistence.room.Dao; import android.arch.persistence.room.Dao;
import android.arch.persistence.room.Delete;
import android.arch.persistence.room.Insert; import android.arch.persistence.room.Insert;
import android.arch.persistence.room.OnConflictStrategy; import android.arch.persistence.room.OnConflictStrategy;
import android.arch.persistence.room.Query; import android.arch.persistence.room.Query;
@ -34,6 +35,9 @@ public interface GoogleTaskListDao {
@Query("SELECT * FROM google_task_lists WHERE deleted = 0") @Query("SELECT * FROM google_task_lists WHERE deleted = 0")
List<GoogleTaskList> getAllActiveLists(); List<GoogleTaskList> getAllActiveLists();
@Query("DELETE FROM google_task_lists WHERE _id = :id")
void deleteById(long id);
@Insert(onConflict = OnConflictStrategy.REPLACE) @Insert(onConflict = OnConflictStrategy.REPLACE)
long insertOrReplace(GoogleTaskList googleTaskList); long insertOrReplace(GoogleTaskList googleTaskList);
@ -46,6 +50,12 @@ public interface GoogleTaskListDao {
@Update @Update
void update(GoogleTaskList googleTaskList); void update(GoogleTaskList googleTaskList);
@Query("DELETE FROM google_task_lists WHERE _id = :id") @Update
void deleteById(long id); void update(GoogleTaskAccount account);
@Delete
void delete(GoogleTaskList list);
@Delete
void delete(GoogleTaskAccount account);
} }

@ -197,9 +197,11 @@ public class Migrations {
@Override @Override
public void migrate(@NonNull SupportSQLiteDatabase database) { public void migrate(@NonNull SupportSQLiteDatabase database) {
database.execSQL( database.execSQL(
"CREATE TABLE IF NOT EXISTS `google_task_accounts` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `account` TEXT)"); "CREATE TABLE IF NOT EXISTS `google_task_accounts` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `account` TEXT, `error` TEXT)");
database.execSQL( database.execSQL(
"ALTER TABLE `google_task_lists` ADD COLUMN `account` TEXT"); "ALTER TABLE `google_task_lists` ADD COLUMN `account` TEXT");
database.execSQL(
"ALTER TABLE `caldav_account` ADD COLUMN `error` TEXT");
} }
}; };

@ -24,12 +24,27 @@ public class NavigationDrawerSubheader extends FilterListItem {
} }
}; };
public boolean error;
private NavigationDrawerSubheader() {} private NavigationDrawerSubheader() {}
public NavigationDrawerSubheader(String listingTitle) { public NavigationDrawerSubheader(String listingTitle, boolean error) {
this.error = error;
this.listingTitle = listingTitle; this.listingTitle = listingTitle;
} }
@Override
protected void readFromParcel(Parcel source) {
super.readFromParcel(source);
error = source.readInt() == 1;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeInt(error ? 1 : 0);
}
@Override @Override
public Type getItemType() { public Type getItemType() {
return Type.SUBHEADER; return Type.SUBHEADER;

@ -31,6 +31,7 @@ import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import javax.inject.Inject; import javax.inject.Inject;
import org.tasks.LocalBroadcastManager;
import org.tasks.R; import org.tasks.R;
import org.tasks.analytics.Tracker; import org.tasks.analytics.Tracker;
import org.tasks.data.GoogleTask; import org.tasks.data.GoogleTask;
@ -41,6 +42,7 @@ import org.tasks.data.GoogleTaskListDao;
import org.tasks.injection.ForApplication; import org.tasks.injection.ForApplication;
import org.tasks.notifications.NotificationManager; import org.tasks.notifications.NotificationManager;
import org.tasks.preferences.DefaultFilterProvider; import org.tasks.preferences.DefaultFilterProvider;
import org.tasks.preferences.PermissionChecker;
import org.tasks.preferences.Preferences; import org.tasks.preferences.Preferences;
import org.tasks.time.DateTime; import org.tasks.time.DateTime;
import timber.log.Timber; import timber.log.Timber;
@ -62,6 +64,9 @@ public class GoogleTaskSynchronizer {
private final TaskCreator taskCreator; private final TaskCreator taskCreator;
private final DefaultFilterProvider defaultFilterProvider; private final DefaultFilterProvider defaultFilterProvider;
private final PlayServices playServices; private final PlayServices playServices;
private final PermissionChecker permissionChecker;
private final GoogleAccountManager googleAccountManager;
private final LocalBroadcastManager localBroadcastManager;
@Inject @Inject
public GoogleTaskSynchronizer( public GoogleTaskSynchronizer(
@ -77,7 +82,10 @@ public class GoogleTaskSynchronizer {
GoogleTaskDao googleTaskDao, GoogleTaskDao googleTaskDao,
TaskCreator taskCreator, TaskCreator taskCreator,
DefaultFilterProvider defaultFilterProvider, DefaultFilterProvider defaultFilterProvider,
PlayServices playServices) { PlayServices playServices,
PermissionChecker permissionChecker,
GoogleAccountManager googleAccountManager,
LocalBroadcastManager localBroadcastManager) {
this.context = context; this.context = context;
this.googleTaskListDao = googleTaskListDao; this.googleTaskListDao = googleTaskListDao;
this.gtasksSyncService = gtasksSyncService; this.gtasksSyncService = gtasksSyncService;
@ -91,6 +99,9 @@ public class GoogleTaskSynchronizer {
this.taskCreator = taskCreator; this.taskCreator = taskCreator;
this.defaultFilterProvider = defaultFilterProvider; this.defaultFilterProvider = defaultFilterProvider;
this.playServices = playServices; this.playServices = playServices;
this.permissionChecker = permissionChecker;
this.googleAccountManager = googleAccountManager;
this.localBroadcastManager = localBroadcastManager;
} }
public static void mergeDates(long remoteDueDate, Task local) { public static void mergeDates(long remoteDueDate, Task local) {
@ -113,14 +124,19 @@ public class GoogleTaskSynchronizer {
Timber.d("%s: start sync", account); Timber.d("%s: start sync", account);
try { try {
synchronize(account); synchronize(account);
account.setError("");
} catch (UserRecoverableAuthIOException e) { } catch (UserRecoverableAuthIOException e) {
Timber.e(e); Timber.e(e);
sendNotification(context, e.getIntent()); sendNotification(context, e.getIntent());
} catch (IOException e) { } catch (IOException e) {
account.setError(e.getMessage());
Timber.e(e); Timber.e(e);
} catch (Exception e) { } catch (Exception e) {
account.setError(e.getMessage());
tracker.reportException(e); tracker.reportException(e);
} finally { } finally {
googleTaskListDao.update(account);
localBroadcastManager.broadcastRefreshList();
Timber.d("%s: end sync", account); Timber.d("%s: end sync", account);
} }
} }
@ -145,6 +161,13 @@ public class GoogleTaskSynchronizer {
} }
private void synchronize(GoogleTaskAccount account) throws IOException { private void synchronize(GoogleTaskAccount account) throws IOException {
if (!permissionChecker.canAccessAccounts() || googleAccountManager.getAccount(account.getAccount()) == null) {
account.setError(context.getString(R.string.cannot_access_account));
googleTaskListDao.update(account);
localBroadcastManager.broadcastRefreshList();
return;
}
GtasksInvoker gtasksInvoker = new GtasksInvoker(context, playServices, account.getAccount()); GtasksInvoker gtasksInvoker = new GtasksInvoker(context, playServices, account.getAccount());
pushLocalChanges(gtasksInvoker); pushLocalChanges(gtasksInvoker);

@ -5,6 +5,7 @@
*/ */
package org.tasks.sync; package org.tasks.sync;
import static java.util.Arrays.asList;
import static org.tasks.PermissionUtil.verifyPermissions; import static org.tasks.PermissionUtil.verifyPermissions;
import android.content.Intent; import android.content.Intent;
@ -12,7 +13,10 @@ import android.os.Bundle;
import android.preference.Preference; import android.preference.Preference;
import android.preference.PreferenceCategory; import android.preference.PreferenceCategory;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.v7.app.AlertDialog;
import com.todoroo.astrid.gtasks.auth.GtasksLoginActivity; import com.todoroo.astrid.gtasks.auth.GtasksLoginActivity;
import com.todoroo.astrid.service.TaskDeleter;
import java.util.List;
import javax.inject.Inject; import javax.inject.Inject;
import org.tasks.R; import org.tasks.R;
import org.tasks.analytics.Tracker; import org.tasks.analytics.Tracker;
@ -24,6 +28,7 @@ import org.tasks.data.CaldavAccount;
import org.tasks.data.CaldavDao; import org.tasks.data.CaldavDao;
import org.tasks.data.GoogleTaskAccount; import org.tasks.data.GoogleTaskAccount;
import org.tasks.data.GoogleTaskDao; import org.tasks.data.GoogleTaskDao;
import org.tasks.data.GoogleTaskList;
import org.tasks.data.GoogleTaskListDao; import org.tasks.data.GoogleTaskListDao;
import org.tasks.dialogs.DialogBuilder; import org.tasks.dialogs.DialogBuilder;
import org.tasks.gtasks.GoogleAccountManager; import org.tasks.gtasks.GoogleAccountManager;
@ -36,7 +41,6 @@ import org.tasks.preferences.ActivityPermissionRequestor;
import org.tasks.preferences.PermissionChecker; import org.tasks.preferences.PermissionChecker;
import org.tasks.preferences.PermissionRequestor; import org.tasks.preferences.PermissionRequestor;
import org.tasks.preferences.Preferences; import org.tasks.preferences.Preferences;
import timber.log.Timber;
public class SynchronizationPreferences extends InjectingPreferenceActivity { public class SynchronizationPreferences extends InjectingPreferenceActivity {
@ -61,6 +65,7 @@ public class SynchronizationPreferences extends InjectingPreferenceActivity {
@Inject JobManager jobManager; @Inject JobManager jobManager;
@Inject CaldavDao caldavDao; @Inject CaldavDao caldavDao;
@Inject Inventory inventory; @Inject Inventory inventory;
@Inject TaskDeleter taskDeleter;
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
@ -73,7 +78,7 @@ public class SynchronizationPreferences extends InjectingPreferenceActivity {
for (CaldavAccount caldavAccount : caldavDao.getAccounts()) { for (CaldavAccount caldavAccount : caldavDao.getAccounts()) {
Preference accountPreferences = new Preference(this); Preference accountPreferences = new Preference(this);
accountPreferences.setTitle(caldavAccount.getName()); accountPreferences.setTitle(caldavAccount.getName());
accountPreferences.setSummary(caldavAccount.getUrl()); accountPreferences.setSummary(caldavAccount.getError());
accountPreferences.setOnPreferenceClickListener( accountPreferences.setOnPreferenceClickListener(
preference -> { preference -> {
Intent intent = new Intent(this, CaldavAccountSettingsActivity.class); Intent intent = new Intent(this, CaldavAccountSettingsActivity.class);
@ -92,19 +97,24 @@ public class SynchronizationPreferences extends InjectingPreferenceActivity {
(PreferenceCategory) findPreference(getString(R.string.gtasks_GPr_header)); (PreferenceCategory) findPreference(getString(R.string.gtasks_GPr_header));
for (GoogleTaskAccount googleTaskAccount : googleTaskListDao.getAccounts()) { for (GoogleTaskAccount googleTaskAccount : googleTaskListDao.getAccounts()) {
String account = googleTaskAccount.getAccount(); String account = googleTaskAccount.getAccount();
if (googleAccountManager.getAccount(account) == null) {
Timber.e("Can't access %s", account);
continue;
}
Preference accountPreferences = new Preference(this); Preference accountPreferences = new Preference(this);
accountPreferences.setTitle(account); accountPreferences.setTitle(account);
accountPreferences.setSummary(googleTaskAccount.getError());
accountPreferences.setOnPreferenceClickListener( accountPreferences.setOnPreferenceClickListener(
preference -> { preference -> {
if (!playServices.refreshAndCheck()) { dialogBuilder
playServices.resolve(SynchronizationPreferences.this); .newDialog()
} else if (permissionRequestor.requestAccountPermissions()) { .setTitle(account)
requestLogin(); .setItems(
} asList(getString(R.string.reinitialize_account), getString(R.string.logout)),
(dialog, which) -> {
if (which == 0) {
addGoogleTaskAccount();
} else {
logoutConfirmation(googleTaskAccount);
}
})
.showThemedListView();
return false; return false;
}); });
googleTaskPreferences.addPreference(accountPreferences); googleTaskPreferences.addPreference(accountPreferences);
@ -128,6 +138,28 @@ public class SynchronizationPreferences extends InjectingPreferenceActivity {
}); });
} }
private void logoutConfirmation(GoogleTaskAccount account) {
String name = account.getAccount();
AlertDialog alertDialog =
dialogBuilder
.newMessageDialog(R.string.logout_warning, name)
.setPositiveButton(
R.string.logout,
(dialog, which) -> {
for (GoogleTaskList list : googleTaskListDao.getActiveLists(name)) {
taskDeleter.markDeleted(googleTaskDao.getActiveTasks(list.getRemoteId()));
googleTaskListDao.delete(list);
}
googleTaskListDao.delete(account);
restart();
})
.setNegativeButton(android.R.string.cancel, null)
.create();
alertDialog.setCanceledOnTouchOutside(false);
alertDialog.setCancelable(false);
alertDialog.show();
}
private void requestLogin() { private void requestLogin() {
startActivityForResult( startActivityForResult(
new Intent(SynchronizationPreferences.this, GtasksLoginActivity.class), REQUEST_LOGIN); new Intent(SynchronizationPreferences.this, GtasksLoginActivity.class), REQUEST_LOGIN);
@ -149,7 +181,7 @@ public class SynchronizationPreferences extends InjectingPreferenceActivity {
}); });
addGoogleTasks.setOnPreferenceClickListener( addGoogleTasks.setOnPreferenceClickListener(
preference -> { preference -> {
requestLogin(); addGoogleTaskAccount();
return false; return false;
}); });
} else { } else {
@ -164,7 +196,7 @@ public class SynchronizationPreferences extends InjectingPreferenceActivity {
addGoogleTasks.setSummary(null); addGoogleTasks.setSummary(null);
addGoogleTasks.setOnPreferenceClickListener( addGoogleTasks.setOnPreferenceClickListener(
preference -> { preference -> {
requestLogin(); addGoogleTaskAccount();
return false; return false;
}); });
} else { } else {
@ -183,6 +215,14 @@ public class SynchronizationPreferences extends InjectingPreferenceActivity {
} }
} }
private void addGoogleTaskAccount() {
if (!playServices.refreshAndCheck()) {
playServices.resolve(this);
} else if (permissionRequestor.requestAccountPermissions()) {
requestLogin();
}
}
private void addCaldavAccount() { private void addCaldavAccount() {
startActivityForResult( startActivityForResult(
new Intent(this, CaldavAccountSettingsActivity.class), REQUEST_CALDAV_SETTINGS); new Intent(this, CaldavAccountSettingsActivity.class), REQUEST_CALDAV_SETTINGS);

@ -0,0 +1,5 @@
<vector android:autoMirrored="true" android:height="24dp"
android:viewportHeight="24.0" android:viewportWidth="24.0"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M3,12c0,2.21 0.91,4.2 2.36,5.64L3,20h6v-6l-2.24,2.24C5.68,15.15 5,13.66 5,12c0,-2.61 1.67,-4.83 4,-5.65L9,4.26C5.55,5.15 3,8.27 3,12zM11,17h2v-2h-2v2zM21,4h-6v6l2.24,-2.24C18.32,8.85 19,10.34 19,12c0,2.61 -1.67,4.83 -4,5.65v2.09c3.45,-0.89 6,-4.01 6,-7.74 0,-2.21 -0.91,-4.2 -2.36,-5.64L21,4zM11,13h2L13,7h-2v6z"/>
</vector>

@ -11,17 +11,40 @@
style="@style/horizontal_divider" style="@style/horizontal_divider"
android:layout_gravity="top"/> android:layout_gravity="top"/>
<ImageView
android:id="@+id/subheader_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_below="@id/divider"
android:paddingTop="12dp"
android:paddingStart="0dp"
android:paddingEnd="@dimen/keyline_first"
android:paddingLeft="0dp"
android:paddingRight="@dimen/keyline_first"
android:alpha="?attr/alpha_secondary"
android:focusable="true"
android:clickable="true"
android:scaleType="center"
android:src="@drawable/ic_sync_problem_black_24dp"
android:tint="?attr/icon_tint"
android:visibility="visible"/>
<CheckedTextView <CheckedTextView
android:id="@+id/subheader_text" android:id="@+id/subheader_text"
android:layout_width="wrap_content" android:layout_width="match_parent"
android:layout_height="fill_parent" android:layout_height="wrap_content"
android:layout_alignParentLeft="true" android:layout_alignParentLeft="true"
android:layout_alignParentStart="true" android:layout_alignParentStart="true"
android:layout_below="@id/divider" android:layout_below="@id/divider"
android:layout_toLeftOf="@id/subheader_icon"
android:layout_toStartOf="@id/subheader_icon"
android:paddingTop="12dp"
android:paddingStart="@dimen/keyline_first" android:paddingStart="@dimen/keyline_first"
android:paddingEnd="0dp" android:paddingEnd="@dimen/keyline_first"
android:paddingLeft="@dimen/keyline_first" android:paddingLeft="@dimen/keyline_first"
android:paddingRight="0dp" android:paddingRight="@dimen/keyline_first"
android:alpha="0.54" android:alpha="0.54"
android:clickable="false" android:clickable="false"
android:ellipsize="end" android:ellipsize="end"

@ -888,5 +888,8 @@ File %1$s contained %2$s.\n\n
<string name="pro_dashclock_extension">Dashclock extension</string> <string name="pro_dashclock_extension">Dashclock extension</string>
<string name="caldav_create_new_collection">Create new collection</string> <string name="caldav_create_new_collection">Create new collection</string>
<string name="requires_pro_subscription">Requires pro subscription</string> <string name="requires_pro_subscription">Requires pro subscription</string>
<string name="logout">Log out</string>
<string name="logout_warning">Log out of %s? All data for this account will be removed from your device</string>
<string name="cannot_access_account">Cannot access account</string>
<string name="reinitialize_account">Reinitialize</string>
</resources> </resources>

Loading…
Cancel
Save