Applying intellij-java-google-style

pull/645/merge
Alex Baker 6 years ago
parent d1bdd60a9c
commit da10291b04

@ -1,21 +1,22 @@
package org.tasks; package org.tasks;
import javax.inject.Inject;
import org.tasks.caldav.CaldavAccountManager; import org.tasks.caldav.CaldavAccountManager;
import org.tasks.preferences.Preferences; import org.tasks.preferences.Preferences;
import javax.inject.Inject;
public class FlavorSetup { public class FlavorSetup {
private final CaldavAccountManager caldavAccountManager;
private final Preferences preferences;
@Inject private final CaldavAccountManager caldavAccountManager;
public FlavorSetup(CaldavAccountManager caldavAccountManager, Preferences preferences) { private final Preferences preferences;
this.caldavAccountManager = caldavAccountManager;
this.preferences = preferences; @Inject
} public FlavorSetup(CaldavAccountManager caldavAccountManager, Preferences preferences) {
this.caldavAccountManager = caldavAccountManager;
this.preferences = preferences;
}
public void setup() { public void setup() {
caldavAccountManager.setBackgroundSynchronization(preferences.getBoolean(R.string.p_background_sync, true)); caldavAccountManager
} .setBackgroundSynchronization(preferences.getBoolean(R.string.p_background_sync, true));
}
} }

@ -1,97 +1,94 @@
package org.tasks.analytics; package org.tasks.analytics;
import android.content.Context; import android.content.Context;
import com.google.android.gms.analytics.ExceptionParser; import com.google.android.gms.analytics.ExceptionParser;
import com.google.android.gms.analytics.ExceptionReporter; import com.google.android.gms.analytics.ExceptionReporter;
import com.google.android.gms.analytics.GoogleAnalytics; import com.google.android.gms.analytics.GoogleAnalytics;
import com.google.android.gms.analytics.HitBuilders; import com.google.android.gms.analytics.HitBuilders;
import com.google.android.gms.analytics.StandardExceptionParser; import com.google.android.gms.analytics.StandardExceptionParser;
import com.google.common.base.Strings; import com.google.common.base.Strings;
import javax.inject.Inject;
import org.tasks.BuildConfig; import org.tasks.BuildConfig;
import org.tasks.R; import org.tasks.R;
import org.tasks.injection.ApplicationScope; import org.tasks.injection.ApplicationScope;
import org.tasks.injection.ForApplication; import org.tasks.injection.ForApplication;
import javax.inject.Inject;
import timber.log.Timber; import timber.log.Timber;
@ApplicationScope @ApplicationScope
public class Tracker { public class Tracker {
private final GoogleAnalytics analytics; private final GoogleAnalytics analytics;
private final com.google.android.gms.analytics.Tracker tracker; private final com.google.android.gms.analytics.Tracker tracker;
private final ExceptionParser exceptionParser; private final ExceptionParser exceptionParser;
private final Context context; private final Context context;
@Inject @Inject
public Tracker(@ForApplication Context context) { public Tracker(@ForApplication Context context) {
this.context = context; this.context = context;
analytics = GoogleAnalytics.getInstance(context); analytics = GoogleAnalytics.getInstance(context);
tracker = analytics.newTracker(R.xml.google_analytics); tracker = analytics.newTracker(R.xml.google_analytics);
tracker.setAppVersion(Integer.toString(BuildConfig.VERSION_CODE)); tracker.setAppVersion(Integer.toString(BuildConfig.VERSION_CODE));
final StandardExceptionParser standardExceptionParser = new StandardExceptionParser(context, null); final StandardExceptionParser standardExceptionParser = new StandardExceptionParser(context,
exceptionParser = (thread, throwable) -> { null);
StringBuilder stack = new StringBuilder() exceptionParser = (thread, throwable) -> {
.append(standardExceptionParser.getDescription(thread, throwable)) StringBuilder stack = new StringBuilder()
.append("\n") .append(standardExceptionParser.getDescription(thread, throwable))
.append(throwable.getClass().getName()) .append("\n")
.append("\n"); .append(throwable.getClass().getName())
for (StackTraceElement element : throwable.getStackTrace()) { .append("\n");
stack.append(element.toString()) for (StackTraceElement element : throwable.getStackTrace()) {
.append("\n"); stack.append(element.toString())
} .append("\n");
return stack.toString(); }
}; return stack.toString();
ExceptionReporter reporter = new ExceptionReporter( };
tracker, ExceptionReporter reporter = new ExceptionReporter(
Thread.getDefaultUncaughtExceptionHandler(), tracker,
context); Thread.getDefaultUncaughtExceptionHandler(),
reporter.setExceptionParser(exceptionParser); context);
Thread.setDefaultUncaughtExceptionHandler(reporter); reporter.setExceptionParser(exceptionParser);
} Thread.setDefaultUncaughtExceptionHandler(reporter);
}
public void setTrackingEnabled(boolean enabled) {
analytics.setAppOptOut(!enabled); public void setTrackingEnabled(boolean enabled) {
} analytics.setAppOptOut(!enabled);
}
public void reportException(Throwable t) {
reportException(Thread.currentThread(), t); public void reportException(Throwable t) {
} reportException(Thread.currentThread(), t);
}
public void reportException(Thread thread, Throwable t) {
Timber.e(t, t.getMessage()); public void reportException(Thread thread, Throwable t) {
tracker.send(new HitBuilders.ExceptionBuilder() Timber.e(t, t.getMessage());
.setDescription(exceptionParser.getDescription(thread.getName(), t)) tracker.send(new HitBuilders.ExceptionBuilder()
.setFatal(false) .setDescription(exceptionParser.getDescription(thread.getName(), t))
.build()); .setFatal(false)
} .build());
}
public void reportEvent(Tracking.Events event) {
reportEvent(event, null); public void reportEvent(Tracking.Events event) {
} reportEvent(event, null);
}
public void reportEvent(Tracking.Events event, String label) {
reportEvent(event, event.action, label); public void reportEvent(Tracking.Events event, String label) {
} reportEvent(event, event.action, label);
}
public void reportEvent(Tracking.Events event, int action, String label) {
reportEvent(event, context.getString(action), label); public void reportEvent(Tracking.Events event, int action, String label) {
} reportEvent(event, context.getString(action), label);
}
public void reportEvent(Tracking.Events event, String action, String label) {
reportEvent(event.category, action, label); public void reportEvent(Tracking.Events event, String action, String label) {
} reportEvent(event.category, action, label);
}
private void reportEvent(int category, String action, String label) {
HitBuilders.EventBuilder eventBuilder = new HitBuilders.EventBuilder() private void reportEvent(int category, String action, String label) {
.setCategory(context.getString(category)) HitBuilders.EventBuilder eventBuilder = new HitBuilders.EventBuilder()
.setAction(action); .setCategory(context.getString(category))
if (!Strings.isNullOrEmpty(label)) { .setAction(action);
eventBuilder.setLabel(label); if (!Strings.isNullOrEmpty(label)) {
} eventBuilder.setLabel(label);
tracker.send(eventBuilder.build());
} }
tracker.send(eventBuilder.build());
}
} }

@ -2,31 +2,31 @@ package org.tasks.billing;
import android.app.Activity; import android.app.Activity;
import android.content.Intent; import android.content.Intent;
import javax.inject.Inject; import javax.inject.Inject;
public class PurchaseHelper { public class PurchaseHelper {
@Inject @Inject
public PurchaseHelper() { public PurchaseHelper() {
} }
public boolean purchase(final Activity activity, public boolean purchase(final Activity activity,
final String sku, final String pref, final String sku, final String pref,
final int requestCode, final PurchaseHelperCallback callback) { final int requestCode, final PurchaseHelperCallback callback) {
callback.purchaseCompleted(false, sku); callback.purchaseCompleted(false, sku);
return false; return false;
} }
public void handleActivityResult(PurchaseHelperCallback callback, int requestCode, int resultCode, Intent data) { public void handleActivityResult(PurchaseHelperCallback callback, int requestCode, int resultCode,
Intent data) {
} }
public void disposeIabHelper() { public void disposeIabHelper() {
} }
public void consumePurchases() { public void consumePurchases() {
} }
} }

@ -1,40 +1,39 @@
package org.tasks.gtasks; package org.tasks.gtasks;
import android.app.Activity; import android.app.Activity;
import com.google.api.client.googleapis.extensions.android.gms.auth.GoogleAccountCredential; import com.google.api.client.googleapis.extensions.android.gms.auth.GoogleAccountCredential;
import com.todoroo.astrid.gtasks.auth.GtasksLoginActivity; import com.todoroo.astrid.gtasks.auth.GtasksLoginActivity;
import javax.inject.Inject; import javax.inject.Inject;
public class PlayServices { public class PlayServices {
@Inject @Inject
public PlayServices() { public PlayServices() {
} }
public boolean isPlayServicesAvailable() { public boolean isPlayServicesAvailable() {
return false; return false;
} }
public boolean refreshAndCheck() { public boolean refreshAndCheck() {
return false; return false;
} }
public void resolve(Activity activity) { public void resolve(Activity activity) {
} }
public String getStatus() { public String getStatus() {
return null; return null;
} }
public boolean clearToken(GoogleAccountCredential googleAccountCredential) { public boolean clearToken(GoogleAccountCredential googleAccountCredential) {
return false; return false;
} }
public void getAuthToken(GtasksLoginActivity gtasksLoginActivity, String a, GtasksLoginActivity.AuthResultHandler authResultHandler) { public void getAuthToken(GtasksLoginActivity gtasksLoginActivity, String a,
GtasksLoginActivity.AuthResultHandler authResultHandler) {
} }
} }

@ -1,28 +1,26 @@
package org.tasks.location; package org.tasks.location;
import org.tasks.data.Location;
import java.util.List; import java.util.List;
import javax.inject.Inject; import javax.inject.Inject;
import org.tasks.data.Location;
@SuppressWarnings("EmptyMethod") @SuppressWarnings("EmptyMethod")
public class GeofenceApi { public class GeofenceApi {
@Inject @Inject
public GeofenceApi() { public GeofenceApi() {
} }
public void register(List<Location> activeGeofences) { public void register(List<Location> activeGeofences) {
} }
public void cancel(Location geofence) { public void cancel(Location geofence) {
} }
public void cancel(List<Location> geofences) { public void cancel(List<Location> geofences) {
} }
} }

@ -3,16 +3,16 @@ package org.tasks.location;
import android.app.Activity; import android.app.Activity;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import org.tasks.data.Location; import org.tasks.data.Location;
import org.tasks.preferences.Preferences; import org.tasks.preferences.Preferences;
public class PlacePicker { public class PlacePicker {
public static Intent getIntent(Activity activity) {
return null;
}
public static Location getPlace(Context context, Intent data, Preferences preferences) { public static Intent getIntent(Activity activity) {
return null; return null;
} }
public static Location getPlace(Context context, Intent data, Preferences preferences) {
return null;
}
} }

@ -3,26 +3,25 @@
* *
* See the file "LICENSE" for the full license governing this code. * See the file "LICENSE" for the full license governing this code.
*/ */
package com.todoroo.andlib.test; package com.todoroo.andlib.test;
import static android.support.test.InstrumentationRegistry.getTargetContext;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
import android.content.res.Configuration; import android.content.res.Configuration;
import android.content.res.Resources; import android.content.res.Resources;
import android.support.test.runner.AndroidJUnit4; import android.support.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.tasks.R;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import org.junit.Test;
import static android.support.test.InstrumentationRegistry.getTargetContext; import org.junit.runner.RunWith;
import static junit.framework.Assert.assertEquals; import org.tasks.R;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
/** /**
* Tests translations for consistency with the default values. You must * Tests translations for consistency with the default values. You must
@ -30,235 +29,252 @@ import static junit.framework.Assert.assertTrue;
* and arrays. * and arrays.
* *
* @author Tim Su <tim@todoroo.com> * @author Tim Su <tim@todoroo.com>
*
*/ */
@RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class)
public class TranslationTests { public class TranslationTests {
public interface Callback<T> { /**
void apply(T entry); * Loop through each locale and call runnable
*/
private void forEachLocale(Callback<Resources> callback) {
Locale[] locales = Locale.getAvailableLocales();
for (Locale locale : locales) {
callback.apply(getResourcesForLocale(locale));
}
}
private Resources getResourcesForLocale(Locale locale) {
Resources resources = getTargetContext().getResources();
Configuration configuration = new Configuration(resources.getConfiguration());
configuration.locale = locale;
return new Resources(resources.getAssets(), resources.getDisplayMetrics(), configuration);
}
/**
* Internal test of format string parser
*/
@Test
public void testFormatStringParser() {
String s = "abc";
FormatStringData data = new FormatStringData(s);
assertEquals(s, data.string);
assertEquals(0, data.characters.length);
s = "abc %s def";
data = new FormatStringData(s);
assertEquals(1, data.characters.length);
assertEquals('s', data.characters[0]);
s = "abc %%s def %d";
data = new FormatStringData(s);
assertEquals(2, data.characters.length);
assertEquals('%', data.characters[0]);
assertEquals('d', data.characters[1]);
assertTrue(data.toString(), data.toString().contains("[%"));
assertTrue(data.toString(), data.toString().contains("d]"));
assertTrue(data.toString(), data.toString().contains(s));
assertTrue(data.matches(new FormatStringData("espanol %% und %d si")));
assertFalse(data.matches(new FormatStringData("ingles %d ja %% pon")));
s = "% abc %";
data = new FormatStringData(s);
assertEquals(2, data.characters.length);
assertEquals(' ', data.characters[0]);
assertEquals('\0', data.characters[1]);
}
/**
* Test that the format specifiers in translations match exactly the
* translations in the default text
*/
@Test
public void testFormatStringsMatch() throws Exception {
final Resources resources = getTargetContext().getResources();
final int[] strings = getResourceIds(R.string.class);
final FormatStringData[] formatStrings = new FormatStringData[strings.length];
final StringBuilder failures = new StringBuilder();
for (int i = 0; i < strings.length; i++) {
try {
String string = resources.getString(strings[i]);
formatStrings[i] = new FormatStringData(string);
} catch (Exception e) {
String name = resources.getResourceName(strings[i]);
failures.append(String.format("error opening %s: %s\n",
name, e.getMessage()));
}
} }
/** forEachLocale(r -> {
* Loop through each locale and call runnable Locale locale = r.getConfiguration().locale;
*/ for (int i = 0; i < strings.length; i++) {
private void forEachLocale(Callback<Resources> callback) { try {
Locale[] locales = Locale.getAvailableLocales(); switch (strings[i]) {
for(Locale locale : locales) { case R.string.abc_shareactionprovider_share_with_application:
callback.apply(getResourcesForLocale(locale)); continue;
}
String string = r.getString(strings[i]);
FormatStringData newFS = new FormatStringData(string);
if (!newFS.matches(formatStrings[i])) {
String name = r.getResourceName(strings[i]);
failures.append(String.format("%s (%s): %s != %s\n",
name, locale.toString(), newFS, formatStrings[i]));
}
} catch (Exception e) {
String name = r.getResourceName(strings[i]);
failures.append(String.format("%s: error opening %s: %s\n",
locale.toString(), name, e.getMessage()));
} }
}
});
assertTrue(failures.toString(), errorCount(failures) == 0);
}
/**
* check if string contains contains substrings
*/
private void contains(Resources r, int resource, StringBuilder failures, String expected) {
String translation = r.getString(resource);
if (!translation.contains(expected)) {
Locale locale = r.getConfiguration().locale;
String name = r.getResourceName(resource);
failures.append(String.format("%s: %s did not contain: %s\n",
locale.toString(), name, expected));
} }
}
private Resources getResourcesForLocale(Locale locale) {
Resources resources = getTargetContext().getResources(); /**
Configuration configuration = new Configuration(resources.getConfiguration()); * Test dollar sign resources
configuration.locale = locale; */
return new Resources(resources.getAssets(), resources.getDisplayMetrics(), configuration); @Test
public void testSpecialStringsMatch() throws Exception {
final StringBuilder failures = new StringBuilder();
forEachLocale(r -> {
contains(r, R.string.CFC_tag_text, failures, "?");
contains(r, R.string.CFC_title_contains_text, failures, "?");
contains(r, R.string.CFC_dueBefore_text, failures, "?");
contains(r, R.string.CFC_tag_contains_text, failures, "?");
contains(r, R.string.CFC_gtasks_list_text, failures, "?");
});
assertEquals(failures.toString(), 0,
failures.toString().replaceAll("[^\n]", "").length());
}
/**
* Count newlines
*/
private int errorCount(StringBuilder failures) {
int count = 0;
int pos = -1;
while (true) {
pos = failures.indexOf("\n", pos + 1);
if (pos == -1) {
return count;
}
count++;
} }
}
/**
* @return an array of all string resource id's
*/
private int[] getResourceIds(Class<?> resources) {
Field[] fields = resources.getDeclaredFields();
List<Integer> ids = new ArrayList<>(fields.length);
for (Field field : fields) {
try {
ids.add(field.getInt(null));
} catch (Exception e) {
// not a field we care about
}
}
int[] idsAsIntArray = new int[ids.size()];
for (int i = 0; i < ids.size(); i++) {
idsAsIntArray[i] = ids.get(i);
}
return idsAsIntArray;
}
private static final class FormatStringData { public interface Callback<T> {
private static final char[] scratch = new char[10];
/** format characters */
public final char[] characters;
/** the original string */
public final String string;
public FormatStringData(String string) {
this.string = string;
int pos = -1;
int count = 0;
while(true) {
pos = string.indexOf('%', ++pos);
if(pos++ == -1)
break;
if(pos >= string.length())
scratch[count++] = '\0';
else
scratch[count++] = string.charAt(pos);
}
characters = new char[count];
for(int i = 0; i < count; i++) {
characters[i] = scratch[i];
}
}
/** test that the characters match */ void apply(T entry);
public boolean matches(FormatStringData other) { }
if(characters.length != other.characters.length)
return false;
outer: for(int i = 0; i < characters.length; i++) {
if(Character.isDigit(characters[i])) {
for(int j = 0; j < other.characters.length; j++)
if(characters[i] == other.characters[j])
break outer;
return false;
} else if(characters[i] != other.characters[i])
return false;
} private static final class FormatStringData {
return true;
}
@Override private static final char[] scratch = new char[10];
public String toString() {
StringBuilder value = new StringBuilder("[");
for(int i = 0; i < characters.length; i++) {
value.append(characters[i]);
if(i < characters.length - 1)
value.append(',');
}
value.append("]: '").append(string).append('\'');
return value.toString();
}
}
/** /**
* Internal test of format string parser * format characters
*/ */
@Test public final char[] characters;
public void testFormatStringParser() {
String s = "abc";
FormatStringData data = new FormatStringData(s);
assertEquals(s, data.string);
assertEquals(0, data.characters.length);
s = "abc %s def";
data = new FormatStringData(s);
assertEquals(1, data.characters.length);
assertEquals('s', data.characters[0]);
s = "abc %%s def %d";
data = new FormatStringData(s);
assertEquals(2, data.characters.length);
assertEquals('%', data.characters[0]);
assertEquals('d', data.characters[1]);
assertTrue(data.toString(), data.toString().contains("[%"));
assertTrue(data.toString(), data.toString().contains("d]"));
assertTrue(data.toString(), data.toString().contains(s));
assertTrue(data.matches(new FormatStringData("espanol %% und %d si")));
assertFalse(data.matches(new FormatStringData("ingles %d ja %% pon")));
s = "% abc %";
data = new FormatStringData(s);
assertEquals(2, data.characters.length);
assertEquals(' ', data.characters[0]);
assertEquals('\0', data.characters[1]);
}
/** /**
* Test that the format specifiers in translations match exactly the * the original string
* translations in the default text
*/ */
@Test public final String string;
public void testFormatStringsMatch() throws Exception {
final Resources resources = getTargetContext().getResources();
final int[] strings = getResourceIds(R.string.class);
final FormatStringData[] formatStrings = new FormatStringData[strings.length];
final StringBuilder failures = new StringBuilder();
for(int i = 0; i < strings.length; i++) {
try {
String string = resources.getString(strings[i]);
formatStrings[i] = new FormatStringData(string);
} catch (Exception e) {
String name = resources.getResourceName(strings[i]);
failures.append(String.format("error opening %s: %s\n",
name, e.getMessage()));
}
}
forEachLocale(r -> {
Locale locale = r.getConfiguration().locale;
for(int i = 0; i < strings.length; i++) {
try {
switch(strings[i]) {
case R.string.abc_shareactionprovider_share_with_application:
continue;
}
String string = r.getString(strings[i]);
FormatStringData newFS = new FormatStringData(string);
if(!newFS.matches(formatStrings[i])) {
String name = r.getResourceName(strings[i]);
failures.append(String.format("%s (%s): %s != %s\n",
name, locale.toString(), newFS, formatStrings[i]));
}
} catch (Exception e) {
String name = r.getResourceName(strings[i]);
failures.append(String.format("%s: error opening %s: %s\n",
locale.toString(), name, e.getMessage()));
}
}
});
assertTrue(failures.toString(), errorCount(failures) == 0); public FormatStringData(String string) {
} this.string = string;
/** int pos = -1;
* check if string contains contains substrings int count = 0;
*/ while (true) {
private void contains(Resources r, int resource, StringBuilder failures, String expected) { pos = string.indexOf('%', ++pos);
String translation = r.getString(resource); if (pos++ == -1) {
if(!translation.contains(expected)) { break;
Locale locale = r.getConfiguration().locale;
String name = r.getResourceName(resource);
failures.append(String.format("%s: %s did not contain: %s\n",
locale.toString(), name, expected));
} }
if (pos >= string.length()) {
scratch[count++] = '\0';
} else {
scratch[count++] = string.charAt(pos);
}
}
characters = new char[count];
for (int i = 0; i < count; i++) {
characters[i] = scratch[i];
}
} }
/** /**
* Test dollar sign resources * test that the characters match
*/
@Test
public void testSpecialStringsMatch() throws Exception {
final StringBuilder failures = new StringBuilder();
forEachLocale(r -> {
contains(r, R.string.CFC_tag_text, failures, "?");
contains(r, R.string.CFC_title_contains_text, failures, "?");
contains(r, R.string.CFC_dueBefore_text, failures, "?");
contains(r, R.string.CFC_tag_contains_text, failures, "?");
contains(r, R.string.CFC_gtasks_list_text, failures, "?");
});
assertEquals(failures.toString(), 0,
failures.toString().replaceAll("[^\n]", "").length());
}
/**
* Count newlines
*/ */
private int errorCount(StringBuilder failures) { public boolean matches(FormatStringData other) {
int count = 0; if (characters.length != other.characters.length) {
int pos = -1; return false;
while(true) { }
pos = failures.indexOf("\n", pos + 1); outer:
if(pos == -1) for (int i = 0; i < characters.length; i++) {
return count; if (Character.isDigit(characters[i])) {
count++; for (int j = 0; j < other.characters.length; j++) {
if (characters[i] == other.characters[j]) {
break outer;
}
}
return false;
} else if (characters[i] != other.characters[i]) {
return false;
} }
}
return true;
} }
/** @Override
* @return an array of all string resource id's public String toString() {
*/ StringBuilder value = new StringBuilder("[");
private int[] getResourceIds(Class<?> resources) { for (int i = 0; i < characters.length; i++) {
Field[] fields = resources.getDeclaredFields(); value.append(characters[i]);
List<Integer> ids = new ArrayList<>(fields.length); if (i < characters.length - 1) {
for (Field field : fields) { value.append(',');
try {
ids.add(field.getInt(null));
} catch (Exception e) {
// not a field we care about
}
} }
int[] idsAsIntArray = new int[ids.size()]; }
for(int i = 0; i < ids.size(); i++) value.append("]: '").append(string).append('\'');
idsAsIntArray[i] = ids.get(i); return value.toString();
return idsAsIntArray;
} }
}
} }

@ -3,20 +3,8 @@
* *
* See the file "LICENSE" for the full license governing this code. * See the file "LICENSE" for the full license governing this code.
*/ */
package com.todoroo.andlib.utility;
import android.content.res.Configuration; package com.todoroo.andlib.utility;
import android.support.test.runner.AndroidJUnit4;
import android.util.DisplayMetrics;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.tasks.Snippet;
import org.tasks.time.DateTime;
import java.util.Locale;
import static android.support.test.InstrumentationRegistry.getTargetContext; import static android.support.test.InstrumentationRegistry.getTargetContext;
import static com.todoroo.andlib.utility.DateUtilities.addCalendarMonthsToUnixtime; import static com.todoroo.andlib.utility.DateUtilities.addCalendarMonthsToUnixtime;
@ -31,273 +19,298 @@ import static org.tasks.Freeze.freezeAt;
import static org.tasks.date.DateTimeUtils.newDate; import static org.tasks.date.DateTimeUtils.newDate;
import static org.tasks.date.DateTimeUtils.newDateTime; import static org.tasks.date.DateTimeUtils.newDateTime;
import android.content.res.Configuration;
import android.support.test.runner.AndroidJUnit4;
import android.util.DisplayMetrics;
import java.util.Locale;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.tasks.Snippet;
import org.tasks.time.DateTime;
@RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class)
public class DateUtilitiesTest { public class DateUtilitiesTest {
private static Locale defaultLocale; private static Locale defaultLocale;
@Before @Before
public void setUp() { public void setUp() {
defaultLocale = Locale.getDefault(); defaultLocale = Locale.getDefault();
setLocale(Locale.US); setLocale(Locale.US);
} }
@After @After
public void tearDown() { public void tearDown() {
DateUtilities.is24HourOverride = null; DateUtilities.is24HourOverride = null;
setLocale(defaultLocale); setLocale(defaultLocale);
} }
private void setLocale(Locale locale) { private void setLocale(Locale locale) {
org.tasks.locale.Locale.setDefault(locale); org.tasks.locale.Locale.setDefault(locale);
Configuration config = new Configuration(); Configuration config = new Configuration();
config.locale = locale; config.locale = locale;
DisplayMetrics metrics = getTargetContext().getResources().getDisplayMetrics(); DisplayMetrics metrics = getTargetContext().getResources().getDisplayMetrics();
getTargetContext().getResources().updateConfiguration(config, metrics); getTargetContext().getResources().updateConfiguration(config, metrics);
} }
public void forEachLocale(Runnable r) { public void forEachLocale(Runnable r) {
Locale[] locales = Locale.getAvailableLocales(); Locale[] locales = Locale.getAvailableLocales();
for(Locale locale : locales) { for (Locale locale : locales) {
setLocale(locale); setLocale(locale);
r.run(); r.run();
} }
} }
@Test @Test
public void testTimeString() { public void testTimeString() {
forEachLocale(() -> { forEachLocale(() -> {
DateTime d = newDateTime(); DateTime d = newDateTime();
DateUtilities.is24HourOverride = false; DateUtilities.is24HourOverride = false;
for (int i = 0; i < 24; i++) { for (int i = 0; i < 24; i++) {
d = d.withHourOfDay(i); d = d.withHourOfDay(i);
getTimeString(getTargetContext(), d); getTimeString(getTargetContext(), d);
} }
DateUtilities.is24HourOverride = true; DateUtilities.is24HourOverride = true;
for (int i = 0; i < 24; i++) { for (int i = 0; i < 24; i++) {
d = d.withHourOfDay(i); d = d.withHourOfDay(i);
getTimeString(getTargetContext(), d); getTimeString(getTargetContext(), d);
} }
}); });
} }
@Test @Test
public void testDateString() { public void testDateString() {
forEachLocale(() -> { forEachLocale(() -> {
DateTime d = newDateTime(); DateTime d = newDateTime();
for (int i = 0; i < 12; i++) { for (int i = 0; i < 12; i++) {
d = d.withMonthOfYear(i); d = d.withMonthOfYear(i);
getDateString(d); getDateString(d);
} }
}); });
} }
@Test @Test
public void testGet24HourTime() { public void testGet24HourTime() {
DateUtilities.is24HourOverride = true; DateUtilities.is24HourOverride = true;
assertEquals("09:05", getTimeString(null, new DateTime(2014, 1, 4, 9, 5, 36))); assertEquals("09:05", getTimeString(null, new DateTime(2014, 1, 4, 9, 5, 36)));
assertEquals("13:00", getTimeString(null, new DateTime(2014, 1, 4, 13, 0, 1))); assertEquals("13:00", getTimeString(null, new DateTime(2014, 1, 4, 13, 0, 1)));
} }
@Test @Test
public void testGetTime() { public void testGetTime() {
DateUtilities.is24HourOverride = false; DateUtilities.is24HourOverride = false;
assertEquals("9:05 AM", getTimeString(null, new DateTime(2014, 1, 4, 9, 5, 36))); assertEquals("9:05 AM", getTimeString(null, new DateTime(2014, 1, 4, 9, 5, 36)));
assertEquals("1:05 PM", getTimeString(null, new DateTime(2014, 1, 4, 13, 5, 36))); assertEquals("1:05 PM", getTimeString(null, new DateTime(2014, 1, 4, 13, 5, 36)));
} }
@Test @Test
public void testGetTimeWithNoMinutes() { public void testGetTimeWithNoMinutes() {
DateUtilities.is24HourOverride = false; DateUtilities.is24HourOverride = false;
assertEquals("1 PM", getTimeString(null, new DateTime(2014, 1, 4, 13, 0, 59))); // derp? assertEquals("1 PM", getTimeString(null, new DateTime(2014, 1, 4, 13, 0, 59))); // derp?
} }
@Test @Test
public void testGetDateStringWithYear() { public void testGetDateStringWithYear() {
assertEquals("Jan 4 '14", getDateString(new DateTime(2014, 1, 4, 0, 0, 0))); assertEquals("Jan 4 '14", getDateString(new DateTime(2014, 1, 4, 0, 0, 0)));
} }
@Test @Test
public void testGetDateStringHidingYear() { public void testGetDateStringHidingYear() {
freezeAt(newDate(2014, 1, 1)).thawAfter(new Snippet() {{ freezeAt(newDate(2014, 1, 1)).thawAfter(new Snippet() {{
assertEquals("Jan 1", getDateString(newDateTime())); assertEquals("Jan 1", getDateString(newDateTime()));
}}); }});
} }
@Test @Test
public void testGetDateStringWithDifferentYear() { public void testGetDateStringWithDifferentYear() {
freezeAt(newDate(2013, 12, 31)).thawAfter(new Snippet() {{ freezeAt(newDate(2013, 12, 31)).thawAfter(new Snippet() {{
assertEquals("Jan 1 '14", getDateString(new DateTime(2014, 1, 1, 0, 0, 0))); assertEquals("Jan 1 '14", getDateString(new DateTime(2014, 1, 1, 0, 0, 0)));
}}); }});
} }
@Test @Test
public void testOneMonthFromStartOfDecember() { public void testOneMonthFromStartOfDecember() {
DateTime now = new DateTime(2013, 12, 1, 12, 19, 45, 192); DateTime now = new DateTime(2013, 12, 1, 12, 19, 45, 192);
final long expected = new DateTime(2014, 1, 1, 12, 19, 45, 192).getMillis(); final long expected = new DateTime(2014, 1, 1, 12, 19, 45, 192).getMillis();
freezeAt(now).thawAfter(new Snippet() {{ freezeAt(now).thawAfter(new Snippet() {{
assertEquals(expected, oneMonthFromNow()); assertEquals(expected, oneMonthFromNow());
}}); }});
} }
@Test @Test
public void testOneMonthFromEndOfDecember() { public void testOneMonthFromEndOfDecember() {
DateTime now = new DateTime(2013, 12, 31, 16, 31, 20, 597); DateTime now = new DateTime(2013, 12, 31, 16, 31, 20, 597);
final long expected = new DateTime(2014, 1, 31, 16, 31, 20, 597).getMillis(); final long expected = new DateTime(2014, 1, 31, 16, 31, 20, 597).getMillis();
freezeAt(now).thawAfter(new Snippet() {{ freezeAt(now).thawAfter(new Snippet() {{
assertEquals(expected, oneMonthFromNow()); assertEquals(expected, oneMonthFromNow());
}}); }});
} }
@Test @Test
public void testGetSixMonthsFromEndOfDecember() { public void testGetSixMonthsFromEndOfDecember() {
final DateTime now = new DateTime(2013, 12, 31, 17, 17, 32, 900); final DateTime now = new DateTime(2013, 12, 31, 17, 17, 32, 900);
final long expected = new DateTime(2014, 7, 1, 17, 17, 32, 900).getMillis(); final long expected = new DateTime(2014, 7, 1, 17, 17, 32, 900).getMillis();
freezeAt(now).thawAfter(new Snippet() {{ freezeAt(now).thawAfter(new Snippet() {{
assertEquals(expected, addCalendarMonthsToUnixtime(now.getMillis(), 6)); assertEquals(expected, addCalendarMonthsToUnixtime(now.getMillis(), 6));
}}); }});
} }
@Test @Test
public void testOneMonthFromEndOfJanuary() { public void testOneMonthFromEndOfJanuary() {
DateTime now = new DateTime(2014, 1, 31, 12, 54, 33, 175); DateTime now = new DateTime(2014, 1, 31, 12, 54, 33, 175);
final long expected = new DateTime(2014, 3, 3, 12, 54, 33, 175).getMillis(); final long expected = new DateTime(2014, 3, 3, 12, 54, 33, 175).getMillis();
freezeAt(now).thawAfter(new Snippet() {{ freezeAt(now).thawAfter(new Snippet() {{
assertEquals(expected, oneMonthFromNow()); assertEquals(expected, oneMonthFromNow());
}}); }});
} }
@Test @Test
public void testOneMonthFromEndOfFebruary() { public void testOneMonthFromEndOfFebruary() {
DateTime now = new DateTime(2014, 2, 28, 9, 19, 7, 990); DateTime now = new DateTime(2014, 2, 28, 9, 19, 7, 990);
final long expected = new DateTime(2014, 3, 28, 9, 19, 7, 990).getMillis(); final long expected = new DateTime(2014, 3, 28, 9, 19, 7, 990).getMillis();
freezeAt(now).thawAfter(new Snippet() {{ freezeAt(now).thawAfter(new Snippet() {{
assertEquals(expected, oneMonthFromNow()); assertEquals(expected, oneMonthFromNow());
}}); }});
} }
@Test @Test
public void testShouldGetStartOfDay() { public void testShouldGetStartOfDay() {
DateTime now = new DateTime(2014, 1, 3, 10, 41, 41, 520); DateTime now = new DateTime(2014, 1, 3, 10, 41, 41, 520);
assertEquals( assertEquals(
now.startOfDay().getMillis(), now.startOfDay().getMillis(),
getStartOfDay(now.getMillis())); getStartOfDay(now.getMillis()));
} }
@Test @Test
public void testGetWeekdayLongString() { public void testGetWeekdayLongString() {
assertEquals("Sunday", getWeekday(newDate(2013, 12, 29))); assertEquals("Sunday", getWeekday(newDate(2013, 12, 29)));
assertEquals("Monday", getWeekday(newDate(2013, 12, 30))); assertEquals("Monday", getWeekday(newDate(2013, 12, 30)));
assertEquals("Tuesday", getWeekday(newDate(2013, 12, 31))); assertEquals("Tuesday", getWeekday(newDate(2013, 12, 31)));
assertEquals("Wednesday", getWeekday(newDate(2014, 1, 1))); assertEquals("Wednesday", getWeekday(newDate(2014, 1, 1)));
assertEquals("Thursday", getWeekday(newDate(2014, 1, 2))); assertEquals("Thursday", getWeekday(newDate(2014, 1, 2)));
assertEquals("Friday", getWeekday(newDate(2014, 1, 3))); assertEquals("Friday", getWeekday(newDate(2014, 1, 3)));
assertEquals("Saturday", getWeekday(newDate(2014, 1, 4))); assertEquals("Saturday", getWeekday(newDate(2014, 1, 4)));
} }
@Test @Test
public void testGetWeekdayShortString() { public void testGetWeekdayShortString() {
assertEquals("Sun", getWeekdayShort(newDate(2013, 12, 29))); assertEquals("Sun", getWeekdayShort(newDate(2013, 12, 29)));
assertEquals("Mon", getWeekdayShort(newDate(2013, 12, 30))); assertEquals("Mon", getWeekdayShort(newDate(2013, 12, 30)));
assertEquals("Tue", getWeekdayShort(newDate(2013, 12, 31))); assertEquals("Tue", getWeekdayShort(newDate(2013, 12, 31)));
assertEquals("Wed", getWeekdayShort(newDate(2014, 1, 1))); assertEquals("Wed", getWeekdayShort(newDate(2014, 1, 1)));
assertEquals("Thu", getWeekdayShort(newDate(2014, 1, 2))); assertEquals("Thu", getWeekdayShort(newDate(2014, 1, 2)));
assertEquals("Fri", getWeekdayShort(newDate(2014, 1, 3))); assertEquals("Fri", getWeekdayShort(newDate(2014, 1, 3)));
assertEquals("Sat", getWeekdayShort(newDate(2014, 1, 4))); assertEquals("Sat", getWeekdayShort(newDate(2014, 1, 4)));
} }
@Test @Test
public void testAddMonthsToTimestamp() { public void testAddMonthsToTimestamp() {
assertEquals(newDate(2014, 1, 1).getMillis(), addCalendarMonthsToUnixtime(newDate(2013, 12, 1).getMillis(), 1)); assertEquals(newDate(2014, 1, 1).getMillis(),
assertEquals(newDate(2014, 12, 31).getMillis(), addCalendarMonthsToUnixtime(newDate(2013, 12, 31).getMillis(), 12)); addCalendarMonthsToUnixtime(newDate(2013, 12, 1).getMillis(), 1));
} assertEquals(newDate(2014, 12, 31).getMillis(),
addCalendarMonthsToUnixtime(newDate(2013, 12, 31).getMillis(), 12));
@Test }
public void testAddMonthsWithLessDays() {
assertEquals(newDate(2014, 3, 3).getMillis(), addCalendarMonthsToUnixtime(newDate(2013, 12, 31).getMillis(), 2)); @Test
} public void testAddMonthsWithLessDays() {
assertEquals(newDate(2014, 3, 3).getMillis(),
@Test addCalendarMonthsToUnixtime(newDate(2013, 12, 31).getMillis(), 2));
public void testAddMonthsWithMoreDays() { }
assertEquals(newDate(2014, 1, 30).getMillis(), addCalendarMonthsToUnixtime(newDate(2013, 11, 30).getMillis(), 2));
} @Test
public void testAddMonthsWithMoreDays() {
@Test assertEquals(newDate(2014, 1, 30).getMillis(),
public void usDateNoYear() { addCalendarMonthsToUnixtime(newDate(2013, 11, 30).getMillis(), 2));
setLocale(Locale.US); }
freezeAt(new DateTime(2018, 1, 1)).thawAfter(() ->
assertEquals("Jan 14", DateUtilities.getRelativeDateStringWithTime(getTargetContext(), new DateTime(2018, 1, 14).getMillis()))); @Test
} public void usDateNoYear() {
setLocale(Locale.US);
@Test freezeAt(new DateTime(2018, 1, 1)).thawAfter(() ->
public void usDateWithYear() { assertEquals("Jan 14", DateUtilities.getRelativeDateStringWithTime(getTargetContext(),
setLocale(Locale.US); new DateTime(2018, 1, 14).getMillis())));
freezeAt(new DateTime(2017, 12, 12)).thawAfter(() -> }
assertEquals("Jan 14 '18", DateUtilities.getRelativeDateStringWithTime(getTargetContext(), new DateTime(2018, 1, 14).getMillis())));
} @Test
public void usDateWithYear() {
@Test setLocale(Locale.US);
public void germanDateNoYear() { freezeAt(new DateTime(2017, 12, 12)).thawAfter(() ->
setLocale(Locale.GERMAN); assertEquals("Jan 14 '18", DateUtilities.getRelativeDateStringWithTime(getTargetContext(),
freezeAt(new DateTime(2018, 1, 1)).thawAfter(() -> new DateTime(2018, 1, 14).getMillis())));
assertEquals("14 Jan.", DateUtilities.getRelativeDateStringWithTime(getTargetContext(), new DateTime(2018, 1, 14).getMillis()))); }
}
@Test
@Test public void germanDateNoYear() {
public void germanDateWithYear() { setLocale(Locale.GERMAN);
setLocale(Locale.GERMAN); freezeAt(new DateTime(2018, 1, 1)).thawAfter(() ->
freezeAt(new DateTime(2017, 12, 12)).thawAfter(() -> assertEquals("14 Jan.", DateUtilities.getRelativeDateStringWithTime(getTargetContext(),
assertEquals("14 Jan. '18", DateUtilities.getRelativeDateStringWithTime(getTargetContext(), new DateTime(2018, 1, 14).getMillis()))); new DateTime(2018, 1, 14).getMillis())));
} }
@Test @Test
public void koreanDateNoYear() { public void germanDateWithYear() {
setLocale(Locale.KOREAN); setLocale(Locale.GERMAN);
freezeAt(new DateTime(2018, 1, 1)).thawAfter(() -> freezeAt(new DateTime(2017, 12, 12)).thawAfter(() ->
assertEquals("1월 14일", DateUtilities.getRelativeDateStringWithTime(getTargetContext(), new DateTime(2018, 1, 14).getMillis()))); assertEquals("14 Jan. '18", DateUtilities.getRelativeDateStringWithTime(getTargetContext(),
} new DateTime(2018, 1, 14).getMillis())));
}
@Test
public void koreanDateWithYear() { @Test
setLocale(Locale.KOREAN); public void koreanDateNoYear() {
freezeAt(new DateTime(2017, 12, 12)).thawAfter(() -> setLocale(Locale.KOREAN);
assertEquals("18년 1월 14일", DateUtilities.getRelativeDateStringWithTime(getTargetContext(), new DateTime(2018, 1, 14).getMillis()))); freezeAt(new DateTime(2018, 1, 1)).thawAfter(() ->
} assertEquals("1월 14일", DateUtilities.getRelativeDateStringWithTime(getTargetContext(),
new DateTime(2018, 1, 14).getMillis())));
@Test }
public void japaneseDateNoYear() {
setLocale(Locale.JAPANESE); @Test
freezeAt(new DateTime(2018, 1, 1)).thawAfter(() -> public void koreanDateWithYear() {
assertEquals("1月 14日", DateUtilities.getRelativeDateStringWithTime(getTargetContext(), new DateTime(2018, 1, 14).getMillis()))); setLocale(Locale.KOREAN);
} freezeAt(new DateTime(2017, 12, 12)).thawAfter(() ->
assertEquals("18년 1월 14일", DateUtilities.getRelativeDateStringWithTime(getTargetContext(),
@Test new DateTime(2018, 1, 14).getMillis())));
public void japaneseDateWithYear() { }
setLocale(Locale.JAPANESE);
freezeAt(new DateTime(2017, 12, 12)).thawAfter(() -> @Test
assertEquals("18年 1月 14日", DateUtilities.getRelativeDateStringWithTime(getTargetContext(), new DateTime(2018, 1, 14).getMillis()))); public void japaneseDateNoYear() {
} setLocale(Locale.JAPANESE);
freezeAt(new DateTime(2018, 1, 1)).thawAfter(() ->
@Test assertEquals("1月 14日", DateUtilities.getRelativeDateStringWithTime(getTargetContext(),
public void chineseDateNoYear() { new DateTime(2018, 1, 14).getMillis())));
setLocale(Locale.CHINESE); }
freezeAt(new DateTime(2018, 1, 1)).thawAfter(() ->
assertEquals("1月 14日", DateUtilities.getRelativeDateStringWithTime(getTargetContext(), new DateTime(2018, 1, 14).getMillis()))); @Test
} public void japaneseDateWithYear() {
setLocale(Locale.JAPANESE);
@Test freezeAt(new DateTime(2017, 12, 12)).thawAfter(() ->
public void chineseDateWithYear() { assertEquals("18年 1月 14日", DateUtilities.getRelativeDateStringWithTime(getTargetContext(),
setLocale(Locale.CHINESE); new DateTime(2018, 1, 14).getMillis())));
freezeAt(new DateTime(2017, 12, 12)).thawAfter(() -> }
assertEquals("18年 1月 14日", DateUtilities.getRelativeDateStringWithTime(getTargetContext(), new DateTime(2018, 1, 14).getMillis())));
} @Test
public void chineseDateNoYear() {
setLocale(Locale.CHINESE);
freezeAt(new DateTime(2018, 1, 1)).thawAfter(() ->
assertEquals("1月 14日", DateUtilities.getRelativeDateStringWithTime(getTargetContext(),
new DateTime(2018, 1, 14).getMillis())));
}
@Test
public void chineseDateWithYear() {
setLocale(Locale.CHINESE);
freezeAt(new DateTime(2017, 12, 12)).thawAfter(() ->
assertEquals("18年 1月 14日", DateUtilities.getRelativeDateStringWithTime(getTargetContext(),
new DateTime(2018, 1, 14).getMillis())));
}
} }

@ -1,79 +1,77 @@
package com.todoroo.andlib.utility; package com.todoroo.andlib.utility;
import android.support.test.runner.AndroidJUnit4; import static android.support.test.InstrumentationRegistry.getTargetContext;
import static com.todoroo.andlib.utility.DateUtilities.getRelativeDay;
import static junit.framework.Assert.assertEquals;
import static org.tasks.Freeze.freezeAt;
import static org.tasks.Freeze.thaw;
import android.support.test.runner.AndroidJUnit4;
import java.util.Locale;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.tasks.time.DateTime; import org.tasks.time.DateTime;
import java.util.Locale;
import static android.support.test.InstrumentationRegistry.getTargetContext;
import static com.todoroo.andlib.utility.DateUtilities.getRelativeDay;
import static junit.framework.Assert.assertEquals;
import static org.tasks.Freeze.freezeAt;
import static org.tasks.Freeze.thaw;
@RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class)
public class RelativeDayTest { public class RelativeDayTest {
private static Locale defaultLocale; private static final DateTime now = new DateTime(2013, 12, 31, 11, 9, 42, 357);
private static final DateTime now = new DateTime(2013, 12, 31, 11, 9, 42, 357); private static Locale defaultLocale;
@Before @Before
public void setUp() { public void setUp() {
defaultLocale = Locale.getDefault(); defaultLocale = Locale.getDefault();
Locale.setDefault(Locale.US); Locale.setDefault(Locale.US);
freezeAt(now); freezeAt(now);
} }
@After @After
public void tearDown() { public void tearDown() {
Locale.setDefault(defaultLocale); Locale.setDefault(defaultLocale);
thaw(); thaw();
} }
@Test @Test
public void testRelativeDayIsToday() { public void testRelativeDayIsToday() {
checkRelativeDay(new DateTime(), "Today", "Today"); checkRelativeDay(new DateTime(), "Today", "Today");
} }
@Test @Test
public void testRelativeDayIsTomorrow() { public void testRelativeDayIsTomorrow() {
checkRelativeDay(new DateTime().plusDays(1), "Tomorrow", "Tmrw"); checkRelativeDay(new DateTime().plusDays(1), "Tomorrow", "Tmrw");
} }
@Test @Test
public void testRelativeDayIsYesterday() { public void testRelativeDayIsYesterday() {
checkRelativeDay(new DateTime().minusDays(1), "Yesterday", "Yest"); checkRelativeDay(new DateTime().minusDays(1), "Yesterday", "Yest");
} }
@Test @Test
public void testRelativeDayTwo() { public void testRelativeDayTwo() {
checkRelativeDay(new DateTime().minusDays(2), "Sunday", "Sun"); checkRelativeDay(new DateTime().minusDays(2), "Sunday", "Sun");
checkRelativeDay(new DateTime().plusDays(2), "Thursday", "Thu"); checkRelativeDay(new DateTime().plusDays(2), "Thursday", "Thu");
} }
@Test @Test
public void testRelativeDaySix() { public void testRelativeDaySix() {
checkRelativeDay(new DateTime().minusDays(6), "Wednesday", "Wed"); checkRelativeDay(new DateTime().minusDays(6), "Wednesday", "Wed");
checkRelativeDay(new DateTime().plusDays(6), "Monday", "Mon"); checkRelativeDay(new DateTime().plusDays(6), "Monday", "Mon");
} }
@Test @Test
public void testRelativeDayOneWeek() { public void testRelativeDayOneWeek() {
checkRelativeDay(new DateTime().minusDays(7), "Dec 24", "Dec 24"); checkRelativeDay(new DateTime().minusDays(7), "Dec 24", "Dec 24");
} }
@Test @Test
public void testRelativeDayOneWeekNextYear() { public void testRelativeDayOneWeekNextYear() {
checkRelativeDay(new DateTime().plusDays(7), "Jan 7 '14", "Jan 7 '14"); checkRelativeDay(new DateTime().plusDays(7), "Jan 7 '14", "Jan 7 '14");
} }
private void checkRelativeDay(DateTime now, String full, String abbreviated) { private void checkRelativeDay(DateTime now, String full, String abbreviated) {
assertEquals(full, getRelativeDay(getTargetContext(), now.getMillis(), false)); assertEquals(full, getRelativeDay(getTargetContext(), now.getMillis(), false));
assertEquals(abbreviated, getRelativeDay(getTargetContext(), now.getMillis(), true)); assertEquals(abbreviated, getRelativeDay(getTargetContext(), now.getMillis(), true));
} }
} }

@ -1,10 +1,16 @@
package com.todoroo.astrid.alarms; package com.todoroo.astrid.alarms;
import android.support.test.runner.AndroidJUnit4; import static com.natpryce.makeiteasy.MakeItEasy.with;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.tasks.makers.TaskMaker.REMINDER_LAST;
import static org.tasks.makers.TaskMaker.newTask;
import android.support.test.runner.AndroidJUnit4;
import com.todoroo.astrid.dao.TaskDao; import com.todoroo.astrid.dao.TaskDao;
import com.todoroo.astrid.data.Task; import com.todoroo.astrid.data.Task;
import javax.inject.Inject;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
@ -18,68 +24,59 @@ import org.tasks.jobs.AlarmJob;
import org.tasks.jobs.JobQueue; import org.tasks.jobs.JobQueue;
import org.tasks.time.DateTime; import org.tasks.time.DateTime;
import javax.inject.Inject;
import static com.natpryce.makeiteasy.MakeItEasy.with;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.tasks.makers.TaskMaker.REMINDER_LAST;
import static org.tasks.makers.TaskMaker.newTask;
@RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class)
public class AlarmJobServiceTest extends InjectingTestCase { public class AlarmJobServiceTest extends InjectingTestCase {
@Inject AlarmDao alarmDao; @Inject AlarmDao alarmDao;
@Inject TaskDao taskDao; @Inject TaskDao taskDao;
private AlarmService alarmService; private AlarmService alarmService;
private JobQueue jobs; private JobQueue jobs;
@Before @Before
public void before() { public void before() {
jobs = mock(JobQueue.class); jobs = mock(JobQueue.class);
alarmService = new AlarmService(alarmDao, jobs); alarmService = new AlarmService(alarmDao, jobs);
} }
@After @After
public void after() { public void after() {
verifyNoMoreInteractions(jobs); verifyNoMoreInteractions(jobs);
} }
@Test @Test
public void scheduleAlarm() { public void scheduleAlarm() {
Task task = newTask(); Task task = newTask();
taskDao.createNew(task); taskDao.createNew(task);
DateTime alarmTime = new DateTime(2017, 9, 24, 19, 57); DateTime alarmTime = new DateTime(2017, 9, 24, 19, 57);
Alarm alarm = new Alarm(task.getId(), alarmTime.getMillis()); Alarm alarm = new Alarm(task.getId(), alarmTime.getMillis());
alarm.setId(alarmDao.insert(alarm)); alarm.setId(alarmDao.insert(alarm));
alarmService.scheduleAllAlarms(); alarmService.scheduleAllAlarms();
InOrder order = inOrder(jobs); InOrder order = inOrder(jobs);
order.verify(jobs).add(new AlarmJob(alarm)); order.verify(jobs).add(new AlarmJob(alarm));
} }
@Test @Test
public void ignoreStaleAlarm() { public void ignoreStaleAlarm() {
DateTime alarmTime = new DateTime(2017, 9, 24, 19, 57); DateTime alarmTime = new DateTime(2017, 9, 24, 19, 57);
Task task = newTask(with(REMINDER_LAST, alarmTime.endOfMinute())); Task task = newTask(with(REMINDER_LAST, alarmTime.endOfMinute()));
taskDao.createNew(task); taskDao.createNew(task);
alarmDao.insert(new Alarm(task.getId(), alarmTime.getMillis())); alarmDao.insert(new Alarm(task.getId(), alarmTime.getMillis()));
alarmService.scheduleAllAlarms(); alarmService.scheduleAllAlarms();
verifyNoMoreInteractions(jobs); verifyNoMoreInteractions(jobs);
} }
@Override @Override
protected void inject(TestComponent component) { protected void inject(TestComponent component) {
component.inject(this); component.inject(this);
} }
} }

@ -3,167 +3,163 @@
* *
* See the file "LICENSE" for the full license governing this code. * See the file "LICENSE" for the full license governing this code.
*/ */
package com.todoroo.astrid.dao; package com.todoroo.astrid.dao;
import android.support.test.runner.AndroidJUnit4; import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertNotSame;
import static junit.framework.Assert.assertNull;
import com.todoroo.andlib.sql.Query; import android.support.test.runner.AndroidJUnit4;
import com.todoroo.andlib.utility.DateUtilities; import com.todoroo.andlib.utility.DateUtilities;
import com.todoroo.astrid.dao.TaskDao.TaskCriteria;
import com.todoroo.astrid.data.Task; import com.todoroo.astrid.data.Task;
import javax.inject.Inject;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.tasks.injection.InjectingTestCase; import org.tasks.injection.InjectingTestCase;
import org.tasks.injection.TestComponent; import org.tasks.injection.TestComponent;
import javax.inject.Inject;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertNotSame;
import static junit.framework.Assert.assertNull;
@RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class)
public class TaskDaoTests extends InjectingTestCase { public class TaskDaoTests extends InjectingTestCase {
@Inject TaskDao taskDao; @Inject TaskDao taskDao;
/** /**
* Test basic task creation, fetch, and save * Test basic task creation, fetch, and save
*/ */
@Test @Test
public void testTaskCreation() { public void testTaskCreation() {
assertEquals(0, taskDao.getAll().size()); assertEquals(0, taskDao.getAll().size());
// create task "happy" // create task "happy"
Task task = new Task(); Task task = new Task();
task.setTitle("happy"); task.setTitle("happy");
taskDao.createNew(task); taskDao.createNew(task);
assertEquals(1, taskDao.getAll().size()); assertEquals(1, taskDao.getAll().size());
long happyId = task.getId(); long happyId = task.getId();
assertNotSame(Task.NO_ID, happyId); assertNotSame(Task.NO_ID, happyId);
task = taskDao.fetch(happyId); task = taskDao.fetch(happyId);
assertEquals("happy", task.getTitle()); assertEquals("happy", task.getTitle());
// create task "sad" // create task "sad"
task = new Task(); task = new Task();
task.setTitle("sad"); task.setTitle("sad");
taskDao.createNew(task); taskDao.createNew(task);
assertEquals(2, taskDao.getAll().size()); assertEquals(2, taskDao.getAll().size());
// rename sad to melancholy // rename sad to melancholy
long sadId = task.getId(); long sadId = task.getId();
assertNotSame(Task.NO_ID, sadId); assertNotSame(Task.NO_ID, sadId);
task.setTitle("melancholy"); task.setTitle("melancholy");
taskDao.save(task); taskDao.save(task);
assertEquals(2, taskDao.getAll().size()); assertEquals(2, taskDao.getAll().size());
// check state // check state
task = taskDao.fetch(happyId); task = taskDao.fetch(happyId);
assertEquals("happy", task.getTitle()); assertEquals("happy", task.getTitle());
task = taskDao.fetch(sadId); task = taskDao.fetch(sadId);
assertEquals("melancholy", task.getTitle()); assertEquals("melancholy", task.getTitle());
} }
/** /**
* Test various task fetch conditions * Test various task fetch conditions
*/ */
@Test @Test
public void testTaskConditions() { public void testTaskConditions() {
// create normal task // create normal task
Task task = new Task(); Task task = new Task();
task.setTitle("normal"); task.setTitle("normal");
taskDao.createNew(task); taskDao.createNew(task);
// create blank task // create blank task
task = new Task(); task = new Task();
task.setTitle(""); task.setTitle("");
taskDao.createNew(task); taskDao.createNew(task);
// create hidden task // create hidden task
task = new Task(); task = new Task();
task.setTitle("hidden"); task.setTitle("hidden");
task.setHideUntil(DateUtilities.now() + 10000); task.setHideUntil(DateUtilities.now() + 10000);
taskDao.createNew(task); taskDao.createNew(task);
// create task with deadlines // create task with deadlines
task = new Task(); task = new Task();
task.setTitle("deadlineInFuture"); task.setTitle("deadlineInFuture");
task.setDueDate(DateUtilities.now() + 10000); task.setDueDate(DateUtilities.now() + 10000);
taskDao.createNew(task); taskDao.createNew(task);
task = new Task(); task = new Task();
task.setTitle("deadlineInPast"); task.setTitle("deadlineInPast");
task.setDueDate(DateUtilities.now() - 10000); task.setDueDate(DateUtilities.now() - 10000);
taskDao.createNew(task); taskDao.createNew(task);
// create completed task // create completed task
task = new Task(); task = new Task();
task.setTitle("completed"); task.setTitle("completed");
task.setCompletionDate(DateUtilities.now() - 10000); task.setCompletionDate(DateUtilities.now() - 10000);
taskDao.createNew(task); taskDao.createNew(task);
// check is active // check is active
assertEquals(5, taskDao.getActiveTasks().size()); assertEquals(5, taskDao.getActiveTasks().size());
// check is visible // check is visible
assertEquals(5, taskDao.getVisibleTasks().size()); assertEquals(5, taskDao.getVisibleTasks().size());
} }
/** /**
* Test task deletion * Test task deletion
*/ */
@Test @Test
public void testTDeletion() { public void testTDeletion() {
assertEquals(0, taskDao.getAll().size()); assertEquals(0, taskDao.getAll().size());
// create task "happy" // create task "happy"
Task task = new Task(); Task task = new Task();
task.setTitle("happy"); task.setTitle("happy");
taskDao.createNew(task); taskDao.createNew(task);
assertEquals(1, taskDao.getAll().size()); assertEquals(1, taskDao.getAll().size());
// delete // delete
long happyId = task.getId(); long happyId = task.getId();
assertEquals(1, taskDao.deleteById(happyId)); assertEquals(1, taskDao.deleteById(happyId));
assertEquals(0, taskDao.getAll().size()); assertEquals(0, taskDao.getAll().size());
} }
/** /**
* Test save without prior create doesn't work * Test save without prior create doesn't work
*/ */
@Test @Test
public void testSaveWithoutCreate() { public void testSaveWithoutCreate() {
// try to save task "happy" // try to save task "happy"
Task task = new Task(); Task task = new Task();
task.setTitle("happy"); task.setTitle("happy");
task.setId(1L); task.setId(1L);
taskDao.save(task); taskDao.save(task);
assertEquals(0, taskDao.getAll().size()); assertEquals(0, taskDao.getAll().size());
} }
/** /**
* Test passing invalid task indices to various things * Test passing invalid task indices to various things
*/ */
@Test @Test
public void testInvalidIndex() { public void testInvalidIndex() {
assertEquals(0, taskDao.getAll().size()); assertEquals(0, taskDao.getAll().size());
assertNull(taskDao.fetch(1)); assertNull(taskDao.fetch(1));
assertEquals(0, taskDao.deleteById(1)); assertEquals(0, taskDao.deleteById(1));
// make sure db still works // make sure db still works
assertEquals(0, taskDao.getAll().size()); assertEquals(0, taskDao.getAll().size());
} }
@Override @Override
protected void inject(TestComponent component) { protected void inject(TestComponent component) {
component.inject(this); component.inject(this);
} }
// TODO check eventing // TODO check eventing
} }

@ -1,17 +1,5 @@
package com.todoroo.astrid.data; package com.todoroo.astrid.data;
import android.support.test.runner.AndroidJUnit4;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.tasks.Snippet;
import org.tasks.time.DateTime;
import java.util.ArrayList;
import java.util.TreeSet;
import static com.todoroo.astrid.data.Task.URGENCY_DAY_AFTER; import static com.todoroo.astrid.data.Task.URGENCY_DAY_AFTER;
import static com.todoroo.astrid.data.Task.URGENCY_IN_TWO_WEEKS; import static com.todoroo.astrid.data.Task.URGENCY_IN_TWO_WEEKS;
import static com.todoroo.astrid.data.Task.URGENCY_NEXT_MONTH; import static com.todoroo.astrid.data.Task.URGENCY_NEXT_MONTH;
@ -31,247 +19,257 @@ import static org.tasks.Freeze.thaw;
import static org.tasks.date.DateTimeUtils.newDateTime; import static org.tasks.date.DateTimeUtils.newDateTime;
import static org.tasks.time.DateTimeUtils.currentTimeMillis; import static org.tasks.time.DateTimeUtils.currentTimeMillis;
import android.support.test.runner.AndroidJUnit4;
import java.util.ArrayList;
import java.util.TreeSet;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.tasks.Snippet;
import org.tasks.time.DateTime;
@RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class)
public class TaskTest { public class TaskTest {
private static final DateTime now = new DateTime(2013, 12, 31, 16, 10, 53, 452); private static final DateTime now = new DateTime(2013, 12, 31, 16, 10, 53, 452);
private static final DateTime specificDueDate = new DateTime(2014, 3, 17, 9, 54, 27, 959); private static final DateTime specificDueDate = new DateTime(2014, 3, 17, 9, 54, 27, 959);
@Before @Before
public void setUp() { public void setUp() {
freezeAt(now); freezeAt(now);
} }
@After @After
public void tearDown() { public void tearDown() {
thaw(); thaw();
} }
@Test @Test
public void testCreateDueDateNoUrgency() { public void testCreateDueDateNoUrgency() {
assertEquals(0, createDueDate(URGENCY_NONE, 1L)); assertEquals(0, createDueDate(URGENCY_NONE, 1L));
} }
@Test @Test
public void testCreateDueDateToday() { public void testCreateDueDateToday() {
long expected = new DateTime(2013, 12, 31, 12, 0, 0, 0).getMillis(); long expected = new DateTime(2013, 12, 31, 12, 0, 0, 0).getMillis();
assertEquals(expected, createDueDate(URGENCY_TODAY, -1L)); assertEquals(expected, createDueDate(URGENCY_TODAY, -1L));
} }
@Test @Test
public void testCreateDueDateTomorrow() { public void testCreateDueDateTomorrow() {
long expected = new DateTime(2014, 1, 1, 12, 0, 0, 0).getMillis(); long expected = new DateTime(2014, 1, 1, 12, 0, 0, 0).getMillis();
assertEquals(expected, createDueDate(URGENCY_TOMORROW, -1L)); assertEquals(expected, createDueDate(URGENCY_TOMORROW, -1L));
} }
@Test @Test
public void testCreateDueDateDayAfter() { public void testCreateDueDateDayAfter() {
long expected = new DateTime(2014, 1, 2, 12, 0, 0, 0).getMillis(); long expected = new DateTime(2014, 1, 2, 12, 0, 0, 0).getMillis();
assertEquals(expected, createDueDate(URGENCY_DAY_AFTER, -1L)); assertEquals(expected, createDueDate(URGENCY_DAY_AFTER, -1L));
} }
@Test @Test
public void testCreateDueDateNextWeek() { public void testCreateDueDateNextWeek() {
long expected = new DateTime(2014, 1, 7, 12, 0, 0, 0).getMillis(); long expected = new DateTime(2014, 1, 7, 12, 0, 0, 0).getMillis();
assertEquals(expected, createDueDate(URGENCY_NEXT_WEEK, -1L)); assertEquals(expected, createDueDate(URGENCY_NEXT_WEEK, -1L));
} }
@Test @Test
public void testCreateDueDateInTwoWeeks() { public void testCreateDueDateInTwoWeeks() {
long expected = new DateTime(2014, 1, 14, 12, 0, 0, 0).getMillis(); long expected = new DateTime(2014, 1, 14, 12, 0, 0, 0).getMillis();
assertEquals(expected, createDueDate(URGENCY_IN_TWO_WEEKS, -1L)); assertEquals(expected, createDueDate(URGENCY_IN_TWO_WEEKS, -1L));
} }
@Test @Test
public void testCreateDueDateNextMonth() { public void testCreateDueDateNextMonth() {
long expected = new DateTime(2014, 1, 31, 12, 0, 0, 0).getMillis(); long expected = new DateTime(2014, 1, 31, 12, 0, 0, 0).getMillis();
assertEquals(expected, createDueDate(URGENCY_NEXT_MONTH, -1L)); assertEquals(expected, createDueDate(URGENCY_NEXT_MONTH, -1L));
} }
@Test @Test
public void testRemoveTimeForSpecificDay() { public void testRemoveTimeForSpecificDay() {
long expected = specificDueDate long expected = specificDueDate
.withHourOfDay(12) .withHourOfDay(12)
.withMinuteOfHour(0) .withMinuteOfHour(0)
.withSecondOfMinute(0) .withSecondOfMinute(0)
.withMillisOfSecond(0) .withMillisOfSecond(0)
.getMillis(); .getMillis();
assertEquals(expected, createDueDate(URGENCY_SPECIFIC_DAY, specificDueDate.getMillis())); assertEquals(expected, createDueDate(URGENCY_SPECIFIC_DAY, specificDueDate.getMillis()));
} }
@Test @Test
public void testRemoveSecondsForSpecificTime() { public void testRemoveSecondsForSpecificTime() {
long expected = specificDueDate long expected = specificDueDate
.withSecondOfMinute(1) .withSecondOfMinute(1)
.withMillisOfSecond(0) .withMillisOfSecond(0)
.getMillis(); .getMillis();
assertEquals(expected, createDueDate(URGENCY_SPECIFIC_DAY_TIME, specificDueDate.getMillis())); assertEquals(expected, createDueDate(URGENCY_SPECIFIC_DAY_TIME, specificDueDate.getMillis()));
} }
@Test @Test
public void testTaskHasDueTime() { public void testTaskHasDueTime() {
Task task = new Task(); Task task = new Task();
task.setDueDate(1388516076000L); task.setDueDate(1388516076000L);
assertTrue(task.hasDueTime()); assertTrue(task.hasDueTime());
assertTrue(task.hasDueDate()); assertTrue(task.hasDueDate());
} }
@Test @Test
public void testTaskHasDueDate() { public void testTaskHasDueDate() {
Task task = new Task(); Task task = new Task();
task.setDueDate(1388469600000L); task.setDueDate(1388469600000L);
assertFalse(task.hasDueTime()); assertFalse(task.hasDueTime());
assertTrue(task.hasDueDate()); assertTrue(task.hasDueDate());
} }
@Test @Test
public void testDoesHaveDueTime() { public void testDoesHaveDueTime() {
assertTrue(hasDueTime(1388516076000L)); assertTrue(hasDueTime(1388516076000L));
} }
@Test @Test
public void testNoDueTime() { public void testNoDueTime() {
assertFalse(hasDueTime(newDateTime().startOfDay().getMillis())); assertFalse(hasDueTime(newDateTime().startOfDay().getMillis()));
assertFalse(hasDueTime(newDateTime().withMillisOfDay(60000).getMillis())); assertFalse(hasDueTime(newDateTime().withMillisOfDay(60000).getMillis()));
} }
@Test @Test
public void testHasDueTime() { public void testHasDueTime() {
assertTrue(hasDueTime(newDateTime().withMillisOfDay(1).getMillis())); assertTrue(hasDueTime(newDateTime().withMillisOfDay(1).getMillis()));
assertTrue(hasDueTime(newDateTime().withMillisOfDay(1000).getMillis())); assertTrue(hasDueTime(newDateTime().withMillisOfDay(1000).getMillis()));
assertTrue(hasDueTime(newDateTime().withMillisOfDay(59999).getMillis())); assertTrue(hasDueTime(newDateTime().withMillisOfDay(59999).getMillis()));
} }
@Test @Test
public void testDoesNotHaveDueTime() { public void testDoesNotHaveDueTime() {
assertFalse(hasDueTime(1388469600000L)); assertFalse(hasDueTime(1388469600000L));
} }
@Test @Test
public void testNewTaskIsNotCompleted() { public void testNewTaskIsNotCompleted() {
assertFalse(new Task().isCompleted()); assertFalse(new Task().isCompleted());
} }
@Test @Test
public void testNewTaskNotDeleted() { public void testNewTaskNotDeleted() {
assertFalse(new Task().isDeleted()); assertFalse(new Task().isDeleted());
} }
@Test @Test
public void testNewTaskNotHidden() { public void testNewTaskNotHidden() {
assertFalse(new Task().isHidden()); assertFalse(new Task().isHidden());
} }
@Test @Test
public void testNewTaskDoesNotHaveDueDateOrTime() { public void testNewTaskDoesNotHaveDueDateOrTime() {
assertFalse(new Task().hasDueDate()); assertFalse(new Task().hasDueDate());
assertFalse(new Task().hasDueTime()); assertFalse(new Task().hasDueTime());
} }
@Test @Test
public void testTaskIsCompleted() { public void testTaskIsCompleted() {
Task task = new Task(); Task task = new Task();
task.setCompletionDate(1L); task.setCompletionDate(1L);
assertTrue(task.isCompleted()); assertTrue(task.isCompleted());
} }
@Test @Test
public void testTaskIsNotHiddenAtHideUntilTime() { public void testTaskIsNotHiddenAtHideUntilTime() {
final long now = currentTimeMillis(); final long now = currentTimeMillis();
freezeAt(now).thawAfter(new Snippet() {{ freezeAt(now).thawAfter(new Snippet() {{
Task task = new Task(); Task task = new Task();
task.setHideUntil(now); task.setHideUntil(now);
assertFalse(task.isHidden()); assertFalse(task.isHidden());
}}); }});
} }
@Test @Test
public void testTaskIsHiddenBeforeHideUntilTime() { public void testTaskIsHiddenBeforeHideUntilTime() {
final long now = currentTimeMillis(); final long now = currentTimeMillis();
freezeAt(now).thawAfter(new Snippet() {{ freezeAt(now).thawAfter(new Snippet() {{
Task task = new Task(); Task task = new Task();
task.setHideUntil(now + 1); task.setHideUntil(now + 1);
assertTrue(task.isHidden()); assertTrue(task.isHidden());
}}); }});
} }
@Test @Test
public void testTaskIsDeleted() { public void testTaskIsDeleted() {
Task task = new Task(); Task task = new Task();
task.setDeletionDate(1L); task.setDeletionDate(1L);
assertTrue(task.isDeleted()); assertTrue(task.isDeleted());
} }
@Test @Test
public void testTaskWithNoDueDateIsOverdue() { public void testTaskWithNoDueDateIsOverdue() {
assertTrue(new Task().isOverdue()); assertTrue(new Task().isOverdue());
} }
@Test @Test
public void testTaskNotOverdueAtDueTime() { public void testTaskNotOverdueAtDueTime() {
final long now = currentTimeMillis(); final long now = currentTimeMillis();
freezeAt(now).thawAfter(new Snippet() {{ freezeAt(now).thawAfter(new Snippet() {{
Task task = new Task(); Task task = new Task();
task.setDueDate(now); task.setDueDate(now);
assertFalse(task.isOverdue()); assertFalse(task.isOverdue());
}}); }});
} }
@Test @Test
public void testTaskIsOverduePastDueTime() { public void testTaskIsOverduePastDueTime() {
final long dueDate = currentTimeMillis(); final long dueDate = currentTimeMillis();
freezeAt(dueDate + 1).thawAfter(new Snippet() {{ freezeAt(dueDate + 1).thawAfter(new Snippet() {{
Task task = new Task(); Task task = new Task();
task.setDueDate(dueDate); task.setDueDate(dueDate);
assertTrue(task.isOverdue()); assertTrue(task.isOverdue());
}}); }});
} }
@Test @Test
public void testTaskNotOverdueBeforeNoonOnDueDate() { public void testTaskNotOverdueBeforeNoonOnDueDate() {
final DateTime dueDate = new DateTime().startOfDay(); final DateTime dueDate = new DateTime().startOfDay();
freezeAt(dueDate.plusHours(12).minusMillis(1)).thawAfter(new Snippet() {{ freezeAt(dueDate.plusHours(12).minusMillis(1)).thawAfter(new Snippet() {{
Task task = new Task(); Task task = new Task();
task.setDueDate(dueDate.getMillis()); task.setDueDate(dueDate.getMillis());
assertFalse(task.hasDueTime()); assertFalse(task.hasDueTime());
assertFalse(task.isOverdue()); assertFalse(task.isOverdue());
}}); }});
} }
@Test @Test
public void testTaskOverdueAtNoonOnDueDate() { public void testTaskOverdueAtNoonOnDueDate() {
final DateTime dueDate = new DateTime().startOfDay(); final DateTime dueDate = new DateTime().startOfDay();
freezeAt(dueDate.plusHours(12)).thawAfter(new Snippet() {{ freezeAt(dueDate.plusHours(12)).thawAfter(new Snippet() {{
Task task = new Task(); Task task = new Task();
task.setDueDate(dueDate.getMillis()); task.setDueDate(dueDate.getMillis());
assertFalse(task.hasDueTime()); assertFalse(task.hasDueTime());
assertFalse(task.isOverdue()); assertFalse(task.isOverdue());
}}); }});
} }
@Test @Test
public void testTaskWithNoDueTimeIsOverdue() { public void testTaskWithNoDueTimeIsOverdue() {
final DateTime dueDate = new DateTime().startOfDay(); final DateTime dueDate = new DateTime().startOfDay();
freezeAt(dueDate.plusDays(1)).thawAfter(new Snippet() {{ freezeAt(dueDate.plusDays(1)).thawAfter(new Snippet() {{
Task task = new Task(); Task task = new Task();
task.setDueDate(dueDate.getMillis()); task.setDueDate(dueDate.getMillis());
assertFalse(task.hasDueTime()); assertFalse(task.hasDueTime());
assertTrue(task.isOverdue()); assertTrue(task.isOverdue());
}}); }});
} }
@Test @Test
public void testSanity() { public void testSanity() {
assertTrue(Task.IMPORTANCE_DO_OR_DIE < Task.IMPORTANCE_MUST_DO); assertTrue(Task.IMPORTANCE_DO_OR_DIE < Task.IMPORTANCE_MUST_DO);
assertTrue(Task.IMPORTANCE_MUST_DO < Task.IMPORTANCE_SHOULD_DO); assertTrue(Task.IMPORTANCE_MUST_DO < Task.IMPORTANCE_SHOULD_DO);
assertTrue(Task.IMPORTANCE_SHOULD_DO < Task.IMPORTANCE_NONE); assertTrue(Task.IMPORTANCE_SHOULD_DO < Task.IMPORTANCE_NONE);
ArrayList<Integer> reminderFlags = new ArrayList<>(); ArrayList<Integer> reminderFlags = new ArrayList<>();
reminderFlags.add(Task.NOTIFY_AFTER_DEADLINE); reminderFlags.add(Task.NOTIFY_AFTER_DEADLINE);
reminderFlags.add(Task.NOTIFY_AT_DEADLINE); reminderFlags.add(Task.NOTIFY_AT_DEADLINE);
reminderFlags.add(Task.NOTIFY_MODE_NONSTOP); reminderFlags.add(Task.NOTIFY_MODE_NONSTOP);
// assert no duplicates // assert no duplicates
assertEquals(new TreeSet<>(reminderFlags).size(), reminderFlags.size()); assertEquals(new TreeSet<>(reminderFlags).size(), reminderFlags.size());
} }
} }

@ -1,46 +1,43 @@
package com.todoroo.astrid.model; package com.todoroo.astrid.model;
import android.support.test.runner.AndroidJUnit4; import static junit.framework.Assert.assertEquals;
import static org.tasks.Freeze.freezeClock;
import static org.tasks.time.DateTimeUtils.currentTimeMillis;
import android.support.test.runner.AndroidJUnit4;
import com.todoroo.astrid.dao.TaskDao; import com.todoroo.astrid.dao.TaskDao;
import com.todoroo.astrid.data.Task; import com.todoroo.astrid.data.Task;
import javax.inject.Inject;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.tasks.Snippet; import org.tasks.Snippet;
import org.tasks.injection.InjectingTestCase; import org.tasks.injection.InjectingTestCase;
import org.tasks.injection.TestComponent; import org.tasks.injection.TestComponent;
import javax.inject.Inject;
import static junit.framework.Assert.assertEquals;
import static org.tasks.Freeze.freezeClock;
import static org.tasks.time.DateTimeUtils.currentTimeMillis;
@RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class)
public class TaskTest extends InjectingTestCase { public class TaskTest extends InjectingTestCase {
@Inject TaskDao taskDao; @Inject TaskDao taskDao;
@Test @Test
public void testSavedTaskHasCreationDate() { public void testSavedTaskHasCreationDate() {
freezeClock().thawAfter(new Snippet() {{ freezeClock().thawAfter(new Snippet() {{
Task task = new Task(); Task task = new Task();
taskDao.createNew(task); taskDao.createNew(task);
assertEquals(currentTimeMillis(), (long) task.getCreationDate()); assertEquals(currentTimeMillis(), (long) task.getCreationDate());
}}); }});
} }
@Test @Test
public void testReadTaskFromDb() { public void testReadTaskFromDb() {
Task task = new Task(); Task task = new Task();
taskDao.createNew(task); taskDao.createNew(task);
final Task fromDb = taskDao.fetch(task.getId()); final Task fromDb = taskDao.fetch(task.getId());
assertEquals(task, fromDb); assertEquals(task, fromDb);
} }
@Override @Override
protected void inject(TestComponent component) { protected void inject(TestComponent component) {
component.inject(this); component.inject(this);
} }
} }

@ -1,29 +1,5 @@
package com.todoroo.astrid.reminders; package com.todoroo.astrid.reminders;
import android.support.test.runner.AndroidJUnit4;
import com.todoroo.astrid.dao.TaskDao;
import com.todoroo.astrid.data.Task;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InOrder;
import org.tasks.R;
import org.tasks.Snippet;
import org.tasks.injection.InjectingTestCase;
import org.tasks.injection.TestComponent;
import org.tasks.jobs.JobQueue;
import org.tasks.jobs.Reminder;
import org.tasks.preferences.Preferences;
import org.tasks.reminders.Random;
import org.tasks.time.DateTime;
import java.util.concurrent.TimeUnit;
import javax.inject.Inject;
import static com.natpryce.makeiteasy.MakeItEasy.with; import static com.natpryce.makeiteasy.MakeItEasy.with;
import static com.todoroo.andlib.utility.DateUtilities.ONE_HOUR; import static com.todoroo.andlib.utility.DateUtilities.ONE_HOUR;
import static com.todoroo.andlib.utility.DateUtilities.ONE_WEEK; import static com.todoroo.andlib.utility.DateUtilities.ONE_WEEK;
@ -48,323 +24,354 @@ import static org.tasks.makers.TaskMaker.REMINDER_LAST;
import static org.tasks.makers.TaskMaker.SNOOZE_TIME; import static org.tasks.makers.TaskMaker.SNOOZE_TIME;
import static org.tasks.makers.TaskMaker.newTask; import static org.tasks.makers.TaskMaker.newTask;
import android.support.test.runner.AndroidJUnit4;
import com.todoroo.astrid.dao.TaskDao;
import com.todoroo.astrid.data.Task;
import java.util.concurrent.TimeUnit;
import javax.inject.Inject;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InOrder;
import org.tasks.R;
import org.tasks.Snippet;
import org.tasks.injection.InjectingTestCase;
import org.tasks.injection.TestComponent;
import org.tasks.jobs.JobQueue;
import org.tasks.jobs.Reminder;
import org.tasks.preferences.Preferences;
import org.tasks.reminders.Random;
import org.tasks.time.DateTime;
@RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class)
public class ReminderServiceTest extends InjectingTestCase { public class ReminderServiceTest extends InjectingTestCase {
@Inject Preferences preferences; @Inject Preferences preferences;
@Inject TaskDao taskDao; @Inject TaskDao taskDao;
private ReminderService service; private ReminderService service;
private Random random; private Random random;
private JobQueue jobs; private JobQueue jobs;
@Before @Before
public void before() { public void before() {
jobs = mock(JobQueue.class); jobs = mock(JobQueue.class);
random = mock(Random.class); random = mock(Random.class);
when(random.nextFloat()).thenReturn(1.0f); when(random.nextFloat()).thenReturn(1.0f);
preferences.reset(); preferences.reset();
service = new ReminderService(preferences, jobs, random, taskDao); service = new ReminderService(preferences, jobs, random, taskDao);
} }
@After @After
public void after() { public void after() {
verifyNoMoreInteractions(jobs); verifyNoMoreInteractions(jobs);
} }
@Override @Override
protected void inject(TestComponent component) { protected void inject(TestComponent component) {
component.inject(this); component.inject(this);
} }
@Test @Test
public void dontScheduleDueDateReminderWhenFlagNotSet() { public void dontScheduleDueDateReminderWhenFlagNotSet() {
service.scheduleAlarm(newTask(with(ID, 1L), with(DUE_TIME, newDateTime()))); service.scheduleAlarm(newTask(with(ID, 1L), with(DUE_TIME, newDateTime())));
verify(jobs).cancelReminder(1); verify(jobs).cancelReminder(1);
} }
@Test @Test
public void dontScheduleDueDateReminderWhenTimeNotSet() { public void dontScheduleDueDateReminderWhenTimeNotSet() {
service.scheduleAlarm(newTask(with(ID, 1L), with(REMINDERS, NOTIFY_AT_DEADLINE))); service.scheduleAlarm(newTask(with(ID, 1L), with(REMINDERS, NOTIFY_AT_DEADLINE)));
verify(jobs).cancelReminder(1); verify(jobs).cancelReminder(1);
} }
@Test @Test
public void schedulePastDueDate() { public void schedulePastDueDate() {
Task task = newTask( Task task = newTask(
with(ID, 1L), with(ID, 1L),
with(DUE_TIME, newDateTime().minusDays(1)), with(DUE_TIME, newDateTime().minusDays(1)),
with(REMINDERS, NOTIFY_AT_DEADLINE)); with(REMINDERS, NOTIFY_AT_DEADLINE));
service.scheduleAlarm(task); service.scheduleAlarm(task);
InOrder order = inOrder(jobs); InOrder order = inOrder(jobs);
order.verify(jobs).cancelReminder(1); order.verify(jobs).cancelReminder(1);
order.verify(jobs).add(new Reminder(1, task.getDueDate(), ReminderService.TYPE_DUE)); order.verify(jobs).add(new Reminder(1, task.getDueDate(), ReminderService.TYPE_DUE));
} }
@Test @Test
public void scheduleFutureDueDate() { public void scheduleFutureDueDate() {
Task task = newTask( Task task = newTask(
with(ID, 1L), with(ID, 1L),
with(DUE_TIME, newDateTime().plusDays(1)), with(DUE_TIME, newDateTime().plusDays(1)),
with(REMINDERS, NOTIFY_AT_DEADLINE)); with(REMINDERS, NOTIFY_AT_DEADLINE));
service.scheduleAlarm(task); service.scheduleAlarm(task);
InOrder order = inOrder(jobs); InOrder order = inOrder(jobs);
order.verify(jobs).cancelReminder(1); order.verify(jobs).cancelReminder(1);
order.verify(jobs).add(new Reminder(1, task.getDueDate(), ReminderService.TYPE_DUE)); order.verify(jobs).add(new Reminder(1, task.getDueDate(), ReminderService.TYPE_DUE));
} }
@Test @Test
public void scheduleReminderAtDefaultDueTime() { public void scheduleReminderAtDefaultDueTime() {
DateTime now = newDateTime(); DateTime now = newDateTime();
Task task = newTask( Task task = newTask(
with(ID, 1L), with(ID, 1L),
with(DUE_DATE, now), with(DUE_DATE, now),
with(REMINDERS, NOTIFY_AT_DEADLINE)); with(REMINDERS, NOTIFY_AT_DEADLINE));
service.scheduleAlarm(task); service.scheduleAlarm(task);
InOrder order = inOrder(jobs); InOrder order = inOrder(jobs);
order.verify(jobs).cancelReminder(1); order.verify(jobs).cancelReminder(1);
order.verify(jobs).add(new Reminder(1, now.startOfDay().withHourOfDay(18).getMillis(), ReminderService.TYPE_DUE)); order.verify(jobs).add(
} new Reminder(1, now.startOfDay().withHourOfDay(18).getMillis(), ReminderService.TYPE_DUE));
}
@Test
public void dontScheduleReminderForCompletedTask() { @Test
Task task = newTask( public void dontScheduleReminderForCompletedTask() {
with(ID, 1L), Task task = newTask(
with(DUE_TIME, newDateTime().plusDays(1)), with(ID, 1L),
with(COMPLETION_TIME, newDateTime()), with(DUE_TIME, newDateTime().plusDays(1)),
with(REMINDERS, NOTIFY_AT_DEADLINE)); with(COMPLETION_TIME, newDateTime()),
with(REMINDERS, NOTIFY_AT_DEADLINE));
service.scheduleAlarm(task);
service.scheduleAlarm(task);
verify(jobs).cancelReminder(1);
} verify(jobs).cancelReminder(1);
}
@Test
public void dontScheduleReminderForDeletedTask() { @Test
Task task = newTask( public void dontScheduleReminderForDeletedTask() {
with(ID, 1L), Task task = newTask(
with(DUE_TIME, newDateTime().plusDays(1)), with(ID, 1L),
with(DELETION_TIME, newDateTime()), with(DUE_TIME, newDateTime().plusDays(1)),
with(REMINDERS, NOTIFY_AT_DEADLINE)); with(DELETION_TIME, newDateTime()),
with(REMINDERS, NOTIFY_AT_DEADLINE));
service.scheduleAlarm(task);
service.scheduleAlarm(task);
verify(jobs).cancelReminder(1);
} verify(jobs).cancelReminder(1);
}
@Test
public void dontScheduleDueDateReminderWhenAlreadyReminded() { @Test
DateTime now = newDateTime(); public void dontScheduleDueDateReminderWhenAlreadyReminded() {
Task task = newTask( DateTime now = newDateTime();
with(ID, 1L), Task task = newTask(
with(DUE_TIME, now), with(ID, 1L),
with(REMINDER_LAST, now.plusSeconds(1)), with(DUE_TIME, now),
with(REMINDERS, NOTIFY_AT_DEADLINE)); with(REMINDER_LAST, now.plusSeconds(1)),
with(REMINDERS, NOTIFY_AT_DEADLINE));
service.scheduleAlarm(task);
service.scheduleAlarm(task);
verify(jobs).cancelReminder(1);
} verify(jobs).cancelReminder(1);
}
@Test
public void ignoreStaleSnoozeTime() { @Test
Task task = newTask( public void ignoreStaleSnoozeTime() {
with(ID, 1L), Task task = newTask(
with(DUE_TIME, newDateTime()), with(ID, 1L),
with(SNOOZE_TIME, newDateTime().minusMinutes(5)), with(DUE_TIME, newDateTime()),
with(REMINDER_LAST, newDateTime().minusMinutes(4)), with(SNOOZE_TIME, newDateTime().minusMinutes(5)),
with(REMINDERS, NOTIFY_AT_DEADLINE)); with(REMINDER_LAST, newDateTime().minusMinutes(4)),
service.scheduleAlarm(task); with(REMINDERS, NOTIFY_AT_DEADLINE));
service.scheduleAlarm(task);
InOrder order = inOrder(jobs);
order.verify(jobs).cancelReminder(1); InOrder order = inOrder(jobs);
order.verify(jobs).add(new Reminder(1, task.getDueDate(), ReminderService.TYPE_DUE)); order.verify(jobs).cancelReminder(1);
} order.verify(jobs).add(new Reminder(1, task.getDueDate(), ReminderService.TYPE_DUE));
}
@Test
public void dontIgnoreMissedSnoozeTime() { @Test
DateTime dueDate = newDateTime(); public void dontIgnoreMissedSnoozeTime() {
Task task = newTask( DateTime dueDate = newDateTime();
with(ID, 1L), Task task = newTask(
with(DUE_TIME, dueDate), with(ID, 1L),
with(SNOOZE_TIME, dueDate.minusMinutes(4)), with(DUE_TIME, dueDate),
with(REMINDER_LAST, dueDate.minusMinutes(5)), with(SNOOZE_TIME, dueDate.minusMinutes(4)),
with(REMINDERS, NOTIFY_AT_DEADLINE)); with(REMINDER_LAST, dueDate.minusMinutes(5)),
service.scheduleAlarm(task); with(REMINDERS, NOTIFY_AT_DEADLINE));
service.scheduleAlarm(task);
InOrder order = inOrder(jobs);
order.verify(jobs).cancelReminder(1); InOrder order = inOrder(jobs);
order.verify(jobs).add(new Reminder(1, task.getReminderSnooze(), ReminderService.TYPE_SNOOZE)); order.verify(jobs).cancelReminder(1);
} order.verify(jobs).add(new Reminder(1, task.getReminderSnooze(), ReminderService.TYPE_SNOOZE));
}
@Test
public void scheduleInitialRandomReminder() { @Test
freezeClock().thawAfter(new Snippet() {{ public void scheduleInitialRandomReminder() {
DateTime now = newDateTime(); freezeClock().thawAfter(new Snippet() {{
when(random.nextFloat()).thenReturn(0.3865f); DateTime now = newDateTime();
Task task = newTask( when(random.nextFloat()).thenReturn(0.3865f);
with(ID, 1L), Task task = newTask(
with(REMINDER_LAST, (DateTime) null), with(ID, 1L),
with(CREATION_TIME, now.minusDays(1)), with(REMINDER_LAST, (DateTime) null),
with(RANDOM_REMINDER_PERIOD, ONE_WEEK)); with(CREATION_TIME, now.minusDays(1)),
with(RANDOM_REMINDER_PERIOD, ONE_WEEK));
service.scheduleAlarm(task);
service.scheduleAlarm(task);
InOrder order = inOrder(jobs);
order.verify(jobs).cancelReminder(1); InOrder order = inOrder(jobs);
order.verify(jobs).add(new Reminder(1L, now.minusDays(1).getMillis() + 584206592, ReminderService.TYPE_RANDOM)); order.verify(jobs).cancelReminder(1);
}}); order.verify(jobs).add(
} new Reminder(1L, now.minusDays(1).getMillis() + 584206592, ReminderService.TYPE_RANDOM));
}});
@Test }
public void scheduleNextRandomReminder() {
freezeClock().thawAfter(new Snippet() {{ @Test
DateTime now = newDateTime(); public void scheduleNextRandomReminder() {
when(random.nextFloat()).thenReturn(0.3865f); freezeClock().thawAfter(new Snippet() {{
Task task = newTask( DateTime now = newDateTime();
with(ID, 1L), when(random.nextFloat()).thenReturn(0.3865f);
with(REMINDER_LAST, now.minusDays(1)), Task task = newTask(
with(CREATION_TIME, now.minusDays(30)), with(ID, 1L),
with(RANDOM_REMINDER_PERIOD, ONE_WEEK)); with(REMINDER_LAST, now.minusDays(1)),
with(CREATION_TIME, now.minusDays(30)),
service.scheduleAlarm(task); with(RANDOM_REMINDER_PERIOD, ONE_WEEK));
InOrder order = inOrder(jobs); service.scheduleAlarm(task);
order.verify(jobs).cancelReminder(1);
order.verify(jobs).add(new Reminder(1L, now.minusDays(1).getMillis() + 584206592, ReminderService.TYPE_RANDOM)); InOrder order = inOrder(jobs);
}}); order.verify(jobs).cancelReminder(1);
} order.verify(jobs).add(
new Reminder(1L, now.minusDays(1).getMillis() + 584206592, ReminderService.TYPE_RANDOM));
@Test }});
public void scheduleOverdueRandomReminder() { }
freezeClock().thawAfter(new Snippet() {{
DateTime now = newDateTime(); @Test
when(random.nextFloat()).thenReturn(0.3865f); public void scheduleOverdueRandomReminder() {
Task task = newTask( freezeClock().thawAfter(new Snippet() {{
with(ID, 1L), DateTime now = newDateTime();
with(REMINDER_LAST, now.minusDays(14)), when(random.nextFloat()).thenReturn(0.3865f);
with(CREATION_TIME, now.minusDays(30)), Task task = newTask(
with(RANDOM_REMINDER_PERIOD, ONE_WEEK)); with(ID, 1L),
with(REMINDER_LAST, now.minusDays(14)),
service.scheduleAlarm(task); with(CREATION_TIME, now.minusDays(30)),
with(RANDOM_REMINDER_PERIOD, ONE_WEEK));
InOrder order = inOrder(jobs);
order.verify(jobs).cancelReminder(1); service.scheduleAlarm(task);
order.verify(jobs).add(new Reminder(1L, now.getMillis() + 10148400, ReminderService.TYPE_RANDOM));
}}); InOrder order = inOrder(jobs);
} order.verify(jobs).cancelReminder(1);
order.verify(jobs)
@Test .add(new Reminder(1L, now.getMillis() + 10148400, ReminderService.TYPE_RANDOM));
public void scheduleOverdueNoLastReminder() { }});
Task task = newTask( }
with(ID, 1L),
with(DUE_TIME, new DateTime(2017, 9, 22, 15, 30)), @Test
with(REMINDER_LAST, (DateTime) null), public void scheduleOverdueNoLastReminder() {
with(REMINDERS, NOTIFY_AFTER_DEADLINE)); Task task = newTask(
with(ID, 1L),
service.scheduleAlarm(task); with(DUE_TIME, new DateTime(2017, 9, 22, 15, 30)),
with(REMINDER_LAST, (DateTime) null),
InOrder order = inOrder(jobs); with(REMINDERS, NOTIFY_AFTER_DEADLINE));
order.verify(jobs).cancelReminder(1);
order.verify(jobs).add(new Reminder(1L, new DateTime(2017, 9, 23, 15, 30, 1, 0).getMillis(), ReminderService.TYPE_OVERDUE)); service.scheduleAlarm(task);
}
InOrder order = inOrder(jobs);
@Test order.verify(jobs).cancelReminder(1);
public void scheduleOverduePastLastReminder() { order.verify(jobs).add(new Reminder(1L, new DateTime(2017, 9, 23, 15, 30, 1, 0).getMillis(),
Task task = newTask( ReminderService.TYPE_OVERDUE));
with(ID, 1L), }
with(DUE_TIME, new DateTime(2017, 9, 22, 15, 30)),
with(REMINDER_LAST, new DateTime(2017, 9, 24, 12, 0)), @Test
with(REMINDERS, NOTIFY_AFTER_DEADLINE)); public void scheduleOverduePastLastReminder() {
Task task = newTask(
service.scheduleAlarm(task); with(ID, 1L),
with(DUE_TIME, new DateTime(2017, 9, 22, 15, 30)),
InOrder order = inOrder(jobs); with(REMINDER_LAST, new DateTime(2017, 9, 24, 12, 0)),
order.verify(jobs).cancelReminder(1); with(REMINDERS, NOTIFY_AFTER_DEADLINE));
order.verify(jobs).add(new Reminder(1L, new DateTime(2017, 9, 24, 15, 30, 1, 0).getMillis(), ReminderService.TYPE_OVERDUE));
} service.scheduleAlarm(task);
@Test InOrder order = inOrder(jobs);
public void scheduleOverdueBeforeLastReminder() { order.verify(jobs).cancelReminder(1);
Task task = newTask( order.verify(jobs).add(new Reminder(1L, new DateTime(2017, 9, 24, 15, 30, 1, 0).getMillis(),
with(ID, 1L), ReminderService.TYPE_OVERDUE));
with(DUE_TIME, new DateTime(2017, 9, 22, 12, 30)), }
with(REMINDER_LAST, new DateTime(2017, 9, 24, 15, 0)),
with(REMINDERS, NOTIFY_AFTER_DEADLINE)); @Test
public void scheduleOverdueBeforeLastReminder() {
service.scheduleAlarm(task); Task task = newTask(
with(ID, 1L),
InOrder order = inOrder(jobs); with(DUE_TIME, new DateTime(2017, 9, 22, 12, 30)),
order.verify(jobs).cancelReminder(1); with(REMINDER_LAST, new DateTime(2017, 9, 24, 15, 0)),
order.verify(jobs).add(new Reminder(1L, new DateTime(2017, 9, 25, 12, 30, 1, 0).getMillis(), ReminderService.TYPE_OVERDUE)); with(REMINDERS, NOTIFY_AFTER_DEADLINE));
}
service.scheduleAlarm(task);
@Test
public void scheduleOverdueWithNoDueTime() { InOrder order = inOrder(jobs);
preferences.setInt(R.string.p_rmd_time, (int) TimeUnit.HOURS.toMillis(15)); order.verify(jobs).cancelReminder(1);
Task task = newTask( order.verify(jobs).add(new Reminder(1L, new DateTime(2017, 9, 25, 12, 30, 1, 0).getMillis(),
with(ID, 1L), ReminderService.TYPE_OVERDUE));
with(DUE_DATE, new DateTime(2017, 9, 22)), }
with(REMINDER_LAST, new DateTime(2017, 9, 23, 12, 17, 59, 999)),
with(REMINDERS, NOTIFY_AFTER_DEADLINE)); @Test
public void scheduleOverdueWithNoDueTime() {
service.scheduleAlarm(task); preferences.setInt(R.string.p_rmd_time, (int) TimeUnit.HOURS.toMillis(15));
Task task = newTask(
InOrder order = inOrder(jobs); with(ID, 1L),
order.verify(jobs).cancelReminder(1); with(DUE_DATE, new DateTime(2017, 9, 22)),
order.verify(jobs).add(new Reminder(1L, new DateTime(2017, 9, 23, 15, 0, 0, 0).getMillis(), ReminderService.TYPE_OVERDUE)); with(REMINDER_LAST, new DateTime(2017, 9, 23, 12, 17, 59, 999)),
} with(REMINDERS, NOTIFY_AFTER_DEADLINE));
@Test service.scheduleAlarm(task);
public void scheduleSubsequentOverdueReminder() {
Task task = newTask( InOrder order = inOrder(jobs);
with(ID, 1L), order.verify(jobs).cancelReminder(1);
with(DUE_TIME, new DateTime(2017, 9, 22, 15, 30)), order.verify(jobs).add(new Reminder(1L, new DateTime(2017, 9, 23, 15, 0, 0, 0).getMillis(),
with(REMINDER_LAST, new DateTime(2017, 9, 23, 15, 30, 59, 999)), ReminderService.TYPE_OVERDUE));
with(REMINDERS, NOTIFY_AFTER_DEADLINE)); }
service.scheduleAlarm(task); @Test
public void scheduleSubsequentOverdueReminder() {
InOrder order = inOrder(jobs); Task task = newTask(
order.verify(jobs).cancelReminder(1); with(ID, 1L),
order.verify(jobs).add(new Reminder(1L, new DateTime(2017, 9, 24, 15, 30, 1, 0).getMillis(), ReminderService.TYPE_OVERDUE)); with(DUE_TIME, new DateTime(2017, 9, 22, 15, 30)),
} with(REMINDER_LAST, new DateTime(2017, 9, 23, 15, 30, 59, 999)),
with(REMINDERS, NOTIFY_AFTER_DEADLINE));
@Test
public void scheduleOverdueAfterLastReminder() { service.scheduleAlarm(task);
Task task = newTask(
with(ID, 1L), InOrder order = inOrder(jobs);
with(DUE_TIME, new DateTime(2017, 9, 22, 15, 30)), order.verify(jobs).cancelReminder(1);
with(REMINDER_LAST, new DateTime(2017, 9, 23, 12, 17, 59, 999)), order.verify(jobs).add(new Reminder(1L, new DateTime(2017, 9, 24, 15, 30, 1, 0).getMillis(),
with(REMINDERS, NOTIFY_AFTER_DEADLINE)); ReminderService.TYPE_OVERDUE));
}
service.scheduleAlarm(task);
@Test
InOrder order = inOrder(jobs); public void scheduleOverdueAfterLastReminder() {
order.verify(jobs).cancelReminder(1); Task task = newTask(
order.verify(jobs).add(new Reminder(1L, new DateTime(2017, 9, 23, 15, 30, 1, 0).getMillis(), ReminderService.TYPE_OVERDUE)); with(ID, 1L),
} with(DUE_TIME, new DateTime(2017, 9, 22, 15, 30)),
with(REMINDER_LAST, new DateTime(2017, 9, 23, 12, 17, 59, 999)),
@Test with(REMINDERS, NOTIFY_AFTER_DEADLINE));
public void snoozeOverridesAll() {
DateTime now = newDateTime(); service.scheduleAlarm(task);
Task task = newTask(
with(ID, 1L), InOrder order = inOrder(jobs);
with(DUE_TIME, now), order.verify(jobs).cancelReminder(1);
with(SNOOZE_TIME, now.plusMonths(12)), order.verify(jobs).add(new Reminder(1L, new DateTime(2017, 9, 23, 15, 30, 1, 0).getMillis(),
with(REMINDERS, NOTIFY_AT_DEADLINE | NOTIFY_AFTER_DEADLINE), ReminderService.TYPE_OVERDUE));
with(RANDOM_REMINDER_PERIOD, ONE_HOUR)); }
service.scheduleAlarm(task); @Test
public void snoozeOverridesAll() {
InOrder order = inOrder(jobs); DateTime now = newDateTime();
order.verify(jobs).cancelReminder(1); Task task = newTask(
order.verify(jobs).add(new Reminder(1, now.plusMonths(12).getMillis(), ReminderService.TYPE_SNOOZE)); with(ID, 1L),
} with(DUE_TIME, now),
with(SNOOZE_TIME, now.plusMonths(12)),
with(REMINDERS, NOTIFY_AT_DEADLINE | NOTIFY_AFTER_DEADLINE),
with(RANDOM_REMINDER_PERIOD, ONE_HOUR));
service.scheduleAlarm(task);
InOrder order = inOrder(jobs);
order.verify(jobs).cancelReminder(1);
order.verify(jobs)
.add(new Reminder(1, now.plusMonths(12).getMillis(), ReminderService.TYPE_SNOOZE));
}
} }

@ -3,268 +3,283 @@
* *
* See the file "LICENSE" for the full license governing this code. * See the file "LICENSE" for the full license governing this code.
*/ */
package com.todoroo.astrid.repeats; package com.todoroo.astrid.repeats;
import android.support.test.runner.AndroidJUnit4; import static junit.framework.Assert.assertEquals;
import static org.tasks.date.DateTimeUtils.newDateTime;
import android.support.test.runner.AndroidJUnit4;
import com.google.ical.values.Frequency; import com.google.ical.values.Frequency;
import com.google.ical.values.RRule; import com.google.ical.values.RRule;
import com.google.ical.values.Weekday; import com.google.ical.values.Weekday;
import com.google.ical.values.WeekdayNum; import com.google.ical.values.WeekdayNum;
import com.todoroo.andlib.utility.DateUtilities; import com.todoroo.andlib.utility.DateUtilities;
import com.todoroo.astrid.data.Task; import com.todoroo.astrid.data.Task;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Calendar;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.tasks.time.DateTime; import org.tasks.time.DateTime;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Calendar;
import static junit.framework.Assert.assertEquals;
import static org.tasks.date.DateTimeUtils.newDateTime;
@RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class)
public class AdvancedRepeatTest { public class AdvancedRepeatTest {
private static final int PREV_PREV = -2; private static final int PREV_PREV = -2;
private static final int PREV = -1; private static final int PREV = -1;
private static final int THIS = 1; private static final int THIS = 1;
private static final int NEXT = 2; private static final int NEXT = 2;
private Task task; private Task task;
private long nextDueDate; private long nextDueDate;
private RRule rrule; private RRule rrule;
@Before public static void assertDateTimeEquals(long date, long other) {
public void setUp() { assertEquals("Expected: " + newDateTime(date) + ", Actual: " + newDateTime(other),
task = new Task(); date, other);
task.setCompletionDate(DateUtilities.now()); }
rrule = new RRule();
} // --- date with time tests
// --- date with time tests @Before
public void setUp() {
@Test task = new Task();
public void testDueDateSpecificTime() throws ParseException { task.setCompletionDate(DateUtilities.now());
buildRRule(1, Frequency.DAILY); rrule = new RRule();
}
// test specific day & time
long dayWithTime = Task.createDueDate(Task.URGENCY_SPECIFIC_DAY_TIME, new DateTime(2010, 8, 1, 10, 4, 0).getMillis()); @Test
task.setDueDate(dayWithTime); public void testDueDateSpecificTime() throws ParseException {
buildRRule(1, Frequency.DAILY);
long nextDayWithTime = dayWithTime + DateUtilities.ONE_DAY;
nextDueDate = RepeatTaskHelper.computeNextDueDate(task, rrule.toIcal(), false); // test specific day & time
assertDateTimeEquals(nextDayWithTime, nextDueDate); long dayWithTime = Task.createDueDate(Task.URGENCY_SPECIFIC_DAY_TIME,
} new DateTime(2010, 8, 1, 10, 4, 0).getMillis());
task.setDueDate(dayWithTime);
@Test
public void testCompletionDateSpecificTime() throws ParseException { long nextDayWithTime = dayWithTime + DateUtilities.ONE_DAY;
buildRRule(1, Frequency.DAILY); nextDueDate = RepeatTaskHelper.computeNextDueDate(task, rrule.toIcal(), false);
assertDateTimeEquals(nextDayWithTime, nextDueDate);
// test specific day & time }
long dayWithTime = Task.createDueDate(Task.URGENCY_SPECIFIC_DAY_TIME, new DateTime(2010, 8, 1, 10, 4, 0).getMillis());
task.setDueDate(dayWithTime); // --- due date tests
DateTime todayWithTime = newDateTime() @Test
.withHourOfDay(10) public void testCompletionDateSpecificTime() throws ParseException {
.withMinuteOfHour(4) buildRRule(1, Frequency.DAILY);
.withSecondOfMinute(1);
long nextDayWithTimeLong = todayWithTime.getMillis(); // test specific day & time
nextDayWithTimeLong += DateUtilities.ONE_DAY; long dayWithTime = Task.createDueDate(Task.URGENCY_SPECIFIC_DAY_TIME,
nextDayWithTimeLong = nextDayWithTimeLong / 1000L * 1000; new DateTime(2010, 8, 1, 10, 4, 0).getMillis());
task.setDueDate(dayWithTime);
nextDueDate = RepeatTaskHelper.computeNextDueDate(task, rrule.toIcal(), true);
assertDateTimeEquals(nextDayWithTimeLong, nextDueDate); DateTime todayWithTime = newDateTime()
} .withHourOfDay(10)
.withMinuteOfHour(4)
// --- due date tests .withSecondOfMinute(1);
long nextDayWithTimeLong = todayWithTime.getMillis();
/** test multiple days per week - DUE DATE */ nextDayWithTimeLong += DateUtilities.ONE_DAY;
@Test nextDayWithTimeLong = nextDayWithTimeLong / 1000L * 1000;
public void testDueDateInPastSingleWeekMultiDay() throws Exception {
buildRRule(1, Frequency.WEEKLY, Weekday.MO, Weekday.WE, Weekday.FR); nextDueDate = RepeatTaskHelper.computeNextDueDate(task, rrule.toIcal(), true);
assertDateTimeEquals(nextDayWithTimeLong, nextDueDate);
setTaskDueDate(THIS, Calendar.SUNDAY); }
computeNextDueDate(false);
assertDueDate(nextDueDate, THIS, Calendar.MONDAY); /**
* test multiple days per week - DUE DATE
setTaskDueDate(THIS, Calendar.MONDAY); */
computeNextDueDate(false); @Test
assertDueDate(nextDueDate, THIS, Calendar.WEDNESDAY); public void testDueDateInPastSingleWeekMultiDay() throws Exception {
buildRRule(1, Frequency.WEEKLY, Weekday.MO, Weekday.WE, Weekday.FR);
setTaskDueDate(THIS, Calendar.FRIDAY);
computeNextDueDate(false); setTaskDueDate(THIS, Calendar.SUNDAY);
assertDueDate(nextDueDate, THIS, Calendar.MONDAY); computeNextDueDate(false);
assertDueDate(nextDueDate, THIS, Calendar.MONDAY);
setTaskDueDate(THIS, Calendar.MONDAY);
computeNextDueDate(false);
assertDueDate(nextDueDate, THIS, Calendar.WEDNESDAY);
setTaskDueDate(THIS, Calendar.FRIDAY);
computeNextDueDate(false);
assertDueDate(nextDueDate, THIS, Calendar.MONDAY);
}
/**
* test single day repeats - DUE DATE
*/
@Test
public void testDueDateSingleDay() throws Exception {
buildRRule(1, Frequency.WEEKLY, Weekday.MO);
setTaskDueDate(PREV_PREV, Calendar.MONDAY);
computeNextDueDate(false);
assertDueDate(nextDueDate, NEXT, Calendar.MONDAY);
setTaskDueDate(PREV_PREV, Calendar.FRIDAY);
computeNextDueDate(false);
assertDueDate(nextDueDate, THIS, Calendar.MONDAY);
setTaskDueDate(PREV, Calendar.MONDAY);
computeNextDueDate(false);
assertDueDate(nextDueDate, NEXT, Calendar.MONDAY);
setTaskDueDate(PREV, Calendar.FRIDAY);
computeNextDueDate(false);
assertDueDate(nextDueDate, THIS, Calendar.MONDAY);
setTaskDueDate(THIS, Calendar.SUNDAY);
computeNextDueDate(false);
assertDueDate(nextDueDate, THIS, Calendar.MONDAY);
setTaskDueDate(THIS, Calendar.MONDAY);
computeNextDueDate(false);
assertDueDate(nextDueDate, NEXT, Calendar.MONDAY);
}
/**
* test multiple days per week - DUE DATE
*/
@Test
public void testDueDateSingleWeekMultiDay() throws Exception {
buildRRule(1, Frequency.WEEKLY, Weekday.MO, Weekday.WE, Weekday.FR);
setTaskDueDate(THIS, Calendar.SUNDAY);
computeNextDueDate(false);
assertDueDate(nextDueDate, THIS, Calendar.MONDAY);
setTaskDueDate(THIS, Calendar.MONDAY);
computeNextDueDate(false);
assertDueDate(nextDueDate, THIS, Calendar.WEDNESDAY);
setTaskDueDate(THIS, Calendar.FRIDAY);
computeNextDueDate(false);
assertDueDate(nextDueDate, THIS, Calendar.MONDAY);
}
// --- completion tests
/**
* test multiple days per week, multiple intervals - DUE DATE
*/
@Test
public void testDueDateMultiWeekMultiDay() throws Exception {
buildRRule(2, Frequency.WEEKLY, Weekday.MO, Weekday.WE, Weekday.FR);
setTaskDueDate(THIS, Calendar.SUNDAY);
computeNextDueDate(false);
assertDueDate(nextDueDate, NEXT, Calendar.MONDAY);
setTaskDueDate(THIS, Calendar.MONDAY);
computeNextDueDate(false);
assertDueDate(nextDueDate, THIS, Calendar.WEDNESDAY);
setTaskDueDate(THIS, Calendar.FRIDAY);
computeNextDueDate(false);
assertDueDate(nextDueDate, NEXT, Calendar.MONDAY);
}
/**
* test multiple days per week - COMPLETE DATE
*/
@Test
public void testCompleteDateSingleWeek() throws Exception {
for (Weekday wday : Weekday.values()) {
buildRRule(1, Frequency.WEEKLY, wday);
computeNextDueDate(true);
long expected = getDate(DateUtilities.now() + DateUtilities.ONE_DAY, THIS, wday.javaDayNum);
nextDueDate = Task.createDueDate(Task.URGENCY_SPECIFIC_DAY, nextDueDate);
assertEquals(expected, nextDueDate);
} }
/** test single day repeats - DUE DATE */ for (Weekday wday1 : Weekday.values()) {
@Test for (Weekday wday2 : Weekday.values()) {
public void testDueDateSingleDay() throws Exception { if (wday1 == wday2) {
buildRRule(1, Frequency.WEEKLY, Weekday.MO); continue;
setTaskDueDate(PREV_PREV, Calendar.MONDAY);
computeNextDueDate(false);
assertDueDate(nextDueDate, NEXT, Calendar.MONDAY);
setTaskDueDate(PREV_PREV, Calendar.FRIDAY);
computeNextDueDate(false);
assertDueDate(nextDueDate, THIS, Calendar.MONDAY);
setTaskDueDate(PREV, Calendar.MONDAY);
computeNextDueDate(false);
assertDueDate(nextDueDate, NEXT, Calendar.MONDAY);
setTaskDueDate(PREV, Calendar.FRIDAY);
computeNextDueDate(false);
assertDueDate(nextDueDate, THIS, Calendar.MONDAY);
setTaskDueDate(THIS, Calendar.SUNDAY);
computeNextDueDate(false);
assertDueDate(nextDueDate, THIS, Calendar.MONDAY);
setTaskDueDate(THIS, Calendar.MONDAY);
computeNextDueDate(false);
assertDueDate(nextDueDate, NEXT, Calendar.MONDAY);
}
/** test multiple days per week - DUE DATE */
@Test
public void testDueDateSingleWeekMultiDay() throws Exception {
buildRRule(1, Frequency.WEEKLY, Weekday.MO, Weekday.WE, Weekday.FR);
setTaskDueDate(THIS, Calendar.SUNDAY);
computeNextDueDate(false);
assertDueDate(nextDueDate, THIS, Calendar.MONDAY);
setTaskDueDate(THIS, Calendar.MONDAY);
computeNextDueDate(false);
assertDueDate(nextDueDate, THIS, Calendar.WEDNESDAY);
setTaskDueDate(THIS, Calendar.FRIDAY);
computeNextDueDate(false);
assertDueDate(nextDueDate, THIS, Calendar.MONDAY);
}
/** test multiple days per week, multiple intervals - DUE DATE */
@Test
public void testDueDateMultiWeekMultiDay() throws Exception {
buildRRule(2, Frequency.WEEKLY, Weekday.MO, Weekday.WE, Weekday.FR);
setTaskDueDate(THIS, Calendar.SUNDAY);
computeNextDueDate(false);
assertDueDate(nextDueDate, NEXT, Calendar.MONDAY);
setTaskDueDate(THIS, Calendar.MONDAY);
computeNextDueDate(false);
assertDueDate(nextDueDate, THIS, Calendar.WEDNESDAY);
setTaskDueDate(THIS, Calendar.FRIDAY);
computeNextDueDate(false);
assertDueDate(nextDueDate, NEXT, Calendar.MONDAY);
}
// --- completion tests
/** test multiple days per week - COMPLETE DATE */
@Test
public void testCompleteDateSingleWeek() throws Exception {
for(Weekday wday : Weekday.values()) {
buildRRule(1, Frequency.WEEKLY, wday);
computeNextDueDate(true);
long expected = getDate(DateUtilities.now() + DateUtilities.ONE_DAY, THIS, wday.javaDayNum);
nextDueDate = Task.createDueDate(Task.URGENCY_SPECIFIC_DAY, nextDueDate);
assertEquals(expected, nextDueDate);
} }
for(Weekday wday1 : Weekday.values()) { buildRRule(1, Frequency.WEEKLY, wday1, wday2);
for(Weekday wday2 : Weekday.values()) { long nextOne = getDate(DateUtilities.now() + DateUtilities.ONE_DAY, THIS, wday1.javaDayNum);
if(wday1 == wday2) long nextTwo = getDate(DateUtilities.now() + DateUtilities.ONE_DAY, THIS, wday2.javaDayNum);
continue; computeNextDueDate(true);
nextDueDate = Task.createDueDate(Task.URGENCY_SPECIFIC_DAY, nextDueDate);
buildRRule(1, Frequency.WEEKLY, wday1, wday2); assertEquals(Math.min(nextOne, nextTwo), nextDueDate);
long nextOne = getDate(DateUtilities.now() + DateUtilities.ONE_DAY, THIS, wday1.javaDayNum); }
long nextTwo = getDate(DateUtilities.now() + DateUtilities.ONE_DAY, THIS, wday2.javaDayNum);
computeNextDueDate(true);
nextDueDate = Task.createDueDate(Task.URGENCY_SPECIFIC_DAY, nextDueDate);
assertEquals(Math.min(nextOne, nextTwo), nextDueDate);
}
}
} }
}
/** test multiple days per week, multiple intervals - COMPLETE DATE */
@Test // --- helpers
public void testCompleteDateMultiWeek() throws Exception {
for(Weekday wday : Weekday.values()) { /**
buildRRule(2, Frequency.WEEKLY, wday); * test multiple days per week, multiple intervals - COMPLETE DATE
computeNextDueDate(true); */
long expected = getDate(DateUtilities.now() + DateUtilities.ONE_DAY, NEXT, wday.javaDayNum); @Test
nextDueDate = Task.createDueDate(Task.URGENCY_SPECIFIC_DAY, nextDueDate); public void testCompleteDateMultiWeek() throws Exception {
assertEquals(expected, nextDueDate); for (Weekday wday : Weekday.values()) {
} buildRRule(2, Frequency.WEEKLY, wday);
computeNextDueDate(true);
for(Weekday wday1 : Weekday.values()) { long expected = getDate(DateUtilities.now() + DateUtilities.ONE_DAY, NEXT, wday.javaDayNum);
for(Weekday wday2 : Weekday.values()) { nextDueDate = Task.createDueDate(Task.URGENCY_SPECIFIC_DAY, nextDueDate);
if(wday1 == wday2) assertEquals(expected, nextDueDate);
continue;
buildRRule(2, Frequency.WEEKLY, wday1, wday2);
long nextOne = getDate(DateUtilities.now() + DateUtilities.ONE_DAY, NEXT, wday1.javaDayNum);
long nextTwo = getDate(DateUtilities.now() + DateUtilities.ONE_DAY, NEXT, wday2.javaDayNum);
computeNextDueDate(true);
nextDueDate = Task.createDueDate(Task.URGENCY_SPECIFIC_DAY, nextDueDate);
assertEquals(Math.min(nextOne, nextTwo), nextDueDate);
}
}
} }
// --- helpers for (Weekday wday1 : Weekday.values()) {
for (Weekday wday2 : Weekday.values()) {
private void computeNextDueDate(boolean fromComplete) throws ParseException{ if (wday1 == wday2) {
nextDueDate = RepeatTaskHelper.computeNextDueDate(task, rrule.toIcal(), fromComplete); continue;
} }
private void buildRRule(int interval, Frequency freq, Weekday... weekdays) {
rrule.setInterval(interval);
rrule.setFreq(freq);
setRRuleDays(rrule, weekdays);
}
private void assertDueDate(long actual, int expectedWhich, int expectedDayOfWeek) {
long expected = getDate(task.getDueDate(), expectedWhich, expectedDayOfWeek);
assertEquals(expected, actual);
}
public static void assertDateTimeEquals(long date, long other) { buildRRule(2, Frequency.WEEKLY, wday1, wday2);
assertEquals("Expected: " + newDateTime(date) + ", Actual: " + newDateTime(other), long nextOne = getDate(DateUtilities.now() + DateUtilities.ONE_DAY, NEXT, wday1.javaDayNum);
date, other); long nextTwo = getDate(DateUtilities.now() + DateUtilities.ONE_DAY, NEXT, wday2.javaDayNum);
computeNextDueDate(true);
nextDueDate = Task.createDueDate(Task.URGENCY_SPECIFIC_DAY, nextDueDate);
assertEquals(Math.min(nextOne, nextTwo), nextDueDate);
}
} }
}
private void setRRuleDays(RRule rrule, Weekday... weekdays) {
ArrayList<WeekdayNum> days = new ArrayList<>(); private void computeNextDueDate(boolean fromComplete) throws ParseException {
for(Weekday wd : weekdays) nextDueDate = RepeatTaskHelper.computeNextDueDate(task, rrule.toIcal(), fromComplete);
days.add(new WeekdayNum(0, wd)); }
rrule.setByDay(days);
private void buildRRule(int interval, Frequency freq, Weekday... weekdays) {
rrule.setInterval(interval);
rrule.setFreq(freq);
setRRuleDays(rrule, weekdays);
}
private void assertDueDate(long actual, int expectedWhich, int expectedDayOfWeek) {
long expected = getDate(task.getDueDate(), expectedWhich, expectedDayOfWeek);
assertEquals(expected, actual);
}
private void setRRuleDays(RRule rrule, Weekday... weekdays) {
ArrayList<WeekdayNum> days = new ArrayList<>();
for (Weekday wd : weekdays) {
days.add(new WeekdayNum(0, wd));
} }
rrule.setByDay(days);
}
private void setTaskDueDate(int which, int day) { private void setTaskDueDate(int which, int day) {
long time = getDate(DateUtilities.now(), which, day); long time = getDate(DateUtilities.now(), which, day);
task.setDueDate(time); task.setDueDate(time);
} }
private long getDate(long start, int which, int dayOfWeek) { private long getDate(long start, int which, int dayOfWeek) {
Calendar c = Calendar.getInstance(); Calendar c = Calendar.getInstance();
c.setTimeInMillis(start); c.setTimeInMillis(start);
int direction = which > 0 ? 1 : -1; int direction = which > 0 ? 1 : -1;
while(c.get(Calendar.DAY_OF_WEEK) != dayOfWeek) { while (c.get(Calendar.DAY_OF_WEEK) != dayOfWeek) {
c.add(Calendar.DAY_OF_MONTH, direction); c.add(Calendar.DAY_OF_MONTH, direction);
}
c.add(Calendar.DAY_OF_MONTH, (Math.abs(which) - 1) * direction * 7);
return Task.createDueDate(Task.URGENCY_SPECIFIC_DAY, c.getTimeInMillis());
} }
c.add(Calendar.DAY_OF_MONTH, (Math.abs(which) - 1) * direction * 7);
return Task.createDueDate(Task.URGENCY_SPECIFIC_DAY, c.getTimeInMillis());
}
} }

@ -1,259 +1,266 @@
package com.todoroo.astrid.repeats; package com.todoroo.astrid.repeats;
import android.support.test.runner.AndroidJUnit4; import static com.todoroo.astrid.repeats.RepeatTaskHelper.computeNextDueDate;
import static java.util.Arrays.asList;
import static junit.framework.Assert.assertEquals;
import android.support.test.runner.AndroidJUnit4;
import com.google.ical.values.Frequency; import com.google.ical.values.Frequency;
import com.google.ical.values.RRule; import com.google.ical.values.RRule;
import com.google.ical.values.Weekday; import com.google.ical.values.Weekday;
import com.google.ical.values.WeekdayNum; import com.google.ical.values.WeekdayNum;
import com.todoroo.astrid.data.Task; import com.todoroo.astrid.data.Task;
import java.text.ParseException;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.tasks.time.DateTime; import org.tasks.time.DateTime;
import java.text.ParseException;
import static com.todoroo.astrid.repeats.RepeatTaskHelper.computeNextDueDate;
import static java.util.Arrays.asList;
import static junit.framework.Assert.assertEquals;
@RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class)
public class NewRepeatTests { public class NewRepeatTests {
@Test @Test
public void testRepeatMinutelyFromDueDate() throws ParseException { public void testRepeatMinutelyFromDueDate() throws ParseException {
DateTime dueDateTime = newDayTime(2016, 8, 26, 12, 30); DateTime dueDateTime = newDayTime(2016, 8, 26, 12, 30);
Task task = newFromDue(Frequency.MINUTELY, 1, dueDateTime); Task task = newFromDue(Frequency.MINUTELY, 1, dueDateTime);
assertEquals(newDayTime(2016, 8, 26, 12, 31), calculateNextDueDate(task)); assertEquals(newDayTime(2016, 8, 26, 12, 31), calculateNextDueDate(task));
} }
@Test @Test
public void testRepeatHourlyFromDueDate() throws ParseException { public void testRepeatHourlyFromDueDate() throws ParseException {
DateTime dueDateTime = newDayTime(2016, 8, 26, 12, 30); DateTime dueDateTime = newDayTime(2016, 8, 26, 12, 30);
Task task = newFromDue(Frequency.HOURLY, 1, dueDateTime); Task task = newFromDue(Frequency.HOURLY, 1, dueDateTime);
assertEquals(newDayTime(2016, 8, 26, 13, 30), calculateNextDueDate(task)); assertEquals(newDayTime(2016, 8, 26, 13, 30), calculateNextDueDate(task));
} }
@Test @Test
public void testRepeatDailyFromDueDate() throws ParseException { public void testRepeatDailyFromDueDate() throws ParseException {
DateTime dueDateTime = newDayTime(2016, 8, 26, 12, 30); DateTime dueDateTime = newDayTime(2016, 8, 26, 12, 30);
Task task = newFromDue(Frequency.DAILY, 1, dueDateTime); Task task = newFromDue(Frequency.DAILY, 1, dueDateTime);
assertEquals(newDayTime(2016, 8, 27, 12, 30), calculateNextDueDate(task)); assertEquals(newDayTime(2016, 8, 27, 12, 30), calculateNextDueDate(task));
} }
@Test @Test
public void testRepeatWeeklyFromDueDate() throws ParseException { public void testRepeatWeeklyFromDueDate() throws ParseException {
DateTime dueDateTime = newDayTime(2016, 8, 28, 1, 34); DateTime dueDateTime = newDayTime(2016, 8, 28, 1, 34);
Task task = newFromDue(Frequency.WEEKLY, 1, dueDateTime); Task task = newFromDue(Frequency.WEEKLY, 1, dueDateTime);
assertEquals(newDayTime(2016, 9, 4, 1, 34), calculateNextDueDate(task)); assertEquals(newDayTime(2016, 9, 4, 1, 34), calculateNextDueDate(task));
} }
@Test @Test
public void testRepeatMonthlyFromDueDate() throws ParseException { public void testRepeatMonthlyFromDueDate() throws ParseException {
DateTime dueDateTime = newDayTime(2016, 8, 28, 1, 44); DateTime dueDateTime = newDayTime(2016, 8, 28, 1, 44);
Task task = newFromDue(Frequency.MONTHLY, 1, dueDateTime); Task task = newFromDue(Frequency.MONTHLY, 1, dueDateTime);
assertEquals(newDayTime(2016, 9, 28, 1, 44), calculateNextDueDate(task)); assertEquals(newDayTime(2016, 9, 28, 1, 44), calculateNextDueDate(task));
} }
@Test @Test
public void testRepeatYearlyFromDueDate() throws ParseException { public void testRepeatYearlyFromDueDate() throws ParseException {
DateTime dueDateTime = newDayTime(2016, 8, 28, 1, 44); DateTime dueDateTime = newDayTime(2016, 8, 28, 1, 44);
Task task = newFromDue(Frequency.YEARLY, 1, dueDateTime); Task task = newFromDue(Frequency.YEARLY, 1, dueDateTime);
assertEquals(newDayTime(2017, 8, 28, 1, 44), calculateNextDueDate(task)); assertEquals(newDayTime(2017, 8, 28, 1, 44), calculateNextDueDate(task));
} }
/** Tests for repeating from completionDate */ /**
* Tests for repeating from completionDate
@Test */
public void testRepeatMinutelyFromCompleteDateCompleteBefore() throws ParseException {
DateTime dueDateTime = newDayTime(2016, 8, 30, 0, 25); @Test
DateTime completionDateTime = newDayTime(2016, 8, 29, 0, 14); public void testRepeatMinutelyFromCompleteDateCompleteBefore() throws ParseException {
Task task = newFromCompleted(Frequency.MINUTELY, 1, dueDateTime, completionDateTime); DateTime dueDateTime = newDayTime(2016, 8, 30, 0, 25);
DateTime completionDateTime = newDayTime(2016, 8, 29, 0, 14);
assertEquals(newDayTime(2016, 8, 29, 0, 15), calculateNextDueDate(task)); Task task = newFromCompleted(Frequency.MINUTELY, 1, dueDateTime, completionDateTime);
}
assertEquals(newDayTime(2016, 8, 29, 0, 15), calculateNextDueDate(task));
@Test }
public void testRepeatMinutelyFromCompleteDateCompleteAfter() throws ParseException {
DateTime dueDateTime = newDayTime(2016, 8, 28, 0, 4); @Test
DateTime completionDateTime = newDayTime(2016, 8, 29, 0, 14); public void testRepeatMinutelyFromCompleteDateCompleteAfter() throws ParseException {
Task task = newFromCompleted(Frequency.MINUTELY, 1, dueDateTime, completionDateTime); DateTime dueDateTime = newDayTime(2016, 8, 28, 0, 4);
DateTime completionDateTime = newDayTime(2016, 8, 29, 0, 14);
assertEquals(newDayTime(2016, 8, 29, 0, 15), calculateNextDueDate(task)); Task task = newFromCompleted(Frequency.MINUTELY, 1, dueDateTime, completionDateTime);
}
assertEquals(newDayTime(2016, 8, 29, 0, 15), calculateNextDueDate(task));
@Test }
public void testRepeatHourlyFromCompleteDateCompleteBefore() throws ParseException {
DateTime dueDateTime = newDayTime(2016, 8, 30, 0, 25); @Test
DateTime completionDateTime = newDayTime(2016, 8, 29, 0, 14); public void testRepeatHourlyFromCompleteDateCompleteBefore() throws ParseException {
Task task = newFromCompleted(Frequency.HOURLY, 1, dueDateTime, completionDateTime); DateTime dueDateTime = newDayTime(2016, 8, 30, 0, 25);
DateTime completionDateTime = newDayTime(2016, 8, 29, 0, 14);
assertEquals(newDayTime(2016, 8, 29, 1, 14), calculateNextDueDate(task)); Task task = newFromCompleted(Frequency.HOURLY, 1, dueDateTime, completionDateTime);
}
assertEquals(newDayTime(2016, 8, 29, 1, 14), calculateNextDueDate(task));
@Test }
public void testRepeatHourlyFromCompleteDateCompleteAfter() throws ParseException {
DateTime dueDateTime = newDayTime(2016, 8, 28, 0, 4); @Test
DateTime completionDateTime = newDayTime(2016, 8, 29, 0, 14); public void testRepeatHourlyFromCompleteDateCompleteAfter() throws ParseException {
Task task = newFromCompleted(Frequency.HOURLY, 1, dueDateTime, completionDateTime); DateTime dueDateTime = newDayTime(2016, 8, 28, 0, 4);
DateTime completionDateTime = newDayTime(2016, 8, 29, 0, 14);
assertEquals(newDayTime(2016, 8, 29, 1, 14), calculateNextDueDate(task)); Task task = newFromCompleted(Frequency.HOURLY, 1, dueDateTime, completionDateTime);
}
assertEquals(newDayTime(2016, 8, 29, 1, 14), calculateNextDueDate(task));
@Test }
public void testRepeatDailyFromCompleteDateCompleteBefore() throws ParseException {
DateTime dueDateTime = newDayTime(2016, 8, 30, 0, 25); @Test
DateTime completionDateTime = newDayTime(2016, 8, 29, 0, 14); public void testRepeatDailyFromCompleteDateCompleteBefore() throws ParseException {
Task task = newFromCompleted(Frequency.DAILY, 1, dueDateTime, completionDateTime); DateTime dueDateTime = newDayTime(2016, 8, 30, 0, 25);
DateTime completionDateTime = newDayTime(2016, 8, 29, 0, 14);
assertEquals(newDayTime(2016, 8, 30, 0, 25), calculateNextDueDate(task)); Task task = newFromCompleted(Frequency.DAILY, 1, dueDateTime, completionDateTime);
}
assertEquals(newDayTime(2016, 8, 30, 0, 25), calculateNextDueDate(task));
@Test }
public void testRepeatDailyFromCompleteDateCompleteAfter() throws ParseException {
DateTime dueDateTime = newDayTime(2016, 8, 28, 0, 4); @Test
DateTime completionDateTime = newDayTime(2016, 8, 29, 0, 14); public void testRepeatDailyFromCompleteDateCompleteAfter() throws ParseException {
Task task = newFromCompleted(Frequency.DAILY, 1, dueDateTime, completionDateTime); DateTime dueDateTime = newDayTime(2016, 8, 28, 0, 4);
DateTime completionDateTime = newDayTime(2016, 8, 29, 0, 14);
assertEquals(newDayTime(2016, 8, 30, 0, 4), calculateNextDueDate(task)); Task task = newFromCompleted(Frequency.DAILY, 1, dueDateTime, completionDateTime);
}
assertEquals(newDayTime(2016, 8, 30, 0, 4), calculateNextDueDate(task));
@Test }
public void testRepeatWeeklyFromCompleteDateCompleteBefore() throws ParseException {
DateTime dueDateTime = newDayTime(2016, 8, 30, 0, 25); @Test
DateTime completionDateTime = newDayTime(2016, 8, 29, 0, 14); public void testRepeatWeeklyFromCompleteDateCompleteBefore() throws ParseException {
Task task = newFromCompleted(Frequency.WEEKLY, 1, dueDateTime, completionDateTime); DateTime dueDateTime = newDayTime(2016, 8, 30, 0, 25);
DateTime completionDateTime = newDayTime(2016, 8, 29, 0, 14);
assertEquals(newDayTime(2016, 9, 5, 0, 25), calculateNextDueDate(task)); Task task = newFromCompleted(Frequency.WEEKLY, 1, dueDateTime, completionDateTime);
}
assertEquals(newDayTime(2016, 9, 5, 0, 25), calculateNextDueDate(task));
@Test }
public void testRepeatWeeklyFromCompleteDateCompleteAfter() throws ParseException {
DateTime dueDateTime = newDayTime(2016, 8, 28, 0, 4); @Test
DateTime completionDateTime = newDayTime(2016, 8, 29, 0, 14); public void testRepeatWeeklyFromCompleteDateCompleteAfter() throws ParseException {
Task task = newFromCompleted(Frequency.WEEKLY, 1, dueDateTime, completionDateTime); DateTime dueDateTime = newDayTime(2016, 8, 28, 0, 4);
DateTime completionDateTime = newDayTime(2016, 8, 29, 0, 14);
assertEquals(newDayTime(2016, 9, 5, 0, 4), calculateNextDueDate(task)); Task task = newFromCompleted(Frequency.WEEKLY, 1, dueDateTime, completionDateTime);
}
assertEquals(newDayTime(2016, 9, 5, 0, 4), calculateNextDueDate(task));
@Test }
public void testRepeatMonthlyFromCompleteDateCompleteBefore() throws ParseException {
DateTime dueDateTime = newDayTime(2016, 8, 30, 0, 25); @Test
DateTime completionDateTime = newDayTime(2016, 8, 29, 0, 14); public void testRepeatMonthlyFromCompleteDateCompleteBefore() throws ParseException {
Task task = newFromCompleted(Frequency.MONTHLY, 1, dueDateTime, completionDateTime); DateTime dueDateTime = newDayTime(2016, 8, 30, 0, 25);
DateTime completionDateTime = newDayTime(2016, 8, 29, 0, 14);
assertEquals(newDayTime(2016, 9, 29, 0, 25), calculateNextDueDate(task)); Task task = newFromCompleted(Frequency.MONTHLY, 1, dueDateTime, completionDateTime);
}
assertEquals(newDayTime(2016, 9, 29, 0, 25), calculateNextDueDate(task));
@Test }
public void testRepeatMonthlyFromCompleteDateCompleteAfter() throws ParseException {
DateTime dueDateTime = newDayTime(2016, 8, 28, 0, 4); @Test
DateTime completionDateTime = newDayTime(2016, 8, 29, 0, 14); public void testRepeatMonthlyFromCompleteDateCompleteAfter() throws ParseException {
Task task = newFromCompleted(Frequency.MONTHLY, 1, dueDateTime, completionDateTime); DateTime dueDateTime = newDayTime(2016, 8, 28, 0, 4);
DateTime completionDateTime = newDayTime(2016, 8, 29, 0, 14);
assertEquals(newDayTime(2016, 9, 29, 0, 4), calculateNextDueDate(task)); Task task = newFromCompleted(Frequency.MONTHLY, 1, dueDateTime, completionDateTime);
}
assertEquals(newDayTime(2016, 9, 29, 0, 4), calculateNextDueDate(task));
@Test }
public void testRepeatYearlyFromCompleteDateCompleteBefore() throws ParseException {
DateTime dueDateTime = newDayTime(2016, 8, 30, 0, 25); @Test
DateTime completionDateTime = newDayTime(2016, 8, 29, 0, 14); public void testRepeatYearlyFromCompleteDateCompleteBefore() throws ParseException {
Task task = newFromCompleted(Frequency.YEARLY, 1, dueDateTime, completionDateTime); DateTime dueDateTime = newDayTime(2016, 8, 30, 0, 25);
DateTime completionDateTime = newDayTime(2016, 8, 29, 0, 14);
assertEquals(newDayTime(2017, 8, 29, 0, 25), calculateNextDueDate(task)); Task task = newFromCompleted(Frequency.YEARLY, 1, dueDateTime, completionDateTime);
}
assertEquals(newDayTime(2017, 8, 29, 0, 25), calculateNextDueDate(task));
@Test }
public void testRepeatYearlyFromCompleteDateCompleteAfter() throws ParseException {
DateTime dueDateTime = newDayTime(2016, 8, 28, 0, 4); @Test
DateTime completionDateTime = newDayTime(2016, 8, 29, 0, 14); public void testRepeatYearlyFromCompleteDateCompleteAfter() throws ParseException {
Task task = newFromCompleted(Frequency.YEARLY, 1, dueDateTime, completionDateTime); DateTime dueDateTime = newDayTime(2016, 8, 28, 0, 4);
DateTime completionDateTime = newDayTime(2016, 8, 29, 0, 14);
assertEquals(newDayTime(2017, 8, 29, 0, 4), calculateNextDueDate(task)); Task task = newFromCompleted(Frequency.YEARLY, 1, dueDateTime, completionDateTime);
assertEquals(newDayTime(2017, 8, 29, 0, 4), calculateNextDueDate(task));
}
@Test
public void testAdvancedRepeatWeeklyFromDueDate() throws ParseException {
DateTime dueDateTime = newDayTime(2016, 8, 29, 0, 25);
Task task = newWeeklyFromDue(1, dueDateTime, new WeekdayNum(0, Weekday.MO),
new WeekdayNum(0, Weekday.WE));
assertEquals(newDayTime(2016, 8, 31, 0, 25), calculateNextDueDate(task));
}
@Test
public void testAdvancedRepeatWeeklyFromCompleteDateCompleteBefore() throws ParseException {
DateTime dueDateTime = newDayTime(2016, 8, 29, 0, 25);
DateTime completionDateTime = newDayTime(2016, 8, 28, 1, 9);
Task task = newWeeklyFromCompleted(1, dueDateTime, completionDateTime,
new WeekdayNum(0, Weekday.MO), new WeekdayNum(0, Weekday.WE));
assertEquals(newDayTime(2016, 8, 29, 0, 25), calculateNextDueDate(task));
}
@Test
public void testAdvancedRepeatWeeklyFromCompleteDateCompleteAfter() throws ParseException {
DateTime dueDateTime = newDayTime(2016, 8, 29, 0, 25);
DateTime completionDateTime = newDayTime(2016, 9, 1, 1, 9);
Task task = newWeeklyFromCompleted(1, dueDateTime, completionDateTime,
new WeekdayNum(0, Weekday.MO), new WeekdayNum(0, Weekday.WE));
assertEquals(newDayTime(2016, 9, 5, 0, 25), calculateNextDueDate(task));
}
private DateTime newDayTime(int year, int month, int day, int hour, int minute) {
return new DateTime(Task.createDueDate(Task.URGENCY_SPECIFIC_DAY_TIME,
new DateTime(year, month, day, hour, minute).getMillis()));
}
private DateTime calculateNextDueDate(Task task) throws ParseException {
return new DateTime(
computeNextDueDate(task, task.sanitizedRecurrence(), task.repeatAfterCompletion()));
}
private Task newFromDue(Frequency frequency, int interval, DateTime dueDateTime) {
return new Task() {{
setRecurrence(getRecurrenceRule(frequency, interval, false));
setDueDate(dueDateTime.getMillis());
}};
}
private Task newWeeklyFromDue(int interval, DateTime dueDateTime, WeekdayNum... weekdays) {
return new Task() {{
setRecurrence(getRecurrenceRule(Frequency.WEEKLY, interval, false, weekdays));
setDueDate(dueDateTime.getMillis());
}};
}
private Task newFromCompleted(Frequency frequency, int interval, DateTime dueDateTime,
DateTime completionDate) {
return new Task() {{
setRecurrence(getRecurrenceRule(frequency, interval, true));
setDueDate(dueDateTime.getMillis());
setCompletionDate(completionDate.getMillis());
}};
}
private Task newWeeklyFromCompleted(int interval, DateTime dueDateTime, DateTime completionDate,
WeekdayNum... weekdays) {
return new Task() {{
setRecurrence(getRecurrenceRule(Frequency.WEEKLY, interval, true, weekdays));
setDueDate(dueDateTime.getMillis());
setCompletionDate(completionDate.getMillis());
}};
}
private String getRecurrenceRule(Frequency frequency, int interval, boolean fromCompletion,
WeekdayNum... weekdays) {
RRule rrule = new RRule();
rrule.setFreq(frequency);
rrule.setInterval(interval);
if (weekdays != null) {
rrule.setByDay(asList(weekdays));
} }
String result = rrule.toIcal();
@Test if (fromCompletion) {
public void testAdvancedRepeatWeeklyFromDueDate() throws ParseException { result += ";FROM=COMPLETION";
DateTime dueDateTime = newDayTime(2016, 8, 29, 0, 25);
Task task = newWeeklyFromDue(1, dueDateTime, new WeekdayNum(0, Weekday.MO), new WeekdayNum(0, Weekday.WE));
assertEquals(newDayTime(2016, 8, 31, 0, 25), calculateNextDueDate(task));
}
@Test
public void testAdvancedRepeatWeeklyFromCompleteDateCompleteBefore() throws ParseException {
DateTime dueDateTime = newDayTime(2016, 8, 29, 0, 25);
DateTime completionDateTime = newDayTime(2016, 8, 28, 1, 9);
Task task = newWeeklyFromCompleted(1, dueDateTime, completionDateTime, new WeekdayNum(0, Weekday.MO), new WeekdayNum(0, Weekday.WE));
assertEquals(newDayTime(2016, 8, 29, 0, 25), calculateNextDueDate(task));
}
@Test
public void testAdvancedRepeatWeeklyFromCompleteDateCompleteAfter() throws ParseException {
DateTime dueDateTime = newDayTime(2016, 8, 29, 0, 25);
DateTime completionDateTime = newDayTime(2016, 9, 1, 1, 9);
Task task = newWeeklyFromCompleted(1, dueDateTime, completionDateTime, new WeekdayNum(0, Weekday.MO), new WeekdayNum(0, Weekday.WE));
assertEquals(newDayTime(2016, 9, 5, 0, 25), calculateNextDueDate(task));
}
private DateTime newDayTime(int year, int month, int day, int hour, int minute) {
return new DateTime(Task.createDueDate(Task.URGENCY_SPECIFIC_DAY_TIME, new DateTime(year, month, day, hour, minute).getMillis()));
}
private DateTime calculateNextDueDate(Task task) throws ParseException {
return new DateTime(computeNextDueDate(task, task.sanitizedRecurrence(), task.repeatAfterCompletion()));
}
private Task newFromDue(Frequency frequency, int interval, DateTime dueDateTime) {
return new Task() {{
setRecurrence(getRecurrenceRule(frequency, interval, false));
setDueDate(dueDateTime.getMillis());
}};
}
private Task newWeeklyFromDue(int interval, DateTime dueDateTime, WeekdayNum... weekdays) {
return new Task() {{
setRecurrence(getRecurrenceRule(Frequency.WEEKLY, interval, false, weekdays));
setDueDate(dueDateTime.getMillis());
}};
}
private Task newFromCompleted(Frequency frequency, int interval, DateTime dueDateTime, DateTime completionDate) {
return new Task() {{
setRecurrence(getRecurrenceRule(frequency, interval, true));
setDueDate(dueDateTime.getMillis());
setCompletionDate(completionDate.getMillis());
}};
}
private Task newWeeklyFromCompleted(int interval, DateTime dueDateTime, DateTime completionDate, WeekdayNum... weekdays) {
return new Task() {{
setRecurrence(getRecurrenceRule(Frequency.WEEKLY, interval, true, weekdays));
setDueDate(dueDateTime.getMillis());
setCompletionDate(completionDate.getMillis());
}};
}
private String getRecurrenceRule(Frequency frequency, int interval, boolean fromCompletion, WeekdayNum... weekdays) {
RRule rrule = new RRule();
rrule.setFreq(frequency);
rrule.setInterval(interval);
if (weekdays != null) {
rrule.setByDay(asList(weekdays));
}
String result = rrule.toIcal();
if (fromCompletion) {
result += ";FROM=COMPLETION";
}
return result;
} }
return result;
}
} }

@ -1,14 +1,26 @@
package com.todoroo.astrid.repeats; package com.todoroo.astrid.repeats;
import static com.natpryce.makeiteasy.MakeItEasy.with;
import static junit.framework.Assert.assertEquals;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.tasks.makers.TaskMaker.AFTER_COMPLETE;
import static org.tasks.makers.TaskMaker.COMPLETION_TIME;
import static org.tasks.makers.TaskMaker.DUE_TIME;
import static org.tasks.makers.TaskMaker.ID;
import static org.tasks.makers.TaskMaker.RRULE;
import static org.tasks.makers.TaskMaker.newTask;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.support.test.runner.AndroidJUnit4; import android.support.test.runner.AndroidJUnit4;
import com.google.ical.values.RRule; import com.google.ical.values.RRule;
import com.todoroo.astrid.alarms.AlarmService; import com.todoroo.astrid.alarms.AlarmService;
import com.todoroo.astrid.dao.TaskDao; import com.todoroo.astrid.dao.TaskDao;
import com.todoroo.astrid.data.Task; import com.todoroo.astrid.data.Task;
import com.todoroo.astrid.gcal.GCalHelper; import com.todoroo.astrid.gcal.GCalHelper;
import java.text.ParseException;
import javax.inject.Inject;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
@ -19,188 +31,173 @@ import org.tasks.injection.InjectingTestCase;
import org.tasks.injection.TestComponent; import org.tasks.injection.TestComponent;
import org.tasks.time.DateTime; import org.tasks.time.DateTime;
import java.text.ParseException;
import javax.inject.Inject;
import static com.natpryce.makeiteasy.MakeItEasy.with;
import static junit.framework.Assert.assertEquals;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.tasks.makers.TaskMaker.AFTER_COMPLETE;
import static org.tasks.makers.TaskMaker.COMPLETION_TIME;
import static org.tasks.makers.TaskMaker.DUE_TIME;
import static org.tasks.makers.TaskMaker.ID;
import static org.tasks.makers.TaskMaker.RRULE;
import static org.tasks.makers.TaskMaker.newTask;
@SuppressLint("NewApi") @SuppressLint("NewApi")
@RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class)
public class RepeatTaskHelperTest extends InjectingTestCase { public class RepeatTaskHelperTest extends InjectingTestCase {
@Inject TaskDao taskDao; @Inject TaskDao taskDao;
private LocalBroadcastManager localBroadcastManager; private LocalBroadcastManager localBroadcastManager;
private AlarmService alarmService; private AlarmService alarmService;
private GCalHelper gCalHelper; private GCalHelper gCalHelper;
private RepeatTaskHelper helper; private RepeatTaskHelper helper;
private InOrder mocks; private InOrder mocks;
@Before @Before
public void before() { public void before() {
alarmService = mock(AlarmService.class); alarmService = mock(AlarmService.class);
gCalHelper = mock(GCalHelper.class); gCalHelper = mock(GCalHelper.class);
localBroadcastManager = mock(LocalBroadcastManager.class); localBroadcastManager = mock(LocalBroadcastManager.class);
mocks = inOrder(alarmService, gCalHelper, localBroadcastManager); mocks = inOrder(alarmService, gCalHelper, localBroadcastManager);
helper = new RepeatTaskHelper(gCalHelper, alarmService, taskDao, localBroadcastManager); helper = new RepeatTaskHelper(gCalHelper, alarmService, taskDao, localBroadcastManager);
} }
@After @After
public void after() { public void after() {
verifyNoMoreInteractions(localBroadcastManager, gCalHelper, alarmService); verifyNoMoreInteractions(localBroadcastManager, gCalHelper, alarmService);
} }
@Test @Test
public void noRepeat() { public void noRepeat() {
helper.handleRepeat(newTask(with(DUE_TIME, new DateTime(2017, 10, 4, 13, 30)))); helper.handleRepeat(newTask(with(DUE_TIME, new DateTime(2017, 10, 4, 13, 30))));
} }
@Test @Test
public void testMinutelyRepeat() throws ParseException { public void testMinutelyRepeat() throws ParseException {
Task task = newTask(with(ID, 1L), Task task = newTask(with(ID, 1L),
with(DUE_TIME, new DateTime(2017, 10, 4, 13, 30)), with(DUE_TIME, new DateTime(2017, 10, 4, 13, 30)),
with(RRULE, new RRule("RRULE:FREQ=MINUTELY;INTERVAL=30"))); with(RRULE, new RRule("RRULE:FREQ=MINUTELY;INTERVAL=30")));
repeatAndVerify(task, repeatAndVerify(task,
new DateTime(2017, 10, 4, 13, 30, 1), new DateTime(2017, 10, 4, 13, 30, 1),
new DateTime(2017, 10, 4, 14, 0, 1)); new DateTime(2017, 10, 4, 14, 0, 1));
} }
@Test @Test
public void testMinutelyRepeatAfterCompletion() throws ParseException { public void testMinutelyRepeatAfterCompletion() throws ParseException {
Task task = newTask(with(ID, 1L), Task task = newTask(with(ID, 1L),
with(DUE_TIME, new DateTime(2017, 10, 4, 13, 30)), with(DUE_TIME, new DateTime(2017, 10, 4, 13, 30)),
with(COMPLETION_TIME, new DateTime(2017, 10, 4, 13, 17, 45, 340)), with(COMPLETION_TIME, new DateTime(2017, 10, 4, 13, 17, 45, 340)),
with(RRULE, new RRule("RRULE:FREQ=MINUTELY;INTERVAL=30")), with(RRULE, new RRule("RRULE:FREQ=MINUTELY;INTERVAL=30")),
with(AFTER_COMPLETE, true)); with(AFTER_COMPLETE, true));
repeatAndVerify(task, repeatAndVerify(task,
new DateTime(2017, 10, 4, 13, 30, 1), new DateTime(2017, 10, 4, 13, 30, 1),
new DateTime(2017, 10, 4, 13, 47, 1)); new DateTime(2017, 10, 4, 13, 47, 1));
} }
@Test @Test
public void testMinutelyDecrementCount() throws ParseException { public void testMinutelyDecrementCount() throws ParseException {
Task task = newTask(with(ID, 1L), Task task = newTask(with(ID, 1L),
with(DUE_TIME, new DateTime(2017, 10, 4, 13, 30)), with(DUE_TIME, new DateTime(2017, 10, 4, 13, 30)),
with(RRULE, new RRule("RRULE:FREQ=MINUTELY;COUNT=2;INTERVAL=30"))); with(RRULE, new RRule("RRULE:FREQ=MINUTELY;COUNT=2;INTERVAL=30")));
repeatAndVerify(task, repeatAndVerify(task,
new DateTime(2017, 10, 4, 13, 30, 1), new DateTime(2017, 10, 4, 13, 30, 1),
new DateTime(2017, 10, 4, 14, 0, 1)); new DateTime(2017, 10, 4, 14, 0, 1));
assertEquals(1, new RRule(task.getRecurrenceWithoutFrom()).getCount()); assertEquals(1, new RRule(task.getRecurrenceWithoutFrom()).getCount());
} }
@Test @Test
public void testMinutelyLastOccurrence() throws ParseException { public void testMinutelyLastOccurrence() throws ParseException {
Task task = newTask(with(ID, 1L), Task task = newTask(with(ID, 1L),
with(DUE_TIME, new DateTime(2017, 10, 4, 13, 30)), with(DUE_TIME, new DateTime(2017, 10, 4, 13, 30)),
with(RRULE, new RRule("RRULE:FREQ=MINUTELY;COUNT=1;INTERVAL=30"))); with(RRULE, new RRule("RRULE:FREQ=MINUTELY;COUNT=1;INTERVAL=30")));
helper.handleRepeat(task); helper.handleRepeat(task);
} }
@Test @Test
public void testHourlyRepeat() throws ParseException { public void testHourlyRepeat() throws ParseException {
Task task = newTask(with(ID, 1L), Task task = newTask(with(ID, 1L),
with(DUE_TIME, new DateTime(2017, 10, 4, 13, 30)), with(DUE_TIME, new DateTime(2017, 10, 4, 13, 30)),
with(RRULE, new RRule("RRULE:FREQ=HOURLY;INTERVAL=6"))); with(RRULE, new RRule("RRULE:FREQ=HOURLY;INTERVAL=6")));
repeatAndVerify(task, repeatAndVerify(task,
new DateTime(2017, 10, 4, 13, 30, 1), new DateTime(2017, 10, 4, 13, 30, 1),
new DateTime(2017, 10, 4, 19, 30, 1)); new DateTime(2017, 10, 4, 19, 30, 1));
} }
@Test @Test
public void testHourlyRepeatAfterCompletion() throws ParseException { public void testHourlyRepeatAfterCompletion() throws ParseException {
Task task = newTask(with(ID, 1L), Task task = newTask(with(ID, 1L),
with(DUE_TIME, new DateTime(2017, 10, 4, 13, 30)), with(DUE_TIME, new DateTime(2017, 10, 4, 13, 30)),
with(COMPLETION_TIME, new DateTime(2017, 10, 4, 13, 17, 45, 340)), with(COMPLETION_TIME, new DateTime(2017, 10, 4, 13, 17, 45, 340)),
with(RRULE, new RRule("RRULE:FREQ=HOURLY;INTERVAL=6")), with(RRULE, new RRule("RRULE:FREQ=HOURLY;INTERVAL=6")),
with(AFTER_COMPLETE, true)); with(AFTER_COMPLETE, true));
repeatAndVerify(task, repeatAndVerify(task,
new DateTime(2017, 10, 4, 13, 30, 1), new DateTime(2017, 10, 4, 13, 30, 1),
new DateTime(2017, 10, 4, 19, 17, 1)); new DateTime(2017, 10, 4, 19, 17, 1));
} }
@Test @Test
public void testDailyRepeat() throws ParseException { public void testDailyRepeat() throws ParseException {
Task task = newTask(with(ID, 1L), Task task = newTask(with(ID, 1L),
with(DUE_TIME, new DateTime(2017, 10, 4, 13, 30)), with(DUE_TIME, new DateTime(2017, 10, 4, 13, 30)),
with(RRULE, new RRule("RRULE:FREQ=DAILY;INTERVAL=6"))); with(RRULE, new RRule("RRULE:FREQ=DAILY;INTERVAL=6")));
repeatAndVerify(task, repeatAndVerify(task,
new DateTime(2017, 10, 4, 13, 30, 1), new DateTime(2017, 10, 4, 13, 30, 1),
new DateTime(2017, 10, 10, 13, 30, 1)); new DateTime(2017, 10, 10, 13, 30, 1));
} }
@Test @Test
public void testRepeatWeeklyNoDays() throws ParseException { public void testRepeatWeeklyNoDays() throws ParseException {
Task task = newTask(with(ID, 1L), Task task = newTask(with(ID, 1L),
with(DUE_TIME, new DateTime(2017, 10, 4, 13, 30)), with(DUE_TIME, new DateTime(2017, 10, 4, 13, 30)),
with(RRULE, new RRule("RRULE:FREQ=WEEKLY;INTERVAL=2"))); with(RRULE, new RRule("RRULE:FREQ=WEEKLY;INTERVAL=2")));
repeatAndVerify(task, repeatAndVerify(task,
new DateTime(2017, 10, 4, 13, 30, 1), new DateTime(2017, 10, 4, 13, 30, 1),
new DateTime(2017, 10, 18, 13, 30, 1)); new DateTime(2017, 10, 18, 13, 30, 1));
} }
@Test @Test
public void testYearly() throws ParseException { public void testYearly() throws ParseException {
Task task = newTask(with(ID, 1L), Task task = newTask(with(ID, 1L),
with(DUE_TIME, new DateTime(2017, 10, 4, 13, 30)), with(DUE_TIME, new DateTime(2017, 10, 4, 13, 30)),
with(RRULE, new RRule("RRULE:FREQ=YEARLY;INTERVAL=3"))); with(RRULE, new RRule("RRULE:FREQ=YEARLY;INTERVAL=3")));
repeatAndVerify(task, repeatAndVerify(task,
new DateTime(2017, 10, 4, 13, 30, 1), new DateTime(2017, 10, 4, 13, 30, 1),
new DateTime(2020, 10, 4, 13, 30, 1)); new DateTime(2020, 10, 4, 13, 30, 1));
} }
@Test @Test
public void testMonthlyRepeat() throws ParseException { public void testMonthlyRepeat() throws ParseException {
Task task = newTask(with(ID, 1L), Task task = newTask(with(ID, 1L),
with(DUE_TIME, new DateTime(2017, 10, 4, 13, 30)), with(DUE_TIME, new DateTime(2017, 10, 4, 13, 30)),
with(RRULE, new RRule("RRULE:FREQ=MONTHLY;INTERVAL=3"))); with(RRULE, new RRule("RRULE:FREQ=MONTHLY;INTERVAL=3")));
repeatAndVerify(task, repeatAndVerify(task,
new DateTime(2017, 10, 4, 13, 30, 1), new DateTime(2017, 10, 4, 13, 30, 1),
new DateTime(2018, 1, 4, 13, 30, 1)); new DateTime(2018, 1, 4, 13, 30, 1));
} }
@Test @Test
public void testMonthlyRepeatAtEndOfMonth() throws ParseException { public void testMonthlyRepeatAtEndOfMonth() throws ParseException {
Task task = newTask(with(ID, 1L), Task task = newTask(with(ID, 1L),
with(DUE_TIME, new DateTime(2017, 1, 31, 13, 30)), with(DUE_TIME, new DateTime(2017, 1, 31, 13, 30)),
with(RRULE, new RRule("RRULE:FREQ=MONTHLY;INTERVAL=1"))); with(RRULE, new RRule("RRULE:FREQ=MONTHLY;INTERVAL=1")));
repeatAndVerify(task, repeatAndVerify(task,
new DateTime(2017, 1, 31, 13, 30, 1), new DateTime(2017, 1, 31, 13, 30, 1),
new DateTime(2017, 2, 28, 13, 30, 1)); new DateTime(2017, 2, 28, 13, 30, 1));
} }
private void repeatAndVerify(Task task, DateTime oldDueDate, DateTime newDueDate) { private void repeatAndVerify(Task task, DateTime oldDueDate, DateTime newDueDate) {
helper.handleRepeat(task); helper.handleRepeat(task);
mocks.verify(gCalHelper).rescheduleRepeatingTask(task); mocks.verify(gCalHelper).rescheduleRepeatingTask(task);
mocks.verify(alarmService).rescheduleAlarms(1, oldDueDate.getMillis(), newDueDate.getMillis()); mocks.verify(alarmService).rescheduleAlarms(1, oldDueDate.getMillis(), newDueDate.getMillis());
mocks.verify(localBroadcastManager).broadcastRepeat(1, oldDueDate.getMillis(), newDueDate.getMillis()); mocks.verify(localBroadcastManager)
} .broadcastRepeat(1, oldDueDate.getMillis(), newDueDate.getMillis());
}
@Override
protected void inject(TestComponent component) { @Override
component.inject(this); protected void inject(TestComponent component) {
} component.inject(this);
}
} }

@ -3,111 +3,107 @@
* *
* See the file "LICENSE" for the full license governing this code. * See the file "LICENSE" for the full license governing this code.
*/ */
package com.todoroo.astrid.service; package com.todoroo.astrid.service;
import android.support.test.runner.AndroidJUnit4; import static junit.framework.Assert.assertEquals;
import android.support.test.runner.AndroidJUnit4;
import com.todoroo.astrid.data.Task; import com.todoroo.astrid.data.Task;
import com.todoroo.astrid.tags.TagService; import com.todoroo.astrid.tags.TagService;
import com.todoroo.astrid.utility.TitleParser; import com.todoroo.astrid.utility.TitleParser;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.tasks.injection.InjectingTestCase;
import org.tasks.injection.TestComponent;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import javax.inject.Inject; import javax.inject.Inject;
import org.junit.Test;
import static junit.framework.Assert.assertEquals; import org.junit.runner.RunWith;
import org.tasks.injection.InjectingTestCase;
import org.tasks.injection.TestComponent;
@RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class)
public class QuickAddMarkupTest extends InjectingTestCase { public class QuickAddMarkupTest extends InjectingTestCase {
@Inject TagService tagService; private final ArrayList<String> tags = new ArrayList<>();
@Inject TagService tagService;
@Override private Task task;
protected void inject(TestComponent component) {
component.inject(this); @Override
} protected void inject(TestComponent component) {
component.inject(this);
@Test }
public void testTags() {
whenTitleIs("this #cool"); @Test
assertTitleBecomes("this"); public void testTags() {
assertTagsAre("cool"); whenTitleIs("this #cool");
assertTitleBecomes("this");
whenTitleIs("#cool task"); assertTagsAre("cool");
assertTitleBecomes("task");
assertTagsAre("cool"); whenTitleIs("#cool task");
assertTitleBecomes("task");
whenTitleIs("doggie #nice #cute"); assertTagsAre("cool");
assertTitleBecomes("doggie");
assertTagsAre("nice", "cute"); whenTitleIs("doggie #nice #cute");
} assertTitleBecomes("doggie");
assertTagsAre("nice", "cute");
@Test }
public void testContexts() {
whenTitleIs("eat @home"); @Test
assertTitleBecomes("eat"); public void testContexts() {
assertTagsAre("home"); whenTitleIs("eat @home");
assertTitleBecomes("eat");
whenTitleIs("buy oatmeal @store @morning"); assertTagsAre("home");
assertTitleBecomes("buy oatmeal");
assertTagsAre("store", "morning"); whenTitleIs("buy oatmeal @store @morning");
assertTitleBecomes("buy oatmeal");
whenTitleIs("look @ me"); assertTagsAre("store", "morning");
assertTitleBecomes("look @ me");
assertTagsAre(); whenTitleIs("look @ me");
} assertTitleBecomes("look @ me");
assertTagsAre();
@Test }
public void testImportances() {
whenTitleIs("eat !1"); // --- helpers
assertTitleBecomes("eat");
assertImportanceIs(Task.IMPORTANCE_SHOULD_DO); @Test
public void testImportances() {
whenTitleIs("super cool!"); whenTitleIs("eat !1");
assertTitleBecomes("super cool!"); assertTitleBecomes("eat");
assertImportanceIs(Task.IMPORTANCE_SHOULD_DO);
whenTitleIs("stay alive !4");
assertTitleBecomes("stay alive"); whenTitleIs("super cool!");
assertImportanceIs(Task.IMPORTANCE_DO_OR_DIE); assertTitleBecomes("super cool!");
}
whenTitleIs("stay alive !4");
@Test assertTitleBecomes("stay alive");
public void testMixed() { assertImportanceIs(Task.IMPORTANCE_DO_OR_DIE);
whenTitleIs("eat #food !2"); }
assertTitleBecomes("eat");
assertTagsAre("food"); @Test
assertImportanceIs(Task.IMPORTANCE_MUST_DO); public void testMixed() {
} whenTitleIs("eat #food !2");
assertTitleBecomes("eat");
// --- helpers assertTagsAre("food");
assertImportanceIs(Task.IMPORTANCE_MUST_DO);
private Task task; }
private final ArrayList<String> tags = new ArrayList<>();
private void assertTagsAre(String... expectedTags) {
private void assertTagsAre(String... expectedTags) { List<String> expected = Arrays.asList(expectedTags);
List<String> expected = Arrays.asList(expectedTags); assertEquals(expected.toString(), tags.toString());
assertEquals(expected.toString(), tags.toString()); }
}
private void assertTitleBecomes(String title) {
private void assertTitleBecomes(String title) { assertEquals(title, task.getTitle());
assertEquals(title, task.getTitle()); }
}
private void whenTitleIs(String title) {
private void whenTitleIs(String title) { task = new Task();
task = new Task(); task.setTitle(title);
task.setTitle(title); tags.clear();
tags.clear(); TitleParser.parse(tagService, task, tags);
TitleParser.parse(tagService, task, tags); }
}
private void assertImportanceIs(int importance) {
private void assertImportanceIs(int importance) { assertEquals(importance, (int) task.getImportance());
assertEquals(importance, (int)task.getImportance()); }
}
} }

@ -1,73 +1,72 @@
package com.todoroo.astrid.subtasks; package com.todoroo.astrid.subtasks;
import android.support.test.runner.AndroidJUnit4; import static junit.framework.Assert.assertEquals;
import android.support.test.runner.AndroidJUnit4;
import com.todoroo.astrid.dao.TaskDao; import com.todoroo.astrid.dao.TaskDao;
import com.todoroo.astrid.data.Task; import com.todoroo.astrid.data.Task;
import org.tasks.data.TaskListMetadata; import javax.inject.Inject;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.tasks.data.TaskListMetadata;
import org.tasks.injection.TestComponent; import org.tasks.injection.TestComponent;
import javax.inject.Inject;
import static junit.framework.Assert.assertEquals;
@RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class)
public class SubtasksHelperTest extends SubtasksTestCase { public class SubtasksHelperTest extends SubtasksTestCase {
@Inject TaskDao taskDao; private static final String[] EXPECTED_ORDER = {"-1", "1", "2", "3", "4", "5", "6"};
private static String EXPECTED_REMOTE = "[\"-1\", [\"6\", \"4\", [\"3\", \"1\"]], \"2\", \"5\"]"
@Override .replaceAll("\\s", "");
public void setUp() { @Inject TaskDao taskDao;
super.setUp();
createTasks(); @Override
TaskListMetadata m = new TaskListMetadata(); public void setUp() {
m.setFilter(TaskListMetadata.FILTER_ID_ALL); super.setUp();
updater.initializeFromSerializedTree(m, filter, SubtasksHelper.convertTreeToRemoteIds(taskDao, DEFAULT_SERIALIZED_TREE)); createTasks();
} TaskListMetadata m = new TaskListMetadata();
m.setFilter(TaskListMetadata.FILTER_ID_ALL);
private void createTask(String title, String uuid) { updater.initializeFromSerializedTree(m, filter,
Task t = new Task(); SubtasksHelper.convertTreeToRemoteIds(taskDao, DEFAULT_SERIALIZED_TREE));
t.setTitle(title); }
t.setUuid(uuid);
taskDao.createNew(t); private void createTask(String title, String uuid) {
Task t = new Task();
t.setTitle(title);
t.setUuid(uuid);
taskDao.createNew(t);
}
private void createTasks() {
createTask("A", "6"); // Local id 1
createTask("B", "4"); // Local id 2
createTask("C", "3"); // Local id 3
createTask("D", "1"); // Local id 4
createTask("E", "2"); // Local id 5
createTask("F", "5"); // Local id 6
}
// Default order: "[-1, [1, 2, [3, 4]], 5, 6]"
@Test
public void testOrderedIdArray() {
String[] ids = SubtasksHelper.getStringIdArray(DEFAULT_SERIALIZED_TREE);
assertEquals(EXPECTED_ORDER.length, ids.length);
for (int i = 0; i < EXPECTED_ORDER.length; i++) {
assertEquals(EXPECTED_ORDER[i], ids[i]);
} }
}
private void createTasks() { @Test
createTask("A", "6"); // Local id 1 public void testLocalToRemoteIdMapping() {
createTask("B", "4"); // Local id 2 String mapped = SubtasksHelper.convertTreeToRemoteIds(taskDao, DEFAULT_SERIALIZED_TREE)
createTask("C", "3"); // Local id 3 .replaceAll("\\s", "");
createTask("D", "1"); // Local id 4 assertEquals(EXPECTED_REMOTE, mapped);
createTask("E", "2"); // Local id 5 }
createTask("F", "5"); // Local id 6
}
private static final String[] EXPECTED_ORDER = { "-1", "1", "2", "3", "4", "5", "6" };
@Test @Override
public void testOrderedIdArray() { protected void inject(TestComponent component) {
String[] ids = SubtasksHelper.getStringIdArray(DEFAULT_SERIALIZED_TREE); super.inject(component);
assertEquals(EXPECTED_ORDER.length, ids.length);
for (int i = 0; i < EXPECTED_ORDER.length; i++) {
assertEquals(EXPECTED_ORDER[i], ids[i]);
}
}
// Default order: "[-1, [1, 2, [3, 4]], 5, 6]" component.inject(this);
}
private static String EXPECTED_REMOTE = "[\"-1\", [\"6\", \"4\", [\"3\", \"1\"]], \"2\", \"5\"]".replaceAll("\\s", "");
@Test
public void testLocalToRemoteIdMapping() {
String mapped = SubtasksHelper.convertTreeToRemoteIds(taskDao, DEFAULT_SERIALIZED_TREE).replaceAll("\\s", "");
assertEquals(EXPECTED_REMOTE, mapped);
}
@Override
protected void inject(TestComponent component) {
super.inject(component);
component.inject(this);
}
} }

@ -1,61 +1,59 @@
package com.todoroo.astrid.subtasks; package com.todoroo.astrid.subtasks;
import android.support.test.runner.AndroidJUnit4; import android.support.test.runner.AndroidJUnit4;
import com.todoroo.astrid.dao.TaskDao; import com.todoroo.astrid.dao.TaskDao;
import com.todoroo.astrid.data.Task; import com.todoroo.astrid.data.Task;
import org.tasks.data.TaskListMetadata; import javax.inject.Inject;
import org.junit.Ignore; import org.junit.Ignore;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.tasks.data.TaskListMetadata;
import javax.inject.Inject;
@RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class)
public class SubtasksMovingTest extends SubtasksTestCase { public class SubtasksMovingTest extends SubtasksTestCase {
@Inject TaskDao taskDao; @Inject TaskDao taskDao;
private Task A, B, C, D, E, F; private Task A, B, C, D, E, F;
// @Override // @Override
protected void disabled_setUp() { protected void disabled_setUp() {
super.setUp(); super.setUp();
createTasks(); createTasks();
TaskListMetadata m = new TaskListMetadata(); TaskListMetadata m = new TaskListMetadata();
m.setFilter(TaskListMetadata.FILTER_ID_ALL); m.setFilter(TaskListMetadata.FILTER_ID_ALL);
updater.initializeFromSerializedTree(m, filter, SubtasksHelper.convertTreeToRemoteIds(taskDao, DEFAULT_SERIALIZED_TREE)); updater.initializeFromSerializedTree(m, filter,
SubtasksHelper.convertTreeToRemoteIds(taskDao, DEFAULT_SERIALIZED_TREE));
// Assert initial state is correct
expectParentAndPosition(A, null, 0); // Assert initial state is correct
expectParentAndPosition(B, A, 0); expectParentAndPosition(A, null, 0);
expectParentAndPosition(C, A, 1); expectParentAndPosition(B, A, 0);
expectParentAndPosition(D, C, 0); expectParentAndPosition(C, A, 1);
expectParentAndPosition(E, null, 1); expectParentAndPosition(D, C, 0);
expectParentAndPosition(F, null, 2); expectParentAndPosition(E, null, 1);
} expectParentAndPosition(F, null, 2);
}
private void createTasks() {
A = createTask("A"); private void createTasks() {
B = createTask("B"); A = createTask("A");
C = createTask("C"); B = createTask("B");
D = createTask("D"); C = createTask("C");
E = createTask("E"); D = createTask("D");
F = createTask("F"); E = createTask("E");
} F = createTask("F");
}
private Task createTask(String title) {
Task task = new Task(); private Task createTask(String title) {
task.setTitle(title); Task task = new Task();
taskDao.createNew(task); task.setTitle(title);
return task; taskDao.createNew(task);
} return task;
}
private void whenTriggerMoveBefore(Task target, Task before) {
String beforeId = (before == null ? "-1" : before.getUuid()); private void whenTriggerMoveBefore(Task target, Task before) {
updater.moveTo(null, filter, target.getUuid(), beforeId); String beforeId = (before == null ? "-1" : before.getUuid());
} updater.moveTo(null, filter, target.getUuid(), beforeId);
}
/* Starting State (see SubtasksTestCase): /* Starting State (see SubtasksTestCase):
* *
@ -67,68 +65,68 @@ public class SubtasksMovingTest extends SubtasksTestCase {
* F * F
*/ */
@Ignore @Ignore
@Test @Test
public void testMoveBeforeIntoSelf() { // Should have no effect public void testMoveBeforeIntoSelf() { // Should have no effect
whenTriggerMoveBefore(A, B); whenTriggerMoveBefore(A, B);
expectParentAndPosition(A, null, 0); expectParentAndPosition(A, null, 0);
expectParentAndPosition(B, A, 0); expectParentAndPosition(B, A, 0);
expectParentAndPosition(C, A, 1); expectParentAndPosition(C, A, 1);
expectParentAndPosition(D, C, 0); expectParentAndPosition(D, C, 0);
expectParentAndPosition(E, null, 1); expectParentAndPosition(E, null, 1);
expectParentAndPosition(F, null, 2); expectParentAndPosition(F, null, 2);
} }
@Ignore @Ignore
@Test @Test
public void testMoveIntoDescendant() { // Should have no effect public void testMoveIntoDescendant() { // Should have no effect
whenTriggerMoveBefore(A, C); whenTriggerMoveBefore(A, C);
expectParentAndPosition(A, null, 0); expectParentAndPosition(A, null, 0);
expectParentAndPosition(B, A, 0); expectParentAndPosition(B, A, 0);
expectParentAndPosition(C, A, 1); expectParentAndPosition(C, A, 1);
expectParentAndPosition(D, C, 0); expectParentAndPosition(D, C, 0);
expectParentAndPosition(E, null, 1); expectParentAndPosition(E, null, 1);
expectParentAndPosition(F, null, 2); expectParentAndPosition(F, null, 2);
} }
@Ignore @Ignore
@Test @Test
public void testMoveToEndOfChildren() { // Should have no effect public void testMoveToEndOfChildren() { // Should have no effect
whenTriggerMoveBefore(A, E); whenTriggerMoveBefore(A, E);
expectParentAndPosition(A, null, 0); expectParentAndPosition(A, null, 0);
expectParentAndPosition(B, A, 0); expectParentAndPosition(B, A, 0);
expectParentAndPosition(C, A, 1); expectParentAndPosition(C, A, 1);
expectParentAndPosition(D, C, 0); expectParentAndPosition(D, C, 0);
expectParentAndPosition(E, null, 1); expectParentAndPosition(E, null, 1);
expectParentAndPosition(F, null, 2); expectParentAndPosition(F, null, 2);
} }
@Ignore @Ignore
@Test @Test
public void testStandardMove() { public void testStandardMove() {
whenTriggerMoveBefore(A, F); whenTriggerMoveBefore(A, F);
expectParentAndPosition(A, null, 1); expectParentAndPosition(A, null, 1);
expectParentAndPosition(B, A, 0); expectParentAndPosition(B, A, 0);
expectParentAndPosition(C, A, 1); expectParentAndPosition(C, A, 1);
expectParentAndPosition(D, C, 0); expectParentAndPosition(D, C, 0);
expectParentAndPosition(E, null, 0); expectParentAndPosition(E, null, 0);
expectParentAndPosition(F, null, 2); expectParentAndPosition(F, null, 2);
} }
@Ignore @Ignore
@Test @Test
public void testMoveToEndOfList() { public void testMoveToEndOfList() {
whenTriggerMoveBefore(A, null); whenTriggerMoveBefore(A, null);
expectParentAndPosition(A, null, 2); expectParentAndPosition(A, null, 2);
expectParentAndPosition(B, A, 0); expectParentAndPosition(B, A, 0);
expectParentAndPosition(C, A, 1); expectParentAndPosition(C, A, 1);
expectParentAndPosition(D, C, 0); expectParentAndPosition(D, C, 0);
expectParentAndPosition(E, null, 0); expectParentAndPosition(E, null, 0);
expectParentAndPosition(F, null, 1); expectParentAndPosition(F, null, 1);
} }
} }

@ -1,66 +1,63 @@
package com.todoroo.astrid.subtasks; package com.todoroo.astrid.subtasks;
import static android.support.test.InstrumentationRegistry.getTargetContext;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertNotNull;
import com.todoroo.astrid.api.Filter; import com.todoroo.astrid.api.Filter;
import com.todoroo.astrid.core.BuiltInFilterExposer; import com.todoroo.astrid.core.BuiltInFilterExposer;
import com.todoroo.astrid.dao.TaskDao; import com.todoroo.astrid.dao.TaskDao;
import org.tasks.data.TaskListMetadataDao;
import com.todoroo.astrid.data.Task; import com.todoroo.astrid.data.Task;
import com.todoroo.astrid.subtasks.SubtasksFilterUpdater.Node; import com.todoroo.astrid.subtasks.SubtasksFilterUpdater.Node;
import javax.inject.Inject;
import org.tasks.data.TaskListMetadataDao;
import org.tasks.injection.InjectingTestCase; import org.tasks.injection.InjectingTestCase;
import org.tasks.injection.TestComponent; import org.tasks.injection.TestComponent;
import org.tasks.preferences.Preferences; import org.tasks.preferences.Preferences;
import javax.inject.Inject;
import static android.support.test.InstrumentationRegistry.getTargetContext;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertNotNull;
/** /**
* Contains useful methods common to all subtasks tests * Contains useful methods common to all subtasks tests
* @author Sam
* *
* @author Sam
*/ */
public class SubtasksTestCase extends InjectingTestCase { public class SubtasksTestCase extends InjectingTestCase {
@Inject TaskListMetadataDao taskListMetadataDao; /* Starting State:
@Inject TaskDao taskDao; *
@Inject Preferences preferences; * A
* B
protected SubtasksFilterUpdater updater; * C
protected Filter filter; * D
* E
/* Starting State: * F
* */
* A public static final String DEFAULT_SERIALIZED_TREE = "[-1, [1, 2, [3, 4]], 5, 6]"
* B .replaceAll("\\s", "");
* C protected SubtasksFilterUpdater updater;
* D protected Filter filter;
* E @Inject TaskListMetadataDao taskListMetadataDao;
* F @Inject TaskDao taskDao;
*/ @Inject Preferences preferences;
public static final String DEFAULT_SERIALIZED_TREE = "[-1, [1, 2, [3, 4]], 5, 6]".replaceAll("\\s", "");
@Override
@Override public void setUp() {
public void setUp() { super.setUp();
super.setUp(); filter = BuiltInFilterExposer.getMyTasksFilter(getTargetContext().getResources());
filter = BuiltInFilterExposer.getMyTasksFilter(getTargetContext().getResources()); preferences.clear(SubtasksFilterUpdater.ACTIVE_TASKS_ORDER);
preferences.clear(SubtasksFilterUpdater.ACTIVE_TASKS_ORDER); updater = new SubtasksFilterUpdater(taskListMetadataDao, taskDao);
updater = new SubtasksFilterUpdater(taskListMetadataDao, taskDao); }
}
@Override
@Override protected void inject(TestComponent component) {
protected void inject(TestComponent component) { component.inject(this);
component.inject(this); }
}
protected void expectParentAndPosition(Task task, Task parent, int positionInParent) {
protected void expectParentAndPosition(Task task, Task parent, int positionInParent) { String parentId = (parent == null ? "-1" : parent.getUuid());
String parentId = (parent == null ? "-1" : parent.getUuid()); Node n = updater.findNodeForTask(task.getUuid());
Node n = updater.findNodeForTask(task.getUuid()); assertNotNull("No node found for task " + task.getTitle(), n);
assertNotNull("No node found for task " + task.getTitle(), n); assertEquals("Parent mismatch", parentId, n.parent.uuid);
assertEquals("Parent mismatch", parentId, n.parent.uuid); assertEquals("Position mismatch", positionInParent, n.parent.children.indexOf(n));
assertEquals("Position mismatch", positionInParent, n.parent.children.indexOf(n)); }
}
} }

@ -1,50 +1,47 @@
package com.todoroo.astrid.sync; package com.todoroo.astrid.sync;
import org.tasks.data.TagDataDao;
import com.todoroo.astrid.dao.TaskDao; import com.todoroo.astrid.dao.TaskDao;
import org.tasks.data.TagData;
import com.todoroo.astrid.data.Task; import com.todoroo.astrid.data.Task;
import javax.inject.Inject;
import org.tasks.data.TagData;
import org.tasks.data.TagDataDao;
import org.tasks.injection.InjectingTestCase; import org.tasks.injection.InjectingTestCase;
import org.tasks.injection.TestComponent; import org.tasks.injection.TestComponent;
import javax.inject.Inject;
public class NewSyncTestCase extends InjectingTestCase { public class NewSyncTestCase extends InjectingTestCase {
@Inject TaskDao taskDao; public static final String SYNC_TASK_TITLE = "new title";
@Inject TagDataDao tagDataDao; public static final int SYNC_TASK_IMPORTANCE = Task.IMPORTANCE_MUST_DO;
@Inject TaskDao taskDao;
protected Task createTask(String title) { @Inject TagDataDao tagDataDao;
Task task = new Task();
task.setTitle(title);
task.setImportance(SYNC_TASK_IMPORTANCE);
taskDao.createNew(task); protected Task createTask(String title) {
return task; Task task = new Task();
} task.setTitle(title);
task.setImportance(SYNC_TASK_IMPORTANCE);
public static final String SYNC_TASK_TITLE = "new title"; taskDao.createNew(task);
public static final int SYNC_TASK_IMPORTANCE = Task.IMPORTANCE_MUST_DO; return task;
}
protected Task createTask() { protected Task createTask() {
return createTask(SYNC_TASK_TITLE); return createTask(SYNC_TASK_TITLE);
} }
protected TagData createTagData(String name) { protected TagData createTagData(String name) {
TagData tag = new TagData(); TagData tag = new TagData();
tag.setName(name); tag.setName(name);
tagDataDao.createNew(tag); tagDataDao.createNew(tag);
return tag; return tag;
} }
protected TagData createTagData() { protected TagData createTagData() {
return createTagData("new tag"); return createTagData("new tag");
} }
@Override @Override
protected void inject(TestComponent component) { protected void inject(TestComponent component) {
component.inject(this); component.inject(this);
} }
} }

@ -1,28 +1,26 @@
package com.todoroo.astrid.sync; package com.todoroo.astrid.sync;
import android.support.test.runner.AndroidJUnit4; import static junit.framework.Assert.assertFalse;
import org.tasks.data.TagData; import android.support.test.runner.AndroidJUnit4;
import com.todoroo.astrid.data.Task; import com.todoroo.astrid.data.Task;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.tasks.data.TagData;
import static junit.framework.Assert.assertFalse;
@RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class)
public class SyncModelTest extends NewSyncTestCase { public class SyncModelTest extends NewSyncTestCase {
@Test @Test
public void testCreateTaskMakesUuid() { public void testCreateTaskMakesUuid() {
Task task = createTask(); Task task = createTask();
assertFalse(Task.NO_UUID.equals(task.getUuid())); assertFalse(Task.NO_UUID.equals(task.getUuid()));
} }
@Test @Test
public void testCreateTagMakesUuid() { public void testCreateTagMakesUuid() {
TagData tag = createTagData(); TagData tag = createTagData();
assertFalse(Task.NO_UUID.equals(tag.getRemoteId())); assertFalse(Task.NO_UUID.equals(tag.getRemoteId()));
} }
} }

@ -1,39 +1,39 @@
package org.tasks; package org.tasks;
import static org.tasks.time.DateTimeUtils.currentTimeMillis;
import org.tasks.time.DateTime; import org.tasks.time.DateTime;
import org.tasks.time.DateTimeUtils; import org.tasks.time.DateTimeUtils;
import static org.tasks.time.DateTimeUtils.currentTimeMillis;
public class Freeze { public class Freeze {
public static Freeze freezeClock() { public static Freeze freezeClock() {
return freezeAt(currentTimeMillis()); return freezeAt(currentTimeMillis());
} }
public static Freeze freezeAt(DateTime dateTime) { public static Freeze freezeAt(DateTime dateTime) {
return freezeAt(dateTime.getMillis()); return freezeAt(dateTime.getMillis());
} }
public static Freeze freezeAt(long millis) { public static Freeze freezeAt(long millis) {
DateTimeUtils.setCurrentMillisFixed(millis); DateTimeUtils.setCurrentMillisFixed(millis);
return new Freeze(); return new Freeze();
} }
public static void thaw() { public static void thaw() {
DateTimeUtils.setCurrentMillisSystem(); DateTimeUtils.setCurrentMillisSystem();
} }
@SuppressWarnings("UnusedParameters") @SuppressWarnings("UnusedParameters")
public void thawAfter(Snippet snippet) { public void thawAfter(Snippet snippet) {
thaw(); thaw();
} }
public void thawAfter(Runnable run) { public void thawAfter(Runnable run) {
try { try {
run.run(); run.run();
} finally { } finally {
thaw(); thaw();
}
} }
}
} }

@ -1,4 +1,5 @@
package org.tasks; package org.tasks;
public class Snippet { public class Snippet {
} }

@ -3,13 +3,14 @@ package org.tasks;
import android.content.Context; import android.content.Context;
public class TestUtilities { public class TestUtilities {
private static boolean mockitoInitialized;
private static boolean mockitoInitialized;
public static void initializeMockito(Context context) {
if (!mockitoInitialized) { public static void initializeMockito(Context context) {
// for mockito: https://code.google.com/p/dexmaker/issues/detail?id=2 if (!mockitoInitialized) {
System.setProperty("dexmaker.dexcache", context.getCacheDir().toString()); // for mockito: https://code.google.com/p/dexmaker/issues/detail?id=2
mockitoInitialized = true; System.setProperty("dexmaker.dexcache", context.getCacheDir().toString());
} mockitoInitialized = true;
} }
}
} }

@ -1,42 +1,41 @@
package org.tasks.date; package org.tasks.date;
import android.support.test.runner.AndroidJUnit4; import static junit.framework.Assert.assertEquals;
import static org.tasks.Freeze.freezeAt;
import static org.tasks.date.DateTimeUtils.newDateUtc;
import static org.tasks.time.DateTimeUtils.currentTimeMillis;
import android.support.test.runner.AndroidJUnit4;
import java.util.TimeZone;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.tasks.Snippet; import org.tasks.Snippet;
import org.tasks.time.DateTime; import org.tasks.time.DateTime;
import java.util.TimeZone;
import static junit.framework.Assert.assertEquals;
import static org.tasks.Freeze.freezeAt;
import static org.tasks.date.DateTimeUtils.newDateUtc;
import static org.tasks.time.DateTimeUtils.currentTimeMillis;
@RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class)
public class DateTimeUtilsTest { public class DateTimeUtilsTest {
private final DateTime now = new DateTime(2014, 1, 1, 15, 17, 53, 0); private final DateTime now = new DateTime(2014, 1, 1, 15, 17, 53, 0);
@Test @Test
public void testGetCurrentTime() { public void testGetCurrentTime() {
freezeAt(now).thawAfter(new Snippet() {{ freezeAt(now).thawAfter(new Snippet() {{
assertEquals(now.getMillis(), currentTimeMillis()); assertEquals(now.getMillis(), currentTimeMillis());
}}); }});
} }
@Test @Test
public void testCreateNewUtcDate() { public void testCreateNewUtcDate() {
DateTime utc = now.toUTC(); DateTime utc = now.toUTC();
DateTime actual = newDateUtc(utc.getYear(), utc.getMonthOfYear(), utc.getDayOfMonth(), utc.getHourOfDay(), utc.getMinuteOfHour(), utc.getSecondOfMinute()); DateTime actual = newDateUtc(utc.getYear(), utc.getMonthOfYear(), utc.getDayOfMonth(),
assertEquals(utc.getMillis(), actual.getMillis()); utc.getHourOfDay(), utc.getMinuteOfHour(), utc.getSecondOfMinute());
} assertEquals(utc.getMillis(), actual.getMillis());
}
@Test
public void testIllegalInstant() { @Test
new DateTime(2015, 7, 24, 0, 0, 0, 0, TimeZone.getTimeZone("Africa/Cairo")); public void testIllegalInstant() {
new DateTime(2015, 10, 18, 0, 0, 0, 0, TimeZone.getTimeZone("America/Sao_Paulo")); new DateTime(2015, 7, 24, 0, 0, 0, 0, TimeZone.getTimeZone("Africa/Cairo"));
new DateTime(2015, 10, 4, 0, 0, 0, 0, TimeZone.getTimeZone("America/Asuncion")); new DateTime(2015, 10, 18, 0, 0, 0, 0, TimeZone.getTimeZone("America/Sao_Paulo"));
} new DateTime(2015, 10, 4, 0, 0, 0, 0, TimeZone.getTimeZone("America/Asuncion"));
}
} }

@ -1,23 +1,23 @@
package org.tasks.injection; package org.tasks.injection;
import org.junit.Before;
import static android.support.test.InstrumentationRegistry.getTargetContext; import static android.support.test.InstrumentationRegistry.getTargetContext;
import static org.tasks.TestUtilities.initializeMockito; import static org.tasks.TestUtilities.initializeMockito;
import org.junit.Before;
public abstract class InjectingTestCase { public abstract class InjectingTestCase {
protected TestComponent component; protected TestComponent component;
@Before @Before
public void setUp() { public void setUp() {
initializeMockito(getTargetContext()); initializeMockito(getTargetContext());
component = DaggerTestComponent.builder() component = DaggerTestComponent.builder()
.testModule(new TestModule(getTargetContext())) .testModule(new TestModule(getTargetContext()))
.build(); .build();
inject(component); inject(component);
} }
protected abstract void inject(TestComponent component); protected abstract void inject(TestComponent component);
} }

@ -2,102 +2,118 @@ package org.tasks.injection;
import android.arch.persistence.room.Room; import android.arch.persistence.room.Room;
import android.content.Context; import android.content.Context;
import com.todoroo.astrid.dao.Database; import com.todoroo.astrid.dao.Database;
import com.todoroo.astrid.dao.TaskDao; import com.todoroo.astrid.dao.TaskDao;
import dagger.Module;
import dagger.Provides;
import org.tasks.data.AlarmDao; import org.tasks.data.AlarmDao;
import org.tasks.data.CaldavDao;
import org.tasks.data.FilterDao;
import org.tasks.data.GoogleTaskDao; import org.tasks.data.GoogleTaskDao;
import org.tasks.data.GoogleTaskListDao; import org.tasks.data.GoogleTaskListDao;
import org.tasks.data.LocationDao; import org.tasks.data.LocationDao;
import org.tasks.data.TagDao; import org.tasks.data.TagDao;
import org.tasks.data.TagDataDao; import org.tasks.data.TagDataDao;
import org.tasks.data.TaskAttachmentDao;
import org.tasks.data.TaskListMetadataDao; import org.tasks.data.TaskListMetadataDao;
import org.tasks.data.UserActivityDao; import org.tasks.data.UserActivityDao;
import org.tasks.notifications.NotificationDao; import org.tasks.notifications.NotificationDao;
import org.tasks.preferences.PermissionChecker; import org.tasks.preferences.PermissionChecker;
import org.tasks.preferences.PermissivePermissionChecker; import org.tasks.preferences.PermissivePermissionChecker;
import dagger.Module;
import dagger.Provides;
@Module @Module
public class TestModule { public class TestModule {
private Context context;
private Context context;
public TestModule(Context context) {
this.context = context; public TestModule(Context context) {
} this.context = context;
}
@Provides
@ApplicationScope @Provides
public Database getDatabase() { @ApplicationScope
return Room.inMemoryDatabaseBuilder(context, Database.class) public Database getDatabase() {
.fallbackToDestructiveMigration() return Room.inMemoryDatabaseBuilder(context, Database.class)
.build(); .fallbackToDestructiveMigration()
} .build();
}
@Provides
public NotificationDao getNotificationDao(Database appDatabase) { @Provides
return appDatabase.notificationDao(); public NotificationDao getNotificationDao(Database appDatabase) {
} return appDatabase.notificationDao();
}
@Provides
public TagDataDao getTagDataDao(Database database) { @Provides
return database.getTagDataDao(); public TagDataDao getTagDataDao(Database database) {
} return database.getTagDataDao();
}
@Provides
public UserActivityDao getUserActivityDao(Database database) { @Provides
return database.getUserActivityDao(); public UserActivityDao getUserActivityDao(Database database) {
} return database.getUserActivityDao();
}
@Provides
public TaskListMetadataDao getTaskListMetadataDao(Database database) { @Provides
return database.getTaskListMetadataDao(); public TaskListMetadataDao getTaskListMetadataDao(Database database) {
} return database.getTaskListMetadataDao();
}
@Provides
public GoogleTaskListDao getGoogleTaskListDao(Database database) { @Provides
return database.getGoogleTaskListDao(); public GoogleTaskListDao getGoogleTaskListDao(Database database) {
} return database.getGoogleTaskListDao();
}
@Provides
public AlarmDao getAlarmDao(Database database) { @Provides
return database.getAlarmDao(); public AlarmDao getAlarmDao(Database database) {
} return database.getAlarmDao();
}
@Provides
public GoogleTaskDao getGoogleTaskDao(Database database) { @Provides
return database.getGoogleTaskDao(); public GoogleTaskDao getGoogleTaskDao(Database database) {
} return database.getGoogleTaskDao();
}
@Provides
public TagDao getTagDao(Database database) { @Provides
return database.getTagDao(); public TagDao getTagDao(Database database) {
} return database.getTagDao();
}
@Provides
public LocationDao getLocationDao(Database database) { @Provides
return database.getLocationDao(); public LocationDao getLocationDao(Database database) {
} return database.getLocationDao();
}
@Provides
public TaskDao getTaskDao(Database database) { @Provides
TaskDao taskDao = database.getTaskDao(); public TaskDao getTaskDao(Database database) {
taskDao.initialize(context); TaskDao taskDao = database.getTaskDao();
return taskDao; taskDao.initialize(context);
} return taskDao;
}
@ApplicationScope
@Provides @Provides
@ForApplication public CaldavDao getCaldavDao(Database database) {
public Context getContext() { return database.getCaldavDao();
return context; }
}
@Provides
@Provides public FilterDao getFilterDao(Database database) {
public PermissionChecker getPermissionChecker() { return database.getFilterDao();
return new PermissivePermissionChecker(context); }
}
@Provides
public TaskAttachmentDao getTaskAttachmentDao(Database database) {
return database.getTaskAttachmentDao();
}
@ApplicationScope
@Provides
@ForApplication
public Context getContext() {
return context;
}
@Provides
public PermissionChecker getPermissionChecker() {
return new PermissivePermissionChecker(context);
}
} }

@ -3,143 +3,147 @@
* *
* See the file "LICENSE" for the full license governing this code. * See the file "LICENSE" for the full license governing this code.
*/ */
package org.tasks.jobs; package org.tasks.jobs;
import android.support.test.runner.AndroidJUnit4; import static android.support.test.InstrumentationRegistry.getTargetContext;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.tasks.date.DateTimeUtils.newDateTime;
import static org.tasks.time.DateTimeUtils.currentTimeMillis;
import android.support.test.runner.AndroidJUnit4;
import com.todoroo.andlib.utility.AndroidUtilities; import com.todoroo.andlib.utility.AndroidUtilities;
import org.tasks.backup.TasksJsonExporter;
import com.todoroo.astrid.dao.TaskDao; import com.todoroo.astrid.dao.TaskDao;
import com.todoroo.astrid.data.Task; import com.todoroo.astrid.data.Task;
import java.io.File;
import java.io.IOException;
import javax.inject.Inject;
import org.junit.After; import org.junit.After;
import org.junit.Ignore; import org.junit.Ignore;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.tasks.R; import org.tasks.R;
import org.tasks.backup.TasksJsonExporter;
import org.tasks.injection.InjectingTestCase; import org.tasks.injection.InjectingTestCase;
import org.tasks.injection.TestComponent; import org.tasks.injection.TestComponent;
import org.tasks.preferences.Preferences; import org.tasks.preferences.Preferences;
import org.tasks.scheduling.AlarmManager; import org.tasks.scheduling.AlarmManager;
import java.io.File;
import java.io.IOException;
import javax.inject.Inject;
import static android.support.test.InstrumentationRegistry.getTargetContext;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.tasks.date.DateTimeUtils.newDateTime;
import static org.tasks.time.DateTimeUtils.currentTimeMillis;
@RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class)
public class BackupServiceTests extends InjectingTestCase { public class BackupServiceTests extends InjectingTestCase {
private static final long BACKUP_WAIT_TIME = 500L; private static final long BACKUP_WAIT_TIME = 500L;
File temporaryDirectory = null;
@Inject TasksJsonExporter xmlExporter;
@Inject TaskDao taskDao;
@Inject Preferences preferences;
@Override
public void setUp() {
super.setUp();
try { File temporaryDirectory = null;
temporaryDirectory = File.createTempFile("backup", Long.toString(System.nanoTime()));
} catch (IOException e) {
throw new RuntimeException(e);
}
if (!(temporaryDirectory.delete())) @Inject TasksJsonExporter xmlExporter;
throw new RuntimeException("Could not delete temp file: " + temporaryDirectory.getAbsolutePath()); @Inject TaskDao taskDao;
if (!(temporaryDirectory.mkdir())) @Inject Preferences preferences;
throw new RuntimeException("Could not create temp directory: " + temporaryDirectory.getAbsolutePath());
preferences.setString(R.string.p_backup_dir, temporaryDirectory.getAbsolutePath()); @Override
public void setUp() {
super.setUp();
// make a temporary task try {
Task task = new Task(); temporaryDirectory = File.createTempFile("backup", Long.toString(System.nanoTime()));
task.setTitle("helicopter"); } catch (IOException e) {
taskDao.createNew(task); throw new RuntimeException(e);
} }
@Override if (!(temporaryDirectory.delete())) {
protected void inject(TestComponent component) { throw new RuntimeException(
component.inject(this); "Could not delete temp file: " + temporaryDirectory.getAbsolutePath());
}
if (!(temporaryDirectory.mkdir())) {
throw new RuntimeException(
"Could not create temp directory: " + temporaryDirectory.getAbsolutePath());
} }
@After preferences.setString(R.string.p_backup_dir, temporaryDirectory.getAbsolutePath());
public void tearDown() {
if (temporaryDirectory != null) { // make a temporary task
for (File file : temporaryDirectory.listFiles()) Task task = new Task();
file.delete(); task.setTitle("helicopter");
temporaryDirectory.delete(); taskDao.createNew(task);
} }
@Override
protected void inject(TestComponent component) {
component.inject(this);
}
@After
public void tearDown() {
if (temporaryDirectory != null) {
for (File file : temporaryDirectory.listFiles()) {
file.delete();
}
temporaryDirectory.delete();
} }
}
@Ignore @Ignore
@Test @Test
public void testBackup() { public void testBackup() {
assertEquals(0, temporaryDirectory.list().length); assertEquals(0, temporaryDirectory.list().length);
preferences.setLong(TasksJsonExporter.PREF_BACKUP_LAST_DATE, 0); preferences.setLong(TasksJsonExporter.PREF_BACKUP_LAST_DATE, 0);
// create a backup // create a backup
BackupJob service = new BackupJob(getTargetContext(), new JobManager(getTargetContext(), mock(AlarmManager.class)), xmlExporter, preferences); BackupJob service = new BackupJob(getTargetContext(),
service.startBackup(getTargetContext()); new JobManager(getTargetContext(), mock(AlarmManager.class)), xmlExporter, preferences);
service.startBackup(getTargetContext());
AndroidUtilities.sleepDeep(BACKUP_WAIT_TIME); AndroidUtilities.sleepDeep(BACKUP_WAIT_TIME);
// assert file created // assert file created
File[] files = temporaryDirectory.listFiles(); File[] files = temporaryDirectory.listFiles();
assertEquals(1, files.length); assertEquals(1, files.length);
assertTrue(files[0].getName().matches(BackupJob.BACKUP_FILE_NAME_REGEX)); assertTrue(files[0].getName().matches(BackupJob.BACKUP_FILE_NAME_REGEX));
// assert summary updated // assert summary updated
assertTrue(preferences.getLong(TasksJsonExporter.PREF_BACKUP_LAST_DATE, 0) > 0); assertTrue(preferences.getLong(TasksJsonExporter.PREF_BACKUP_LAST_DATE, 0) > 0);
} }
@Test @Test
public void testDeletion() throws IOException { public void testDeletion() throws IOException {
// create a bunch of backups // create a bunch of backups
assertEquals(0, temporaryDirectory.list().length); assertEquals(0, temporaryDirectory.list().length);
// create some user files // create some user files
File myFile = new File(temporaryDirectory, "beans"); File myFile = new File(temporaryDirectory, "beans");
myFile.createNewFile(); myFile.createNewFile();
// create some backup files // create some backup files
for (int i = 0; i < 10; i++) { for (int i = 0; i < 10; i++) {
String name = String.format("auto.%02d%s.xml", i, newDateTime().toString("MMdd-HHmm")); String name = String.format("auto.%02d%s.xml", i, newDateTime().toString("MMdd-HHmm"));
File tempFile = new File(temporaryDirectory, name); File tempFile = new File(temporaryDirectory, name);
tempFile.createNewFile(); tempFile.createNewFile();
} }
// make one really old // make one really old
File[] files = temporaryDirectory.listFiles(); File[] files = temporaryDirectory.listFiles();
files[4].setLastModified(currentTimeMillis() - 20000); files[4].setLastModified(currentTimeMillis() - 20000);
// assert files created // assert files created
assertEquals(11, files.length); assertEquals(11, files.length);
// backup // backup
BackupJob service = new BackupJob(getTargetContext(), new JobManager(getTargetContext(), mock(AlarmManager.class)), xmlExporter, preferences); BackupJob service = new BackupJob(getTargetContext(),
service.startBackup(getTargetContext()); new JobManager(getTargetContext(), mock(AlarmManager.class)), xmlExporter, preferences);
service.startBackup(getTargetContext());
AndroidUtilities.sleepDeep(BACKUP_WAIT_TIME); AndroidUtilities.sleepDeep(BACKUP_WAIT_TIME);
// assert the oldest file was deleted // assert the oldest file was deleted
assertTrue(temporaryDirectory.listFiles().length < 11); assertTrue(temporaryDirectory.listFiles().length < 11);
assertFalse(files[4].exists()); assertFalse(files[4].exists());
// assert user file still exists // assert user file still exists
service.startBackup(getTargetContext()); service.startBackup(getTargetContext());
assertTrue(myFile.exists()); assertTrue(myFile.exists());
} }
} }

@ -1,20 +1,5 @@
package org.tasks.jobs; package org.tasks.jobs;
import android.support.test.runner.AndroidJUnit4;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InOrder;
import org.tasks.Freeze;
import org.tasks.Snippet;
import org.tasks.preferences.Preferences;
import org.tasks.time.DateTime;
import java.util.List;
import java.util.concurrent.TimeUnit;
import static com.google.common.collect.Sets.newHashSet; import static com.google.common.collect.Sets.newHashSet;
import static com.todoroo.astrid.reminders.ReminderService.TYPE_DUE; import static com.todoroo.astrid.reminders.ReminderService.TYPE_DUE;
import static com.todoroo.astrid.reminders.ReminderService.TYPE_SNOOZE; import static com.todoroo.astrid.reminders.ReminderService.TYPE_SNOOZE;
@ -30,292 +15,306 @@ import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import static org.tasks.time.DateTimeUtils.currentTimeMillis; import static org.tasks.time.DateTimeUtils.currentTimeMillis;
import android.support.test.runner.AndroidJUnit4;
import java.util.List;
import java.util.concurrent.TimeUnit;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InOrder;
import org.tasks.Freeze;
import org.tasks.Snippet;
import org.tasks.preferences.Preferences;
import org.tasks.time.DateTime;
@RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class)
public class JobQueueTest { public class JobQueueTest {
private static final long ONE_MINUTE = TimeUnit.MINUTES.toMillis(1); private static final long ONE_MINUTE = TimeUnit.MINUTES.toMillis(1);
private static final String TAG = NotificationJob.TAG; private static final String TAG = NotificationJob.TAG;
private JobQueue queue;
private JobManager jobManager;
private Preferences preferences;
@Before
public void before() {
preferences = mock(Preferences.class);
when(preferences.adjustForQuietHours(anyLong())).then(returnsFirstArg());
jobManager = mock(JobManager.class);
queue = new JobQueue(preferences, jobManager);
}
@After
public void after() {
verifyNoMoreInteractions(jobManager);
}
@Test
public void alarmAndReminderSameTimeSameID() {
long now = currentTimeMillis();
queue.add(new Reminder(1, now, TYPE_DUE));
queue.add(new AlarmJob(1, 1, now));
verify(jobManager).schedule(TAG, now); private JobQueue queue;
private JobManager jobManager;
private Preferences preferences;
Freeze.freezeAt(now).thawAfter(new Snippet() {{ @Before
assertEquals( public void before() {
newHashSet(new AlarmJob(1, 1, now), preferences = mock(Preferences.class);
new Reminder(1, now, TYPE_DUE)), when(preferences.adjustForQuietHours(anyLong())).then(returnsFirstArg());
newHashSet(queue.getOverdueJobs())); jobManager = mock(JobManager.class);
}}); queue = new JobQueue(preferences, jobManager);
} }
@Test @After
public void removeAlarmLeaveReminder() { public void after() {
long now = currentTimeMillis(); verifyNoMoreInteractions(jobManager);
}
queue.add(new Reminder(1, now, TYPE_DUE)); @Test
queue.add(new AlarmJob(1, 1, now)); public void alarmAndReminderSameTimeSameID() {
long now = currentTimeMillis();
verify(jobManager).schedule(TAG, now); queue.add(new Reminder(1, now, TYPE_DUE));
queue.add(new AlarmJob(1, 1, now));
queue.remove(singletonList(new AlarmJob(1, 1, now))); verify(jobManager).schedule(TAG, now);
Freeze.freezeAt(now).thawAfter(new Snippet() {{ Freeze.freezeAt(now).thawAfter(new Snippet() {{
assertEquals( assertEquals(
singletonList(new Reminder(1, now, TYPE_DUE)), newHashSet(new AlarmJob(1, 1, now),
queue.getOverdueJobs()); new Reminder(1, now, TYPE_DUE)),
}}); newHashSet(queue.getOverdueJobs()));
} }});
}
@Test @Test
public void removeReminderLeaveAlarm() { public void removeAlarmLeaveReminder() {
long now = currentTimeMillis(); long now = currentTimeMillis();
queue.add(new Reminder(1, now, TYPE_DUE)); queue.add(new Reminder(1, now, TYPE_DUE));
queue.add(new AlarmJob(1, 1, now)); queue.add(new AlarmJob(1, 1, now));
verify(jobManager).schedule(TAG, now); verify(jobManager).schedule(TAG, now);
queue.remove(singletonList(new Reminder(1, now, TYPE_DUE))); queue.remove(singletonList(new AlarmJob(1, 1, now)));
Freeze.freezeAt(now).thawAfter(new Snippet() {{ Freeze.freezeAt(now).thawAfter(new Snippet() {{
assertEquals( assertEquals(
singletonList(new AlarmJob(1, 1, now)), singletonList(new Reminder(1, now, TYPE_DUE)),
queue.getOverdueJobs()); queue.getOverdueJobs());
}}); }});
} }
@Test @Test
public void twoJobsAtSameTime() { public void removeReminderLeaveAlarm() {
queue.add(new Reminder(1, 1, 0)); long now = currentTimeMillis();
queue.add(new Reminder(2, 1, 0));
verify(jobManager).schedule(TAG, 1); queue.add(new Reminder(1, now, TYPE_DUE));
queue.add(new AlarmJob(1, 1, now));
assertEquals(2, queue.size()); verify(jobManager).schedule(TAG, now);
}
@Test queue.remove(singletonList(new Reminder(1, now, TYPE_DUE)));
public void rescheduleForFirstJob() {
queue.add(new Reminder(1, 1, 0));
verify(jobManager).schedule(TAG, 1); Freeze.freezeAt(now).thawAfter(new Snippet() {{
} assertEquals(
singletonList(new AlarmJob(1, 1, now)),
queue.getOverdueJobs());
}});
}
@Test @Test
public void dontRescheduleForLaterJobs() { public void twoJobsAtSameTime() {
queue.add(new Reminder(1, 1, 0)); queue.add(new Reminder(1, 1, 0));
queue.add(new Reminder(2, 2, 0)); queue.add(new Reminder(2, 1, 0));
verify(jobManager).schedule(TAG, 1); verify(jobManager).schedule(TAG, 1);
}
@Test assertEquals(2, queue.size());
public void rescheduleForNewerJob() { }
queue.add(new Reminder(1, 2, 0));
queue.add(new Reminder(1, 1, 0));
InOrder order = inOrder(jobManager); @Test
order.verify(jobManager).schedule(TAG, 2); public void rescheduleForFirstJob() {
order.verify(jobManager).schedule(TAG, 1); queue.add(new Reminder(1, 1, 0));
}
@Test verify(jobManager).schedule(TAG, 1);
public void rescheduleWhenCancelingOnlyJob() { }
queue.add(new Reminder(1, 2, 0));
queue.cancelReminder(1);
InOrder order = inOrder(jobManager); @Test
order.verify(jobManager).schedule(TAG, 2); public void dontRescheduleForLaterJobs() {
order.verify(jobManager).cancel(TAG); queue.add(new Reminder(1, 1, 0));
} queue.add(new Reminder(2, 2, 0));
@Test verify(jobManager).schedule(TAG, 1);
public void rescheduleWhenCancelingFirstJob() { }
queue.add(new Reminder(1, 1, 0));
queue.add(new Reminder(2, 2, 0));
queue.cancelReminder(1); @Test
public void rescheduleForNewerJob() {
queue.add(new Reminder(1, 2, 0));
queue.add(new Reminder(1, 1, 0));
InOrder order = inOrder(jobManager); InOrder order = inOrder(jobManager);
order.verify(jobManager).schedule(TAG, 1); order.verify(jobManager).schedule(TAG, 2);
order.verify(jobManager).schedule(TAG, 2); order.verify(jobManager).schedule(TAG, 1);
} }
@Test @Test
public void dontRescheduleWhenCancelingLaterJob() { public void rescheduleWhenCancelingOnlyJob() {
queue.add(new Reminder(1, 1, 0)); queue.add(new Reminder(1, 2, 0));
queue.add(new Reminder(2, 2, 0)); queue.cancelReminder(1);
queue.cancelReminder(2); InOrder order = inOrder(jobManager);
order.verify(jobManager).schedule(TAG, 2);
order.verify(jobManager).cancel(TAG);
}
verify(jobManager).schedule(TAG, 1); @Test
} public void rescheduleWhenCancelingFirstJob() {
queue.add(new Reminder(1, 1, 0));
queue.add(new Reminder(2, 2, 0));
@Test queue.cancelReminder(1);
public void nextScheduledTimeIsZeroWhenQueueIsEmpty() {
when(preferences.adjustForQuietHours(anyLong())).thenReturn(1234L);
assertEquals(0, queue.nextScheduledTime()); InOrder order = inOrder(jobManager);
} order.verify(jobManager).schedule(TAG, 1);
order.verify(jobManager).schedule(TAG, 2);
}
@Test @Test
public void adjustNextScheduledTimeForQuietHours() { public void dontRescheduleWhenCancelingLaterJob() {
when(preferences.adjustForQuietHours(anyLong())).thenReturn(1234L); queue.add(new Reminder(1, 1, 0));
queue.add(new Reminder(1, 1, 1)); queue.add(new Reminder(2, 2, 0));
verify(jobManager).schedule(TAG, 1234); queue.cancelReminder(2);
}
@Test verify(jobManager).schedule(TAG, 1);
public void overdueJobsAreReturned() { }
long now = currentTimeMillis();
queue.add(new Reminder(1, now, TYPE_DUE)); @Test
queue.add(new Reminder(2, now + ONE_MINUTE, TYPE_DUE)); public void nextScheduledTimeIsZeroWhenQueueIsEmpty() {
when(preferences.adjustForQuietHours(anyLong())).thenReturn(1234L);
verify(jobManager).schedule(TAG, now); assertEquals(0, queue.nextScheduledTime());
}
Freeze.freezeAt(now).thawAfter(new Snippet() {{ @Test
assertEquals( public void adjustNextScheduledTimeForQuietHours() {
singletonList(new Reminder(1, now, TYPE_DUE)), when(preferences.adjustForQuietHours(anyLong())).thenReturn(1234L);
queue.getOverdueJobs()); queue.add(new Reminder(1, 1, 1));
}});
}
@Test verify(jobManager).schedule(TAG, 1234);
public void twoOverdueJobsAtSameTimeReturned() { }
long now = currentTimeMillis();
queue.add(new Reminder(1, now, TYPE_DUE)); @Test
queue.add(new Reminder(2, now, TYPE_DUE)); public void overdueJobsAreReturned() {
long now = currentTimeMillis();
verify(jobManager).schedule(TAG, now); queue.add(new Reminder(1, now, TYPE_DUE));
queue.add(new Reminder(2, now + ONE_MINUTE, TYPE_DUE));
Freeze.freezeAt(now).thawAfter(new Snippet() {{ verify(jobManager).schedule(TAG, now);
assertEquals(
asList(new Reminder(1, now, TYPE_DUE), new Reminder(2, now, TYPE_DUE)),
queue.getOverdueJobs());
}});
}
@Test Freeze.freezeAt(now).thawAfter(new Snippet() {{
public void twoOverdueJobsAtDifferentTimes() { assertEquals(
long now = currentTimeMillis(); singletonList(new Reminder(1, now, TYPE_DUE)),
queue.getOverdueJobs());
queue.add(new Reminder(1, now, TYPE_DUE)); }});
queue.add(new Reminder(2, now + ONE_MINUTE, TYPE_DUE)); }
verify(jobManager).schedule(TAG, now);
Freeze.freezeAt(now + 2 * ONE_MINUTE).thawAfter(new Snippet() {{
assertEquals(
asList(new Reminder(1, now, TYPE_DUE), new Reminder(2, now + ONE_MINUTE, TYPE_DUE)),
queue.getOverdueJobs());
}});
}
@Test @Test
public void overdueJobsAreRemoved() { public void twoOverdueJobsAtSameTimeReturned() {
long now = currentTimeMillis(); long now = currentTimeMillis();
queue.add(new Reminder(1, now, TYPE_DUE)); queue.add(new Reminder(1, now, TYPE_DUE));
queue.add(new Reminder(2, now + ONE_MINUTE, TYPE_DUE)); queue.add(new Reminder(2, now, TYPE_DUE));
verify(jobManager).schedule(TAG, now);
Freeze.freezeAt(now).thawAfter(new Snippet() {{ verify(jobManager).schedule(TAG, now);
queue.remove(queue.getOverdueJobs());
}});
assertEquals( Freeze.freezeAt(now).thawAfter(new Snippet() {{
singletonList(new Reminder(2, now + ONE_MINUTE, TYPE_DUE)), assertEquals(
queue.getJobs()); asList(new Reminder(1, now, TYPE_DUE), new Reminder(2, now, TYPE_DUE)),
} queue.getOverdueJobs());
}});
}
@Test
public void twoOverdueJobsAtDifferentTimes() {
long now = currentTimeMillis();
queue.add(new Reminder(1, now, TYPE_DUE));
queue.add(new Reminder(2, now + ONE_MINUTE, TYPE_DUE));
verify(jobManager).schedule(TAG, now);
Freeze.freezeAt(now + 2 * ONE_MINUTE).thawAfter(new Snippet() {{
assertEquals(
asList(new Reminder(1, now, TYPE_DUE), new Reminder(2, now + ONE_MINUTE, TYPE_DUE)),
queue.getOverdueJobs());
}});
}
@Test
public void overdueJobsAreRemoved() {
long now = currentTimeMillis();
queue.add(new Reminder(1, now, TYPE_DUE));
queue.add(new Reminder(2, now + ONE_MINUTE, TYPE_DUE));
verify(jobManager).schedule(TAG, now);
@Test Freeze.freezeAt(now).thawAfter(new Snippet() {{
public void multipleOverduePeriodsLapsed() { queue.remove(queue.getOverdueJobs());
long now = currentTimeMillis(); }});
queue.add(new Reminder(1, now, TYPE_DUE)); assertEquals(
queue.add(new Reminder(2, now + ONE_MINUTE, TYPE_DUE)); singletonList(new Reminder(2, now + ONE_MINUTE, TYPE_DUE)),
queue.add(new Reminder(3, now + 2 * ONE_MINUTE, TYPE_DUE)); queue.getJobs());
}
verify(jobManager).schedule(TAG, now); @Test
public void multipleOverduePeriodsLapsed() {
long now = currentTimeMillis();
Freeze.freezeAt(now + ONE_MINUTE).thawAfter(new Snippet() {{ queue.add(new Reminder(1, now, TYPE_DUE));
queue.remove(queue.getOverdueJobs()); queue.add(new Reminder(2, now + ONE_MINUTE, TYPE_DUE));
}}); queue.add(new Reminder(3, now + 2 * ONE_MINUTE, TYPE_DUE));
assertEquals( verify(jobManager).schedule(TAG, now);
singletonList(new Reminder(3, now + 2 * ONE_MINUTE, TYPE_DUE)),
queue.getJobs()); Freeze.freezeAt(now + ONE_MINUTE).thawAfter(new Snippet() {{
} queue.remove(queue.getOverdueJobs());
}});
@Test assertEquals(
public void clearShouldCancelExisting() { singletonList(new Reminder(3, now + 2 * ONE_MINUTE, TYPE_DUE)),
queue.add(new Reminder(1, 1, 0)); queue.getJobs());
}
queue.clear(); @Test
public void clearShouldCancelExisting() {
InOrder order = inOrder(jobManager); queue.add(new Reminder(1, 1, 0));
order.verify(jobManager).schedule(TAG, 1);
order.verify(jobManager).cancel(TAG);
assertEquals(0, queue.size());
}
@Test queue.clear();
public void ignoreInvalidCancel() {
long now = currentTimeMillis(); InOrder order = inOrder(jobManager);
order.verify(jobManager).schedule(TAG, 1);
queue.add(new Reminder(1, now, TYPE_DUE)); order.verify(jobManager).cancel(TAG);
queue.cancelReminder(2); assertEquals(0, queue.size());
}
verify(jobManager).schedule(TAG, now); @Test
} public void ignoreInvalidCancel() {
long now = currentTimeMillis();
@Test
public void allDuringSameMinuteAreOverdue() { queue.add(new Reminder(1, now, TYPE_DUE));
DateTime now = new DateTime(2017, 9, 3, 0, 14, 6, 455); queue.cancelReminder(2);
DateTime due = new DateTime(2017, 9, 3, 0, 14, 0, 0);
DateTime snooze = new DateTime(2017, 9, 3, 0, 14, 59, 999); verify(jobManager).schedule(TAG, now);
}
queue.add(new Reminder(1, due.getMillis(), TYPE_DUE));
queue.add(new Reminder(2, snooze.getMillis(), TYPE_SNOOZE)); @Test
queue.add(new Reminder(3, due.plusMinutes(1).getMillis(), TYPE_DUE)); public void allDuringSameMinuteAreOverdue() {
DateTime now = new DateTime(2017, 9, 3, 0, 14, 6, 455);
verify(jobManager).schedule(TAG, due.getMillis()); DateTime due = new DateTime(2017, 9, 3, 0, 14, 0, 0);
DateTime snooze = new DateTime(2017, 9, 3, 0, 14, 59, 999);
Freeze.freezeAt(now).thawAfter(new Snippet() {{
List<? extends JobQueueEntry> overdueJobs = queue.getOverdueJobs(); queue.add(new Reminder(1, due.getMillis(), TYPE_DUE));
assertEquals( queue.add(new Reminder(2, snooze.getMillis(), TYPE_SNOOZE));
asList(new Reminder(1, due.getMillis(), TYPE_DUE), new Reminder(2, snooze.getMillis(), TYPE_SNOOZE)), queue.add(new Reminder(3, due.plusMinutes(1).getMillis(), TYPE_DUE));
overdueJobs);
queue.remove(overdueJobs); verify(jobManager).schedule(TAG, due.getMillis());
assertEquals(
singletonList(new Reminder(3, due.plusMinutes(1).getMillis(), TYPE_DUE)), Freeze.freezeAt(now).thawAfter(new Snippet() {{
queue.getJobs()); List<? extends JobQueueEntry> overdueJobs = queue.getOverdueJobs();
}}); assertEquals(
} asList(new Reminder(1, due.getMillis(), TYPE_DUE),
new Reminder(2, snooze.getMillis(), TYPE_SNOOZE)),
overdueJobs);
queue.remove(overdueJobs);
assertEquals(
singletonList(new Reminder(3, due.plusMinutes(1).getMillis(), TYPE_DUE)),
queue.getJobs());
}});
}
} }

@ -1,34 +1,33 @@
package org.tasks.makers; package org.tasks.makers;
import static com.natpryce.makeiteasy.Property.newProperty;
import static org.tasks.makers.Maker.make;
import com.natpryce.makeiteasy.Instantiator; import com.natpryce.makeiteasy.Instantiator;
import com.natpryce.makeiteasy.Property; import com.natpryce.makeiteasy.Property;
import com.natpryce.makeiteasy.PropertyValue; import com.natpryce.makeiteasy.PropertyValue;
import org.tasks.data.GoogleTaskList; import org.tasks.data.GoogleTaskList;
import static com.natpryce.makeiteasy.Property.newProperty;
import static org.tasks.makers.Maker.make;
public class GtaskListMaker { public class GtaskListMaker {
public static final Property<GoogleTaskList, Long> ID = newProperty(); public static final Property<GoogleTaskList, Long> ID = newProperty();
public static final Property<GoogleTaskList, Integer> ORDER = newProperty(); public static final Property<GoogleTaskList, Integer> ORDER = newProperty();
public static final Property<GoogleTaskList, String> REMOTE_ID = newProperty(); public static final Property<GoogleTaskList, String> REMOTE_ID = newProperty();
public static final Property<GoogleTaskList, Long> LAST_SYNC = newProperty(); public static final Property<GoogleTaskList, Long> LAST_SYNC = newProperty();
public static final Property<GoogleTaskList, String> NAME = newProperty(); public static final Property<GoogleTaskList, String> NAME = newProperty();
public static final Property<GoogleTaskList, Integer> COLOR = newProperty(); public static final Property<GoogleTaskList, Integer> COLOR = newProperty();
private static final Instantiator<GoogleTaskList> instantiator = lookup -> new GoogleTaskList() {{
public static GoogleTaskList newGtaskList(PropertyValue<? super GoogleTaskList, ?>... properties) { setDeleted(0L);
return make(instantiator, properties); setId(lookup.valueOf(GtaskListMaker.ID, 0L));
} setRemoteId(lookup.valueOf(REMOTE_ID, "1"));
setTitle(lookup.valueOf(NAME, "Default"));
setRemoteOrder(lookup.valueOf(ORDER, 0));
setLastSync(lookup.valueOf(LAST_SYNC, 0L));
setColor(lookup.valueOf(COLOR, -1));
}};
private static final Instantiator<GoogleTaskList> instantiator = lookup -> new GoogleTaskList() {{ public static GoogleTaskList newGtaskList(
setDeleted(0L); PropertyValue<? super GoogleTaskList, ?>... properties) {
setId(lookup.valueOf(GtaskListMaker.ID, 0L)); return make(instantiator, properties);
setRemoteId(lookup.valueOf(REMOTE_ID, "1")); }
setTitle(lookup.valueOf(NAME, "Default"));
setRemoteOrder(lookup.valueOf(ORDER, 0));
setLastSync(lookup.valueOf(LAST_SYNC, 0L));
setColor(lookup.valueOf(COLOR, -1));
}};
} }

@ -1,13 +1,15 @@
package org.tasks.makers; package org.tasks.makers;
import static com.natpryce.makeiteasy.MakeItEasy.a;
import com.natpryce.makeiteasy.Instantiator; import com.natpryce.makeiteasy.Instantiator;
import com.natpryce.makeiteasy.PropertyValue; import com.natpryce.makeiteasy.PropertyValue;
import static com.natpryce.makeiteasy.MakeItEasy.a;
public class Maker { public class Maker {
@SuppressWarnings("unchecked")
public static <T> T make(Instantiator<T> instantiator, PropertyValue<? super T, ?>... properties) { @SuppressWarnings("unchecked")
return com.natpryce.makeiteasy.MakeItEasy.make(a(instantiator, properties)); public static <T> T make(Instantiator<T> instantiator,
} PropertyValue<? super T, ?>... properties) {
return com.natpryce.makeiteasy.MakeItEasy.make(a(instantiator, properties));
}
} }

@ -1,112 +1,110 @@
package org.tasks.makers; package org.tasks.makers;
import static com.natpryce.makeiteasy.Property.newProperty;
import static org.tasks.date.DateTimeUtils.newDateTime;
import static org.tasks.makers.Maker.make;
import com.google.common.base.Strings; import com.google.common.base.Strings;
import com.google.ical.values.RRule; import com.google.ical.values.RRule;
import com.natpryce.makeiteasy.Instantiator; import com.natpryce.makeiteasy.Instantiator;
import com.natpryce.makeiteasy.Property; import com.natpryce.makeiteasy.Property;
import com.natpryce.makeiteasy.PropertyValue; import com.natpryce.makeiteasy.PropertyValue;
import com.todoroo.astrid.data.Task; import com.todoroo.astrid.data.Task;
import org.tasks.time.DateTime; import org.tasks.time.DateTime;
import static com.natpryce.makeiteasy.Property.newProperty;
import static org.tasks.date.DateTimeUtils.newDateTime;
import static org.tasks.makers.Maker.make;
public class TaskMaker { public class TaskMaker {
public static Property<Task, Long> ID = newProperty(); public static Property<Task, Long> ID = newProperty();
public static Property<Task, String> TITLE = newProperty(); public static Property<Task, String> TITLE = newProperty();
public static Property<Task, DateTime> DUE_DATE = newProperty(); public static Property<Task, DateTime> DUE_DATE = newProperty();
public static Property<Task, DateTime> DUE_TIME = newProperty(); public static Property<Task, DateTime> DUE_TIME = newProperty();
public static Property<Task, Integer> PRIORITY = newProperty(); public static Property<Task, Integer> PRIORITY = newProperty();
public static Property<Task, DateTime> REMINDER_LAST = newProperty(); public static Property<Task, DateTime> REMINDER_LAST = newProperty();
public static Property<Task, Long> RANDOM_REMINDER_PERIOD = newProperty(); public static Property<Task, Long> RANDOM_REMINDER_PERIOD = newProperty();
public static Property<Task, Integer> HIDE_TYPE = newProperty(); public static Property<Task, Integer> HIDE_TYPE = newProperty();
public static Property<Task, Integer> REMINDERS = newProperty(); public static Property<Task, Integer> REMINDERS = newProperty();
public static Property<Task, DateTime> CREATION_TIME = newProperty(); public static Property<Task, DateTime> CREATION_TIME = newProperty();
public static Property<Task, DateTime> COMPLETION_TIME = newProperty(); public static Property<Task, DateTime> COMPLETION_TIME = newProperty();
public static Property<Task, DateTime> DELETION_TIME = newProperty(); public static Property<Task, DateTime> DELETION_TIME = newProperty();
public static Property<Task, DateTime> SNOOZE_TIME = newProperty(); public static Property<Task, DateTime> SNOOZE_TIME = newProperty();
public static Property<Task, RRule> RRULE = newProperty(); public static Property<Task, RRule> RRULE = newProperty();
public static Property<Task, Boolean> AFTER_COMPLETE = newProperty(); public static Property<Task, Boolean> AFTER_COMPLETE = newProperty();
private static final Instantiator<Task> instantiator = lookup -> {
@SafeVarargs Task task = new Task();
public static Task newTask(PropertyValue<? super Task, ?>... properties) {
return make(instantiator, properties); String title = lookup.valueOf(TITLE, (String) null);
if (!Strings.isNullOrEmpty(title)) {
task.setTitle(title);
} }
private static final Instantiator<Task> instantiator = lookup -> { long id = lookup.valueOf(ID, Task.NO_ID);
Task task = new Task(); if (id != Task.NO_ID) {
task.setId(id);
String title = lookup.valueOf(TITLE, (String) null); }
if (!Strings.isNullOrEmpty(title)) {
task.setTitle(title); int priority = lookup.valueOf(PRIORITY, -1);
} if (priority >= 0) {
task.setImportance(priority);
long id = lookup.valueOf(ID, Task.NO_ID); }
if (id != Task.NO_ID) {
task.setId(id); DateTime dueDate = lookup.valueOf(DUE_DATE, (DateTime) null);
} if (dueDate != null) {
task.setDueDate(Task.createDueDate(Task.URGENCY_SPECIFIC_DAY, dueDate.getMillis()));
int priority = lookup.valueOf(PRIORITY, -1); }
if (priority >= 0) {
task.setImportance(priority); DateTime dueTime = lookup.valueOf(DUE_TIME, (DateTime) null);
} if (dueTime != null) {
task.setDueDate(Task.createDueDate(Task.URGENCY_SPECIFIC_DAY_TIME, dueTime.getMillis()));
DateTime dueDate = lookup.valueOf(DUE_DATE, (DateTime) null); }
if (dueDate != null) {
task.setDueDate(Task.createDueDate(Task.URGENCY_SPECIFIC_DAY, dueDate.getMillis())); DateTime completionTime = lookup.valueOf(COMPLETION_TIME, (DateTime) null);
} if (completionTime != null) {
task.setCompletionDate(completionTime.getMillis());
DateTime dueTime = lookup.valueOf(DUE_TIME, (DateTime) null); }
if (dueTime != null) {
task.setDueDate(Task.createDueDate(Task.URGENCY_SPECIFIC_DAY_TIME, dueTime.getMillis())); DateTime deletedTime = lookup.valueOf(DELETION_TIME, (DateTime) null);
} if (deletedTime != null) {
task.setDeletionDate(deletedTime.getMillis());
DateTime completionTime = lookup.valueOf(COMPLETION_TIME, (DateTime) null); }
if (completionTime != null) {
task.setCompletionDate(completionTime.getMillis()); DateTime snoozeTime = lookup.valueOf(SNOOZE_TIME, (DateTime) null);
} if (snoozeTime != null) {
task.setReminderSnooze(snoozeTime.getMillis());
DateTime deletedTime = lookup.valueOf(DELETION_TIME, (DateTime) null); }
if (deletedTime != null) {
task.setDeletionDate(deletedTime.getMillis()); int hideType = lookup.valueOf(HIDE_TYPE, -1);
} if (hideType >= 0) {
task.setHideUntil(task.createHideUntil(hideType, 0));
DateTime snoozeTime = lookup.valueOf(SNOOZE_TIME, (DateTime) null); }
if (snoozeTime != null) {
task.setReminderSnooze(snoozeTime.getMillis()); int reminderFlags = lookup.valueOf(REMINDERS, -1);
} if (reminderFlags >= 0) {
task.setReminderFlags(reminderFlags);
int hideType = lookup.valueOf(HIDE_TYPE, -1); }
if (hideType >= 0) {
task.setHideUntil(task.createHideUntil(hideType, 0)); DateTime reminderLast = lookup.valueOf(REMINDER_LAST, (DateTime) null);
} if (reminderLast != null) {
task.setReminderLast(reminderLast.getMillis());
int reminderFlags = lookup.valueOf(REMINDERS, -1); }
if (reminderFlags >= 0) {
task.setReminderFlags(reminderFlags); long randomReminderPeriod = lookup.valueOf(RANDOM_REMINDER_PERIOD, 0L);
} if (randomReminderPeriod > 0) {
task.setReminderPeriod(randomReminderPeriod);
DateTime reminderLast = lookup.valueOf(REMINDER_LAST, (DateTime) null); }
if (reminderLast != null) {
task.setReminderLast(reminderLast.getMillis()); RRule rrule = lookup.valueOf(RRULE, (RRule) null);
} if (rrule != null) {
task.setRecurrence(rrule, lookup.valueOf(AFTER_COMPLETE, false));
long randomReminderPeriod = lookup.valueOf(RANDOM_REMINDER_PERIOD, 0L); }
if (randomReminderPeriod > 0) {
task.setReminderPeriod(randomReminderPeriod); DateTime creationTime = lookup.valueOf(CREATION_TIME, newDateTime());
} task.setCreationDate(creationTime.getMillis());
RRule rrule = lookup.valueOf(RRULE, (RRule) null); return task;
if (rrule != null) { };
task.setRecurrence(rrule, lookup.valueOf(AFTER_COMPLETE, false));
} @SafeVarargs
public static Task newTask(PropertyValue<? super Task, ?>... properties) {
DateTime creationTime = lookup.valueOf(CREATION_TIME, newDateTime()); return make(instantiator, properties);
task.setCreationDate(creationTime.getMillis()); }
return task;
};
} }

@ -1,41 +1,41 @@
package org.tasks.preferences; package org.tasks.preferences;
import android.content.Context; import android.content.Context;
import org.tasks.injection.ForApplication; import org.tasks.injection.ForApplication;
public class PermissivePermissionChecker extends PermissionChecker { public class PermissivePermissionChecker extends PermissionChecker {
public PermissivePermissionChecker(@ForApplication Context context) {
super(context); public PermissivePermissionChecker(@ForApplication Context context) {
} super(context);
}
@Override
public boolean canAccessCalendars() { @Override
return true; public boolean canAccessCalendars() {
} return true;
}
@Override
public boolean canWriteToExternalStorage() { @Override
return true; public boolean canWriteToExternalStorage() {
} return true;
}
@Override
public boolean canAccessAccounts() { @Override
return true; public boolean canAccessAccounts() {
} return true;
}
@Override
public boolean canAccessLocation() { @Override
return true; public boolean canAccessLocation() {
} return true;
}
@Override
public boolean canAccessMic() { @Override
return true; public boolean canAccessMic() {
} return true;
}
@Override
public boolean canAccessMissedCallPermissions() { @Override
return true; public boolean canAccessMissedCallPermissions() {
} return true;
}
} }

@ -1,154 +1,152 @@
package org.tasks.preferences; package org.tasks.preferences;
import static android.support.test.InstrumentationRegistry.getTargetContext;
import static junit.framework.Assert.assertEquals;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.support.test.runner.AndroidJUnit4; import android.support.test.runner.AndroidJUnit4;
import java.util.concurrent.TimeUnit;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.tasks.R; import org.tasks.R;
import org.tasks.time.DateTime; import org.tasks.time.DateTime;
import java.util.concurrent.TimeUnit;
import static android.support.test.InstrumentationRegistry.getTargetContext;
import static junit.framework.Assert.assertEquals;
@RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class)
public class PreferenceTests { public class PreferenceTests {
@SuppressLint("NewApi") @SuppressLint("NewApi")
private static final int MILLIS_PER_HOUR = (int) TimeUnit.HOURS.toMillis(1); private static final int MILLIS_PER_HOUR = (int) TimeUnit.HOURS.toMillis(1);
private Preferences preferences; private Preferences preferences;
@Before @Before
public void setUp() { public void setUp() {
preferences = new Preferences(getTargetContext(), null); preferences = new Preferences(getTargetContext(), null);
preferences.clear(); preferences.clear();
preferences.setBoolean(R.string.p_rmd_enable_quiet, true); preferences.setBoolean(R.string.p_rmd_enable_quiet, true);
} }
@Test @Test
public void testNotQuietWhenQuietHoursDisabled() { public void testNotQuietWhenQuietHoursDisabled() {
preferences.setBoolean(R.string.p_rmd_enable_quiet, false); preferences.setBoolean(R.string.p_rmd_enable_quiet, false);
setQuietHoursStart(22); setQuietHoursStart(22);
setQuietHoursEnd(10); setQuietHoursEnd(10);
long dueDate = new DateTime(2015, 12, 29, 8, 0, 1).getMillis(); long dueDate = new DateTime(2015, 12, 29, 8, 0, 1).getMillis();
assertEquals(dueDate, preferences.adjustForQuietHours(dueDate)); assertEquals(dueDate, preferences.adjustForQuietHours(dueDate));
} }
@Test @Test
public void testIsQuietAtStartOfQuietHoursNoWrap() { public void testIsQuietAtStartOfQuietHoursNoWrap() {
setQuietHoursStart(18); setQuietHoursStart(18);
setQuietHoursEnd(19); setQuietHoursEnd(19);
long dueDate = new DateTime(2015, 12, 29, 18, 0, 1).getMillis(); long dueDate = new DateTime(2015, 12, 29, 18, 0, 1).getMillis();
assertEquals(new DateTime(2015, 12, 29, 19, 0).getMillis(), assertEquals(new DateTime(2015, 12, 29, 19, 0).getMillis(),
preferences.adjustForQuietHours(dueDate)); preferences.adjustForQuietHours(dueDate));
} }
@Test @Test
public void testIsQuietAtStartOfQuietHoursWrap() { public void testIsQuietAtStartOfQuietHoursWrap() {
setQuietHoursStart(22); setQuietHoursStart(22);
setQuietHoursEnd(10); setQuietHoursEnd(10);
long dueDate = new DateTime(2015, 12, 29, 22, 0, 1).getMillis(); long dueDate = new DateTime(2015, 12, 29, 22, 0, 1).getMillis();
assertEquals(new DateTime(2015, 12, 30, 10, 0).getMillis(), assertEquals(new DateTime(2015, 12, 30, 10, 0).getMillis(),
preferences.adjustForQuietHours(dueDate)); preferences.adjustForQuietHours(dueDate));
} }
@Test @Test
public void testAdjustForQuietHoursNightWrap() { public void testAdjustForQuietHoursNightWrap() {
setQuietHoursStart(22); setQuietHoursStart(22);
setQuietHoursEnd(10); setQuietHoursEnd(10);
long dueDate = new DateTime(2015, 12, 29, 23, 30).getMillis(); long dueDate = new DateTime(2015, 12, 29, 23, 30).getMillis();
assertEquals(new DateTime(2015, 12, 30, 10, 0).getMillis(), assertEquals(new DateTime(2015, 12, 30, 10, 0).getMillis(),
preferences.adjustForQuietHours(dueDate)); preferences.adjustForQuietHours(dueDate));
} }
@Test @Test
public void testAdjustForQuietHoursMorningWrap() { public void testAdjustForQuietHoursMorningWrap() {
setQuietHoursStart(22); setQuietHoursStart(22);
setQuietHoursEnd(10); setQuietHoursEnd(10);
long dueDate = new DateTime(2015, 12, 30, 7, 15).getMillis(); long dueDate = new DateTime(2015, 12, 30, 7, 15).getMillis();
assertEquals(new DateTime(2015, 12, 30, 10, 0).getMillis(), assertEquals(new DateTime(2015, 12, 30, 10, 0).getMillis(),
preferences.adjustForQuietHours(dueDate)); preferences.adjustForQuietHours(dueDate));
} }
@Test @Test
public void testAdjustForQuietHoursWhenStartAndEndAreSame() { public void testAdjustForQuietHoursWhenStartAndEndAreSame() {
setQuietHoursStart(18); setQuietHoursStart(18);
setQuietHoursEnd(18); setQuietHoursEnd(18);
long dueDate = new DateTime(2015, 12, 29, 18, 0, 0).getMillis(); long dueDate = new DateTime(2015, 12, 29, 18, 0, 0).getMillis();
assertEquals(dueDate, preferences.adjustForQuietHours(dueDate)); assertEquals(dueDate, preferences.adjustForQuietHours(dueDate));
} }
@Test @Test
public void testIsNotQuietAtEndOfQuietHoursNoWrap() { public void testIsNotQuietAtEndOfQuietHoursNoWrap() {
setQuietHoursStart(17); setQuietHoursStart(17);
setQuietHoursEnd(18); setQuietHoursEnd(18);
long dueDate = new DateTime(2015, 12, 29, 18, 0).getMillis(); long dueDate = new DateTime(2015, 12, 29, 18, 0).getMillis();
assertEquals(dueDate, preferences.adjustForQuietHours(dueDate)); assertEquals(dueDate, preferences.adjustForQuietHours(dueDate));
} }
@Test @Test
public void testIsNotQuietAtEndOfQuietHoursWrap() { public void testIsNotQuietAtEndOfQuietHoursWrap() {
setQuietHoursStart(22); setQuietHoursStart(22);
setQuietHoursEnd(10); setQuietHoursEnd(10);
long dueDate = new DateTime(2015, 12, 29, 10, 0).getMillis(); long dueDate = new DateTime(2015, 12, 29, 10, 0).getMillis();
assertEquals(dueDate, preferences.adjustForQuietHours(dueDate)); assertEquals(dueDate, preferences.adjustForQuietHours(dueDate));
} }
@Test @Test
public void testIsNotQuietBeforeNoWrap() { public void testIsNotQuietBeforeNoWrap() {
setQuietHoursStart(17); setQuietHoursStart(17);
setQuietHoursEnd(18); setQuietHoursEnd(18);
long dueDate = new DateTime(2015, 12, 29, 11, 30).getMillis(); long dueDate = new DateTime(2015, 12, 29, 11, 30).getMillis();
assertEquals(dueDate, preferences.adjustForQuietHours(dueDate)); assertEquals(dueDate, preferences.adjustForQuietHours(dueDate));
} }
@Test @Test
public void testIsNotQuietAfterNoWrap() { public void testIsNotQuietAfterNoWrap() {
setQuietHoursStart(17); setQuietHoursStart(17);
setQuietHoursEnd(18); setQuietHoursEnd(18);
long dueDate = new DateTime(2015, 12, 29, 22, 15).getMillis(); long dueDate = new DateTime(2015, 12, 29, 22, 15).getMillis();
assertEquals(dueDate, preferences.adjustForQuietHours(dueDate)); assertEquals(dueDate, preferences.adjustForQuietHours(dueDate));
} }
@Test @Test
public void testIsNotQuietWrap() { public void testIsNotQuietWrap() {
setQuietHoursStart(22); setQuietHoursStart(22);
setQuietHoursEnd(10); setQuietHoursEnd(10);
long dueDate = new DateTime(2015, 12, 29, 13, 45).getMillis(); long dueDate = new DateTime(2015, 12, 29, 13, 45).getMillis();
assertEquals(dueDate, preferences.adjustForQuietHours(dueDate)); assertEquals(dueDate, preferences.adjustForQuietHours(dueDate));
} }
private void setQuietHoursStart(int hour) { private void setQuietHoursStart(int hour) {
preferences.setInt(R.string.p_rmd_quietStart, hour * MILLIS_PER_HOUR); preferences.setInt(R.string.p_rmd_quietStart, hour * MILLIS_PER_HOUR);
} }
private void setQuietHoursEnd(int hour) { private void setQuietHoursEnd(int hour) {
preferences.setInt(R.string.p_rmd_quietEnd, hour * MILLIS_PER_HOUR); preferences.setInt(R.string.p_rmd_quietEnd, hour * MILLIS_PER_HOUR);
} }
} }

@ -1,57 +1,57 @@
package org.tasks.repeats; package org.tasks.repeats;
import android.support.test.runner.AndroidJUnit4; import static android.support.test.InstrumentationRegistry.getTargetContext;
import static junit.framework.Assert.assertEquals;
import android.support.test.runner.AndroidJUnit4;
import com.google.ical.values.RRule; import com.google.ical.values.RRule;
import java.text.ParseException;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.tasks.locale.Locale; import org.tasks.locale.Locale;
import java.text.ParseException;
import static android.support.test.InstrumentationRegistry.getTargetContext;
import static junit.framework.Assert.assertEquals;
@RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class)
public class RepeatRuleToStringTest { public class RepeatRuleToStringTest {
@Test @Test
public void weekly() { public void weekly() {
assertEquals("Repeats weekly", toString("RRULE:FREQ=WEEKLY;INTERVAL=1")); assertEquals("Repeats weekly", toString("RRULE:FREQ=WEEKLY;INTERVAL=1"));
} }
@Test @Test
public void weeklyPlural() { public void weeklyPlural() {
assertEquals("Repeats every 2 weeks", toString("RRULE:FREQ=WEEKLY;INTERVAL=2")); assertEquals("Repeats every 2 weeks", toString("RRULE:FREQ=WEEKLY;INTERVAL=2"));
} }
@Test @Test
public void weeklyByDay() { public void weeklyByDay() {
assertEquals("Repeats weekly on Mon, Tue, Wed, Thu, Fri", toString("RRULE:FREQ=WEEKLY;INTERVAL=1;BYDAY=MO,TU,WE,TH,FR")); assertEquals("Repeats weekly on Mon, Tue, Wed, Thu, Fri",
} toString("RRULE:FREQ=WEEKLY;INTERVAL=1;BYDAY=MO,TU,WE,TH,FR"));
}
@Test
public void printDaysInRepeatRuleOrder() { @Test
assertEquals("Repeats weekly on Fri, Thu, Wed, Tue, Mon", toString("RRULE:FREQ=WEEKLY;INTERVAL=1;BYDAY=FR,TH,WE,TU,MO")); public void printDaysInRepeatRuleOrder() {
} assertEquals("Repeats weekly on Fri, Thu, Wed, Tue, Mon",
toString("RRULE:FREQ=WEEKLY;INTERVAL=1;BYDAY=FR,TH,WE,TU,MO"));
@Test }
public void useLocaleForDays() {
assertEquals("Wiederhole wöchentlich am Sa., So.", toString("de", "RRULE:FREQ=WEEKLY;INTERVAL=1;BYDAY=SA,SU")); @Test
} public void useLocaleForDays() {
assertEquals("Wiederhole wöchentlich am Sa., So.",
private String toString(String rrule) { toString("de", "RRULE:FREQ=WEEKLY;INTERVAL=1;BYDAY=SA,SU"));
return toString(null, rrule); }
}
private String toString(String rrule) {
private String toString(String language, String rrule) { return toString(null, rrule);
try { }
Locale locale = new Locale(java.util.Locale.getDefault(), language, -1);
return new RepeatRuleToString(locale.createConfigurationContext(getTargetContext()), locale) private String toString(String language, String rrule) {
.toString(new RRule(rrule)); try {
} catch (ParseException e) { Locale locale = new Locale(java.util.Locale.getDefault(), language, -1);
throw new RuntimeException(e); return new RepeatRuleToString(locale.createConfigurationContext(getTargetContext()), locale)
} .toString(new RRule(rrule));
} catch (ParseException e) {
throw new RuntimeException(e);
} }
}
} }

@ -1,337 +1,356 @@
package org.tasks.time; package org.tasks.time;
import android.support.test.runner.AndroidJUnit4; import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
import android.support.test.runner.AndroidJUnit4;
import java.util.TimeZone;
import java.util.concurrent.TimeUnit;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.tasks.Freeze; import org.tasks.Freeze;
import org.tasks.Snippet; import org.tasks.Snippet;
import java.util.TimeZone;
import java.util.concurrent.TimeUnit;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
@RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class)
public class DateTimeTest { public class DateTimeTest {
@Test
public void testGetMillisOfDay() {
assertEquals(7248412, new DateTime(2015, 10, 6, 2, 0, 48, 412).getMillisOfDay());
}
@Test
public void testWithMillisOfDay() {
assertEquals(
new DateTime(2015, 10, 6, 2, 0, 48, 412),
new DateTime(2015, 10, 6, 0, 0, 0, 0).withMillisOfDay(7248412));
}
@Test
public void testWithMillisOfDayDuringDST() {
TimeZone def = TimeZone.getDefault();
try {
TimeZone.setDefault(TimeZone.getTimeZone("America/Chicago"));
assertEquals(2, new DateTime(2015, 10, 31, 2, 0, 0).withMillisOfDay((int) TimeUnit.HOURS.toMillis(2)).getHourOfDay());
} finally {
TimeZone.setDefault(def);
}
}
@Test
public void testWithMillisOfDayAfterDST() {
TimeZone def = TimeZone.getDefault();
try {
TimeZone.setDefault(TimeZone.getTimeZone("America/Chicago"));
assertEquals(2, new DateTime(2015, 11, 2, 2, 0, 0).withMillisOfDay((int) TimeUnit.HOURS.toMillis(2)).getHourOfDay());
} finally {
TimeZone.setDefault(def);
}
}
@Test
public void testWithMillisOfDayStartDST() {
TimeZone def = TimeZone.getDefault();
try {
TimeZone.setDefault(TimeZone.getTimeZone("America/Chicago"));
assertEquals(1, new DateTime(2015, 3, 8, 0, 0, 0).withMillisOfDay((int) TimeUnit.HOURS.toMillis(1)).getHourOfDay());
assertEquals(3, new DateTime(2015, 3, 8, 0, 0, 0).withMillisOfDay((int) TimeUnit.HOURS.toMillis(2)).getHourOfDay());
assertEquals(3, new DateTime(2015, 3, 8, 0, 0, 0).withMillisOfDay((int) TimeUnit.HOURS.toMillis(3)).getHourOfDay());
assertEquals(4, new DateTime(2015, 3, 8, 0, 0, 0).withMillisOfDay((int) TimeUnit.HOURS.toMillis(4)).getHourOfDay());
assertEquals(
new DateTime(2015, 3, 8, 0, 0, 0).withMillisOfDay((int) TimeUnit.HOURS.toMillis(2)).getMillis(),
new DateTime(2015, 3, 8, 0, 0, 0).withMillisOfDay((int) TimeUnit.HOURS.toMillis(3)).getMillis());
} finally {
TimeZone.setDefault(def);
}
}
@Test
public void testWithMillisOfDayEndDST() {
TimeZone def = TimeZone.getDefault();
try {
TimeZone.setDefault(TimeZone.getTimeZone("America/Chicago"));
assertEquals(1, new DateTime(2015, 11, 1, 0, 0, 0).withMillisOfDay((int) TimeUnit.HOURS.toMillis(1)).getHourOfDay());
assertEquals(2, new DateTime(2015, 11, 1, 0, 0, 0).withMillisOfDay((int) TimeUnit.HOURS.toMillis(2)).getHourOfDay());
assertEquals(3, new DateTime(2015, 11, 1, 0, 0, 0).withMillisOfDay((int) TimeUnit.HOURS.toMillis(3)).getHourOfDay());
} finally {
TimeZone.setDefault(def);
}
}
@Test
public void testPlusMonths() {
assertEquals(
new DateTime(2015, 11, 6, 2, 0, 48, 412),
new DateTime(2015, 10, 6, 2, 0, 48, 412).plusMonths(1));
}
@Test
public void testPlusMonthsWrapYear() {
assertEquals(
new DateTime(2016, 1, 6, 2, 0, 48, 412),
new DateTime(2015, 10, 6, 2, 0, 48, 412).plusMonths(3));
}
@Test
public void testGetDayOfMonth() {
assertEquals(5, new DateTime(2015, 10, 5, 0, 0, 0).getDayOfMonth());
}
@Test
public void testPlusDays() {
assertEquals(
new DateTime(2015, 10, 6, 2, 0, 48, 412),
new DateTime(2015, 10, 5, 2, 0, 48, 412).plusDays(1));
}
@Test
public void testPlusDaysWrapMonth() {
assertEquals(
new DateTime(2015, 11, 1, 2, 0, 48, 412),
new DateTime(2015, 10, 31, 2, 0, 48, 412).plusDays(1));
}
@Test
public void testMinuteOfHour() {
assertEquals(43, new DateTime(2015, 10, 5, 2, 43, 48).getMinuteOfHour());
}
@Test
public void testIsEndOfMonth() {
assertTrue(new DateTime(2014, 1, 31, 0, 0, 0).isLastDayOfMonth());
assertTrue(new DateTime(2014, 2, 28, 0, 0, 0).isLastDayOfMonth());
assertTrue(new DateTime(2014, 3, 31, 0, 0, 0).isLastDayOfMonth());
assertTrue(new DateTime(2014, 4, 30, 0, 0, 0).isLastDayOfMonth());
assertTrue(new DateTime(2014, 5, 31, 0, 0, 0).isLastDayOfMonth());
assertTrue(new DateTime(2014, 6, 30, 0, 0, 0).isLastDayOfMonth());
assertTrue(new DateTime(2014, 7, 31, 0, 0, 0).isLastDayOfMonth());
assertTrue(new DateTime(2014, 8, 31, 0, 0, 0).isLastDayOfMonth());
assertTrue(new DateTime(2014, 9, 30, 0, 0, 0).isLastDayOfMonth());
assertTrue(new DateTime(2014, 10, 31, 0, 0, 0).isLastDayOfMonth());
assertTrue(new DateTime(2014, 11, 30, 0, 0, 0).isLastDayOfMonth());
assertTrue(new DateTime(2014, 12, 31, 0, 0, 0).isLastDayOfMonth());
}
@Test
public void testNotTheEndOfTheMonth() {
for (int month = 1; month <= 12; month++) {
int lastDay = new DateTime(2014, month, 1, 0, 0, 0, 0).getNumberOfDaysInMonth();
for (int day = 1; day < lastDay; day++) {
assertFalse(new DateTime(2014, month, day, 0, 0, 0).isLastDayOfMonth());
}
}
}
@Test
public void testCheckEndOfMonthDuringLeapYear() {
assertFalse(new DateTime(2016, 2, 28, 0, 0, 0).isLastDayOfMonth());
assertTrue(new DateTime(2016, 2, 29, 0, 0, 0).isLastDayOfMonth());
}
@Test
public void testNumberOfDaysInMonth() {
assertEquals(31, new DateTime(2015, 1, 5, 9, 45, 34).getNumberOfDaysInMonth());
assertEquals(28, new DateTime(2015, 2, 5, 9, 45, 34).getNumberOfDaysInMonth());
assertEquals(31, new DateTime(2015, 3, 5, 9, 45, 34).getNumberOfDaysInMonth());
assertEquals(30, new DateTime(2015, 4, 5, 9, 45, 34).getNumberOfDaysInMonth());
assertEquals(31, new DateTime(2015, 5, 5, 9, 45, 34).getNumberOfDaysInMonth());
assertEquals(30, new DateTime(2015, 6, 5, 9, 45, 34).getNumberOfDaysInMonth());
assertEquals(31, new DateTime(2015, 7, 5, 9, 45, 34).getNumberOfDaysInMonth());
assertEquals(31, new DateTime(2015, 8, 5, 9, 45, 34).getNumberOfDaysInMonth());
assertEquals(30, new DateTime(2015, 9, 5, 9, 45, 34).getNumberOfDaysInMonth());
assertEquals(31, new DateTime(2015, 10, 5, 9, 45, 34).getNumberOfDaysInMonth());
assertEquals(30, new DateTime(2015, 11, 5, 9, 45, 34).getNumberOfDaysInMonth());
assertEquals(31, new DateTime(2015, 12, 5, 9, 45, 34).getNumberOfDaysInMonth());
}
@Test
public void testWithMillisOfSecond() {
assertEquals(
new DateTime(2015, 11, 6, 13, 34, 56, 453),
new DateTime(2015, 11, 6, 13, 34, 56, 0).withMillisOfSecond(453));
}
@Test @Test
public void testWithHourOfDay() { public void testGetMillisOfDay() {
assertEquals( assertEquals(7248412, new DateTime(2015, 10, 6, 2, 0, 48, 412).getMillisOfDay());
new DateTime(2015, 11, 6, 23, 0, 0), }
new DateTime(2015, 11, 6, 1, 0, 0).withHourOfDay(23));
} @Test
public void testWithMillisOfDay() {
@Test assertEquals(
public void testWithMinuteOfHour() { new DateTime(2015, 10, 6, 2, 0, 48, 412),
assertEquals( new DateTime(2015, 10, 6, 0, 0, 0, 0).withMillisOfDay(7248412));
new DateTime(2015, 11, 6, 23, 13, 0), }
new DateTime(2015, 11, 6, 23, 1, 0).withMinuteOfHour(13));
} @Test
public void testWithMillisOfDayDuringDST() {
@Test TimeZone def = TimeZone.getDefault();
public void testWithSecondOfMinute() { try {
assertEquals( TimeZone.setDefault(TimeZone.getTimeZone("America/Chicago"));
new DateTime(2015, 11, 6, 23, 13, 56), assertEquals(2,
new DateTime(2015, 11, 6, 23, 13, 1).withSecondOfMinute(56)); new DateTime(2015, 10, 31, 2, 0, 0).withMillisOfDay((int) TimeUnit.HOURS.toMillis(2))
} .getHourOfDay());
} finally {
@Test TimeZone.setDefault(def);
public void testGetYear() { }
assertEquals(2015, new DateTime(2015, 1, 1, 1, 1, 1).getYear()); }
}
@Test
@Test public void testWithMillisOfDayAfterDST() {
public void testMinusMinutes() { TimeZone def = TimeZone.getDefault();
assertEquals( try {
new DateTime(2015, 11, 4, 23, 59, 0), TimeZone.setDefault(TimeZone.getTimeZone("America/Chicago"));
new DateTime(2015, 11, 5, 0, 1, 0).minusMinutes(2)); assertEquals(2,
} new DateTime(2015, 11, 2, 2, 0, 0).withMillisOfDay((int) TimeUnit.HOURS.toMillis(2))
.getHourOfDay());
@Test } finally {
public void testIsBefore() { TimeZone.setDefault(def);
assertTrue(new DateTime(2015, 11, 4, 23, 59, 0) }
.isBefore(new DateTime(2015, 11, 4, 23, 59, 1))); }
assertFalse(new DateTime(2015, 11, 4, 23, 59, 0) @Test
.isBefore(new DateTime(2015, 11, 4, 23, 59, 0))); public void testWithMillisOfDayStartDST() {
} TimeZone def = TimeZone.getDefault();
try {
@Test TimeZone.setDefault(TimeZone.getTimeZone("America/Chicago"));
public void testGetMonthOfYear() { assertEquals(1,
assertEquals(1, new DateTime(2015, 1, 2, 3, 4, 5).getMonthOfYear()); new DateTime(2015, 3, 8, 0, 0, 0).withMillisOfDay((int) TimeUnit.HOURS.toMillis(1))
} .getHourOfDay());
assertEquals(3,
@Test new DateTime(2015, 3, 8, 0, 0, 0).withMillisOfDay((int) TimeUnit.HOURS.toMillis(2))
public void testIsAfter() { .getHourOfDay());
assertTrue(new DateTime(2015, 11, 4, 23, 59, 1) assertEquals(3,
.isAfter(new DateTime(2015, 11, 4, 23, 59, 0))); new DateTime(2015, 3, 8, 0, 0, 0).withMillisOfDay((int) TimeUnit.HOURS.toMillis(3))
.getHourOfDay());
assertFalse(new DateTime(2015, 11, 4, 23, 59, 0) assertEquals(4,
.isAfter(new DateTime(2015, 11, 4, 23, 59, 0))); new DateTime(2015, 3, 8, 0, 0, 0).withMillisOfDay((int) TimeUnit.HOURS.toMillis(4))
} .getHourOfDay());
@Test assertEquals(
public void testWithYear() { new DateTime(2015, 3, 8, 0, 0, 0).withMillisOfDay((int) TimeUnit.HOURS.toMillis(2))
assertEquals( .getMillis(),
new DateTime(2016, 1, 1, 1, 1, 1), new DateTime(2015, 3, 8, 0, 0, 0).withMillisOfDay((int) TimeUnit.HOURS.toMillis(3))
new DateTime(2015, 1, 1, 1, 1, 1).withYear(2016)); .getMillis());
} } finally {
TimeZone.setDefault(def);
@Test }
public void testWithMonthOfYear() { }
assertEquals(
new DateTime(2015, 1, 2, 3, 4, 5), @Test
new DateTime(2015, 2, 2, 3, 4, 5).withMonthOfYear(1)); public void testWithMillisOfDayEndDST() {
} TimeZone def = TimeZone.getDefault();
try {
@Test TimeZone.setDefault(TimeZone.getTimeZone("America/Chicago"));
public void testGetHourOfDay() { assertEquals(1,
assertEquals(3, new DateTime(2015, 1, 2, 3, 4, 5).getHourOfDay()); new DateTime(2015, 11, 1, 0, 0, 0).withMillisOfDay((int) TimeUnit.HOURS.toMillis(1))
} .getHourOfDay());
assertEquals(2,
@Test new DateTime(2015, 11, 1, 0, 0, 0).withMillisOfDay((int) TimeUnit.HOURS.toMillis(2))
public void testWithDayOfMonth() { .getHourOfDay());
assertEquals( assertEquals(3,
new DateTime(2015, 1, 2, 3, 4, 5), new DateTime(2015, 11, 1, 0, 0, 0).withMillisOfDay((int) TimeUnit.HOURS.toMillis(3))
new DateTime(2015, 1, 1, 3, 4, 5).withDayOfMonth(2)); .getHourOfDay());
} } finally {
TimeZone.setDefault(def);
@Test }
public void testPlusMinutes() { }
assertEquals(
new DateTime(2015, 1, 2, 3, 4, 5), @Test
new DateTime(2015, 1, 2, 2, 59, 5).plusMinutes(5)); public void testPlusMonths() {
} assertEquals(
new DateTime(2015, 11, 6, 2, 0, 48, 412),
@Test new DateTime(2015, 10, 6, 2, 0, 48, 412).plusMonths(1));
public void testPlusHours() { }
assertEquals(
new DateTime(2015, 1, 2, 3, 4, 5), @Test
new DateTime(2015, 1, 1, 3, 4, 5).plusHours(24)); public void testPlusMonthsWrapYear() {
} assertEquals(
new DateTime(2016, 1, 6, 2, 0, 48, 412),
@Test new DateTime(2015, 10, 6, 2, 0, 48, 412).plusMonths(3));
public void testPlusWeeks() { }
assertEquals(
new DateTime(2015, 1, 2, 3, 4, 5), @Test
new DateTime(2014, 12, 12, 3, 4, 5).plusWeeks(3)); public void testGetDayOfMonth() {
} assertEquals(5, new DateTime(2015, 10, 5, 0, 0, 0).getDayOfMonth());
}
@Test
public void testIsBeforeNow() { @Test
Freeze.freezeAt(new DateTime(2015, 10, 6, 16, 15, 27)).thawAfter(new Snippet() {{ public void testPlusDays() {
assertFalse(new DateTime(2015, 10, 6, 16, 15, 27).isBeforeNow()); assertEquals(
assertTrue(new DateTime(2015, 10, 6, 16, 15, 26).isBeforeNow()); new DateTime(2015, 10, 6, 2, 0, 48, 412),
}}); new DateTime(2015, 10, 5, 2, 0, 48, 412).plusDays(1));
} }
@Test @Test
public void testMinusMillis() { public void testPlusDaysWrapMonth() {
assertEquals( assertEquals(
new DateTime(2015, 11, 6, 16, 18, 20, 452), new DateTime(2015, 11, 1, 2, 0, 48, 412),
new DateTime(2015, 11, 6, 16, 18, 21, 374).minusMillis(922)); new DateTime(2015, 10, 31, 2, 0, 48, 412).plusDays(1));
} }
@Test @Test
public void testMinusDays() { public void testMinuteOfHour() {
assertEquals( assertEquals(43, new DateTime(2015, 10, 5, 2, 43, 48).getMinuteOfHour());
new DateTime(2015, 11, 6, 16, 19, 16), }
new DateTime(2015, 12, 4, 16, 19, 16).minusDays(28));
@Test
assertEquals( public void testIsEndOfMonth() {
new DateTime(2015, 11, 6, 16, 19, 16), assertTrue(new DateTime(2014, 1, 31, 0, 0, 0).isLastDayOfMonth());
new DateTime(2015, 11, 7, 16, 19, 16).minusDays(1)); assertTrue(new DateTime(2014, 2, 28, 0, 0, 0).isLastDayOfMonth());
} assertTrue(new DateTime(2014, 3, 31, 0, 0, 0).isLastDayOfMonth());
assertTrue(new DateTime(2014, 4, 30, 0, 0, 0).isLastDayOfMonth());
@Test assertTrue(new DateTime(2014, 5, 31, 0, 0, 0).isLastDayOfMonth());
public void testGetSecondOfMinute() { assertTrue(new DateTime(2014, 6, 30, 0, 0, 0).isLastDayOfMonth());
assertEquals(32, new DateTime(2015, 11, 6, 16, 19, 32).getSecondOfMinute()); assertTrue(new DateTime(2014, 7, 31, 0, 0, 0).isLastDayOfMonth());
} assertTrue(new DateTime(2014, 8, 31, 0, 0, 0).isLastDayOfMonth());
assertTrue(new DateTime(2014, 9, 30, 0, 0, 0).isLastDayOfMonth());
@Test assertTrue(new DateTime(2014, 10, 31, 0, 0, 0).isLastDayOfMonth());
public void testToUTC() { assertTrue(new DateTime(2014, 11, 30, 0, 0, 0).isLastDayOfMonth());
TimeZone def = TimeZone.getDefault(); assertTrue(new DateTime(2014, 12, 31, 0, 0, 0).isLastDayOfMonth());
try { }
TimeZone.setDefault(TimeZone.getTimeZone("America/Chicago"));
assertEquals( @Test
new DateTime(2015, 10, 6, 14, 45, 15, 0, TimeZone.getTimeZone("GMT")), public void testNotTheEndOfTheMonth() {
new DateTime(2015, 10, 6, 9, 45, 15).toUTC()); for (int month = 1; month <= 12; month++) {
} finally { int lastDay = new DateTime(2014, month, 1, 0, 0, 0, 0).getNumberOfDaysInMonth();
TimeZone.setDefault(def); for (int day = 1; day < lastDay; day++) {
} assertFalse(new DateTime(2014, month, day, 0, 0, 0).isLastDayOfMonth());
} }
}
@Test }
public void testStartOfMinute() {
assertEquals( @Test
new DateTime(2017, 9, 3, 0, 51, 0, 0), public void testCheckEndOfMonthDuringLeapYear() {
new DateTime(2017, 9, 3, 0, 51, 13, 427).startOfMinute()); assertFalse(new DateTime(2016, 2, 28, 0, 0, 0).isLastDayOfMonth());
} assertTrue(new DateTime(2016, 2, 29, 0, 0, 0).isLastDayOfMonth());
}
@Test
public void testEndOfMinute() { @Test
assertEquals( public void testNumberOfDaysInMonth() {
new DateTime(2017, 9, 22, 14, 47, 59, 999), assertEquals(31, new DateTime(2015, 1, 5, 9, 45, 34).getNumberOfDaysInMonth());
new DateTime(2017, 9, 22, 14, 47, 14, 453).endOfMinute()); assertEquals(28, new DateTime(2015, 2, 5, 9, 45, 34).getNumberOfDaysInMonth());
} assertEquals(31, new DateTime(2015, 3, 5, 9, 45, 34).getNumberOfDaysInMonth());
assertEquals(30, new DateTime(2015, 4, 5, 9, 45, 34).getNumberOfDaysInMonth());
assertEquals(31, new DateTime(2015, 5, 5, 9, 45, 34).getNumberOfDaysInMonth());
assertEquals(30, new DateTime(2015, 6, 5, 9, 45, 34).getNumberOfDaysInMonth());
assertEquals(31, new DateTime(2015, 7, 5, 9, 45, 34).getNumberOfDaysInMonth());
assertEquals(31, new DateTime(2015, 8, 5, 9, 45, 34).getNumberOfDaysInMonth());
assertEquals(30, new DateTime(2015, 9, 5, 9, 45, 34).getNumberOfDaysInMonth());
assertEquals(31, new DateTime(2015, 10, 5, 9, 45, 34).getNumberOfDaysInMonth());
assertEquals(30, new DateTime(2015, 11, 5, 9, 45, 34).getNumberOfDaysInMonth());
assertEquals(31, new DateTime(2015, 12, 5, 9, 45, 34).getNumberOfDaysInMonth());
}
@Test
public void testWithMillisOfSecond() {
assertEquals(
new DateTime(2015, 11, 6, 13, 34, 56, 453),
new DateTime(2015, 11, 6, 13, 34, 56, 0).withMillisOfSecond(453));
}
@Test
public void testWithHourOfDay() {
assertEquals(
new DateTime(2015, 11, 6, 23, 0, 0),
new DateTime(2015, 11, 6, 1, 0, 0).withHourOfDay(23));
}
@Test
public void testWithMinuteOfHour() {
assertEquals(
new DateTime(2015, 11, 6, 23, 13, 0),
new DateTime(2015, 11, 6, 23, 1, 0).withMinuteOfHour(13));
}
@Test
public void testWithSecondOfMinute() {
assertEquals(
new DateTime(2015, 11, 6, 23, 13, 56),
new DateTime(2015, 11, 6, 23, 13, 1).withSecondOfMinute(56));
}
@Test
public void testGetYear() {
assertEquals(2015, new DateTime(2015, 1, 1, 1, 1, 1).getYear());
}
@Test
public void testMinusMinutes() {
assertEquals(
new DateTime(2015, 11, 4, 23, 59, 0),
new DateTime(2015, 11, 5, 0, 1, 0).minusMinutes(2));
}
@Test
public void testIsBefore() {
assertTrue(new DateTime(2015, 11, 4, 23, 59, 0)
.isBefore(new DateTime(2015, 11, 4, 23, 59, 1)));
assertFalse(new DateTime(2015, 11, 4, 23, 59, 0)
.isBefore(new DateTime(2015, 11, 4, 23, 59, 0)));
}
@Test
public void testGetMonthOfYear() {
assertEquals(1, new DateTime(2015, 1, 2, 3, 4, 5).getMonthOfYear());
}
@Test
public void testIsAfter() {
assertTrue(new DateTime(2015, 11, 4, 23, 59, 1)
.isAfter(new DateTime(2015, 11, 4, 23, 59, 0)));
assertFalse(new DateTime(2015, 11, 4, 23, 59, 0)
.isAfter(new DateTime(2015, 11, 4, 23, 59, 0)));
}
@Test
public void testWithYear() {
assertEquals(
new DateTime(2016, 1, 1, 1, 1, 1),
new DateTime(2015, 1, 1, 1, 1, 1).withYear(2016));
}
@Test
public void testWithMonthOfYear() {
assertEquals(
new DateTime(2015, 1, 2, 3, 4, 5),
new DateTime(2015, 2, 2, 3, 4, 5).withMonthOfYear(1));
}
@Test
public void testGetHourOfDay() {
assertEquals(3, new DateTime(2015, 1, 2, 3, 4, 5).getHourOfDay());
}
@Test
public void testWithDayOfMonth() {
assertEquals(
new DateTime(2015, 1, 2, 3, 4, 5),
new DateTime(2015, 1, 1, 3, 4, 5).withDayOfMonth(2));
}
@Test
public void testPlusMinutes() {
assertEquals(
new DateTime(2015, 1, 2, 3, 4, 5),
new DateTime(2015, 1, 2, 2, 59, 5).plusMinutes(5));
}
@Test
public void testPlusHours() {
assertEquals(
new DateTime(2015, 1, 2, 3, 4, 5),
new DateTime(2015, 1, 1, 3, 4, 5).plusHours(24));
}
@Test
public void testPlusWeeks() {
assertEquals(
new DateTime(2015, 1, 2, 3, 4, 5),
new DateTime(2014, 12, 12, 3, 4, 5).plusWeeks(3));
}
@Test
public void testIsBeforeNow() {
Freeze.freezeAt(new DateTime(2015, 10, 6, 16, 15, 27)).thawAfter(new Snippet() {{
assertFalse(new DateTime(2015, 10, 6, 16, 15, 27).isBeforeNow());
assertTrue(new DateTime(2015, 10, 6, 16, 15, 26).isBeforeNow());
}});
}
@Test
public void testMinusMillis() {
assertEquals(
new DateTime(2015, 11, 6, 16, 18, 20, 452),
new DateTime(2015, 11, 6, 16, 18, 21, 374).minusMillis(922));
}
@Test
public void testMinusDays() {
assertEquals(
new DateTime(2015, 11, 6, 16, 19, 16),
new DateTime(2015, 12, 4, 16, 19, 16).minusDays(28));
assertEquals(
new DateTime(2015, 11, 6, 16, 19, 16),
new DateTime(2015, 11, 7, 16, 19, 16).minusDays(1));
}
@Test
public void testGetSecondOfMinute() {
assertEquals(32, new DateTime(2015, 11, 6, 16, 19, 32).getSecondOfMinute());
}
@Test
public void testToUTC() {
TimeZone def = TimeZone.getDefault();
try {
TimeZone.setDefault(TimeZone.getTimeZone("America/Chicago"));
assertEquals(
new DateTime(2015, 10, 6, 14, 45, 15, 0, TimeZone.getTimeZone("GMT")),
new DateTime(2015, 10, 6, 9, 45, 15).toUTC());
} finally {
TimeZone.setDefault(def);
}
}
@Test
public void testStartOfMinute() {
assertEquals(
new DateTime(2017, 9, 3, 0, 51, 0, 0),
new DateTime(2017, 9, 3, 0, 51, 13, 427).startOfMinute());
}
@Test
public void testEndOfMinute() {
assertEquals(
new DateTime(2017, 9, 22, 14, 47, 59, 999),
new DateTime(2017, 9, 22, 14, 47, 14, 453).endOfMinute());
}
} }

@ -13,39 +13,39 @@ import com.todoroo.astrid.service.TitleParserTest;
import com.todoroo.astrid.subtasks.SubtasksHelperTest; import com.todoroo.astrid.subtasks.SubtasksHelperTest;
import com.todoroo.astrid.subtasks.SubtasksTestCase; import com.todoroo.astrid.subtasks.SubtasksTestCase;
import com.todoroo.astrid.sync.NewSyncTestCase; import com.todoroo.astrid.sync.NewSyncTestCase;
import org.tasks.jobs.BackupServiceTests;
import dagger.Component; import dagger.Component;
import org.tasks.jobs.BackupServiceTests;
@ApplicationScope @ApplicationScope
@Component(modules = TestModule.class) @Component(modules = TestModule.class)
public interface TestComponent { public interface TestComponent {
Database getDatabase();
void inject(ReminderServiceTest reminderServiceTest); Database getDatabase();
void inject(ReminderServiceTest reminderServiceTest);
void inject(TaskTest taskTest); void inject(TaskTest taskTest);
void inject(TaskDaoTests taskDaoTests); void inject(TaskDaoTests taskDaoTests);
void inject(MetadataDaoTests metadataDaoTests); void inject(MetadataDaoTests metadataDaoTests);
void inject(Astrid3ProviderTests astrid3ProviderTests); void inject(Astrid3ProviderTests astrid3ProviderTests);
void inject(NewSyncTestCase newSyncTestCase); void inject(NewSyncTestCase newSyncTestCase);
void inject(SubtasksTestCase subtasksTestCase); void inject(SubtasksTestCase subtasksTestCase);
void inject(SubtasksHelperTest subtasksHelperTest); void inject(SubtasksHelperTest subtasksHelperTest);
void inject(QuickAddMarkupTest quickAddMarkupTest); void inject(QuickAddMarkupTest quickAddMarkupTest);
void inject(TitleParserTest titleParserTest); void inject(TitleParserTest titleParserTest);
void inject(NewRepeatTests newRepeatTests); void inject(NewRepeatTests newRepeatTests);
void inject(BackupServiceTests backupServiceTests); void inject(BackupServiceTests backupServiceTests);
NotificationTests.NotificationTestsComponent plus(NotificationTests.NotificationTestsModule notificationTestsModule); NotificationTests.NotificationTestsComponent plus(
NotificationTests.NotificationTestsModule notificationTestsModule);
} }

@ -13,39 +13,39 @@ import com.todoroo.astrid.service.TitleParserTest;
import com.todoroo.astrid.subtasks.SubtasksHelperTest; import com.todoroo.astrid.subtasks.SubtasksHelperTest;
import com.todoroo.astrid.subtasks.SubtasksTestCase; import com.todoroo.astrid.subtasks.SubtasksTestCase;
import com.todoroo.astrid.sync.NewSyncTestCase; import com.todoroo.astrid.sync.NewSyncTestCase;
import org.tasks.jobs.BackupServiceTests;
import dagger.Component; import dagger.Component;
import org.tasks.jobs.BackupServiceTests;
@ApplicationScope @ApplicationScope
@Component(modules = TestModule.class) @Component(modules = TestModule.class)
public interface TestComponent { public interface TestComponent {
Database getDatabase();
void inject(ReminderServiceTest reminderServiceTest); Database getDatabase();
void inject(ReminderServiceTest reminderServiceTest);
void inject(TaskTest taskTest); void inject(TaskTest taskTest);
void inject(TaskDaoTests taskDaoTests); void inject(TaskDaoTests taskDaoTests);
void inject(MetadataDaoTests metadataDaoTests); void inject(MetadataDaoTests metadataDaoTests);
void inject(Astrid3ProviderTests astrid3ProviderTests); void inject(Astrid3ProviderTests astrid3ProviderTests);
void inject(NewSyncTestCase newSyncTestCase); void inject(NewSyncTestCase newSyncTestCase);
void inject(SubtasksTestCase subtasksTestCase); void inject(SubtasksTestCase subtasksTestCase);
void inject(SubtasksHelperTest subtasksHelperTest); void inject(SubtasksHelperTest subtasksHelperTest);
void inject(QuickAddMarkupTest quickAddMarkupTest); void inject(QuickAddMarkupTest quickAddMarkupTest);
void inject(TitleParserTest titleParserTest); void inject(TitleParserTest titleParserTest);
void inject(NewRepeatTests newRepeatTests); void inject(NewRepeatTests newRepeatTests);
void inject(BackupServiceTests backupServiceTests); void inject(BackupServiceTests backupServiceTests);
NotificationTests.NotificationTestsComponent plus(NotificationTests.NotificationTestsModule notificationTestsModule); NotificationTests.NotificationTestsComponent plus(
NotificationTests.NotificationTestsModule notificationTestsModule);
} }

@ -3,14 +3,19 @@
* *
* See the file "LICENSE" for the full license governing this code. * See the file "LICENSE" for the full license governing this code.
*/ */
package com.todoroo.astrid.gtasks; package com.todoroo.astrid.gtasks;
import android.support.test.runner.AndroidJUnit4; import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertTrue;
import android.support.test.runner.AndroidJUnit4;
import com.google.api.services.tasks.model.TaskList; import com.google.api.services.tasks.model.TaskList;
import com.todoroo.astrid.dao.TaskDao; import com.todoroo.astrid.dao.TaskDao;
import com.todoroo.astrid.data.Task; import com.todoroo.astrid.data.Task;
import java.util.ArrayList;
import java.util.List;
import javax.inject.Inject;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.tasks.data.GoogleTask; import org.tasks.data.GoogleTask;
@ -19,189 +24,181 @@ import org.tasks.data.GoogleTaskList;
import org.tasks.injection.InjectingTestCase; import org.tasks.injection.InjectingTestCase;
import org.tasks.injection.TestComponent; import org.tasks.injection.TestComponent;
import java.util.ArrayList;
import java.util.List;
import javax.inject.Inject;
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertTrue;
@SuppressWarnings("nls") @SuppressWarnings("nls")
@RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class)
public class GtasksIndentActionTest extends InjectingTestCase { public class GtasksIndentActionTest extends InjectingTestCase {
@Inject GtasksListService gtasksListService; @Inject GtasksListService gtasksListService;
@Inject GtasksTaskListUpdater gtasksTaskListUpdater; @Inject GtasksTaskListUpdater gtasksTaskListUpdater;
@Inject TaskDao taskDao; @Inject TaskDao taskDao;
@Inject GoogleTaskDao googleTaskDao; @Inject GoogleTaskDao googleTaskDao;
private Task task; private Task task;
private GoogleTaskList storeList; private GoogleTaskList storeList;
@Test @Test
public void testIndentWithoutMetadata() { public void testIndentWithoutMetadata() {
givenTask(taskWithoutMetadata()); givenTask(taskWithoutMetadata());
whenIncreaseIndent(); whenIncreaseIndent();
// should not crash // should not crash
} }
@Test @Test
public void disabled_testIndentWithMetadataButNoOtherTasks() { public void disabled_testIndentWithMetadataButNoOtherTasks() {
givenTask(taskWithMetadata(0, 0)); givenTask(taskWithMetadata(0, 0));
whenIncreaseIndent(); whenIncreaseIndent();
thenExpectIndentationLevel(0); thenExpectIndentationLevel(0);
} }
@Test @Test
public void testIndentWithMetadata() { public void testIndentWithMetadata() {
taskWithMetadata(0, 0); taskWithMetadata(0, 0);
givenTask(taskWithMetadata(1, 0)); givenTask(taskWithMetadata(1, 0));
whenIncreaseIndent(); whenIncreaseIndent();
thenExpectIndentationLevel(1); thenExpectIndentationLevel(1);
} }
@Test @Test
public void testDeindentWithMetadata() { public void testDeindentWithMetadata() {
givenTask(taskWithMetadata(0, 1)); givenTask(taskWithMetadata(0, 1));
whenDecreaseIndent(); whenDecreaseIndent();
thenExpectIndentationLevel(0); thenExpectIndentationLevel(0);
} }
@Test @Test
public void testDeindentWithoutMetadata() { public void testDeindentWithoutMetadata() {
givenTask(taskWithoutMetadata()); givenTask(taskWithoutMetadata());
whenDecreaseIndent(); whenDecreaseIndent();
// should not crash // should not crash
} }
@Test @Test
public void testDeindentWhenAlreadyZero() { public void testDeindentWhenAlreadyZero() {
givenTask(taskWithMetadata(0, 0)); givenTask(taskWithMetadata(0, 0));
whenDecreaseIndent(); whenDecreaseIndent();
thenExpectIndentationLevel(0); thenExpectIndentationLevel(0);
} }
@Test @Test
public void testIndentWithChildren() { public void testIndentWithChildren() {
taskWithMetadata(0, 0); taskWithMetadata(0, 0);
givenTask(taskWithMetadata(1, 0)); givenTask(taskWithMetadata(1, 0));
Task child = taskWithMetadata(2, 1); Task child = taskWithMetadata(2, 1);
whenIncreaseIndent(); whenIncreaseIndent();
thenExpectIndentationLevel(1); thenExpectIndentationLevel(1);
thenExpectIndentationLevel(child, 2); thenExpectIndentationLevel(child, 2);
} }
@Test @Test
public void testDeindentWithChildren() { public void testDeindentWithChildren() {
taskWithMetadata(0, 0); taskWithMetadata(0, 0);
givenTask(taskWithMetadata(1, 1)); givenTask(taskWithMetadata(1, 1));
Task child = taskWithMetadata(2, 2); Task child = taskWithMetadata(2, 2);
whenDecreaseIndent(); whenDecreaseIndent();
thenExpectIndentationLevel(0); thenExpectIndentationLevel(0);
thenExpectIndentationLevel(child, 1); thenExpectIndentationLevel(child, 1);
} }
@Test @Test
public void testIndentWithSiblings() { public void testIndentWithSiblings() {
taskWithMetadata(0, 0); taskWithMetadata(0, 0);
givenTask(taskWithMetadata(1, 0)); givenTask(taskWithMetadata(1, 0));
Task sibling = taskWithMetadata(2, 0); Task sibling = taskWithMetadata(2, 0);
whenIncreaseIndent(); whenIncreaseIndent();
thenExpectIndentationLevel(1); thenExpectIndentationLevel(1);
thenExpectIndentationLevel(sibling, 0); thenExpectIndentationLevel(sibling, 0);
} }
@Test @Test
public void testIndentWithChildrensChildren() { public void testIndentWithChildrensChildren() {
taskWithMetadata(0, 0); taskWithMetadata(0, 0);
givenTask(taskWithMetadata(1, 0)); givenTask(taskWithMetadata(1, 0));
Task child = taskWithMetadata(2, 1); Task child = taskWithMetadata(2, 1);
Task grandchild = taskWithMetadata(3, 2); Task grandchild = taskWithMetadata(3, 2);
whenIncreaseIndent(); whenIncreaseIndent();
thenExpectIndentationLevel(1); thenExpectIndentationLevel(1);
thenExpectIndentationLevel(child, 2); thenExpectIndentationLevel(child, 2);
thenExpectIndentationLevel(grandchild, 3); thenExpectIndentationLevel(grandchild, 3);
} }
// --- helpers // --- helpers
private void whenIncreaseIndent() { private void whenIncreaseIndent() {
gtasksTaskListUpdater.indent(storeList, task.getId(), 1); gtasksTaskListUpdater.indent(storeList, task.getId(), 1);
} }
private void whenDecreaseIndent() { private void whenDecreaseIndent() {
gtasksTaskListUpdater.indent(storeList, task.getId(), -1); gtasksTaskListUpdater.indent(storeList, task.getId(), -1);
} }
@Override @Override
public void setUp() { public void setUp() {
super.setUp(); super.setUp();
List<TaskList> items = new ArrayList<>(); List<TaskList> items = new ArrayList<>();
TaskList list = new TaskList(); TaskList list = new TaskList();
list.setId("list"); list.setId("list");
list.setTitle("Test Tasks"); list.setTitle("Test Tasks");
items.add(list); items.add(list);
gtasksListService.updateLists(items); gtasksListService.updateLists(items);
storeList = gtasksListService.getLists().get(0); storeList = gtasksListService.getLists().get(0);
} }
@Override @Override
protected void inject(TestComponent component) { protected void inject(TestComponent component) {
component.inject(this); component.inject(this);
} }
private Task taskWithMetadata(long order, int indentation) { private Task taskWithMetadata(long order, int indentation) {
Task newTask = new Task(); Task newTask = new Task();
taskDao.createNew(newTask); taskDao.createNew(newTask);
GoogleTask metadata = new GoogleTask(newTask.getId(), "list"); GoogleTask metadata = new GoogleTask(newTask.getId(), "list");
metadata.setIndent(indentation); metadata.setIndent(indentation);
metadata.setOrder(order); metadata.setOrder(order);
googleTaskDao.insert(metadata); googleTaskDao.insert(metadata);
return newTask; return newTask;
} }
private void thenExpectIndentationLevel(int expected) { private void thenExpectIndentationLevel(int expected) {
thenExpectIndentationLevel(task, expected); thenExpectIndentationLevel(task, expected);
} }
private void thenExpectIndentationLevel(Task targetTask, int expected) { private void thenExpectIndentationLevel(Task targetTask, int expected) {
GoogleTask metadata = googleTaskDao.getByTaskId(targetTask.getId()); GoogleTask metadata = googleTaskDao.getByTaskId(targetTask.getId());
assertNotNull("task has metadata", metadata); assertNotNull("task has metadata", metadata);
int indentation = metadata.getIndent(); int indentation = metadata.getIndent();
assertTrue("indentation: " + indentation, assertTrue("indentation: " + indentation,
indentation == expected); indentation == expected);
} }
private void givenTask(Task taskToTest) { private void givenTask(Task taskToTest) {
task = taskToTest; task = taskToTest;
} }
private Task taskWithoutMetadata() { private Task taskWithoutMetadata() {
Task task = new Task(); Task task = new Task();
taskDao.createNew(task); taskDao.createNew(task);
return task; return task;
} }
} }

@ -1,12 +1,24 @@
package com.todoroo.astrid.gtasks; package com.todoroo.astrid.gtasks;
import android.support.test.runner.AndroidJUnit4; import static com.natpryce.makeiteasy.MakeItEasy.with;
import static java.util.Arrays.asList;
import static java.util.Collections.singletonList;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertNull;
import static org.tasks.makers.GtaskListMaker.ID;
import static org.tasks.makers.GtaskListMaker.LAST_SYNC;
import static org.tasks.makers.GtaskListMaker.NAME;
import static org.tasks.makers.GtaskListMaker.REMOTE_ID;
import static org.tasks.makers.GtaskListMaker.newGtaskList;
import static org.tasks.makers.RemoteGtaskListMaker.newRemoteList;
import static org.tasks.time.DateTimeUtils.currentTimeMillis;
import android.support.test.runner.AndroidJUnit4;
import com.google.api.client.util.DateTime; import com.google.api.client.util.DateTime;
import com.google.api.services.tasks.model.TaskList; import com.google.api.services.tasks.model.TaskList;
import com.todoroo.astrid.dao.TaskDao; import com.todoroo.astrid.dao.TaskDao;
import com.todoroo.astrid.service.TaskDeleter; import com.todoroo.astrid.service.TaskDeleter;
import javax.inject.Inject;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.tasks.LocalBroadcastManager; import org.tasks.LocalBroadcastManager;
@ -17,116 +29,102 @@ import org.tasks.injection.InjectingTestCase;
import org.tasks.injection.TestComponent; import org.tasks.injection.TestComponent;
import org.tasks.makers.RemoteGtaskListMaker; import org.tasks.makers.RemoteGtaskListMaker;
import javax.inject.Inject;
import static com.natpryce.makeiteasy.MakeItEasy.with;
import static java.util.Arrays.asList;
import static java.util.Collections.singletonList;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertNull;
import static org.tasks.makers.GtaskListMaker.ID;
import static org.tasks.makers.GtaskListMaker.LAST_SYNC;
import static org.tasks.makers.GtaskListMaker.NAME;
import static org.tasks.makers.GtaskListMaker.REMOTE_ID;
import static org.tasks.makers.GtaskListMaker.newGtaskList;
import static org.tasks.makers.RemoteGtaskListMaker.newRemoteList;
import static org.tasks.time.DateTimeUtils.currentTimeMillis;
@RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class)
public class GtasksListServiceTest extends InjectingTestCase { public class GtasksListServiceTest extends InjectingTestCase {
@Inject TaskDeleter taskDeleter; @Inject TaskDeleter taskDeleter;
@Inject LocalBroadcastManager localBroadcastManager; @Inject LocalBroadcastManager localBroadcastManager;
@Inject GoogleTaskDao googleTaskDao; @Inject GoogleTaskDao googleTaskDao;
@Inject TaskDao taskDao; @Inject TaskDao taskDao;
@Inject GoogleTaskListDao googleTaskListDao; @Inject GoogleTaskListDao googleTaskListDao;
private GtasksListService gtasksListService; private GtasksListService gtasksListService;
@Override @Override
public void setUp() { public void setUp() {
super.setUp(); super.setUp();
gtasksListService = new GtasksListService(googleTaskListDao, taskDeleter, gtasksListService = new GtasksListService(googleTaskListDao, taskDeleter,
localBroadcastManager, googleTaskDao, taskDao); localBroadcastManager, googleTaskDao, taskDao);
} }
@Override @Override
protected void inject(TestComponent component) { protected void inject(TestComponent component) {
component.inject(this); component.inject(this);
} }
@Test @Test
public void testCreateNewList() { public void testCreateNewList() {
setLists(newRemoteList( setLists(newRemoteList(
with(RemoteGtaskListMaker.REMOTE_ID, "1"), with(RemoteGtaskListMaker.REMOTE_ID, "1"),
with(RemoteGtaskListMaker.NAME, "Default"))); with(RemoteGtaskListMaker.NAME, "Default")));
assertEquals( assertEquals(
newGtaskList( newGtaskList(
with(ID, 1L), with(ID, 1L),
with(REMOTE_ID, "1"), with(REMOTE_ID, "1"),
with(NAME, "Default")), with(NAME, "Default")),
googleTaskListDao.getById(1L)); googleTaskListDao.getById(1L));
} }
@Test @Test
public void testGetListByRemoteId() { public void testGetListByRemoteId() {
GoogleTaskList list = newGtaskList(with(REMOTE_ID, "1")); GoogleTaskList list = newGtaskList(with(REMOTE_ID, "1"));
list.setId(googleTaskListDao.insertOrReplace(list)); list.setId(googleTaskListDao.insertOrReplace(list));
assertEquals(list, gtasksListService.getList("1")); assertEquals(list, gtasksListService.getList("1"));
} }
@Test @Test
public void testGetListReturnsNullWhenNotFound() { public void testGetListReturnsNullWhenNotFound() {
assertNull(gtasksListService.getList("1")); assertNull(gtasksListService.getList("1"));
} }
@Test @Test
public void testDeleteMissingList() { public void testDeleteMissingList() {
googleTaskListDao.insertOrReplace(newGtaskList(with(ID, 1L), with(REMOTE_ID, "1"))); googleTaskListDao.insertOrReplace(newGtaskList(with(ID, 1L), with(REMOTE_ID, "1")));
TaskList taskList = newRemoteList(with(RemoteGtaskListMaker.REMOTE_ID, "2")); TaskList taskList = newRemoteList(with(RemoteGtaskListMaker.REMOTE_ID, "2"));
setLists(taskList); setLists(taskList);
assertEquals(singletonList(newGtaskList(with(ID, 2L), with(REMOTE_ID, "2"))), assertEquals(singletonList(newGtaskList(with(ID, 2L), with(REMOTE_ID, "2"))),
googleTaskListDao.getActiveLists()); googleTaskListDao.getActiveLists());
} }
@Test @Test
public void testUpdateListName() { public void testUpdateListName() {
googleTaskListDao.insertOrReplace(newGtaskList( googleTaskListDao.insertOrReplace(newGtaskList(
with(ID, 1L), with(ID, 1L),
with(REMOTE_ID, "1"), with(REMOTE_ID, "1"),
with(NAME, "oldName"))); with(NAME, "oldName")));
setLists(newRemoteList( setLists(newRemoteList(
with(RemoteGtaskListMaker.REMOTE_ID, "1"), with(RemoteGtaskListMaker.REMOTE_ID, "1"),
with(RemoteGtaskListMaker.NAME, "newName"))); with(RemoteGtaskListMaker.NAME, "newName")));
assertEquals("newName", googleTaskListDao.getById(1).getTitle()); assertEquals("newName", googleTaskListDao.getById(1).getTitle());
} }
@Test @Test
public void testNewListLastSyncIsZero() { public void testNewListLastSyncIsZero() {
setLists(new TaskList().setId("1")); setLists(new TaskList().setId("1"));
assertEquals(0L, gtasksListService.getList("1").getLastSync()); assertEquals(0L, gtasksListService.getList("1").getLastSync());
} }
@Test @Test
public void testNewListNeedsUpdate() { public void testNewListNeedsUpdate() {
TaskList taskList = new TaskList().setId("1").setTitle("Default").setUpdated(new DateTime(currentTimeMillis())); TaskList taskList = new TaskList().setId("1").setTitle("Default")
.setUpdated(new DateTime(currentTimeMillis()));
setLists(taskList);
setLists(taskList);
assertEquals(
asList(newGtaskList(with(ID, 1L), with(REMOTE_ID, "1"), with(LAST_SYNC, 0L))), assertEquals(
gtasksListService.getListsToUpdate(asList(taskList))); asList(newGtaskList(with(ID, 1L), with(REMOTE_ID, "1"), with(LAST_SYNC, 0L))),
} gtasksListService.getListsToUpdate(asList(taskList)));
}
private void setLists(TaskList... list) {
gtasksListService.updateLists(asList(list)); private void setLists(TaskList... list) {
} gtasksListService.updateLists(asList(list));
}
} }

@ -3,14 +3,21 @@
* *
* See the file "LICENSE" for the full license governing this code. * See the file "LICENSE" for the full license governing this code.
*/ */
package com.todoroo.astrid.gtasks; package com.todoroo.astrid.gtasks;
import static android.support.test.InstrumentationRegistry.getTargetContext;
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertNull;
import android.content.Context; import android.content.Context;
import android.support.test.runner.AndroidJUnit4; import android.support.test.runner.AndroidJUnit4;
import com.todoroo.astrid.dao.TaskDao; import com.todoroo.astrid.dao.TaskDao;
import com.todoroo.astrid.data.Task; import com.todoroo.astrid.data.Task;
import dagger.Module;
import dagger.Provides;
import dagger.Subcomponent;
import javax.inject.Inject;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.tasks.data.GoogleTask; import org.tasks.data.GoogleTask;
@ -19,120 +26,101 @@ import org.tasks.injection.InjectingTestCase;
import org.tasks.injection.TestComponent; import org.tasks.injection.TestComponent;
import org.tasks.preferences.Preferences; import org.tasks.preferences.Preferences;
import javax.inject.Inject;
import dagger.Module;
import dagger.Provides;
import dagger.Subcomponent;
import static android.support.test.InstrumentationRegistry.getTargetContext;
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertNull;
@SuppressWarnings("nls") @SuppressWarnings("nls")
@RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class)
public class GtasksMetadataServiceTest extends InjectingTestCase { public class GtasksMetadataServiceTest extends InjectingTestCase {
@Module @Inject TaskDao taskDao;
public class GtasksMetadataServiceTestModule { @Inject GoogleTaskDao googleTaskDao;
private final GtasksTestPreferenceService service; private Task task;
private GoogleTask metadata;
public GtasksMetadataServiceTestModule(Context context) { @Override
service = new GtasksTestPreferenceService(new Preferences(context, null)); protected void inject(TestComponent component) {
} component
.plus(new GtasksMetadataServiceTestModule(getTargetContext()))
.inject(this);
}
@Provides @Test
public GtasksTestPreferenceService getGtasksTestPreferenceService() { public void testMetadataFound() {
return service; givenTask(taskWithMetadata(null));
}
@Provides whenSearchForMetadata();
public GtasksPreferenceService getGtasksPreferenceService() {
return service;
}
}
@Subcomponent(modules = GtasksMetadataServiceTest.GtasksMetadataServiceTestModule.class) thenExpectMetadataFound();
public interface GtasksMetadataServiceTestComponent { }
void inject(GtasksMetadataServiceTest gtasksMetadataServiceTest);
}
@Inject GtasksTestPreferenceService preferences; @Test
@Inject TaskDao taskDao; public void testMetadataDoesntExist() {
@Inject GoogleTaskDao googleTaskDao; givenTask(taskWithoutMetadata());
private Task task; whenSearchForMetadata();
private GoogleTask metadata;
@Override thenExpectNoMetadataFound();
public void setUp() { }
super.setUp();
if (preferences.getDefaultList() == null) { private void thenExpectNoMetadataFound() {
preferences.setDefaultList("list"); assertNull(metadata);
} }
}
@Override private void thenExpectMetadataFound() {
protected void inject(TestComponent component) { assertNotNull(metadata);
component }
.plus(new GtasksMetadataServiceTestModule(getTargetContext()))
.inject(this);
}
@Test // --- helpers
public void testMetadataFound() {
givenTask(taskWithMetadata(null));
whenSearchForMetadata(); private void whenSearchForMetadata() {
metadata = googleTaskDao.getByTaskId(task.getId());
}
thenExpectMetadataFound(); private Task taskWithMetadata(String id) {
Task task = new Task();
task.setTitle("cats");
taskDao.createNew(task);
GoogleTask metadata = new GoogleTask(task.getId(), "");
if (id != null) {
metadata.setRemoteId(id);
} }
metadata.setTask(task.getId());
googleTaskDao.insert(metadata);
return task;
}
@Test private void givenTask(Task taskToTest) {
public void testMetadataDoesntExist() { task = taskToTest;
givenTask(taskWithoutMetadata()); }
whenSearchForMetadata(); private Task taskWithoutMetadata() {
Task task = new Task();
task.setTitle("dogs");
taskDao.createNew(task);
return task;
}
thenExpectNoMetadataFound(); @Subcomponent(modules = GtasksMetadataServiceTest.GtasksMetadataServiceTestModule.class)
} public interface GtasksMetadataServiceTestComponent {
// --- helpers void inject(GtasksMetadataServiceTest gtasksMetadataServiceTest);
}
private void thenExpectNoMetadataFound() { @Module
assertNull(metadata); public class GtasksMetadataServiceTestModule {
}
private void thenExpectMetadataFound() { private final GtasksTestPreferenceService service;
assertNotNull(metadata);
}
private void whenSearchForMetadata() {
metadata = googleTaskDao.getByTaskId(task.getId());
}
private Task taskWithMetadata(String id) { public GtasksMetadataServiceTestModule(Context context) {
Task task = new Task(); service = new GtasksTestPreferenceService(new Preferences(context, null));
task.setTitle("cats");
taskDao.createNew(task);
GoogleTask metadata = new GoogleTask(task.getId(), "");
if (id != null) {
metadata.setRemoteId(id);
}
metadata.setTask(task.getId());
googleTaskDao.insert(metadata);
return task;
} }
private void givenTask(Task taskToTest) { @Provides
task = taskToTest; public GtasksTestPreferenceService getGtasksTestPreferenceService() {
return service;
} }
private Task taskWithoutMetadata() { @Provides
Task task = new Task(); public GtasksPreferenceService getGtasksPreferenceService() {
task.setTitle("dogs"); return service;
taskDao.createNew(task);
return task;
} }
}
} }

@ -3,14 +3,19 @@
* *
* See the file "LICENSE" for the full license governing this code. * See the file "LICENSE" for the full license governing this code.
*/ */
package com.todoroo.astrid.gtasks; package com.todoroo.astrid.gtasks;
import android.support.test.runner.AndroidJUnit4; import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertNotNull;
import android.support.test.runner.AndroidJUnit4;
import com.google.api.services.tasks.model.TaskList; import com.google.api.services.tasks.model.TaskList;
import com.todoroo.astrid.dao.TaskDao; import com.todoroo.astrid.dao.TaskDao;
import com.todoroo.astrid.data.Task; import com.todoroo.astrid.data.Task;
import java.util.ArrayList;
import java.util.List;
import javax.inject.Inject;
import org.junit.Ignore; import org.junit.Ignore;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
@ -20,179 +25,173 @@ import org.tasks.data.GoogleTaskList;
import org.tasks.injection.InjectingTestCase; import org.tasks.injection.InjectingTestCase;
import org.tasks.injection.TestComponent; import org.tasks.injection.TestComponent;
import java.util.ArrayList;
import java.util.List;
import javax.inject.Inject;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertNotNull;
@SuppressWarnings("nls") @SuppressWarnings("nls")
@RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class)
public class GtasksTaskListUpdaterTest extends InjectingTestCase { public class GtasksTaskListUpdaterTest extends InjectingTestCase {
private static final int VALUE_UNSET = -1; private static final int VALUE_UNSET = -1;
@Inject GtasksTaskListUpdater gtasksTaskListUpdater; @Inject GtasksTaskListUpdater gtasksTaskListUpdater;
@Inject GtasksListService gtasksListService; @Inject GtasksListService gtasksListService;
@Inject TaskDao taskDao; @Inject TaskDao taskDao;
@Inject GoogleTaskDao googleTaskDao; @Inject GoogleTaskDao googleTaskDao;
@Test @Test
public void testBasicParentComputation() { public void testBasicParentComputation() {
Task[] tasks = givenTasksABCDE(); Task[] tasks = givenTasksABCDE();
whenCalculatingParentsAndSiblings(); whenCalculatingParentsAndSiblings();
thenExpectParent(tasks[0], null); thenExpectParent(tasks[0], null);
thenExpectParent(tasks[1], tasks[0]); thenExpectParent(tasks[1], tasks[0]);
thenExpectParent(tasks[2], tasks[0]); thenExpectParent(tasks[2], tasks[0]);
thenExpectParent(tasks[3], tasks[2]); thenExpectParent(tasks[3], tasks[2]);
thenExpectParent(tasks[4], null); thenExpectParent(tasks[4], null);
} }
@Test @Test
public void testBasicSiblingComputation() { public void testBasicSiblingComputation() {
Task[] tasks = givenTasksABCDE(); Task[] tasks = givenTasksABCDE();
whenCalculatingParentsAndSiblings(); whenCalculatingParentsAndSiblings();
thenExpectSibling(tasks[0], null); thenExpectSibling(tasks[0], null);
thenExpectSibling(tasks[1], null); thenExpectSibling(tasks[1], null);
thenExpectSibling(tasks[2], tasks[1]); thenExpectSibling(tasks[2], tasks[1]);
thenExpectSibling(tasks[3], null); thenExpectSibling(tasks[3], null);
thenExpectSibling(tasks[4], tasks[0]); thenExpectSibling(tasks[4], tasks[0]);
} }
@Ignore @Ignore
@Test @Test
public void testMetadataParentComputation() { public void testMetadataParentComputation() {
Task[] tasks = givenTasksABCDE(); Task[] tasks = givenTasksABCDE();
thenExpectMetadataParent(tasks[0], null); thenExpectMetadataParent(tasks[0], null);
thenExpectMetadataParent(tasks[1], tasks[0]); thenExpectMetadataParent(tasks[1], tasks[0]);
thenExpectMetadataParent(tasks[2], tasks[0]); thenExpectMetadataParent(tasks[2], tasks[0]);
thenExpectMetadataParent(tasks[3], tasks[2]); thenExpectMetadataParent(tasks[3], tasks[2]);
thenExpectMetadataParent(tasks[4], null); thenExpectMetadataParent(tasks[4], null);
} }
@Test @Test
public void testMetadataOrderComputation() { public void testMetadataOrderComputation() {
Task[] tasks = givenTasksABCDE(); Task[] tasks = givenTasksABCDE();
thenExpectMetadataIndentAndOrder(tasks[0], 0, 0); thenExpectMetadataIndentAndOrder(tasks[0], 0, 0);
thenExpectMetadataIndentAndOrder(tasks[1], 1, 1); thenExpectMetadataIndentAndOrder(tasks[1], 1, 1);
thenExpectMetadataIndentAndOrder(tasks[2], 2, 1); thenExpectMetadataIndentAndOrder(tasks[2], 2, 1);
thenExpectMetadataIndentAndOrder(tasks[3], 3, 2); thenExpectMetadataIndentAndOrder(tasks[3], 3, 2);
thenExpectMetadataIndentAndOrder(tasks[4], 4, 0); thenExpectMetadataIndentAndOrder(tasks[4], 4, 0);
} }
@Ignore @Ignore
@Test @Test
public void testNewTaskOrder() { public void testNewTaskOrder() {
givenTasksABCDE(); givenTasksABCDE();
Task newTask = createTask("F", VALUE_UNSET, 0); Task newTask = createTask("F", VALUE_UNSET, 0);
thenExpectMetadataIndentAndOrder(newTask, 5, 0); thenExpectMetadataIndentAndOrder(newTask, 5, 0);
}
// --- helpers
private void thenExpectMetadataIndentAndOrder(Task task, long order, int indent) {
GoogleTask metadata = googleTaskDao.getByTaskId(task.getId());
assertNotNull("metadata was found", metadata);
assertEquals("order", order, metadata.getOrder());
assertEquals("indentation", indent, metadata.getIndent());
}
private void thenExpectMetadataParent(Task task, Task expectedParent) {
GoogleTask metadata = googleTaskDao.getByTaskId(task.getId());
long parent = metadata.getParent();
if (expectedParent == null) {
assertEquals("Task " + task.getTitle() + " parent none", 0, parent);
} else {
assertEquals("Task " + task.getTitle() + " parent " +
expectedParent.getTitle(), expectedParent.getId(), parent);
} }
}
// --- helpers private void thenExpectSibling(Task task, Task expectedSibling) {
long sibling = gtasksTaskListUpdater.siblings.get(task.getId());
private void thenExpectMetadataIndentAndOrder(Task task, long order, int indent) { if (expectedSibling == null) {
GoogleTask metadata = googleTaskDao.getByTaskId(task.getId()); assertEquals("Task " + task.getTitle() + " sibling null", 0L, sibling);
assertNotNull("metadata was found", metadata); } else {
assertEquals("order", order, metadata.getOrder()); assertEquals("Task " + task.getTitle() + " sibling " +
assertEquals("indentation", indent, metadata.getIndent()); expectedSibling.getTitle(), expectedSibling.getId(), sibling);
} }
}
private void thenExpectMetadataParent(Task task, Task expectedParent) {
GoogleTask metadata = googleTaskDao.getByTaskId(task.getId()); private void thenExpectParent(Task task, Task expectedParent) {
long parent = metadata.getParent(); long parent = gtasksTaskListUpdater.parents.get(task.getId());
if(expectedParent == null) if (expectedParent == null) {
assertEquals("Task " + task.getTitle() + " parent none", 0, parent); assertEquals("Task " + task.getTitle() + " parent null", 0L, parent);
else } else {
assertEquals("Task " + task.getTitle() + " parent " + assertEquals("Task " + task.getTitle() + " parent " +
expectedParent.getTitle(), expectedParent.getId(), parent); expectedParent.getTitle(), expectedParent.getId(), parent);
} }
}
private void thenExpectSibling(Task task, Task expectedSibling) {
long sibling = gtasksTaskListUpdater.siblings.get(task.getId()); @Override
if(expectedSibling == null) public void setUp() {
assertEquals("Task " + task.getTitle() + " sibling null", 0L, sibling); super.setUp();
else
assertEquals("Task " + task.getTitle() + " sibling " + List<TaskList> items = new ArrayList<>();
expectedSibling.getTitle(), expectedSibling.getId(), sibling); TaskList list = new TaskList();
list.setId("1");
list.setTitle("Tim's Tasks");
items.add(list);
gtasksListService.updateLists(items);
}
@Override
protected void inject(TestComponent component) {
component.inject(this);
}
private void whenCalculatingParentsAndSiblings() {
createParentSiblingMaps();
}
void createParentSiblingMaps() {
for (GoogleTaskList list : gtasksListService.getLists()) {
gtasksTaskListUpdater.updateParentSiblingMapsFor(list);
} }
}
private void thenExpectParent(Task task, Task expectedParent) {
long parent = gtasksTaskListUpdater.parents.get(task.getId()); /**
if(expectedParent == null) * A
assertEquals("Task " + task.getTitle() + " parent null", 0L, parent); * B
else * C
assertEquals("Task " + task.getTitle() + " parent " + * D
expectedParent.getTitle(), expectedParent.getId(), parent); * E
*/
private Task[] givenTasksABCDE() {
return new Task[]{
createTask("A", 0, 0),
createTask("B", 1, 1),
createTask("C", 2, 1),
createTask("D", 3, 2),
createTask("E", 4, 0),
};
}
private Task createTask(String title, long order, int indent) {
Task task = new Task();
task.setTitle(title);
taskDao.createNew(task);
GoogleTask metadata = new GoogleTask(task.getId(), "1");
if (order != VALUE_UNSET) {
metadata.setOrder(order);
} }
if (indent != VALUE_UNSET) {
@Override metadata.setIndent(indent);
public void setUp() {
super.setUp();
List<TaskList> items = new ArrayList<>();
TaskList list = new TaskList();
list.setId("1");
list.setTitle("Tim's Tasks");
items.add(list);
gtasksListService.updateLists(items);
}
@Override
protected void inject(TestComponent component) {
component.inject(this);
}
private void whenCalculatingParentsAndSiblings() {
createParentSiblingMaps();
}
void createParentSiblingMaps() {
for(GoogleTaskList list : gtasksListService.getLists()) {
gtasksTaskListUpdater.updateParentSiblingMapsFor(list);
}
}
/**
* A
* B
* C
* D
* E
*/
private Task[] givenTasksABCDE() {
return new Task[] {
createTask("A", 0, 0),
createTask("B", 1, 1),
createTask("C", 2, 1),
createTask("D", 3, 2),
createTask("E", 4, 0),
};
}
private Task createTask(String title, long order, int indent) {
Task task = new Task();
task.setTitle(title);
taskDao.createNew(task);
GoogleTask metadata = new GoogleTask(task.getId(), "1");
if(order != VALUE_UNSET) {
metadata.setOrder(order);
}
if(indent != VALUE_UNSET) {
metadata.setIndent(indent);
}
googleTaskDao.insert(metadata);
return task;
} }
googleTaskDao.insert(metadata);
return task;
}
} }

@ -3,14 +3,19 @@
* *
* See the file "LICENSE" for the full license governing this code. * See the file "LICENSE" for the full license governing this code.
*/ */
package com.todoroo.astrid.gtasks; package com.todoroo.astrid.gtasks;
import android.support.test.runner.AndroidJUnit4; import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertNotNull;
import android.support.test.runner.AndroidJUnit4;
import com.google.api.services.tasks.model.TaskList; import com.google.api.services.tasks.model.TaskList;
import com.todoroo.astrid.dao.TaskDao; import com.todoroo.astrid.dao.TaskDao;
import com.todoroo.astrid.data.Task; import com.todoroo.astrid.data.Task;
import java.util.ArrayList;
import java.util.List;
import javax.inject.Inject;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.tasks.data.GoogleTask; import org.tasks.data.GoogleTask;
@ -19,27 +24,19 @@ import org.tasks.data.GoogleTaskList;
import org.tasks.injection.InjectingTestCase; import org.tasks.injection.InjectingTestCase;
import org.tasks.injection.TestComponent; import org.tasks.injection.TestComponent;
import java.util.ArrayList;
import java.util.List;
import javax.inject.Inject;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertNotNull;
@SuppressWarnings("nls") @SuppressWarnings("nls")
@RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class)
public class GtasksTaskMovingTest extends InjectingTestCase { public class GtasksTaskMovingTest extends InjectingTestCase {
private static final int VALUE_UNSET = -1; private static final int VALUE_UNSET = -1;
@Inject GtasksListService gtasksListService; @Inject GtasksListService gtasksListService;
@Inject GtasksTaskListUpdater gtasksTaskListUpdater; @Inject GtasksTaskListUpdater gtasksTaskListUpdater;
@Inject TaskDao taskDao; @Inject TaskDao taskDao;
@Inject GoogleTaskDao googleTaskDao; @Inject GoogleTaskDao googleTaskDao;
private Task A, B, C, D, E, F; private Task A, B, C, D, E, F;
private GoogleTaskList list; private GoogleTaskList list;
/* Starting State: /* Starting State:
* *
@ -51,41 +48,41 @@ public class GtasksTaskMovingTest extends InjectingTestCase {
* F * F
*/ */
@Test @Test
public void testMoveDownFromListBottom() { public void testMoveDownFromListBottom() {
givenTasksABCDEF(); givenTasksABCDEF();
whenTriggerMove(F, null); whenTriggerMove(F, null);
thenExpectMetadataOrderAndIndent(E, 4, 0); thenExpectMetadataOrderAndIndent(E, 4, 0);
thenExpectMetadataOrderAndIndent(F, 5, 0); thenExpectMetadataOrderAndIndent(F, 5, 0);
} }
@Test @Test
public void testMoveDownToListBottom() { public void testMoveDownToListBottom() {
givenTasksABCDEF(); givenTasksABCDEF();
whenTriggerMove(E, null); whenTriggerMove(E, null);
thenExpectMetadataOrderAndIndent(E, 5, 0); thenExpectMetadataOrderAndIndent(E, 5, 0);
thenExpectMetadataOrderAndIndent(F, 4, 0); thenExpectMetadataOrderAndIndent(F, 4, 0);
} }
@Test @Test
public void testMoveUpSimple() { public void testMoveUpSimple() {
givenTasksABCDEF(); givenTasksABCDEF();
whenTriggerMove(F, E); whenTriggerMove(F, E);
thenExpectMetadataOrderAndIndent(E, 5, 0); thenExpectMetadataOrderAndIndent(E, 5, 0);
thenExpectMetadataOrderAndIndent(F, 4, 0); thenExpectMetadataOrderAndIndent(F, 4, 0);
} }
@Test @Test
public void testMoveUpWithSubtasks() { public void testMoveUpWithSubtasks() {
givenTasksABCDEF(); givenTasksABCDEF();
whenTriggerMove(C, B); whenTriggerMove(C, B);
/* /*
* A * A
@ -94,17 +91,17 @@ public class GtasksTaskMovingTest extends InjectingTestCase {
* B * B
*/ */
thenExpectMetadataOrderAndIndent(A, 0, 0); thenExpectMetadataOrderAndIndent(A, 0, 0);
thenExpectMetadataOrderAndIndent(B, 3, 1); thenExpectMetadataOrderAndIndent(B, 3, 1);
thenExpectMetadataOrderAndIndent(C, 1, 1); thenExpectMetadataOrderAndIndent(C, 1, 1);
thenExpectMetadataOrderAndIndent(D, 2, 2); thenExpectMetadataOrderAndIndent(D, 2, 2);
} }
@Test @Test
public void testMoveDownThroughSubtasks() { public void testMoveDownThroughSubtasks() {
givenTasksABCDEF(); givenTasksABCDEF();
whenTriggerMove(B, E); whenTriggerMove(B, E);
/* /*
* A * A
@ -114,17 +111,17 @@ public class GtasksTaskMovingTest extends InjectingTestCase {
* E * E
*/ */
thenExpectMetadataOrderAndIndent(A, 0, 0); thenExpectMetadataOrderAndIndent(A, 0, 0);
thenExpectMetadataOrderAndIndent(B, 3, 0); thenExpectMetadataOrderAndIndent(B, 3, 0);
thenExpectMetadataOrderAndIndent(C, 1, 1); thenExpectMetadataOrderAndIndent(C, 1, 1);
thenExpectMetadataOrderAndIndent(D, 2, 2); thenExpectMetadataOrderAndIndent(D, 2, 2);
} }
@Test @Test
public void testMoveUpAboveParent() { public void testMoveUpAboveParent() {
givenTasksABCDEF(); givenTasksABCDEF();
whenTriggerMove(B, A); whenTriggerMove(B, A);
/* /*
* B * B
@ -135,16 +132,16 @@ public class GtasksTaskMovingTest extends InjectingTestCase {
* F * F
*/ */
thenExpectMetadataOrderAndIndent(A, 1, 0); thenExpectMetadataOrderAndIndent(A, 1, 0);
thenExpectMetadataOrderAndIndent(B, 0, 0); thenExpectMetadataOrderAndIndent(B, 0, 0);
thenExpectMetadataOrderAndIndent(C, 2, 1); thenExpectMetadataOrderAndIndent(C, 2, 1);
} }
@Test @Test
public void testMoveDownWithChildren() { public void testMoveDownWithChildren() {
givenTasksABCDEF(); givenTasksABCDEF();
whenTriggerMove(C, F); whenTriggerMove(C, F);
/* /*
* A * A
@ -155,18 +152,18 @@ public class GtasksTaskMovingTest extends InjectingTestCase {
* F * F
*/ */
thenExpectMetadataOrderAndIndent(A, 0, 0); thenExpectMetadataOrderAndIndent(A, 0, 0);
thenExpectMetadataOrderAndIndent(B, 1, 1); thenExpectMetadataOrderAndIndent(B, 1, 1);
thenExpectMetadataOrderAndIndent(C, 3, 0); thenExpectMetadataOrderAndIndent(C, 3, 0);
thenExpectMetadataOrderAndIndent(D, 4, 1); thenExpectMetadataOrderAndIndent(D, 4, 1);
thenExpectMetadataOrderAndIndent(E, 2, 0); thenExpectMetadataOrderAndIndent(E, 2, 0);
} }
@Test @Test
public void testMoveDownIndentingTwice() { public void testMoveDownIndentingTwice() {
givenTasksABCDEF(); givenTasksABCDEF();
whenTriggerMove(D, F); whenTriggerMove(D, F);
/* /*
* A * A
@ -176,18 +173,18 @@ public class GtasksTaskMovingTest extends InjectingTestCase {
* D * D
*/ */
thenExpectMetadataOrderAndIndent(A, 0, 0); thenExpectMetadataOrderAndIndent(A, 0, 0);
thenExpectMetadataOrderAndIndent(B, 1, 1); thenExpectMetadataOrderAndIndent(B, 1, 1);
thenExpectMetadataOrderAndIndent(C, 2, 1); thenExpectMetadataOrderAndIndent(C, 2, 1);
thenExpectMetadataOrderAndIndent(D, 4, 0); thenExpectMetadataOrderAndIndent(D, 4, 0);
thenExpectMetadataOrderAndIndent(E, 3, 0); thenExpectMetadataOrderAndIndent(E, 3, 0);
} }
@Test @Test
public void testMoveUpMultiple() { public void testMoveUpMultiple() {
givenTasksABCDEF(); givenTasksABCDEF();
whenTriggerMove(C, A); whenTriggerMove(C, A);
/* /*
* C * C
@ -196,17 +193,17 @@ public class GtasksTaskMovingTest extends InjectingTestCase {
* B * B
*/ */
thenExpectMetadataOrderAndIndent(A, 2, 0); thenExpectMetadataOrderAndIndent(A, 2, 0);
thenExpectMetadataOrderAndIndent(B, 3, 1); thenExpectMetadataOrderAndIndent(B, 3, 1);
thenExpectMetadataOrderAndIndent(C, 0, 0); thenExpectMetadataOrderAndIndent(C, 0, 0);
thenExpectMetadataOrderAndIndent(D, 1, 1); thenExpectMetadataOrderAndIndent(D, 1, 1);
} }
@Test @Test
public void testMoveUpIntoSublist() { public void testMoveUpIntoSublist() {
givenTasksABCDEF(); givenTasksABCDEF();
whenTriggerMove(F, D); whenTriggerMove(F, D);
/* /*
* A * A
@ -216,19 +213,19 @@ public class GtasksTaskMovingTest extends InjectingTestCase {
* D * D
*/ */
thenExpectMetadataOrderAndIndent(A, 0, 0); thenExpectMetadataOrderAndIndent(A, 0, 0);
thenExpectMetadataOrderAndIndent(B, 1, 1); thenExpectMetadataOrderAndIndent(B, 1, 1);
thenExpectMetadataOrderAndIndent(C, 2, 1); thenExpectMetadataOrderAndIndent(C, 2, 1);
thenExpectMetadataOrderAndIndent(D, 4, 2); thenExpectMetadataOrderAndIndent(D, 4, 2);
thenExpectMetadataOrderAndIndent(E, 5, 0); thenExpectMetadataOrderAndIndent(E, 5, 0);
thenExpectMetadataOrderAndIndent(F, 3, 2); thenExpectMetadataOrderAndIndent(F, 3, 2);
} }
@Test @Test
public void testMoveDownMultiple() { public void testMoveDownMultiple() {
givenTasksABCDEF(); givenTasksABCDEF();
whenTriggerMove(B, F); whenTriggerMove(B, F);
/* /*
* A * A
@ -238,77 +235,78 @@ public class GtasksTaskMovingTest extends InjectingTestCase {
* B * B
*/ */
thenExpectMetadataOrderAndIndent(A, 0, 0); thenExpectMetadataOrderAndIndent(A, 0, 0);
thenExpectMetadataOrderAndIndent(B, 4, 0); thenExpectMetadataOrderAndIndent(B, 4, 0);
thenExpectMetadataOrderAndIndent(C, 1, 1); thenExpectMetadataOrderAndIndent(C, 1, 1);
thenExpectMetadataOrderAndIndent(D, 2, 2); thenExpectMetadataOrderAndIndent(D, 2, 2);
thenExpectMetadataOrderAndIndent(E, 3, 0); thenExpectMetadataOrderAndIndent(E, 3, 0);
thenExpectMetadataOrderAndIndent(F, 5, 0); thenExpectMetadataOrderAndIndent(F, 5, 0);
} }
// --- helpers
// --- helpers
/**
/** moveTo = null => move to end */ * moveTo = null => move to end
private void whenTriggerMove(Task target, Task moveTo) { */
gtasksTaskListUpdater.moveTo(list, target.getId(), moveTo == null ? -1 : moveTo.getId()); private void whenTriggerMove(Task target, Task moveTo) {
} gtasksTaskListUpdater.moveTo(list, target.getId(), moveTo == null ? -1 : moveTo.getId());
}
private void thenExpectMetadataOrderAndIndent(Task task, long order, int indent) {
GoogleTask metadata = googleTaskDao.getByTaskId(task.getId()); private void thenExpectMetadataOrderAndIndent(Task task, long order, int indent) {
assertNotNull("metadata was found", metadata); GoogleTask metadata = googleTaskDao.getByTaskId(task.getId());
assertEquals("order", order, metadata.getOrder()); assertNotNull("metadata was found", metadata);
assertEquals("indentation", indent, metadata.getIndent()); assertEquals("order", order, metadata.getOrder());
} assertEquals("indentation", indent, metadata.getIndent());
}
@Override
public void setUp() { @Override
super.setUp(); public void setUp() {
super.setUp();
List<TaskList> items = new ArrayList<>();
TaskList taskList = new TaskList(); List<TaskList> items = new ArrayList<>();
taskList.setId("1"); TaskList taskList = new TaskList();
taskList.setTitle("Tim's Tasks"); taskList.setId("1");
items.add(taskList); taskList.setTitle("Tim's Tasks");
gtasksListService.updateLists(items); items.add(taskList);
gtasksListService.updateLists(items);
list = gtasksListService.getLists().get(0);
list = gtasksListService.getLists().get(0);
}
@Override
protected void inject(TestComponent component) {
component.inject(this);
}
/**
* A
* B
* C
* D
* E
* F
*/
private void givenTasksABCDEF() {
A = createTask("A", 0, 0);
B = createTask("B", 1, 1);
C = createTask("C", 2, 1);
D = createTask("D", 3, 2);
E = createTask("E", 4, 0);
F = createTask("F", 5, 0);
}
private Task createTask(String title, long order, int indent) {
Task task = new Task();
task.setTitle(title);
taskDao.createNew(task);
GoogleTask metadata = new GoogleTask(task.getId(), "1");
if (order != VALUE_UNSET) {
metadata.setOrder(order);
} }
if (indent != VALUE_UNSET) {
@Override metadata.setIndent(indent);
protected void inject(TestComponent component) {
component.inject(this);
}
/**
* A
* B
* C
* D
* E
* F
*/
private void givenTasksABCDEF() {
A = createTask("A", 0, 0);
B = createTask("B", 1, 1);
C = createTask("C", 2, 1);
D = createTask("D", 3, 2);
E = createTask("E", 4, 0);
F = createTask("F", 5, 0);
}
private Task createTask(String title, long order, int indent) {
Task task = new Task();
task.setTitle(title);
taskDao.createNew(task);
GoogleTask metadata = new GoogleTask(task.getId(), "1");
if(order != VALUE_UNSET) {
metadata.setOrder(order);
}
if(indent != VALUE_UNSET) {
metadata.setIndent(indent);
}
googleTaskDao.insert(metadata);
return task;
} }
googleTaskDao.insert(metadata);
return task;
}
} }

@ -3,18 +3,19 @@
* *
* See the file "LICENSE" for the full license governing this code. * See the file "LICENSE" for the full license governing this code.
*/ */
package com.todoroo.astrid.gtasks; package com.todoroo.astrid.gtasks;
import org.tasks.preferences.Preferences; import org.tasks.preferences.Preferences;
public class GtasksTestPreferenceService extends GtasksPreferenceService { public class GtasksTestPreferenceService extends GtasksPreferenceService {
public GtasksTestPreferenceService(Preferences preferences) { public GtasksTestPreferenceService(Preferences preferences) {
super(preferences); super(preferences);
} }
@Override @Override
public long getLastSyncDate() { public long getLastSyncDate() {
return 0L; return 0L;
} }
} }

@ -1,17 +1,5 @@
package com.todoroo.astrid.gtasks.api; package com.todoroo.astrid.gtasks.api;
import android.support.test.runner.AndroidJUnit4;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.tasks.time.DateTime;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;
import static com.todoroo.astrid.gtasks.api.GtasksApiUtilities.gtasksCompletedTimeToUnixTime; import static com.todoroo.astrid.gtasks.api.GtasksApiUtilities.gtasksCompletedTimeToUnixTime;
import static com.todoroo.astrid.gtasks.api.GtasksApiUtilities.gtasksDueTimeToUnixTime; import static com.todoroo.astrid.gtasks.api.GtasksApiUtilities.gtasksDueTimeToUnixTime;
import static com.todoroo.astrid.gtasks.api.GtasksApiUtilities.unixTimeToGtasksCompletionTime; import static com.todoroo.astrid.gtasks.api.GtasksApiUtilities.unixTimeToGtasksCompletionTime;
@ -19,66 +7,77 @@ import static com.todoroo.astrid.gtasks.api.GtasksApiUtilities.unixTimeToGtasksD
import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertNull; import static junit.framework.Assert.assertNull;
import android.support.test.runner.AndroidJUnit4;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.tasks.time.DateTime;
@RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class)
public class GtasksApiUtilitiesTest { public class GtasksApiUtilitiesTest {
private static final Locale defaultLocale = Locale.getDefault(); private static final Locale defaultLocale = Locale.getDefault();
private static final TimeZone defaultDateTimeZone = TimeZone.getDefault(); private static final TimeZone defaultDateTimeZone = TimeZone.getDefault();
@Before @Before
public void setUp() { public void setUp() {
Locale.setDefault(Locale.US); Locale.setDefault(Locale.US);
TimeZone.setDefault(TimeZone.getTimeZone("America/Chicago")); TimeZone.setDefault(TimeZone.getTimeZone("America/Chicago"));
} }
@After @After
public void tearDown() { public void tearDown() {
Locale.setDefault(defaultLocale); Locale.setDefault(defaultLocale);
TimeZone.setDefault(defaultDateTimeZone); TimeZone.setDefault(defaultDateTimeZone);
} }
@Test @Test
public void testConvertUnixToGoogleCompletionTime() { public void testConvertUnixToGoogleCompletionTime() {
long now = new DateTime(2014, 1, 8, 8, 53, 20, 109).getMillis(); long now = new DateTime(2014, 1, 8, 8, 53, 20, 109).getMillis();
assertEquals(now, unixTimeToGtasksCompletionTime(now).getValue()); assertEquals(now, unixTimeToGtasksCompletionTime(now).getValue());
} }
@Test @Test
public void testConvertGoogleCompletedTimeToUnixTime() { public void testConvertGoogleCompletedTimeToUnixTime() {
long now = new DateTime(2014, 1, 8, 8, 53, 20, 109).getMillis(); long now = new DateTime(2014, 1, 8, 8, 53, 20, 109).getMillis();
com.google.api.client.util.DateTime gtime = new com.google.api.client.util.DateTime(now); com.google.api.client.util.DateTime gtime = new com.google.api.client.util.DateTime(now);
assertEquals(now, gtasksCompletedTimeToUnixTime(gtime)); assertEquals(now, gtasksCompletedTimeToUnixTime(gtime));
} }
@Test @Test
public void testConvertDueDateTimeToGoogleDueDate() { public void testConvertDueDateTimeToGoogleDueDate() {
DateTime now = new DateTime(2014, 1, 8, 8, 53, 20, 109); DateTime now = new DateTime(2014, 1, 8, 8, 53, 20, 109);
assertEquals( assertEquals(
new DateTime(2014, 1, 8, 0, 0, 0, 0, TimeZone.getTimeZone("GMT")).getMillis(), new DateTime(2014, 1, 8, 0, 0, 0, 0, TimeZone.getTimeZone("GMT")).getMillis(),
unixTimeToGtasksDueDate(now.getMillis()).getValue()); unixTimeToGtasksDueDate(now.getMillis()).getValue());
} }
@Test @Test
public void testConvertGoogleDueDateToUnixTime() { public void testConvertGoogleDueDateToUnixTime() {
com.google.api.client.util.DateTime googleDueDate = com.google.api.client.util.DateTime googleDueDate =
new com.google.api.client.util.DateTime( new com.google.api.client.util.DateTime(
new Date(new DateTime(2014, 1, 8, 0, 0, 0, 0).getMillis()), TimeZone.getTimeZone("GMT")); new Date(new DateTime(2014, 1, 8, 0, 0, 0, 0).getMillis()),
TimeZone.getTimeZone("GMT"));
assertEquals(
new DateTime(2014, 1, 8, 6, 0, 0, 0).getMillis(), assertEquals(
gtasksDueTimeToUnixTime(googleDueDate)); new DateTime(2014, 1, 8, 6, 0, 0, 0).getMillis(),
} gtasksDueTimeToUnixTime(googleDueDate));
}
@Test
public void testConvertToInvalidGtaskTimes() { @Test
assertNull(unixTimeToGtasksCompletionTime(-1)); public void testConvertToInvalidGtaskTimes() {
assertNull(unixTimeToGtasksDueDate(-1)); assertNull(unixTimeToGtasksCompletionTime(-1));
} assertNull(unixTimeToGtasksDueDate(-1));
}
@Test
public void testConvertFromInvalidGtaskTimes() { @Test
assertEquals(0, gtasksCompletedTimeToUnixTime(null)); public void testConvertFromInvalidGtaskTimes() {
assertEquals(0, gtasksDueTimeToUnixTime(null)); assertEquals(0, gtasksCompletedTimeToUnixTime(null));
} assertEquals(0, gtasksDueTimeToUnixTime(null));
}
} }

@ -1,13 +1,5 @@
package org.tasks.gtasks; package org.tasks.gtasks;
import android.support.test.runner.AndroidJUnit4;
import com.todoroo.astrid.data.Task;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.tasks.time.DateTime;
import static com.natpryce.makeiteasy.MakeItEasy.with; import static com.natpryce.makeiteasy.MakeItEasy.with;
import static com.todoroo.astrid.data.Task.HIDE_UNTIL_DUE; import static com.todoroo.astrid.data.Task.HIDE_UNTIL_DUE;
import static com.todoroo.astrid.data.Task.HIDE_UNTIL_DUE_TIME; import static com.todoroo.astrid.data.Task.HIDE_UNTIL_DUE_TIME;
@ -18,93 +10,102 @@ import static org.tasks.makers.TaskMaker.DUE_TIME;
import static org.tasks.makers.TaskMaker.HIDE_TYPE; import static org.tasks.makers.TaskMaker.HIDE_TYPE;
import static org.tasks.makers.TaskMaker.newTask; import static org.tasks.makers.TaskMaker.newTask;
import android.support.test.runner.AndroidJUnit4;
import com.todoroo.astrid.data.Task;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.tasks.time.DateTime;
@RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class)
public class GoogleTaskSyncAdapterTest { public class GoogleTaskSyncAdapterTest {
@Test @Test
public void testMergeDate() { public void testMergeDate() {
Task local = newTask(with(DUE_DATE, new DateTime(2016, 3, 12))); Task local = newTask(with(DUE_DATE, new DateTime(2016, 3, 12)));
mergeDates(newTask(with(DUE_DATE, new DateTime(2016, 3, 11))).getDueDate(), local); mergeDates(newTask(with(DUE_DATE, new DateTime(2016, 3, 11))).getDueDate(), local);
assertEquals( assertEquals(
new DateTime(2016, 3, 11, 12, 0).getMillis(), new DateTime(2016, 3, 11, 12, 0).getMillis(),
local.getDueDate().longValue()); local.getDueDate().longValue());
} }
@Test @Test
public void testMergeTime() { public void testMergeTime() {
Task local = newTask(with(DUE_TIME, new DateTime(2016, 3, 11, 13, 30))); Task local = newTask(with(DUE_TIME, new DateTime(2016, 3, 11, 13, 30)));
mergeDates(newTask(with(DUE_DATE, new DateTime(2016, 3, 11))).getDueDate(), local); mergeDates(newTask(with(DUE_DATE, new DateTime(2016, 3, 11))).getDueDate(), local);
assertEquals( assertEquals(
new DateTime(2016, 3, 11, 13, 30, 1).getMillis(), new DateTime(2016, 3, 11, 13, 30, 1).getMillis(),
local.getDueDate().longValue()); local.getDueDate().longValue());
} }
@Test @Test
public void testDueDateAdjustHideBackwards() { public void testDueDateAdjustHideBackwards() {
Task local = newTask(with(DUE_DATE, new DateTime(2016, 3, 12)), with(HIDE_TYPE, HIDE_UNTIL_DUE)); Task local = newTask(with(DUE_DATE, new DateTime(2016, 3, 12)),
with(HIDE_TYPE, HIDE_UNTIL_DUE));
mergeDates(newTask(with(DUE_DATE, new DateTime(2016, 3, 11))).getDueDate(), local); mergeDates(newTask(with(DUE_DATE, new DateTime(2016, 3, 11))).getDueDate(), local);
assertEquals( assertEquals(
new DateTime(2016, 3, 11).getMillis(), new DateTime(2016, 3, 11).getMillis(),
local.getHideUntil().longValue()); local.getHideUntil().longValue());
} }
@Test @Test
public void testDueDateAdjustHideForwards() { public void testDueDateAdjustHideForwards() {
Task local = newTask(with(DUE_DATE, new DateTime(2016, 3, 12)), with(HIDE_TYPE, HIDE_UNTIL_DUE)); Task local = newTask(with(DUE_DATE, new DateTime(2016, 3, 12)),
with(HIDE_TYPE, HIDE_UNTIL_DUE));
mergeDates(newTask(with(DUE_DATE, new DateTime(2016, 3, 14))).getDueDate(), local); mergeDates(newTask(with(DUE_DATE, new DateTime(2016, 3, 14))).getDueDate(), local);
assertEquals( assertEquals(
new DateTime(2016, 3, 14).getMillis(), new DateTime(2016, 3, 14).getMillis(),
local.getHideUntil().longValue()); local.getHideUntil().longValue());
} }
@Test @Test
public void testDueTimeAdjustHideBackwards() { public void testDueTimeAdjustHideBackwards() {
Task local = newTask(with(DUE_TIME, new DateTime(2016, 3, 12, 13, 30)), Task local = newTask(with(DUE_TIME, new DateTime(2016, 3, 12, 13, 30)),
with(HIDE_TYPE, HIDE_UNTIL_DUE_TIME)); with(HIDE_TYPE, HIDE_UNTIL_DUE_TIME));
mergeDates(newTask(with(DUE_DATE, new DateTime(2016, 3, 11))).getDueDate(), local); mergeDates(newTask(with(DUE_DATE, new DateTime(2016, 3, 11))).getDueDate(), local);
assertEquals( assertEquals(
new DateTime(2016, 3, 11, 13, 30, 1).getMillis(), new DateTime(2016, 3, 11, 13, 30, 1).getMillis(),
local.getHideUntil().longValue()); local.getHideUntil().longValue());
} }
@Test @Test
public void testDueTimeAdjustTimeForwards() { public void testDueTimeAdjustTimeForwards() {
Task local = newTask(with(DUE_TIME, new DateTime(2016, 3, 12, 13, 30)), Task local = newTask(with(DUE_TIME, new DateTime(2016, 3, 12, 13, 30)),
with(HIDE_TYPE, HIDE_UNTIL_DUE_TIME)); with(HIDE_TYPE, HIDE_UNTIL_DUE_TIME));
mergeDates(newTask(with(DUE_DATE, new DateTime(2016, 3, 14))).getDueDate(), local); mergeDates(newTask(with(DUE_DATE, new DateTime(2016, 3, 14))).getDueDate(), local);
assertEquals( assertEquals(
new DateTime(2016, 3, 14, 13, 30, 1).getMillis(), new DateTime(2016, 3, 14, 13, 30, 1).getMillis(),
local.getHideUntil().longValue()); local.getHideUntil().longValue());
} }
@Test @Test
public void testDueDateClearHide() { public void testDueDateClearHide() {
Task local = newTask(with(DUE_DATE, new DateTime(2016, 3, 12)), with(HIDE_TYPE, HIDE_UNTIL_DUE)); Task local = newTask(with(DUE_DATE, new DateTime(2016, 3, 12)),
with(HIDE_TYPE, HIDE_UNTIL_DUE));
mergeDates(newTask().getDueDate(), local); mergeDates(newTask().getDueDate(), local);
assertEquals(0, local.getHideUntil().longValue()); assertEquals(0, local.getHideUntil().longValue());
} }
@Test @Test
public void testDueTimeClearHide() { public void testDueTimeClearHide() {
Task local = newTask(with(DUE_TIME, new DateTime(2016, 3, 12, 13, 30)), Task local = newTask(with(DUE_TIME, new DateTime(2016, 3, 12, 13, 30)),
with(HIDE_TYPE, HIDE_UNTIL_DUE_TIME)); with(HIDE_TYPE, HIDE_UNTIL_DUE_TIME));
mergeDates(newTask().getDueDate(), local); mergeDates(newTask().getDueDate(), local);
assertEquals(0, local.getHideUntil().longValue()); assertEquals(0, local.getHideUntil().longValue());
} }
} }

@ -15,44 +15,43 @@ import com.todoroo.astrid.service.TitleParserTest;
import com.todoroo.astrid.subtasks.SubtasksHelperTest; import com.todoroo.astrid.subtasks.SubtasksHelperTest;
import com.todoroo.astrid.subtasks.SubtasksTestCase; import com.todoroo.astrid.subtasks.SubtasksTestCase;
import com.todoroo.astrid.sync.NewSyncTestCase; import com.todoroo.astrid.sync.NewSyncTestCase;
import org.tasks.jobs.BackupServiceTests;
import dagger.Component; import dagger.Component;
import org.tasks.jobs.BackupServiceTests;
@ApplicationScope @ApplicationScope
@Component(modules = TestModule.class) @Component(modules = TestModule.class)
public interface TestComponent { public interface TestComponent {
GtasksMetadataServiceTest.GtasksMetadataServiceTestComponent plus(GtasksMetadataServiceTest.GtasksMetadataServiceTestModule gtasksMetadataServiceTestModule); GtasksMetadataServiceTest.GtasksMetadataServiceTestComponent plus(
GtasksMetadataServiceTest.GtasksMetadataServiceTestModule gtasksMetadataServiceTestModule);
void inject(GtasksIndentActionTest gtasksIndentActionTest); void inject(GtasksIndentActionTest gtasksIndentActionTest);
void inject(GtasksTaskMovingTest gtasksTaskMovingTest); void inject(GtasksTaskMovingTest gtasksTaskMovingTest);
void inject(GtasksListServiceTest gtasksListServiceTest); void inject(GtasksListServiceTest gtasksListServiceTest);
void inject(GtasksTaskListUpdaterTest gtasksTaskListUpdaterTest); void inject(GtasksTaskListUpdaterTest gtasksTaskListUpdaterTest);
void inject(ReminderServiceTest reminderServiceTest); void inject(ReminderServiceTest reminderServiceTest);
void inject(TaskTest taskTest); void inject(TaskTest taskTest);
void inject(TaskDaoTests taskDaoTests); void inject(TaskDaoTests taskDaoTests);
void inject(NewSyncTestCase newSyncTestCase); void inject(NewSyncTestCase newSyncTestCase);
void inject(SubtasksTestCase subtasksTestCase); void inject(SubtasksTestCase subtasksTestCase);
void inject(SubtasksHelperTest subtasksHelperTest); void inject(SubtasksHelperTest subtasksHelperTest);
void inject(QuickAddMarkupTest quickAddMarkupTest); void inject(QuickAddMarkupTest quickAddMarkupTest);
void inject(TitleParserTest titleParserTest); void inject(TitleParserTest titleParserTest);
void inject(BackupServiceTests backupServiceTests); void inject(BackupServiceTests backupServiceTests);
void inject(AlarmJobServiceTest alarmServiceTest); void inject(AlarmJobServiceTest alarmServiceTest);
void inject(RepeatTaskHelperTest repeatTaskHelperTest); void inject(RepeatTaskHelperTest repeatTaskHelperTest);
} }

@ -1,25 +1,25 @@
package org.tasks.makers; package org.tasks.makers;
import static com.natpryce.makeiteasy.Property.newProperty;
import static org.tasks.makers.Maker.make;
import static org.tasks.time.DateTimeUtils.currentTimeMillis;
import com.google.api.client.util.DateTime; import com.google.api.client.util.DateTime;
import com.google.api.services.tasks.model.TaskList; import com.google.api.services.tasks.model.TaskList;
import com.natpryce.makeiteasy.Instantiator; import com.natpryce.makeiteasy.Instantiator;
import com.natpryce.makeiteasy.Property; import com.natpryce.makeiteasy.Property;
import com.natpryce.makeiteasy.PropertyValue; import com.natpryce.makeiteasy.PropertyValue;
import static com.natpryce.makeiteasy.Property.newProperty;
import static org.tasks.time.DateTimeUtils.currentTimeMillis;
import static org.tasks.makers.Maker.make;
public class RemoteGtaskListMaker { public class RemoteGtaskListMaker {
public static final Property<TaskList, String> REMOTE_ID = newProperty();
public static final Property<TaskList, String> NAME = newProperty();
public static TaskList newRemoteList(PropertyValue<? super TaskList, ?>... properties) { public static final Property<TaskList, String> REMOTE_ID = newProperty();
return make(instantiator, properties); public static final Property<TaskList, String> NAME = newProperty();
} private static final Instantiator<TaskList> instantiator = lookup -> new TaskList()
.setId(lookup.valueOf(REMOTE_ID, "1"))
.setTitle(lookup.valueOf(NAME, "Default"))
.setUpdated(new DateTime(currentTimeMillis()));
private static final Instantiator<TaskList> instantiator = lookup -> new TaskList() public static TaskList newRemoteList(PropertyValue<? super TaskList, ?>... properties) {
.setId(lookup.valueOf(REMOTE_ID, "1")) return make(instantiator, properties);
.setTitle(lookup.valueOf(NAME, "Default")) }
.setUpdated(new DateTime(currentTimeMillis()));
} }

@ -3,4 +3,5 @@ package org.tasks;
import android.support.multidex.MultiDexApplication; import android.support.multidex.MultiDexApplication;
public class BaseApplication extends MultiDexApplication { public class BaseApplication extends MultiDexApplication {
} }

@ -3,50 +3,47 @@ package org.tasks;
import android.app.Application; import android.app.Application;
import android.content.Context; import android.content.Context;
import android.os.StrictMode; import android.os.StrictMode;
import com.facebook.stetho.Stetho; import com.facebook.stetho.Stetho;
import com.facebook.stetho.timber.StethoTree; import com.facebook.stetho.timber.StethoTree;
import com.squareup.leakcanary.LeakCanary; import com.squareup.leakcanary.LeakCanary;
import javax.inject.Inject;
import org.tasks.injection.ForApplication; import org.tasks.injection.ForApplication;
import org.tasks.preferences.Preferences; import org.tasks.preferences.Preferences;
import javax.inject.Inject;
import timber.log.Timber; import timber.log.Timber;
public class BuildSetup { public class BuildSetup {
private final Context context;
private final Preferences preferences;
@Inject private final Context context;
public BuildSetup(@ForApplication Context context, Preferences preferences) { private final Preferences preferences;
this.context = context;
this.preferences = preferences; @Inject
public BuildSetup(@ForApplication Context context, Preferences preferences) {
this.context = context;
this.preferences = preferences;
}
public boolean setup() {
Timber.plant(new Timber.DebugTree());
Timber.plant(new StethoTree());
Stetho.initializeWithDefaults(context);
Application application = (Application) context.getApplicationContext();
if (LeakCanary.isInAnalyzerProcess(context)) {
return false;
} }
LeakCanary.install(application);
public boolean setup() { if (preferences.getBoolean(R.string.p_strict_mode, false)) {
Timber.plant(new Timber.DebugTree()); StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
Timber.plant(new StethoTree()); .detectDiskReads()
Stetho.initializeWithDefaults(context); .detectDiskWrites()
Application application = (Application) context.getApplicationContext(); .detectNetwork()
if (LeakCanary.isInAnalyzerProcess(context)) { .penaltyLog()
return false; .build());
} StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
LeakCanary.install(application); .detectLeakedSqlLiteObjects()
if (preferences.getBoolean(R.string.p_strict_mode, false)) { .detectLeakedClosableObjects()
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder() .penaltyLog()
.detectDiskReads() .build());
.detectDiskWrites()
.detectNetwork()
.penaltyLog()
.build());
StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
.detectLeakedSqlLiteObjects()
.detectLeakedClosableObjects()
.penaltyLog()
.build());
}
return true;
} }
return true;
}
} }

@ -1,21 +1,22 @@
package org.tasks; package org.tasks;
import javax.inject.Inject;
import org.tasks.caldav.CaldavAccountManager; import org.tasks.caldav.CaldavAccountManager;
import org.tasks.preferences.Preferences; import org.tasks.preferences.Preferences;
import javax.inject.Inject;
public class FlavorSetup { public class FlavorSetup {
private final CaldavAccountManager caldavAccountManager;
private final Preferences preferences;
@Inject private final CaldavAccountManager caldavAccountManager;
public FlavorSetup(CaldavAccountManager caldavAccountManager, Preferences preferences) { private final Preferences preferences;
this.caldavAccountManager = caldavAccountManager;
this.preferences = preferences; @Inject
} public FlavorSetup(CaldavAccountManager caldavAccountManager, Preferences preferences) {
this.caldavAccountManager = caldavAccountManager;
this.preferences = preferences;
}
public void setup() { public void setup() {
caldavAccountManager.setBackgroundSynchronization(preferences.getBoolean(R.string.p_background_sync, true)); caldavAccountManager
} .setBackgroundSynchronization(preferences.getBoolean(R.string.p_background_sync, true));
}
} }

@ -1,41 +1,40 @@
package org.tasks.analytics; package org.tasks.analytics;
import javax.inject.Inject; import javax.inject.Inject;
import timber.log.Timber; import timber.log.Timber;
public class Tracker { public class Tracker {
@Inject @Inject
public Tracker() { public Tracker() {
} }
public void setTrackingEnabled(boolean enabled) { public void setTrackingEnabled(boolean enabled) {
} }
public void reportException(Throwable t) { public void reportException(Throwable t) {
Timber.e(t, t.getMessage()); Timber.e(t, t.getMessage());
} }
public void reportException(Thread thread, Throwable t) { public void reportException(Thread thread, Throwable t) {
Timber.e(t, t.getMessage()); Timber.e(t, t.getMessage());
} }
public void reportEvent(Tracking.Events event) { public void reportEvent(Tracking.Events event) {
} }
public void reportEvent(Tracking.Events event, String string) { public void reportEvent(Tracking.Events event, String string) {
} }
public void reportEvent(Tracking.Events setPreference, int resId, String s) { public void reportEvent(Tracking.Events setPreference, int resId, String s) {
} }
public void reportEvent(Tracking.Events category, String action, String label) { public void reportEvent(Tracking.Events category, String action, String label) {
} }
} }

@ -2,36 +2,36 @@ package org.tasks.billing;
import android.app.Activity; import android.app.Activity;
import android.content.Intent; import android.content.Intent;
import org.tasks.preferences.Preferences;
import javax.inject.Inject; import javax.inject.Inject;
import org.tasks.preferences.Preferences;
public class PurchaseHelper { public class PurchaseHelper {
private Preferences preferences;
@Inject private Preferences preferences;
public PurchaseHelper(Preferences preferences) {
this.preferences = preferences; @Inject
} public PurchaseHelper(Preferences preferences) {
this.preferences = preferences;
}
public boolean purchase(final Activity activity, public boolean purchase(final Activity activity,
final String sku, final String pref, final String sku, final String pref,
final int requestCode, final PurchaseHelperCallback callback) { final int requestCode, final PurchaseHelperCallback callback) {
preferences.setBoolean(pref, true); preferences.setBoolean(pref, true);
callback.purchaseCompleted(true, sku); callback.purchaseCompleted(true, sku);
return true; return true;
} }
public void handleActivityResult(PurchaseHelperCallback callback, int requestCode, int resultCode, Intent data) { public void handleActivityResult(PurchaseHelperCallback callback, int requestCode, int resultCode,
Intent data) {
} }
public void disposeIabHelper() { public void disposeIabHelper() {
} }
public void consumePurchases() { public void consumePurchases() {
} }
} }

@ -1,40 +1,39 @@
package org.tasks.gtasks; package org.tasks.gtasks;
import android.app.Activity; import android.app.Activity;
import com.google.api.client.googleapis.extensions.android.gms.auth.GoogleAccountCredential; import com.google.api.client.googleapis.extensions.android.gms.auth.GoogleAccountCredential;
import com.todoroo.astrid.gtasks.auth.GtasksLoginActivity; import com.todoroo.astrid.gtasks.auth.GtasksLoginActivity;
import javax.inject.Inject; import javax.inject.Inject;
public class PlayServices { public class PlayServices {
@Inject @Inject
public PlayServices() { public PlayServices() {
} }
public boolean isPlayServicesAvailable() { public boolean isPlayServicesAvailable() {
return false; return false;
} }
public boolean refreshAndCheck() { public boolean refreshAndCheck() {
return false; return false;
} }
public void resolve(Activity activity) { public void resolve(Activity activity) {
} }
public String getStatus() { public String getStatus() {
return null; return null;
} }
public boolean clearToken(GoogleAccountCredential googleAccountCredential) { public boolean clearToken(GoogleAccountCredential googleAccountCredential) {
return false; return false;
} }
public void getAuthToken(GtasksLoginActivity gtasksLoginActivity, String a, GtasksLoginActivity.AuthResultHandler authResultHandler) { public void getAuthToken(GtasksLoginActivity gtasksLoginActivity, String a,
GtasksLoginActivity.AuthResultHandler authResultHandler) {
} }
} }

@ -1,28 +1,26 @@
package org.tasks.location; package org.tasks.location;
import org.tasks.data.Location;
import java.util.List; import java.util.List;
import javax.inject.Inject; import javax.inject.Inject;
import org.tasks.data.Location;
@SuppressWarnings("EmptyMethod") @SuppressWarnings("EmptyMethod")
public class GeofenceApi { public class GeofenceApi {
@Inject @Inject
public GeofenceApi() { public GeofenceApi() {
} }
public void register(List<Location> activeGeofences) { public void register(List<Location> activeGeofences) {
} }
public void cancel(Location geofence) { public void cancel(Location geofence) {
} }
public void cancel(List<Location> geofences) { public void cancel(List<Location> geofences) {
} }
} }

@ -3,16 +3,16 @@ package org.tasks.location;
import android.app.Activity; import android.app.Activity;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import org.tasks.data.Location; import org.tasks.data.Location;
import org.tasks.preferences.Preferences; import org.tasks.preferences.Preferences;
public class PlacePicker { public class PlacePicker {
public static Intent getIntent(Activity activity) {
return null;
}
public static Location getPlace(Context context, Intent data, Preferences preferences) { public static Intent getIntent(Activity activity) {
return null; return null;
} }
public static Location getPlace(Context context, Intent data, Preferences preferences) {
return null;
}
} }

@ -33,28 +33,29 @@ import android.content.Intent;
* has been acquired.</p> * has been acquired.</p>
*/ */
public class IabBroadcastReceiver extends BroadcastReceiver { public class IabBroadcastReceiver extends BroadcastReceiver {
/**
* Listener interface for received broadcast messages.
*/
public interface IabBroadcastListener {
void receivedBroadcast();
}
/** /**
* The Intent action that this Receiver should filter for. * The Intent action that this Receiver should filter for.
*/ */
public static final String ACTION = "com.android.vending.billing.PURCHASES_UPDATED"; public static final String ACTION = "com.android.vending.billing.PURCHASES_UPDATED";
private final IabBroadcastListener mListener;
private final IabBroadcastListener mListener; public IabBroadcastReceiver(IabBroadcastListener listener) {
mListener = listener;
}
public IabBroadcastReceiver(IabBroadcastListener listener) { @Override
mListener = listener; public void onReceive(Context context, Intent intent) {
if (mListener != null) {
mListener.receivedBroadcast();
} }
}
@Override /**
public void onReceive(Context context, Intent intent) { * Listener interface for received broadcast messages.
if (mListener != null) { */
mListener.receivedBroadcast(); public interface IabBroadcastListener {
}
} void receivedBroadcast();
}
} }

@ -23,22 +23,30 @@ package com.android.vending.billing;
*/ */
@SuppressWarnings("ALL") @SuppressWarnings("ALL")
public class IabException extends Exception { public class IabException extends Exception {
IabResult mResult;
IabResult mResult;
public IabException(IabResult r) {
this(r, null); public IabException(IabResult r) {
} this(r, null);
public IabException(int response, String message) { }
this(new IabResult(response, message));
} public IabException(int response, String message) {
public IabException(IabResult r, Exception cause) { this(new IabResult(response, message));
super(r.getMessage(), cause); }
mResult = r;
} public IabException(IabResult r, Exception cause) {
public IabException(int response, String message, Exception cause) { super(r.getMessage(), cause);
this(new IabResult(response, message), cause); mResult = r;
} }
/** Returns the IAB result (error) that this exception signals. */ public IabException(int response, String message, Exception cause) {
public IabResult getResult() { return mResult; } this(new IabResult(response, message), cause);
}
/**
* Returns the IAB result (error) that this exception signals.
*/
public IabResult getResult() {
return mResult;
}
} }

@ -25,22 +25,37 @@ package com.android.vending.billing;
*/ */
@SuppressWarnings("ALL") @SuppressWarnings("ALL")
public class IabResult { public class IabResult {
int mResponse;
String mMessage; int mResponse;
String mMessage;
public IabResult(int response, String message) {
mResponse = response; public IabResult(int response, String message) {
if (message == null || message.trim().length() == 0) { mResponse = response;
mMessage = IabHelper.getResponseDesc(response); if (message == null || message.trim().length() == 0) {
} mMessage = IabHelper.getResponseDesc(response);
else { } else {
mMessage = message + " (response: " + IabHelper.getResponseDesc(response) + ")"; mMessage = message + " (response: " + IabHelper.getResponseDesc(response) + ")";
}
} }
public int getResponse() { return mResponse; } }
public String getMessage() { return mMessage; }
public boolean isSuccess() { return mResponse == IabHelper.BILLING_RESPONSE_RESULT_OK; } public int getResponse() {
public boolean isFailure() { return !isSuccess(); } return mResponse;
public String toString() { return "IabResult: " + getMessage(); } }
public String getMessage() {
return mMessage;
}
public boolean isSuccess() {
return mResponse == IabHelper.BILLING_RESPONSE_RESULT_OK;
}
public boolean isFailure() {
return !isSuccess();
}
public String toString() {
return "IabResult: " + getMessage();
}
} }

@ -26,67 +26,87 @@ import java.util.Map;
*/ */
@SuppressWarnings("ALL") @SuppressWarnings("ALL")
public class Inventory { public class Inventory {
Map<String,SkuDetails> mSkuMap = new HashMap<String,SkuDetails>();
Map<String,Purchase> mPurchaseMap = new HashMap<String,Purchase>();
Inventory() { } Map<String, SkuDetails> mSkuMap = new HashMap<String, SkuDetails>();
Map<String, Purchase> mPurchaseMap = new HashMap<String, Purchase>();
/** Returns the listing details for an in-app product. */ Inventory() {
public SkuDetails getSkuDetails(String sku) { }
return mSkuMap.get(sku);
}
/** Returns purchase information for a given product, or null if there is no purchase. */ /**
public Purchase getPurchase(String sku) { * Returns the listing details for an in-app product.
return mPurchaseMap.get(sku); */
} public SkuDetails getSkuDetails(String sku) {
return mSkuMap.get(sku);
}
/** Returns whether or not there exists a purchase of the given product. */ /**
public boolean hasPurchase(String sku) { * Returns purchase information for a given product, or null if there is no purchase.
return mPurchaseMap.containsKey(sku); */
} public Purchase getPurchase(String sku) {
return mPurchaseMap.get(sku);
}
/** Return whether or not details about the given product are available. */ /**
public boolean hasDetails(String sku) { * Returns whether or not there exists a purchase of the given product.
return mSkuMap.containsKey(sku); */
} public boolean hasPurchase(String sku) {
return mPurchaseMap.containsKey(sku);
}
/** /**
* Erase a purchase (locally) from the inventory, given its product ID. This just * Return whether or not details about the given product are available.
* modifies the Inventory object locally and has no effect on the server! This is */
* useful when you have an existing Inventory object which you know to be up to date, public boolean hasDetails(String sku) {
* and you have just consumed an item successfully, which means that erasing its return mSkuMap.containsKey(sku);
* purchase data from the Inventory you already have is quicker than querying for }
* a new Inventory.
*/
public void erasePurchase(String sku) {
if (mPurchaseMap.containsKey(sku)) mPurchaseMap.remove(sku);
}
/** Returns a list of all owned product IDs. */ /**
List<String> getAllOwnedSkus() { * Erase a purchase (locally) from the inventory, given its product ID. This just
return new ArrayList<String>(mPurchaseMap.keySet()); * modifies the Inventory object locally and has no effect on the server! This is
* useful when you have an existing Inventory object which you know to be up to date,
* and you have just consumed an item successfully, which means that erasing its
* purchase data from the Inventory you already have is quicker than querying for
* a new Inventory.
*/
public void erasePurchase(String sku) {
if (mPurchaseMap.containsKey(sku)) {
mPurchaseMap.remove(sku);
} }
}
/** Returns a list of all owned product IDs of a given type */ /**
List<String> getAllOwnedSkus(String itemType) { * Returns a list of all owned product IDs.
List<String> result = new ArrayList<String>(); */
for (Purchase p : mPurchaseMap.values()) { List<String> getAllOwnedSkus() {
if (p.getItemType().equals(itemType)) result.add(p.getSku()); return new ArrayList<String>(mPurchaseMap.keySet());
} }
return result;
}
/** Returns a list of all purchases. */ /**
List<Purchase> getAllPurchases() { * Returns a list of all owned product IDs of a given type
return new ArrayList<Purchase>(mPurchaseMap.values()); */
List<String> getAllOwnedSkus(String itemType) {
List<String> result = new ArrayList<String>();
for (Purchase p : mPurchaseMap.values()) {
if (p.getItemType().equals(itemType)) {
result.add(p.getSku());
}
} }
return result;
}
void addSkuDetails(SkuDetails d) { /**
mSkuMap.put(d.getSku(), d); * Returns a list of all purchases.
} */
List<Purchase> getAllPurchases() {
return new ArrayList<Purchase>(mPurchaseMap.values());
}
void addPurchase(Purchase p) { void addSkuDetails(SkuDetails d) {
mPurchaseMap.put(p.getSku(), p); mSkuMap.put(d.getSku(), d);
} }
void addPurchase(Purchase p) {
mPurchaseMap.put(p.getSku(), p);
}
} }

@ -23,45 +23,80 @@ import org.json.JSONObject;
*/ */
@SuppressWarnings("ALL") @SuppressWarnings("ALL")
public class Purchase { public class Purchase {
String mItemType; // ITEM_TYPE_INAPP or ITEM_TYPE_SUBS
String mOrderId; String mItemType; // ITEM_TYPE_INAPP or ITEM_TYPE_SUBS
String mPackageName; String mOrderId;
String mSku; String mPackageName;
long mPurchaseTime; String mSku;
int mPurchaseState; long mPurchaseTime;
String mDeveloperPayload; int mPurchaseState;
String mToken; String mDeveloperPayload;
String mOriginalJson; String mToken;
String mSignature; String mOriginalJson;
boolean mIsAutoRenewing; String mSignature;
boolean mIsAutoRenewing;
public Purchase(String itemType, String jsonPurchaseInfo, String signature) throws JSONException {
mItemType = itemType; public Purchase(String itemType, String jsonPurchaseInfo, String signature) throws JSONException {
mOriginalJson = jsonPurchaseInfo; mItemType = itemType;
JSONObject o = new JSONObject(mOriginalJson); mOriginalJson = jsonPurchaseInfo;
mOrderId = o.optString("orderId"); JSONObject o = new JSONObject(mOriginalJson);
mPackageName = o.optString("packageName"); mOrderId = o.optString("orderId");
mSku = o.optString("productId"); mPackageName = o.optString("packageName");
mPurchaseTime = o.optLong("purchaseTime"); mSku = o.optString("productId");
mPurchaseState = o.optInt("purchaseState"); mPurchaseTime = o.optLong("purchaseTime");
mDeveloperPayload = o.optString("developerPayload"); mPurchaseState = o.optInt("purchaseState");
mToken = o.optString("token", o.optString("purchaseToken")); mDeveloperPayload = o.optString("developerPayload");
mIsAutoRenewing = o.optBoolean("autoRenewing"); mToken = o.optString("token", o.optString("purchaseToken"));
mSignature = signature; mIsAutoRenewing = o.optBoolean("autoRenewing");
} mSignature = signature;
}
public String getItemType() { return mItemType; }
public String getOrderId() { return mOrderId; } public String getItemType() {
public String getPackageName() { return mPackageName; } return mItemType;
public String getSku() { return mSku; } }
public long getPurchaseTime() { return mPurchaseTime; }
public int getPurchaseState() { return mPurchaseState; } public String getOrderId() {
public String getDeveloperPayload() { return mDeveloperPayload; } return mOrderId;
public String getToken() { return mToken; } }
public String getOriginalJson() { return mOriginalJson; }
public String getSignature() { return mSignature; } public String getPackageName() {
public boolean isAutoRenewing() { return mIsAutoRenewing; } return mPackageName;
}
@Override
public String toString() { return "PurchaseInfo(type:" + mItemType + "):" + mOriginalJson; } public String getSku() {
return mSku;
}
public long getPurchaseTime() {
return mPurchaseTime;
}
public int getPurchaseState() {
return mPurchaseState;
}
public String getDeveloperPayload() {
return mDeveloperPayload;
}
public String getToken() {
return mToken;
}
public String getOriginalJson() {
return mOriginalJson;
}
public String getSignature() {
return mSignature;
}
public boolean isAutoRenewing() {
return mIsAutoRenewing;
}
@Override
public String toString() {
return "PurchaseInfo(type:" + mItemType + "):" + mOriginalJson;
}
} }

@ -19,7 +19,6 @@ import android.annotation.SuppressLint;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Base64; import android.util.Base64;
import android.util.Log; import android.util.Log;
import java.security.InvalidKeyException; import java.security.InvalidKeyException;
import java.security.KeyFactory; import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
@ -41,84 +40,87 @@ import java.security.spec.X509EncodedKeySpec;
@SuppressWarnings("ALL") @SuppressWarnings("ALL")
@SuppressLint("all") @SuppressLint("all")
public class Security { public class Security {
private static final String TAG = "IABUtil/Security";
private static final String KEY_FACTORY_ALGORITHM = "RSA"; private static final String TAG = "IABUtil/Security";
private static final String SIGNATURE_ALGORITHM = "SHA1withRSA";
/** private static final String KEY_FACTORY_ALGORITHM = "RSA";
* Verifies that the data was signed with the given signature, and returns private static final String SIGNATURE_ALGORITHM = "SHA1withRSA";
* the verified purchase. The data is in JSON format and signed
* with a private key. The data also contains the {@link PurchaseState}
* and product ID of the purchase.
* @param base64PublicKey the base64-encoded public key to use for verifying.
* @param signedData the signed JSON string (signed, not encrypted)
* @param signature the signature for the data, signed with the private key
*/
public static boolean verifyPurchase(String base64PublicKey, String signedData, String signature) {
if (TextUtils.isEmpty(signedData) || TextUtils.isEmpty(base64PublicKey) ||
TextUtils.isEmpty(signature)) {
Log.e(TAG, "Purchase verification failed: missing data.");
return false;
}
PublicKey key = Security.generatePublicKey(base64PublicKey); /**
return Security.verify(key, signedData, signature); * Verifies that the data was signed with the given signature, and returns
* the verified purchase. The data is in JSON format and signed
* with a private key. The data also contains the {@link PurchaseState}
* and product ID of the purchase.
*
* @param base64PublicKey the base64-encoded public key to use for verifying.
* @param signedData the signed JSON string (signed, not encrypted)
* @param signature the signature for the data, signed with the private key
*/
public static boolean verifyPurchase(String base64PublicKey, String signedData,
String signature) {
if (TextUtils.isEmpty(signedData) || TextUtils.isEmpty(base64PublicKey) ||
TextUtils.isEmpty(signature)) {
Log.e(TAG, "Purchase verification failed: missing data.");
return false;
} }
/** PublicKey key = Security.generatePublicKey(base64PublicKey);
* Generates a PublicKey instance from a string containing the return Security.verify(key, signedData, signature);
* Base64-encoded public key. }
*
* @param encodedPublicKey Base64-encoded public key /**
* @throws IllegalArgumentException if encodedPublicKey is invalid * Generates a PublicKey instance from a string containing the
*/ * Base64-encoded public key.
public static PublicKey generatePublicKey(String encodedPublicKey) { *
try { * @param encodedPublicKey Base64-encoded public key
byte[] decodedKey = Base64.decode(encodedPublicKey, Base64.DEFAULT); * @throws IllegalArgumentException if encodedPublicKey is invalid
KeyFactory keyFactory = KeyFactory.getInstance(KEY_FACTORY_ALGORITHM); */
return keyFactory.generatePublic(new X509EncodedKeySpec(decodedKey)); public static PublicKey generatePublicKey(String encodedPublicKey) {
} catch (NoSuchAlgorithmException e) { try {
throw new RuntimeException(e); byte[] decodedKey = Base64.decode(encodedPublicKey, Base64.DEFAULT);
} catch (InvalidKeySpecException e) { KeyFactory keyFactory = KeyFactory.getInstance(KEY_FACTORY_ALGORITHM);
Log.e(TAG, "Invalid key specification."); return keyFactory.generatePublic(new X509EncodedKeySpec(decodedKey));
throw new IllegalArgumentException(e); } catch (NoSuchAlgorithmException e) {
} throw new RuntimeException(e);
} catch (InvalidKeySpecException e) {
Log.e(TAG, "Invalid key specification.");
throw new IllegalArgumentException(e);
} }
}
/** /**
* Verifies that the signature from the server matches the computed * Verifies that the signature from the server matches the computed
* signature on the data. Returns true if the data is correctly signed. * signature on the data. Returns true if the data is correctly signed.
* *
* @param publicKey public key associated with the developer account * @param publicKey public key associated with the developer account
* @param signedData signed data from server * @param signedData signed data from server
* @param signature server signature * @param signature server signature
* @return true if the data and signature match * @return true if the data and signature match
*/ */
public static boolean verify(PublicKey publicKey, String signedData, String signature) { public static boolean verify(PublicKey publicKey, String signedData, String signature) {
byte[] signatureBytes; byte[] signatureBytes;
try { try {
signatureBytes = Base64.decode(signature, Base64.DEFAULT); signatureBytes = Base64.decode(signature, Base64.DEFAULT);
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
Log.e(TAG, "Base64 decoding failed."); Log.e(TAG, "Base64 decoding failed.");
return false; return false;
} }
try { try {
Signature sig = Signature.getInstance(SIGNATURE_ALGORITHM); Signature sig = Signature.getInstance(SIGNATURE_ALGORITHM);
sig.initVerify(publicKey); sig.initVerify(publicKey);
sig.update(signedData.getBytes()); sig.update(signedData.getBytes());
if (!sig.verify(signatureBytes)) { if (!sig.verify(signatureBytes)) {
Log.e(TAG, "Signature verification failed."); Log.e(TAG, "Signature verification failed.");
return false;
}
return true;
} catch (NoSuchAlgorithmException e) {
Log.e(TAG, "NoSuchAlgorithmException.");
} catch (InvalidKeyException e) {
Log.e(TAG, "Invalid key specification.");
} catch (SignatureException e) {
Log.e(TAG, "Signature exception.");
}
return false; return false;
}
return true;
} catch (NoSuchAlgorithmException e) {
Log.e(TAG, "NoSuchAlgorithmException.");
} catch (InvalidKeyException e) {
Log.e(TAG, "Invalid key specification.");
} catch (SignatureException e) {
Log.e(TAG, "Signature exception.");
} }
return false;
}
} }

@ -23,43 +23,64 @@ import org.json.JSONObject;
*/ */
@SuppressWarnings("ALL") @SuppressWarnings("ALL")
public class SkuDetails { public class SkuDetails {
private final String mItemType;
private final String mSku;
private final String mType;
private final String mPrice;
private final long mPriceAmountMicros;
private final String mPriceCurrencyCode;
private final String mTitle;
private final String mDescription;
private final String mJson;
public SkuDetails(String jsonSkuDetails) throws JSONException { private final String mItemType;
this(IabHelper.ITEM_TYPE_INAPP, jsonSkuDetails); private final String mSku;
} private final String mType;
private final String mPrice;
private final long mPriceAmountMicros;
private final String mPriceCurrencyCode;
private final String mTitle;
private final String mDescription;
private final String mJson;
public SkuDetails(String itemType, String jsonSkuDetails) throws JSONException { public SkuDetails(String jsonSkuDetails) throws JSONException {
mItemType = itemType; this(IabHelper.ITEM_TYPE_INAPP, jsonSkuDetails);
mJson = jsonSkuDetails; }
JSONObject o = new JSONObject(mJson);
mSku = o.optString("productId");
mType = o.optString("type");
mPrice = o.optString("price");
mPriceAmountMicros = o.optLong("price_amount_micros");
mPriceCurrencyCode = o.optString("price_currency_code");
mTitle = o.optString("title");
mDescription = o.optString("description");
}
public String getSku() { return mSku; } public SkuDetails(String itemType, String jsonSkuDetails) throws JSONException {
public String getType() { return mType; } mItemType = itemType;
public String getPrice() { return mPrice; } mJson = jsonSkuDetails;
public long getPriceAmountMicros() { return mPriceAmountMicros; } JSONObject o = new JSONObject(mJson);
public String getPriceCurrencyCode() { return mPriceCurrencyCode; } mSku = o.optString("productId");
public String getTitle() { return mTitle; } mType = o.optString("type");
public String getDescription() { return mDescription; } mPrice = o.optString("price");
mPriceAmountMicros = o.optLong("price_amount_micros");
mPriceCurrencyCode = o.optString("price_currency_code");
mTitle = o.optString("title");
mDescription = o.optString("description");
}
@Override public String getSku() {
public String toString() { return mSku;
return "SkuDetails:" + mJson; }
}
public String getType() {
return mType;
}
public String getPrice() {
return mPrice;
}
public long getPriceAmountMicros() {
return mPriceAmountMicros;
}
public String getPriceCurrencyCode() {
return mPriceCurrencyCode;
}
public String getTitle() {
return mTitle;
}
public String getDescription() {
return mDescription;
}
@Override
public String toString() {
return "SkuDetails:" + mJson;
}
} }

@ -1,42 +1,41 @@
package org.tasks; package org.tasks;
import com.todoroo.astrid.gtasks.GtasksPreferenceService; import com.todoroo.astrid.gtasks.GtasksPreferenceService;
import javax.inject.Inject;
import org.tasks.billing.InventoryHelper; import org.tasks.billing.InventoryHelper;
import org.tasks.caldav.CaldavAccountManager; import org.tasks.caldav.CaldavAccountManager;
import org.tasks.gtasks.GoogleAccountManager; import org.tasks.gtasks.GoogleAccountManager;
import org.tasks.gtasks.PlayServices; import org.tasks.gtasks.PlayServices;
import org.tasks.preferences.Preferences; import org.tasks.preferences.Preferences;
import javax.inject.Inject;
public class FlavorSetup { public class FlavorSetup {
private final GtasksPreferenceService gtasksPreferenceService; private final GtasksPreferenceService gtasksPreferenceService;
private final InventoryHelper inventoryHelper; private final InventoryHelper inventoryHelper;
private final Preferences preferences; private final Preferences preferences;
private final PlayServices playServices; private final PlayServices playServices;
private final GoogleAccountManager googleAccountManager; private final GoogleAccountManager googleAccountManager;
private final CaldavAccountManager caldavAccountManager; private final CaldavAccountManager caldavAccountManager;
@Inject @Inject
public FlavorSetup(GtasksPreferenceService gtasksPreferenceService, InventoryHelper inventoryHelper, public FlavorSetup(GtasksPreferenceService gtasksPreferenceService,
Preferences preferences, PlayServices playServices, InventoryHelper inventoryHelper,
GoogleAccountManager googleAccountManager, CaldavAccountManager caldavAccountManager) { Preferences preferences, PlayServices playServices,
this.gtasksPreferenceService = gtasksPreferenceService; GoogleAccountManager googleAccountManager, CaldavAccountManager caldavAccountManager) {
this.inventoryHelper = inventoryHelper; this.gtasksPreferenceService = gtasksPreferenceService;
this.preferences = preferences; this.inventoryHelper = inventoryHelper;
this.playServices = playServices; this.preferences = preferences;
this.googleAccountManager = googleAccountManager; this.playServices = playServices;
this.caldavAccountManager = caldavAccountManager; this.googleAccountManager = googleAccountManager;
} this.caldavAccountManager = caldavAccountManager;
}
public void setup() { public void setup() {
inventoryHelper.initialize(); inventoryHelper.initialize();
gtasksPreferenceService.stopOngoing(); // if sync ongoing flag was set, clear it gtasksPreferenceService.stopOngoing(); // if sync ongoing flag was set, clear it
boolean backgroundSyncEnabled = preferences.getBoolean(R.string.p_background_sync, true); boolean backgroundSyncEnabled = preferences.getBoolean(R.string.p_background_sync, true);
googleAccountManager.setBackgroundSynchronization(backgroundSyncEnabled); googleAccountManager.setBackgroundSynchronization(backgroundSyncEnabled);
caldavAccountManager.setBackgroundSynchronization(backgroundSyncEnabled); caldavAccountManager.setBackgroundSynchronization(backgroundSyncEnabled);
playServices.refresh(); playServices.refresh();
} }
} }

@ -1,7 +1,6 @@
package org.tasks.analytics; package org.tasks.analytics;
import android.content.Context; import android.content.Context;
import com.android.vending.billing.IabResult; import com.android.vending.billing.IabResult;
import com.google.android.gms.analytics.ExceptionParser; import com.google.android.gms.analytics.ExceptionParser;
import com.google.android.gms.analytics.ExceptionReporter; import com.google.android.gms.analytics.ExceptionReporter;
@ -9,98 +8,96 @@ import com.google.android.gms.analytics.GoogleAnalytics;
import com.google.android.gms.analytics.HitBuilders; import com.google.android.gms.analytics.HitBuilders;
import com.google.android.gms.analytics.StandardExceptionParser; import com.google.android.gms.analytics.StandardExceptionParser;
import com.google.common.base.Strings; import com.google.common.base.Strings;
import javax.inject.Inject;
import org.tasks.BuildConfig; import org.tasks.BuildConfig;
import org.tasks.R; import org.tasks.R;
import org.tasks.injection.ApplicationScope; import org.tasks.injection.ApplicationScope;
import org.tasks.injection.ForApplication; import org.tasks.injection.ForApplication;
import javax.inject.Inject;
import timber.log.Timber; import timber.log.Timber;
@ApplicationScope @ApplicationScope
public class Tracker { public class Tracker {
private final GoogleAnalytics analytics; private final GoogleAnalytics analytics;
private final com.google.android.gms.analytics.Tracker tracker; private final com.google.android.gms.analytics.Tracker tracker;
private final ExceptionParser exceptionParser; private final ExceptionParser exceptionParser;
private final Context context; private final Context context;
@Inject @Inject
public Tracker(@ForApplication Context context) { public Tracker(@ForApplication Context context) {
this.context = context; this.context = context;
analytics = GoogleAnalytics.getInstance(context); analytics = GoogleAnalytics.getInstance(context);
tracker = analytics.newTracker(R.xml.google_analytics); tracker = analytics.newTracker(R.xml.google_analytics);
tracker.setAppVersion(Integer.toString(BuildConfig.VERSION_CODE)); tracker.setAppVersion(Integer.toString(BuildConfig.VERSION_CODE));
final StandardExceptionParser standardExceptionParser = new StandardExceptionParser(context, null); final StandardExceptionParser standardExceptionParser = new StandardExceptionParser(context,
exceptionParser = (thread, throwable) -> { null);
StringBuilder stack = new StringBuilder() exceptionParser = (thread, throwable) -> {
.append(standardExceptionParser.getDescription(thread, throwable)) StringBuilder stack = new StringBuilder()
.append("\n") .append(standardExceptionParser.getDescription(thread, throwable))
.append(throwable.getClass().getName()) .append("\n")
.append("\n"); .append(throwable.getClass().getName())
for (StackTraceElement element : throwable.getStackTrace()) { .append("\n");
stack.append(element.toString()) for (StackTraceElement element : throwable.getStackTrace()) {
.append("\n"); stack.append(element.toString())
} .append("\n");
return stack.toString(); }
}; return stack.toString();
ExceptionReporter reporter = new ExceptionReporter( };
tracker, ExceptionReporter reporter = new ExceptionReporter(
Thread.getDefaultUncaughtExceptionHandler(), tracker,
context); Thread.getDefaultUncaughtExceptionHandler(),
reporter.setExceptionParser(exceptionParser); context);
Thread.setDefaultUncaughtExceptionHandler(reporter); reporter.setExceptionParser(exceptionParser);
} Thread.setDefaultUncaughtExceptionHandler(reporter);
}
public void setTrackingEnabled(boolean enabled) {
analytics.setAppOptOut(!enabled); public void setTrackingEnabled(boolean enabled) {
} analytics.setAppOptOut(!enabled);
}
public void reportException(Throwable t) {
reportException(Thread.currentThread(), t); public void reportException(Throwable t) {
} reportException(Thread.currentThread(), t);
}
public void reportException(Thread thread, Throwable t) {
Timber.e(t, t.getMessage()); public void reportException(Thread thread, Throwable t) {
tracker.send(new HitBuilders.ExceptionBuilder() Timber.e(t, t.getMessage());
.setDescription(exceptionParser.getDescription(thread.getName(), t)) tracker.send(new HitBuilders.ExceptionBuilder()
.setFatal(false) .setDescription(exceptionParser.getDescription(thread.getName(), t))
.build()); .setFatal(false)
} .build());
}
public void reportEvent(Tracking.Events event) {
reportEvent(event, null); public void reportEvent(Tracking.Events event) {
} reportEvent(event, null);
}
public void reportEvent(Tracking.Events event, String label) {
reportEvent(event, event.action, label); public void reportEvent(Tracking.Events event, String label) {
} reportEvent(event, event.action, label);
}
public void reportEvent(Tracking.Events event, int action, String label) {
reportEvent(event, context.getString(action), label); public void reportEvent(Tracking.Events event, int action, String label) {
} reportEvent(event, context.getString(action), label);
}
public void reportEvent(Tracking.Events event, String action, String label) {
reportEvent(event.category, action, label); public void reportEvent(Tracking.Events event, String action, String label) {
} reportEvent(event.category, action, label);
}
private void reportEvent(int category, String action, String label) {
HitBuilders.EventBuilder eventBuilder = new HitBuilders.EventBuilder() private void reportEvent(int category, String action, String label) {
.setCategory(context.getString(category)) HitBuilders.EventBuilder eventBuilder = new HitBuilders.EventBuilder()
.setAction(action); .setCategory(context.getString(category))
if (!Strings.isNullOrEmpty(label)) { .setAction(action);
eventBuilder.setLabel(label); if (!Strings.isNullOrEmpty(label)) {
} eventBuilder.setLabel(label);
tracker.send(eventBuilder.build());
}
public void reportIabResult(IabResult result, String sku) {
tracker.send(new HitBuilders.EventBuilder()
.setCategory(context.getString(R.string.tracking_category_iab))
.setAction(sku)
.setLabel(result.getMessage())
.build());
} }
tracker.send(eventBuilder.build());
}
public void reportIabResult(IabResult result, String sku) {
tracker.send(new HitBuilders.EventBuilder()
.setCategory(context.getString(R.string.tracking_category_iab))
.setAction(sku)
.setLabel(result.getMessage())
.build());
}
} }

@ -2,100 +2,96 @@ package org.tasks.billing;
import android.content.Context; import android.content.Context;
import android.content.IntentFilter; import android.content.IntentFilter;
import com.android.vending.billing.IabBroadcastReceiver; import com.android.vending.billing.IabBroadcastReceiver;
import com.android.vending.billing.IabHelper; import com.android.vending.billing.IabHelper;
import com.android.vending.billing.Inventory; import com.android.vending.billing.Inventory;
import com.android.vending.billing.Purchase; import com.android.vending.billing.Purchase;
import java.util.concurrent.Executor;
import javax.inject.Inject;
import javax.inject.Named;
import org.tasks.LocalBroadcastManager; import org.tasks.LocalBroadcastManager;
import org.tasks.R; import org.tasks.R;
import org.tasks.injection.ApplicationScope; import org.tasks.injection.ApplicationScope;
import org.tasks.injection.ForApplication; import org.tasks.injection.ForApplication;
import org.tasks.preferences.Preferences; import org.tasks.preferences.Preferences;
import java.util.concurrent.Executor;
import javax.inject.Inject;
import javax.inject.Named;
import timber.log.Timber; import timber.log.Timber;
@ApplicationScope @ApplicationScope
public class InventoryHelper implements IabBroadcastReceiver.IabBroadcastListener { public class InventoryHelper implements IabBroadcastReceiver.IabBroadcastListener {
private final Context context; private final Context context;
private final Preferences preferences; private final Preferences preferences;
private final LocalBroadcastManager localBroadcastManager; private final LocalBroadcastManager localBroadcastManager;
private final Executor executor; private final Executor executor;
private Inventory inventory; private Inventory inventory;
@Inject @Inject
public InventoryHelper(@ForApplication Context context, Preferences preferences, public InventoryHelper(@ForApplication Context context, Preferences preferences,
LocalBroadcastManager localBroadcastManager, @Named("iab-executor") Executor executor) { LocalBroadcastManager localBroadcastManager, @Named("iab-executor") Executor executor) {
this.context = context; this.context = context;
this.preferences = preferences; this.preferences = preferences;
this.localBroadcastManager = localBroadcastManager; this.localBroadcastManager = localBroadcastManager;
this.executor = executor; this.executor = executor;
}
public void initialize() {
context.registerReceiver(new IabBroadcastReceiver(this),
new IntentFilter(IabBroadcastReceiver.ACTION));
refreshInventory();
}
public void refreshInventory() {
final IabHelper helper = new IabHelper(context, context.getString(R.string.gp_key), executor);
helper.startSetup(getSetupListener(helper));
}
private IabHelper.OnIabSetupFinishedListener getSetupListener(final IabHelper helper) {
return result -> {
if (result.isSuccess()) {
helper.queryInventoryAsync(getQueryListener(helper));
} else {
Timber.e("setup failed: %s", result.getMessage());
helper.dispose();
}
};
}
private IabHelper.QueryInventoryFinishedListener getQueryListener(final IabHelper helper) {
return (result, inv) -> {
if (result.isSuccess()) {
inventory = inv;
checkPurchase(R.string.sku_tasker, R.string.p_purchased_tasker);
checkPurchase(R.string.sku_dashclock, R.string.p_purchased_dashclock);
checkPurchase(R.string.sku_themes, R.string.p_purchased_themes);
localBroadcastManager.broadcastRefresh();
} else {
Timber.e("query inventory failed: %s", result.getMessage());
}
helper.dispose();
};
}
@Override
public void receivedBroadcast() {
refreshInventory();
}
private void checkPurchase(int skuRes, final int prefRes) {
final String sku = context.getString(skuRes);
if (inventory.hasPurchase(sku)) {
Timber.d("Found purchase: %s", sku);
preferences.setBoolean(prefRes, true);
} else {
Timber.d("No purchase: %s", sku);
} }
}
public void initialize() { public void erasePurchase(String sku) {
context.registerReceiver(new IabBroadcastReceiver(this), new IntentFilter(IabBroadcastReceiver.ACTION)); inventory.erasePurchase(sku);
refreshInventory(); }
}
public void refreshInventory() { public Purchase getPurchase(String sku) {
final IabHelper helper = new IabHelper(context, context.getString(R.string.gp_key), executor); return inventory.getPurchase(sku);
helper.startSetup(getSetupListener(helper)); }
}
private IabHelper.OnIabSetupFinishedListener getSetupListener(final IabHelper helper) {
return result -> {
if (result.isSuccess()) {
helper.queryInventoryAsync(getQueryListener(helper));
} else {
Timber.e("setup failed: %s", result.getMessage());
helper.dispose();
}
};
}
private IabHelper.QueryInventoryFinishedListener getQueryListener(final IabHelper helper) {
return (result, inv) -> {
if (result.isSuccess()) {
inventory = inv;
checkPurchase(R.string.sku_tasker, R.string.p_purchased_tasker);
checkPurchase(R.string.sku_dashclock, R.string.p_purchased_dashclock);
checkPurchase(R.string.sku_themes, R.string.p_purchased_themes);
localBroadcastManager.broadcastRefresh();
} else {
Timber.e("query inventory failed: %s", result.getMessage());
}
helper.dispose();
};
}
@Override
public void receivedBroadcast() {
refreshInventory();
}
private void checkPurchase(int skuRes, final int prefRes) {
final String sku = context.getString(skuRes);
if (inventory.hasPurchase(sku)) {
Timber.d("Found purchase: %s", sku);
preferences.setBoolean(prefRes, true);
} else {
Timber.d("No purchase: %s", sku);
}
}
public void erasePurchase(String sku) {
inventory.erasePurchase(sku);
}
public Purchase getPurchase(String sku) {
return inventory.getPurchase(sku);
}
} }

@ -4,12 +4,15 @@ import android.app.Activity;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.widget.Toast; import android.widget.Toast;
import com.android.vending.billing.IabHelper; import com.android.vending.billing.IabHelper;
import com.android.vending.billing.IabResult; import com.android.vending.billing.IabResult;
import com.android.vending.billing.Purchase; import com.android.vending.billing.Purchase;
import com.google.common.base.Strings; import com.google.common.base.Strings;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;
import javax.inject.Inject;
import javax.inject.Named;
import org.tasks.BuildConfig; import org.tasks.BuildConfig;
import org.tasks.LocalBroadcastManager; import org.tasks.LocalBroadcastManager;
import org.tasks.R; import org.tasks.R;
@ -17,161 +20,157 @@ import org.tasks.analytics.Tracker;
import org.tasks.injection.ApplicationScope; import org.tasks.injection.ApplicationScope;
import org.tasks.injection.ForApplication; import org.tasks.injection.ForApplication;
import org.tasks.preferences.Preferences; import org.tasks.preferences.Preferences;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;
import javax.inject.Inject;
import javax.inject.Named;
import timber.log.Timber; import timber.log.Timber;
@ApplicationScope @ApplicationScope
public class PurchaseHelper implements IabHelper.OnIabSetupFinishedListener { public class PurchaseHelper implements IabHelper.OnIabSetupFinishedListener {
private final Context context; private final Context context;
private final Preferences preferences; private final Preferences preferences;
private final Tracker tracker; private final Tracker tracker;
private final InventoryHelper inventory; private final InventoryHelper inventory;
private final Executor executor; private final Executor executor;
private LocalBroadcastManager localBroadcastManager; private LocalBroadcastManager localBroadcastManager;
private PurchaseHelperCallback activityResultCallback; private PurchaseHelperCallback activityResultCallback;
private IabHelper iabHelper; private IabHelper iabHelper;
@Inject @Inject
public PurchaseHelper(@ForApplication Context context, Preferences preferences, Tracker tracker, public PurchaseHelper(@ForApplication Context context, Preferences preferences, Tracker tracker,
InventoryHelper inventory, @Named("iab-executor") Executor executor, InventoryHelper inventory, @Named("iab-executor") Executor executor,
LocalBroadcastManager localBroadcastManager) { LocalBroadcastManager localBroadcastManager) {
this.context = context; this.context = context;
this.preferences = preferences; this.preferences = preferences;
this.tracker = tracker; this.tracker = tracker;
this.inventory = inventory; this.inventory = inventory;
this.executor = executor; this.executor = executor;
this.localBroadcastManager = localBroadcastManager; this.localBroadcastManager = localBroadcastManager;
}
@Override
public void onIabSetupFinished(IabResult result) {
if (result.isFailure()) {
Timber.e("in-app billing setup failed: %s", result.getMessage());
} }
}
@Override
public void onIabSetupFinished(IabResult result) { public boolean purchase(final Activity activity, final String sku, final String pref,
if (result.isFailure()) { final int requestCode, final PurchaseHelperCallback callback) {
Timber.e("in-app billing setup failed: %s", result.getMessage()); launchPurchaseFlow(activity, sku, pref, requestCode, callback);
} return true;
} }
public boolean purchase(final Activity activity, final String sku, final String pref, final int requestCode, final PurchaseHelperCallback callback) { public void consumePurchases() {
launchPurchaseFlow(activity, sku, pref, requestCode, callback); if (BuildConfig.DEBUG) {
return true; final List<Purchase> purchases = new ArrayList<>();
} final Purchase tasker = inventory.getPurchase(context.getString(R.string.sku_tasker));
final Purchase dashclock = inventory.getPurchase(context.getString(R.string.sku_dashclock));
public void consumePurchases() { final Purchase themes = inventory.getPurchase(context.getString(R.string.sku_themes));
if (BuildConfig.DEBUG) { if (tasker != null) {
final List<Purchase> purchases = new ArrayList<>(); purchases.add(tasker);
final Purchase tasker = inventory.getPurchase(context.getString(R.string.sku_tasker)); }
final Purchase dashclock = inventory.getPurchase(context.getString(R.string.sku_dashclock)); if (dashclock != null) {
final Purchase themes = inventory.getPurchase(context.getString(R.string.sku_themes)); purchases.add(dashclock);
if (tasker != null) { }
purchases.add(tasker); if (themes != null) {
} purchases.add(themes);
if (dashclock != null) { }
purchases.add(dashclock); final IabHelper iabHelper = new IabHelper(context, context.getString(R.string.gp_key),
} executor);
if (themes != null) { iabHelper.enableDebugLogging(true);
purchases.add(themes); iabHelper.startSetup(result -> {
} if (result.isSuccess()) {
final IabHelper iabHelper = new IabHelper(context, context.getString(R.string.gp_key), executor); iabHelper.consumeAsync(purchases, (purchases1, results) -> {
iabHelper.enableDebugLogging(true); for (int i = 0; i < purchases1.size(); i++) {
iabHelper.startSetup(result -> { Purchase purchase = purchases1.get(i);
if (result.isSuccess()) { IabResult iabResult = results.get(i);
iabHelper.consumeAsync(purchases, (purchases1, results) -> { if (iabResult.isSuccess()) {
for (int i = 0; i < purchases1.size() ; i++) { if (purchase.equals(tasker)) {
Purchase purchase = purchases1.get(i); preferences.setBoolean(R.string.p_purchased_tasker, false);
IabResult iabResult = results.get(i); } else if (purchase.equals(dashclock)) {
if (iabResult.isSuccess()) { preferences.setBoolean(R.string.p_purchased_dashclock, false);
if (purchase.equals(tasker)) { } else if (purchase.equals(themes)) {
preferences.setBoolean(R.string.p_purchased_tasker, false); preferences.setBoolean(R.string.p_purchased_themes, false);
} else if (purchase.equals(dashclock)) {
preferences.setBoolean(R.string.p_purchased_dashclock, false);
} else if (purchase.equals(themes)) {
preferences.setBoolean(R.string.p_purchased_themes, false);
} else {
Timber.e("Unhandled consumption for purchase: %s", purchase);
}
inventory.erasePurchase(purchase.getSku());
Timber.d("Consumed %s", purchase);
} else {
Timber.e("Consume failed: %s, %s", purchase, iabResult);
}
}
iabHelper.dispose();
});
} else { } else {
Timber.e("setup failed: %s", result.getMessage()); Timber.e("Unhandled consumption for purchase: %s", purchase);
iabHelper.dispose();
} }
}); inventory.erasePurchase(purchase.getSku());
Timber.d("Consumed %s", purchase);
} else {
Timber.e("Consume failed: %s, %s", purchase, iabResult);
}
}
iabHelper.dispose();
});
} else {
Timber.e("setup failed: %s", result.getMessage());
iabHelper.dispose();
} }
});
} }
}
private void launchPurchaseFlow(final Activity activity, final String sku, final String pref, final int requestCode, final PurchaseHelperCallback callback) {
if (iabHelper != null) { private void launchPurchaseFlow(final Activity activity, final String sku, final String pref,
Toast.makeText(activity, R.string.billing_service_busy, Toast.LENGTH_LONG).show(); final int requestCode, final PurchaseHelperCallback callback) {
callback.purchaseCompleted(false, sku); if (iabHelper != null) {
return; Toast.makeText(activity, R.string.billing_service_busy, Toast.LENGTH_LONG).show();
} callback.purchaseCompleted(false, sku);
iabHelper = new IabHelper(context, context.getString(R.string.gp_key), executor); return;
iabHelper.enableDebugLogging(BuildConfig.DEBUG);
Timber.d("%s: startSetup", iabHelper);
iabHelper.startSetup(result -> {
if (result.isSuccess()) {
try {
Timber.d("%s: launchPurchaseFlow for %s", iabHelper, sku);
iabHelper.launchPurchaseFlow(activity, sku, requestCode, (result1, info) -> {
Timber.d(result1.toString());
tracker.reportIabResult(result1, sku);
if (result1.isSuccess()) {
if (!Strings.isNullOrEmpty(pref)) {
preferences.setBoolean(pref, true);
localBroadcastManager.broadcastRefresh();
}
inventory.refreshInventory();
} else if (result1.getResponse() != IabHelper.BILLING_RESPONSE_RESULT_USER_CANCELED &&
result1.getResponse() != IabHelper.IABHELPER_USER_CANCELLED) {
Toast.makeText(activity, result1.getMessage(), Toast.LENGTH_LONG).show();
}
if (activityResultCallback != null) {
activityResultCallback.purchaseCompleted(result1.isSuccess(), sku);
}
disposeIabHelper();
});
} catch (IllegalStateException e) {
tracker.reportException(e);
Toast.makeText(activity, R.string.billing_service_busy, Toast.LENGTH_LONG).show();
callback.purchaseCompleted(false, sku);
disposeIabHelper();
}
} else {
Timber.e(result.toString());
Toast.makeText(activity, result.getMessage(), Toast.LENGTH_LONG).show();
callback.purchaseCompleted(false, sku);
disposeIabHelper();
}
});
} }
iabHelper = new IabHelper(context, context.getString(R.string.gp_key), executor);
public void disposeIabHelper() { iabHelper.enableDebugLogging(BuildConfig.DEBUG);
if (iabHelper != null) { Timber.d("%s: startSetup", iabHelper);
Timber.d("%s: dispose", iabHelper); iabHelper.startSetup(result -> {
iabHelper.dispose(); if (result.isSuccess()) {
iabHelper = null; try {
Timber.d("%s: launchPurchaseFlow for %s", iabHelper, sku);
iabHelper.launchPurchaseFlow(activity, sku, requestCode, (result1, info) -> {
Timber.d(result1.toString());
tracker.reportIabResult(result1, sku);
if (result1.isSuccess()) {
if (!Strings.isNullOrEmpty(pref)) {
preferences.setBoolean(pref, true);
localBroadcastManager.broadcastRefresh();
}
inventory.refreshInventory();
} else if (result1.getResponse() != IabHelper.BILLING_RESPONSE_RESULT_USER_CANCELED &&
result1.getResponse() != IabHelper.IABHELPER_USER_CANCELLED) {
Toast.makeText(activity, result1.getMessage(), Toast.LENGTH_LONG).show();
}
if (activityResultCallback != null) {
activityResultCallback.purchaseCompleted(result1.isSuccess(), sku);
}
disposeIabHelper();
});
} catch (IllegalStateException e) {
tracker.reportException(e);
Toast.makeText(activity, R.string.billing_service_busy, Toast.LENGTH_LONG).show();
callback.purchaseCompleted(false, sku);
disposeIabHelper();
} }
} else {
Timber.e(result.toString());
Toast.makeText(activity, result.getMessage(), Toast.LENGTH_LONG).show();
callback.purchaseCompleted(false, sku);
disposeIabHelper();
}
});
}
public void disposeIabHelper() {
if (iabHelper != null) {
Timber.d("%s: dispose", iabHelper);
iabHelper.dispose();
iabHelper = null;
} }
}
public void handleActivityResult(PurchaseHelperCallback callback, int requestCode, int resultCode, Intent data) { public void handleActivityResult(PurchaseHelperCallback callback, int requestCode, int resultCode,
this.activityResultCallback = callback; Intent data) {
this.activityResultCallback = callback;
if (iabHelper != null) { if (iabHelper != null) {
iabHelper.handleActivityResult(requestCode, resultCode, data); iabHelper.handleActivityResult(requestCode, resultCode, data);
}
} }
}
} }

@ -4,7 +4,6 @@ import android.accounts.Account;
import android.app.Activity; import android.app.Activity;
import android.content.Context; import android.content.Context;
import android.widget.Toast; import android.widget.Toast;
import com.google.android.gms.auth.GoogleAuthException; import com.google.android.gms.auth.GoogleAuthException;
import com.google.android.gms.auth.GoogleAuthUtil; import com.google.android.gms.auth.GoogleAuthUtil;
import com.google.android.gms.auth.UserRecoverableAuthException; import com.google.android.gms.auth.UserRecoverableAuthException;
@ -13,103 +12,103 @@ import com.google.android.gms.common.GoogleApiAvailability;
import com.google.api.client.googleapis.extensions.android.gms.auth.GoogleAccountCredential; import com.google.api.client.googleapis.extensions.android.gms.auth.GoogleAccountCredential;
import com.google.api.services.tasks.TasksScopes; import com.google.api.services.tasks.TasksScopes;
import com.todoroo.astrid.gtasks.auth.GtasksLoginActivity; import com.todoroo.astrid.gtasks.auth.GtasksLoginActivity;
import java.io.IOException;
import javax.inject.Inject;
import org.tasks.R; import org.tasks.R;
import org.tasks.injection.ForApplication; import org.tasks.injection.ForApplication;
import org.tasks.preferences.Preferences; import org.tasks.preferences.Preferences;
import java.io.IOException;
import javax.inject.Inject;
import timber.log.Timber; import timber.log.Timber;
public class PlayServices { public class PlayServices {
private static final int REQUEST_RESOLUTION = 10000; private static final int REQUEST_RESOLUTION = 10000;
private final Context context; private final Context context;
private final Preferences preferences; private final Preferences preferences;
private final GoogleAccountManager accountManager; private final GoogleAccountManager accountManager;
@Inject @Inject
public PlayServices(@ForApplication Context context, Preferences preferences, public PlayServices(@ForApplication Context context, Preferences preferences,
GoogleAccountManager googleAccountManager) { GoogleAccountManager googleAccountManager) {
this.context = context; this.context = context;
this.preferences = preferences; this.preferences = preferences;
this.accountManager = googleAccountManager; this.accountManager = googleAccountManager;
}
public boolean refreshAndCheck() {
refresh();
return isPlayServicesAvailable();
}
public boolean isPlayServicesAvailable() {
return getResult() == ConnectionResult.SUCCESS;
}
public void refresh() {
GoogleApiAvailability instance = GoogleApiAvailability.getInstance();
int googlePlayServicesAvailable = instance.isGooglePlayServicesAvailable(context);
preferences.setInt(R.string.play_services_available, googlePlayServicesAvailable);
if (googlePlayServicesAvailable == ConnectionResult.SUCCESS) {
preferences.setBoolean(R.string.warned_play_services, false);
} }
Timber.d(getStatus());
public boolean refreshAndCheck() { }
refresh();
return isPlayServicesAvailable(); public void resolve(Activity activity) {
GoogleApiAvailability googleApiAvailability = GoogleApiAvailability.getInstance();
int error = preferences.getInt(R.string.play_services_available, -1);
if (googleApiAvailability.isUserResolvableError(error)) {
googleApiAvailability.getErrorDialog(activity, error, REQUEST_RESOLUTION).show();
} else {
Toast.makeText(activity, R.string.common_google_play_services_notification_ticker,
Toast.LENGTH_LONG).show();
} }
}
public boolean isPlayServicesAvailable() {
return getResult() == ConnectionResult.SUCCESS; public String getStatus() {
return GoogleApiAvailability.getInstance().getErrorString(getResult());
}
private int getResult() {
return preferences.getInt(R.string.play_services_available, -1);
}
public boolean clearToken(GoogleAccountCredential credential) {
try {
String token = credential.getToken();
Timber.d("Invalidating %s", token);
GoogleAuthUtil.clearToken(context, token);
GoogleAuthUtil
.getToken(context, credential.getSelectedAccount(), "oauth2:" + TasksScopes.TASKS, null);
return true;
} catch (GoogleAuthException e) {
Timber.e(e, e.getMessage());
return false;
} catch (IOException e) {
Timber.e(e, e.getMessage());
return true;
} }
}
public void refresh() {
GoogleApiAvailability instance = GoogleApiAvailability.getInstance(); public void getAuthToken(final Activity activity, final String accountName,
int googlePlayServicesAvailable = instance.isGooglePlayServicesAvailable(context); final GtasksLoginActivity.AuthResultHandler handler) {
preferences.setInt(R.string.play_services_available, googlePlayServicesAvailable); final Account account = accountManager.getAccount(accountName);
if (googlePlayServicesAvailable == ConnectionResult.SUCCESS) { if (account == null) {
preferences.setBoolean(R.string.warned_play_services, false); handler.authenticationFailed(
} activity.getString(R.string.gtasks_error_accountNotFound, accountName));
Timber.d(getStatus()); } else {
} new Thread(() -> {
public void resolve(Activity activity) {
GoogleApiAvailability googleApiAvailability = GoogleApiAvailability.getInstance();
int error = preferences.getInt(R.string.play_services_available, -1);
if (googleApiAvailability.isUserResolvableError(error)) {
googleApiAvailability.getErrorDialog(activity, error, REQUEST_RESOLUTION).show();
} else {
Toast.makeText(activity, R.string.common_google_play_services_notification_ticker, Toast.LENGTH_LONG).show();
}
}
public String getStatus() {
return GoogleApiAvailability.getInstance().getErrorString(getResult());
}
private int getResult() {
return preferences.getInt(R.string.play_services_available, -1);
}
public boolean clearToken(GoogleAccountCredential credential){
try { try {
String token = credential.getToken(); GoogleAuthUtil.getToken(activity, account, "oauth2:" + TasksScopes.TASKS, null);
Timber.d("Invalidating %s", token); handler.authenticationSuccessful(accountName);
GoogleAuthUtil.clearToken(context, token); } catch (UserRecoverableAuthException e) {
GoogleAuthUtil.getToken(context, credential.getSelectedAccount(), "oauth2:" + TasksScopes.TASKS, null); Timber.e(e, e.getMessage());
return true; activity.startActivityForResult(e.getIntent(), GtasksLoginActivity.RC_REQUEST_OAUTH);
} catch (GoogleAuthException e) { } catch (GoogleAuthException | IOException e) {
Timber.e(e, e.getMessage()); Timber.e(e, e.getMessage());
return false; handler.authenticationFailed(activity.getString(R.string.gtasks_GLA_errorIOAuth));
} catch (IOException e) {
Timber.e(e, e.getMessage());
return true;
}
}
public void getAuthToken(final Activity activity, final String accountName, final GtasksLoginActivity.AuthResultHandler handler) {
final Account account = accountManager.getAccount(accountName);
if (account == null) {
handler.authenticationFailed(activity.getString(R.string.gtasks_error_accountNotFound, accountName));
} else {
new Thread(() -> {
try {
GoogleAuthUtil.getToken(activity, account, "oauth2:" + TasksScopes.TASKS, null);
handler.authenticationSuccessful(accountName);
} catch(UserRecoverableAuthException e) {
Timber.e(e, e.getMessage());
activity.startActivityForResult(e.getIntent(), GtasksLoginActivity.RC_REQUEST_OAUTH);
} catch(GoogleAuthException | IOException e) {
Timber.e(e, e.getMessage());
handler.authenticationFailed(activity.getString(R.string.gtasks_GLA_errorIOAuth));
}
}).start();
} }
}).start();
} }
}
} }

@ -1,111 +1,110 @@
package org.tasks.location; package org.tasks.location;
import static com.google.android.gms.location.Geofence.NEVER_EXPIRE;
import static com.google.common.collect.Iterables.transform;
import static com.google.common.collect.Lists.newArrayList;
import static java.util.Collections.singletonList;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.app.PendingIntent; import android.app.PendingIntent;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import com.google.android.gms.common.api.PendingResult; import com.google.android.gms.common.api.PendingResult;
import com.google.android.gms.common.api.Status; import com.google.android.gms.common.api.Status;
import com.google.android.gms.location.GeofencingRequest; import com.google.android.gms.location.GeofencingRequest;
import com.google.android.gms.location.LocationServices; import com.google.android.gms.location.LocationServices;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import java.util.List;
import java.util.concurrent.TimeUnit;
import javax.inject.Inject;
import org.tasks.R; import org.tasks.R;
import org.tasks.data.Location; import org.tasks.data.Location;
import org.tasks.injection.ForApplication; import org.tasks.injection.ForApplication;
import org.tasks.preferences.PermissionChecker; import org.tasks.preferences.PermissionChecker;
import org.tasks.preferences.Preferences; import org.tasks.preferences.Preferences;
import java.util.List;
import java.util.concurrent.TimeUnit;
import javax.inject.Inject;
import timber.log.Timber; import timber.log.Timber;
import static com.google.android.gms.location.Geofence.NEVER_EXPIRE;
import static com.google.common.collect.Iterables.transform;
import static com.google.common.collect.Lists.newArrayList;
import static java.util.Collections.singletonList;
public class GeofenceApi { public class GeofenceApi {
private final Context context; private final Context context;
private final Preferences preferences; private final Preferences preferences;
private final PermissionChecker permissionChecker; private final PermissionChecker permissionChecker;
@Inject @Inject
public GeofenceApi(@ForApplication Context context, Preferences preferences, PermissionChecker permissionChecker) { public GeofenceApi(@ForApplication Context context, Preferences preferences,
this.context = context; PermissionChecker permissionChecker) {
this.preferences = preferences; this.context = context;
this.permissionChecker = permissionChecker; this.preferences = preferences;
} this.permissionChecker = permissionChecker;
}
public void register(final List<Location> locations) {
if (locations.isEmpty() || !permissionChecker.canAccessLocation()) { public void register(final List<Location> locations) {
return; if (locations.isEmpty() || !permissionChecker.canAccessLocation()) {
} return;
newClient(client -> {
@SuppressWarnings("ResourceType")
@SuppressLint("MissingPermission")
PendingResult<Status> result = LocationServices.GeofencingApi.addGeofences(
client,
getRequests(locations),
PendingIntent.getBroadcast(context, 0, new Intent(context, GeofenceTransitionsIntentService.Broadcast.class), PendingIntent.FLAG_UPDATE_CURRENT));
result.setResultCallback(status -> {
if (status.isSuccess()) {
Timber.i("Registered %s", locations);
} else {
Timber.e("Failed to register %s", locations);
}
client.disconnect();
});
});
}
public void cancel(final Location location) {
cancel(singletonList(location));
} }
public void cancel(final List<Location> locations) { newClient(client -> {
if (locations.isEmpty() || !permissionChecker.canAccessLocation()) { @SuppressWarnings("ResourceType")
return; @SuppressLint("MissingPermission")
PendingResult<Status> result = LocationServices.GeofencingApi.addGeofences(
client,
getRequests(locations),
PendingIntent.getBroadcast(context, 0,
new Intent(context, GeofenceTransitionsIntentService.Broadcast.class),
PendingIntent.FLAG_UPDATE_CURRENT));
result.setResultCallback(status -> {
if (status.isSuccess()) {
Timber.i("Registered %s", locations);
} else {
Timber.e("Failed to register %s", locations);
} }
List<String> ids = Lists.transform(locations, geofence -> Long.toString(geofence.getId())); client.disconnect();
});
newClient(client -> LocationServices.GeofencingApi.removeGeofences(client, ids) });
.setResultCallback(status -> { }
if (status.isSuccess()) {
Timber.i("Removed %s", locations);
} else {
Timber.e("Failed to remove %s", locations);
}
client.disconnect(); public void cancel(final Location location) {
})); cancel(singletonList(location));
} }
private void newClient(final GoogleApi.GoogleApiClientConnectionHandler handler) {
new GoogleApi(context).connect(handler);
}
private List<com.google.android.gms.location.Geofence> getRequests(List<Location> locations) { public void cancel(final List<Location> locations) {
return newArrayList(transform(locations, this::toGoogleGeofence)); if (locations.isEmpty() || !permissionChecker.canAccessLocation()) {
return;
} }
private com.google.android.gms.location.Geofence toGoogleGeofence(Location location) { List<String> ids = Lists.transform(locations, geofence -> Long.toString(geofence.getId()));
int radius = preferences.getIntegerFromString(R.string.p_geofence_radius, 250);
int responsiveness = (int) TimeUnit.SECONDS.toMillis(preferences.getIntegerFromString(R.string.p_geofence_responsiveness, 60)); newClient(client -> LocationServices.GeofencingApi.removeGeofences(client, ids)
return new com.google.android.gms.location.Geofence.Builder() .setResultCallback(status -> {
.setCircularRegion(location.getLatitude(), location.getLongitude(), radius) if (status.isSuccess()) {
.setNotificationResponsiveness(responsiveness) Timber.i("Removed %s", locations);
.setRequestId(Long.toString(location.getId())) } else {
.setTransitionTypes(GeofencingRequest.INITIAL_TRIGGER_ENTER) Timber.e("Failed to remove %s", locations);
.setExpirationDuration(NEVER_EXPIRE) }
.build();
} client.disconnect();
}));
}
private void newClient(final GoogleApi.GoogleApiClientConnectionHandler handler) {
new GoogleApi(context).connect(handler);
}
private List<com.google.android.gms.location.Geofence> getRequests(List<Location> locations) {
return newArrayList(transform(locations, this::toGoogleGeofence));
}
private com.google.android.gms.location.Geofence toGoogleGeofence(Location location) {
int radius = preferences.getIntegerFromString(R.string.p_geofence_radius, 250);
int responsiveness = (int) TimeUnit.SECONDS
.toMillis(preferences.getIntegerFromString(R.string.p_geofence_responsiveness, 60));
return new com.google.android.gms.location.Geofence.Builder()
.setCircularRegion(location.getLatitude(), location.getLongitude(), radius)
.setNotificationResponsiveness(responsiveness)
.setRequestId(Long.toString(location.getId()))
.setTransitionTypes(GeofencingRequest.INITIAL_TRIGGER_ENTER)
.setExpirationDuration(NEVER_EXPIRE)
.build();
}
} }

@ -4,70 +4,68 @@ import android.content.BroadcastReceiver;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.support.v4.app.JobIntentService; import android.support.v4.app.JobIntentService;
import com.google.android.gms.location.GeofencingEvent; import com.google.android.gms.location.GeofencingEvent;
import com.todoroo.astrid.reminders.ReminderService; import com.todoroo.astrid.reminders.ReminderService;
import java.util.List;
import javax.inject.Inject;
import org.tasks.Notifier; import org.tasks.Notifier;
import org.tasks.data.Location; import org.tasks.data.Location;
import org.tasks.data.LocationDao; import org.tasks.data.LocationDao;
import org.tasks.injection.InjectingJobIntentService; import org.tasks.injection.InjectingJobIntentService;
import org.tasks.injection.IntentServiceComponent; import org.tasks.injection.IntentServiceComponent;
import org.tasks.jobs.JobManager; import org.tasks.jobs.JobManager;
import java.util.List;
import javax.inject.Inject;
import timber.log.Timber; import timber.log.Timber;
public class GeofenceTransitionsIntentService extends InjectingJobIntentService { public class GeofenceTransitionsIntentService extends InjectingJobIntentService {
public static class Broadcast extends BroadcastReceiver { @Inject LocationDao locationDao;
@Override @Inject Notifier notifier;
public void onReceive(Context context, Intent intent) {
JobIntentService.enqueueWork(context, GeofenceTransitionsIntentService.class, JobManager.JOB_ID_GEOFENCE_TRANSITION, intent);
}
}
@Inject LocationDao locationDao;
@Inject Notifier notifier;
@Override @Override
protected void onHandleWork(Intent intent) { protected void onHandleWork(Intent intent) {
super.onHandleWork(intent); super.onHandleWork(intent);
GeofencingEvent geofencingEvent = GeofencingEvent.fromIntent(intent); GeofencingEvent geofencingEvent = GeofencingEvent.fromIntent(intent);
if (geofencingEvent.hasError()) { if (geofencingEvent.hasError()) {
Timber.e("geofence error code %s", geofencingEvent.getErrorCode()); Timber.e("geofence error code %s", geofencingEvent.getErrorCode());
return; return;
} }
int transitionType = geofencingEvent.getGeofenceTransition(); int transitionType = geofencingEvent.getGeofenceTransition();
List<com.google.android.gms.location.Geofence> triggeringGeofences = geofencingEvent.getTriggeringGeofences(); List<com.google.android.gms.location.Geofence> triggeringGeofences = geofencingEvent
Timber.i("Received geofence transition: %s, %s", transitionType, triggeringGeofences); .getTriggeringGeofences();
if (transitionType == com.google.android.gms.location.Geofence.GEOFENCE_TRANSITION_ENTER) { Timber.i("Received geofence transition: %s, %s", transitionType, triggeringGeofences);
for (com.google.android.gms.location.Geofence triggerGeofence : triggeringGeofences) { if (transitionType == com.google.android.gms.location.Geofence.GEOFENCE_TRANSITION_ENTER) {
triggerNotification(triggerGeofence); for (com.google.android.gms.location.Geofence triggerGeofence : triggeringGeofences) {
} triggerNotification(triggerGeofence);
} else { }
Timber.w("invalid geofence transition type: %s", transitionType); } else {
} Timber.w("invalid geofence transition type: %s", transitionType);
} }
}
@Override @Override
protected void inject(IntentServiceComponent component) { protected void inject(IntentServiceComponent component) {
component.inject(this); component.inject(this);
}
private void triggerNotification(com.google.android.gms.location.Geofence triggeringGeofence) {
String requestId = triggeringGeofence.getRequestId();
try {
Location location = locationDao.getGeofence(Long.parseLong(requestId));
notifier.triggerTaskNotification(location.getTask(), ReminderService.TYPE_ALARM);
} catch (Exception e) {
Timber.e(e, "Error triggering geofence %s: %s", requestId, e.getMessage());
} }
}
private void triggerNotification(com.google.android.gms.location.Geofence triggeringGeofence) { public static class Broadcast extends BroadcastReceiver {
String requestId = triggeringGeofence.getRequestId();
try { @Override
Location location = locationDao.getGeofence(Long.parseLong(requestId)); public void onReceive(Context context, Intent intent) {
notifier.triggerTaskNotification(location.getTask(), ReminderService.TYPE_ALARM); JobIntentService.enqueueWork(context, GeofenceTransitionsIntentService.class,
} catch(Exception e) { JobManager.JOB_ID_GEOFENCE_TRANSITION, intent);
Timber.e(e, "Error triggering geofence %s: %s", requestId, e.getMessage());
}
} }
}
} }

@ -2,55 +2,54 @@ package org.tasks.location;
import android.content.Context; import android.content.Context;
import android.os.Bundle; import android.os.Bundle;
import com.google.android.gms.common.api.GoogleApiClient; import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.location.LocationServices; import com.google.android.gms.location.LocationServices;
import com.google.android.gms.location.places.Places; import com.google.android.gms.location.places.Places;
import org.tasks.injection.ForApplication;
import javax.inject.Inject; import javax.inject.Inject;
import org.tasks.injection.ForApplication;
import timber.log.Timber; import timber.log.Timber;
public class GoogleApi implements GoogleApiClient.ConnectionCallbacks { public class GoogleApi implements GoogleApiClient.ConnectionCallbacks {
private final GoogleApiClient.Builder builder; private final GoogleApiClient.Builder builder;
private GoogleApiClient googleApiClient; private GoogleApiClient googleApiClient;
private GoogleApiClientConnectionHandler googleApiClientConnectionHandler; private GoogleApiClientConnectionHandler googleApiClientConnectionHandler;
public interface GoogleApiClientConnectionHandler { @Inject
void onConnect(GoogleApiClient client); public GoogleApi(@ForApplication Context context) {
} builder = new GoogleApiClient.Builder(context)
.addApi(LocationServices.API)
@Inject .addApi(Places.GEO_DATA_API)
public GoogleApi(@ForApplication Context context) { .addConnectionCallbacks(this);
builder = new GoogleApiClient.Builder(context) }
.addApi(LocationServices.API)
.addApi(Places.GEO_DATA_API) public void connect(final GoogleApiClientConnectionHandler googleApiClientConnectionHandler) {
.addConnectionCallbacks(this); connect(googleApiClientConnectionHandler,
} connectionResult -> Timber.e("onConnectionFailed(%s)", connectionResult));
}
public void connect(final GoogleApiClientConnectionHandler googleApiClientConnectionHandler) {
connect(googleApiClientConnectionHandler, connectionResult -> Timber.e("onConnectionFailed(%s)", connectionResult)); private void connect(final GoogleApiClientConnectionHandler googleApiClientConnectionHandler,
} GoogleApiClient.OnConnectionFailedListener onConnectionFailedListener) {
this.googleApiClientConnectionHandler = googleApiClientConnectionHandler;
private void connect(final GoogleApiClientConnectionHandler googleApiClientConnectionHandler, GoogleApiClient.OnConnectionFailedListener onConnectionFailedListener) { googleApiClient = builder
this.googleApiClientConnectionHandler = googleApiClientConnectionHandler; .addOnConnectionFailedListener(onConnectionFailedListener)
googleApiClient = builder .build();
.addOnConnectionFailedListener(onConnectionFailedListener) googleApiClient.connect();
.build(); }
googleApiClient.connect();
} @Override
public void onConnected(Bundle bundle) {
@Override Timber.i("onConnected(Bundle)");
public void onConnected(Bundle bundle) { googleApiClientConnectionHandler.onConnect(googleApiClient);
Timber.i("onConnected(Bundle)"); }
googleApiClientConnectionHandler.onConnect(googleApiClient);
} @Override
public void onConnectionSuspended(int i) {
@Override Timber.i("onConnectionSuspended(%s)", i);
public void onConnectionSuspended(int i) { }
Timber.i("onConnectionSuspended(%s)", i);
} public interface GoogleApiClientConnectionHandler {
void onConnect(GoogleApiClient client);
}
} }

@ -4,43 +4,42 @@ import android.app.Activity;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.widget.Toast; import android.widget.Toast;
import com.google.android.gms.common.GooglePlayServicesNotAvailableException; import com.google.android.gms.common.GooglePlayServicesNotAvailableException;
import com.google.android.gms.common.GooglePlayServicesRepairableException; import com.google.android.gms.common.GooglePlayServicesRepairableException;
import com.google.android.gms.location.places.Place; import com.google.android.gms.location.places.Place;
import com.google.android.gms.maps.model.LatLng; import com.google.android.gms.maps.model.LatLng;
import org.tasks.R; import org.tasks.R;
import org.tasks.data.Location; import org.tasks.data.Location;
import org.tasks.preferences.Preferences; import org.tasks.preferences.Preferences;
import timber.log.Timber; import timber.log.Timber;
public class PlacePicker { public class PlacePicker {
public static Intent getIntent(Activity activity) {
com.google.android.gms.location.places.ui.PlacePicker.IntentBuilder builder =
new com.google.android.gms.location.places.ui.PlacePicker.IntentBuilder();
try {
return builder.build(activity);
} catch (GooglePlayServicesRepairableException e) {
Timber.e(e, e.getMessage());
activity.startActivity(e.getIntent());
} catch (GooglePlayServicesNotAvailableException e) {
Timber.e(e, e.getMessage());
Toast.makeText(activity, R.string.common_google_play_services_notification_ticker, Toast.LENGTH_LONG).show();
}
return null;
}
public static Location getPlace(Context context, Intent data, Preferences preferences) { public static Intent getIntent(Activity activity) {
Place place = com.google.android.gms.location.places.ui.PlacePicker.getPlace(context, data); com.google.android.gms.location.places.ui.PlacePicker.IntentBuilder builder =
LatLng latLng = place.getLatLng(); new com.google.android.gms.location.places.ui.PlacePicker.IntentBuilder();
Location location = new Location(); try {
location.setName(place.getName().toString()); return builder.build(activity);
location.setLatitude(latLng.latitude); } catch (GooglePlayServicesRepairableException e) {
location.setLongitude(latLng.longitude); Timber.e(e, e.getMessage());
location.setRadius(preferences.getIntegerFromString(R.string.p_geofence_radius, 250)); activity.startActivity(e.getIntent());
Timber.i("Picked %s", location); } catch (GooglePlayServicesNotAvailableException e) {
return location; Timber.e(e, e.getMessage());
Toast.makeText(activity, R.string.common_google_play_services_notification_ticker,
Toast.LENGTH_LONG).show();
} }
return null;
}
public static Location getPlace(Context context, Intent data, Preferences preferences) {
Place place = com.google.android.gms.location.places.ui.PlacePicker.getPlace(context, data);
LatLng latLng = place.getLatLng();
Location location = new Location();
location.setName(place.getName().toString());
location.setLatitude(latLng.latitude);
location.setLongitude(latLng.longitude);
location.setRadius(preferences.getIntegerFromString(R.string.p_geofence_radius, 250));
Timber.i("Picked %s", location);
return location;
}
} }

@ -3,6 +3,7 @@
* *
* See the file "LICENSE" for the full license governing this code. * See the file "LICENSE" for the full license governing this code.
*/ */
package com.mdimension.jchronic; package com.mdimension.jchronic;
import com.mdimension.jchronic.handlers.Handler; import com.mdimension.jchronic.handlers.Handler;
@ -15,7 +16,6 @@ import com.mdimension.jchronic.tags.Separator;
import com.mdimension.jchronic.tags.TimeZone; import com.mdimension.jchronic.tags.TimeZone;
import com.mdimension.jchronic.utils.Span; import com.mdimension.jchronic.utils.Span;
import com.mdimension.jchronic.utils.Token; import com.mdimension.jchronic.utils.Token;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
@ -38,35 +38,35 @@ public class AstridChronic {
* Options are: * Options are:
* *
* [<tt>:context</tt>] * [<tt>:context</tt>]
* <tt>:past</tt> or <tt>:future</tt> (defaults to <tt>:future</tt>) * <tt>:past</tt> or <tt>:future</tt> (defaults to <tt>:future</tt>)
* *
* If your string represents a birthday, you can set <tt>:context</tt> to <tt>:past</tt> * If your string represents a birthday, you can set <tt>:context</tt> to <tt>:past</tt>
* and if an ambiguous string is given, it will assume it is in the * and if an ambiguous string is given, it will assume it is in the
* past. Specify <tt>:future</tt> or omit to set a future context. * past. Specify <tt>:future</tt> or omit to set a future context.
* *
* [<tt>:now</tt>] * [<tt>:now</tt>]
* Time (defaults to Time.now) * Time (defaults to Time.now)
* *
* By setting <tt>:now</tt> to a Time, all computations will be based off * By setting <tt>:now</tt> to a Time, all computations will be based off
* of that time instead of Time.now * of that time instead of Time.now
* *
* [<tt>:guess</tt>] * [<tt>:guess</tt>]
* +true+ or +false+ (defaults to +true+) * +true+ or +false+ (defaults to +true+)
* *
* By default, the parser will guess a single point in time for the * By default, the parser will guess a single point in time for the
* given date or time. If you'd rather have the entire time span returned, * given date or time. If you'd rather have the entire time span returned,
* set <tt>:guess</tt> to +false+ and a Chronic::Span will be returned. * set <tt>:guess</tt> to +false+ and a Chronic::Span will be returned.
* *
* [<tt>:ambiguous_time_range</tt>] * [<tt>:ambiguous_time_range</tt>]
* Integer or <tt>:none</tt> (defaults to <tt>6</tt> (6am-6pm)) * Integer or <tt>:none</tt> (defaults to <tt>6</tt> (6am-6pm))
* *
* If an Integer is given, ambiguous times (like 5:00) will be * If an Integer is given, ambiguous times (like 5:00) will be
* assumed to be within the range of that time in the AM to that time * assumed to be within the range of that time in the AM to that time
* in the PM. For example, if you set it to <tt>7</tt>, then the parser will * in the PM. For example, if you set it to <tt>7</tt>, then the parser will
* look for the time between 7am and 7pm. In the case of 5:00, it would * look for the time between 7am and 7pm. In the case of 5:00, it would
* assume that means 5:00pm. If <tt>:none</tt> is given, no assumption * assume that means 5:00pm. If <tt>:none</tt> is given, no assumption
* will be made, and the first matching instance of that time will * will be made, and the first matching instance of that time will
* be used. * be used.
*/ */
private static Span parse(String text, Options options) { private static Span parse(String text, Options options) {
// store now for later =) // store now for later =)
@ -79,9 +79,9 @@ public class AstridChronic {
List<Token> tokens = AstridChronic.baseTokenize(normalizedText); List<Token> tokens = AstridChronic.baseTokenize(normalizedText);
try { try {
tokens = Repeater.scan(tokens, options); tokens = Repeater.scan(tokens, options);
} catch (Throwable e) { } catch (Throwable e) {
throw new RuntimeException("Failed to scan tokens.", e); throw new RuntimeException("Failed to scan tokens.", e);
} }
tokens = Grabber.scan(tokens, options); tokens = Grabber.scan(tokens, options);
@ -135,7 +135,8 @@ public class AstridChronic {
normalizedText = normalizedText.replaceAll("\\bthis past\\b", "last"); normalizedText = normalizedText.replaceAll("\\bthis past\\b", "last");
normalizedText = normalizedText.replaceAll("\\bthis last\\b", "last"); normalizedText = normalizedText.replaceAll("\\bthis last\\b", "last");
normalizedText = normalizedText.replaceAll("\\b(?:in|during) the (morning)\\b", "$1"); normalizedText = normalizedText.replaceAll("\\b(?:in|during) the (morning)\\b", "$1");
normalizedText = normalizedText.replaceAll("\\b(?:in the|during the|at) (afternoon|evening|night)\\b", "$1"); normalizedText = normalizedText
.replaceAll("\\b(?:in the|during the|at) (afternoon|evening|night)\\b", "$1");
normalizedText = normalizedText.replaceAll("\\btonight\\b", "this night"); normalizedText = normalizedText.replaceAll("\\btonight\\b", "this night");
normalizedText = normalizedText.replaceAll("(?=\\w)([ap]m|oclock)\\b", " $1"); normalizedText = normalizedText.replaceAll("(?=\\w)([ap]m|oclock)\\b", " $1");
normalizedText = normalizedText.replaceAll("\\b(hence|after|from)\\b", "future"); normalizedText = normalizedText.replaceAll("\\b(hence|after|from)\\b", "future");
@ -174,10 +175,9 @@ public class AstridChronic {
long guessValue; long guessValue;
if (span.getWidth() > 1) { if (span.getWidth() > 1) {
guessValue = span.getBegin() + (span.getWidth() / 2); guessValue = span.getBegin() + (span.getWidth() / 2);
} } else {
else {
guessValue = span.getBegin(); guessValue = span.getBegin();
} }
return new Span(guessValue, guessValue); return new Span(guessValue, guessValue);
} }
} }

@ -3,10 +3,10 @@
* *
* See the file "LICENSE" for the full license governing this code. * See the file "LICENSE" for the full license governing this code.
*/ */
package com.todoroo.andlib.data; package com.todoroo.andlib.data;
import android.text.TextUtils; import android.text.TextUtils;
import com.todoroo.andlib.sql.Field; import com.todoroo.andlib.sql.Field;
/** /**
@ -16,140 +16,143 @@ import com.todoroo.andlib.sql.Field;
* value is null, it may be of an incorrect type, in which case an exception is * value is null, it may be of an incorrect type, in which case an exception is
* thrown, or the correct type, in which case the value is returned. * thrown, or the correct type, in which case the value is returned.
* *
* @param <TYPE> a database supported type, such as String or Integer
* @author Tim Su <tim@todoroo.com> * @author Tim Su <tim@todoroo.com>
*
* @param <TYPE>
* a database supported type, such as String or Integer
*/ */
public abstract class Property<TYPE> extends Field implements Cloneable { public abstract class Property<TYPE> extends Field implements Cloneable {
// --- implementation // --- implementation
/** The database table name this property */ /**
public final Table table; * The database table name this property
*/
/** The database column name for this property */ public final Table table;
public final String name;
/**
/** * The database column name for this property
* Create a property by table and column name. Uses the default property */
* expression which is derived from default table name public final String name;
*/
Property(Table table, String columnName) { /**
this(table, columnName, (table == null) ? (columnName) : (table.name() + "." + columnName)); * Create a property by table and column name. Uses the default property
* expression which is derived from default table name
*/
Property(Table table, String columnName) {
this(table, columnName, (table == null) ? (columnName) : (table.name() + "." + columnName));
}
/**
* Create a property by table and column name, manually specifying an
* expression to use in SQL
*/
Property(Table table, String columnName, String expression) {
super(expression);
this.table = table;
this.name = columnName;
}
/**
* Return a clone of this property
*/
@Override
public Property<TYPE> clone() {
try {
return (Property<TYPE>) super.clone();
} catch (CloneNotSupportedException e) {
throw new RuntimeException(e);
} }
}
/**
* Create a property by table and column name, manually specifying an /**
* expression to use in SQL * Return a clone of this property
*/ */
Property(Table table, String columnName, String expression) { Property<TYPE> cloneAs(String tableAlias, String columnAlias) {
super(expression); Table aliasedTable = this.table;
this.table = table; if (!TextUtils.isEmpty(tableAlias)) {
this.name = columnName; aliasedTable = table.as(tableAlias);
} }
/** try {
* Return a clone of this property Property<TYPE> newInstance = this.getClass().getConstructor(Table.class, String.class)
*/ .newInstance(aliasedTable, this.name);
@Override if (!TextUtils.isEmpty(columnAlias)) {
public Property<TYPE> clone() { return (Property<TYPE>) newInstance.as(columnAlias);
try { }
return (Property<TYPE>) super.clone(); return newInstance;
} catch (CloneNotSupportedException e) { } catch (Exception e) {
throw new RuntimeException(e); throw new RuntimeException(e);
}
} }
}
/** // --- children
* Return a clone of this property
*/
Property<TYPE> cloneAs(String tableAlias, String columnAlias) {
Table aliasedTable = this.table;
if (!TextUtils.isEmpty(tableAlias)) {
aliasedTable = table.as(tableAlias);
}
try {
Property<TYPE> newInstance = this.getClass().getConstructor(Table.class, String.class).newInstance(aliasedTable, this.name);
if(!TextUtils.isEmpty(columnAlias)) {
return (Property<TYPE>) newInstance.as(columnAlias);
}
return newInstance;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
// --- children public String getColumnName() {
if (hasAlias()) {
return alias;
}
return name;
}
/**
* Integer property type. See {@link Property}
*
* @author Tim Su <tim@todoroo.com>
*/
public static class IntegerProperty extends Property<Integer> {
public IntegerProperty(Table table, String name) {
super(table, name);
}
/** IntegerProperty(String name, String expression) {
* Integer property type. See {@link Property} super(null, name, expression);
* }
* @author Tim Su <tim@todoroo.com> }
*
*/
public static class IntegerProperty extends Property<Integer> {
public IntegerProperty(Table table, String name) { /**
super(table, name); * String property type. See {@link Property}
} *
* @author Tim Su <tim@todoroo.com>
*/
public static class StringProperty extends Property<String> {
IntegerProperty(String name, String expression) { public StringProperty(Table table, String name) {
super(null, name, expression); super(table, name);
}
} }
/** @Override
* String property type. See {@link Property} public StringProperty as(String newAlias) {
* return (StringProperty) super.as(newAlias);
* @author Tim Su <tim@todoroo.com>
*
*/
public static class StringProperty extends Property<String> {
public StringProperty(Table table, String name) {
super(table, name);
}
@Override
public StringProperty as(String newAlias) {
return (StringProperty) super.as(newAlias);
}
} }
}
/**
* Long property type. See {@link Property}
*
* @author Tim Su <tim@todoroo.com>
*/
public static class LongProperty extends Property<Long> {
/** public LongProperty(Table table, String name) {
* Long property type. See {@link Property} super(table, name);
*
* @author Tim Su <tim@todoroo.com>
*
*/
public static class LongProperty extends Property<Long> {
public LongProperty(Table table, String name) {
super(table, name);
}
@Override
public LongProperty cloneAs(String tableAlias, String columnAlias) {
return (LongProperty) super.cloneAs(tableAlias, columnAlias);
}
} }
public String getColumnName() { @Override
if (hasAlias()) { public LongProperty cloneAs(String tableAlias, String columnAlias) {
return alias; return (LongProperty) super.cloneAs(tableAlias, columnAlias);
}
return name;
} }
}
// --- pseudo-properties
// --- pseudo-properties /**
* Runs a SQL function and returns the result as a string
*/
public static class CountProperty extends IntegerProperty {
/** Runs a SQL function and returns the result as a string */ public CountProperty() {
public static class CountProperty extends IntegerProperty { super("count", "COUNT(1)");
public CountProperty() { alias = "count";
super("count", "COUNT(1)");
alias = "count";
}
} }
}
} }

@ -3,6 +3,7 @@
* *
* See the file "LICENSE" for the full license governing this code. * See the file "LICENSE" for the full license governing this code.
*/ */
package com.todoroo.andlib.data; package com.todoroo.andlib.data;
import com.todoroo.andlib.sql.SqlTable; import com.todoroo.andlib.sql.SqlTable;
@ -12,41 +13,41 @@ import com.todoroo.andlib.sql.SqlTable;
* clone the table when it returns. * clone the table when it returns.
* *
* @author Tim Su <tim@todoroo.com> * @author Tim Su <tim@todoroo.com>
*
*/ */
public final class Table extends SqlTable { public final class Table extends SqlTable {
public final String name;
public Table(String name) { public final String name;
this(name, null);
}
private Table(String name, String alias) { public Table(String name) {
super(name); this(name, null);
this.name = name; }
this.alias = alias;
}
/** private Table(String name, String alias) {
* Create a new join table based on this table, but with an alias super(name);
*/ this.name = name;
@Override this.alias = alias;
public Table as(String newAlias) { }
return new Table(name, newAlias);
} /**
* Create a new join table based on this table, but with an alias
*/
@Override
public Table as(String newAlias) {
return new Table(name, newAlias);
}
@Override @Override
public String toString() { public String toString() {
if(hasAlias()) { if (hasAlias()) {
return expression + " AS " + alias; //$NON-NLS-1$ return expression + " AS " + alias; //$NON-NLS-1$
}
return expression;
} }
return expression;
}
public String name() { public String name() {
if(hasAlias()) { if (hasAlias()) {
return alias; return alias;
}
return name;
} }
return name;
}
} }

@ -3,6 +3,7 @@
* *
* See the file "LICENSE" for the full license governing this code. * See the file "LICENSE" for the full license governing this code.
*/ */
package com.todoroo.andlib.sql; package com.todoroo.andlib.sql;
import static com.todoroo.andlib.sql.SqlConstants.AND; import static com.todoroo.andlib.sql.SqlConstants.AND;
@ -13,64 +14,64 @@ import static com.todoroo.andlib.sql.SqlConstants.RIGHT_PARENTHESIS;
import static com.todoroo.andlib.sql.SqlConstants.SPACE; import static com.todoroo.andlib.sql.SqlConstants.SPACE;
public abstract class Criterion { public abstract class Criterion {
final Operator operator;
public Criterion(Operator operator) { public static final Criterion all = new Criterion(Operator.exists) {
this.operator = operator; @Override
protected void populate(StringBuilder sb) {
sb.append(1);
} }
};
final Operator operator;
public static final Criterion all = new Criterion(Operator.exists) { public Criterion(Operator operator) {
@Override this.operator = operator;
protected void populate(StringBuilder sb) { }
sb.append(1);
}
};
public static Criterion and(final Criterion criterion, final Criterion... criterions) { public static Criterion and(final Criterion criterion, final Criterion... criterions) {
return new Criterion(Operator.and) { return new Criterion(Operator.and) {
@Override @Override
protected void populate(StringBuilder sb) { protected void populate(StringBuilder sb) {
sb.append(criterion); sb.append(criterion);
for (Criterion c : criterions) { for (Criterion c : criterions) {
sb.append(SPACE).append(AND).append(SPACE).append(c); sb.append(SPACE).append(AND).append(SPACE).append(c);
} }
} }
}; };
} }
public static Criterion or(final Criterion criterion, final Criterion... criterions) { public static Criterion or(final Criterion criterion, final Criterion... criterions) {
return new Criterion(Operator.or) { return new Criterion(Operator.or) {
@Override @Override
protected void populate(StringBuilder sb) { protected void populate(StringBuilder sb) {
sb.append(criterion); sb.append(criterion);
for (Criterion c : criterions) { for (Criterion c : criterions) {
sb.append(SPACE).append(OR).append(SPACE).append(c.toString()); sb.append(SPACE).append(OR).append(SPACE).append(c.toString());
} }
} }
}; };
} }
public static Criterion not(final Criterion criterion) { public static Criterion not(final Criterion criterion) {
return new Criterion(Operator.not) { return new Criterion(Operator.not) {
@Override @Override
protected void populate(StringBuilder sb) { protected void populate(StringBuilder sb) {
sb.append(NOT).append(SPACE); sb.append(NOT).append(SPACE);
criterion.populate(sb); criterion.populate(sb);
} }
}; };
} }
protected abstract void populate(StringBuilder sb); protected abstract void populate(StringBuilder sb);
@Override @Override
public String toString() { public String toString() {
StringBuilder builder = new StringBuilder(LEFT_PARENTHESIS); StringBuilder builder = new StringBuilder(LEFT_PARENTHESIS);
populate(builder); populate(builder);
builder.append(RIGHT_PARENTHESIS); builder.append(RIGHT_PARENTHESIS);
return builder.toString(); return builder.toString();
} }
} }

@ -3,79 +3,82 @@
* *
* See the file "LICENSE" for the full license governing this code. * See the file "LICENSE" for the full license governing this code.
*/ */
package com.todoroo.andlib.sql; package com.todoroo.andlib.sql;
import static com.todoroo.andlib.sql.SqlConstants.AS; import static com.todoroo.andlib.sql.SqlConstants.AS;
import static com.todoroo.andlib.sql.SqlConstants.SPACE; import static com.todoroo.andlib.sql.SqlConstants.SPACE;
public abstract class DBObject<T extends DBObject<?>> implements Cloneable { public abstract class DBObject<T extends DBObject<?>> implements Cloneable {
protected String alias;
protected final String expression;
DBObject(String expression){ protected final String expression;
this.expression = expression; protected String alias;
}
protected T as(String newAlias) { DBObject(String expression) {
try { this.expression = expression;
T clone = (T) clone(); }
clone.alias = newAlias;
return clone;
} catch (CloneNotSupportedException e) {
throw new RuntimeException(e);
}
}
protected boolean hasAlias() { protected T as(String newAlias) {
return alias != null; try {
T clone = (T) clone();
clone.alias = newAlias;
return clone;
} catch (CloneNotSupportedException e) {
throw new RuntimeException(e);
} }
}
@Override protected boolean hasAlias() {
public boolean equals(Object o) { return alias != null;
if (this == o) { }
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
DBObject<?> dbObject = (DBObject<?>) o; @Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
if (alias != null ? !alias.equals(dbObject.alias) : dbObject.alias != null) { DBObject<?> dbObject = (DBObject<?>) o;
return false;
}
if (expression != null ? !expression.equals(dbObject.expression) : dbObject.expression != null) {
return false;
}
return true; if (alias != null ? !alias.equals(dbObject.alias) : dbObject.alias != null) {
return false;
} }
if (expression != null ? !expression.equals(dbObject.expression)
@Override : dbObject.expression != null) {
public int hashCode() { return false;
int result = alias != null ? alias.hashCode() : 0;
result = 31 * result + (expression != null ? expression.hashCode() : 0);
return result;
} }
@Override return true;
public String toString() { }
if (hasAlias()) {
return alias; @Override
} public int hashCode() {
return expression; int result = alias != null ? alias.hashCode() : 0;
result = 31 * result + (expression != null ? expression.hashCode() : 0);
return result;
}
@Override
public String toString() {
if (hasAlias()) {
return alias;
} }
return expression;
}
public final String toStringInSelect() { public final String toStringInSelect() {
StringBuilder sb = new StringBuilder(expression); StringBuilder sb = new StringBuilder(expression);
if (hasAlias()) { if (hasAlias()) {
sb.append(SPACE).append(AS).append(SPACE).append(alias); sb.append(SPACE).append(AS).append(SPACE).append(alias);
} else { } else {
int pos = expression.indexOf('.'); int pos = expression.indexOf('.');
if(pos > 0) { if (pos > 0) {
sb.append(SPACE).append(AS).append(SPACE).append(expression.substring(pos + 1)); sb.append(SPACE).append(AS).append(SPACE).append(expression.substring(pos + 1));
} }
}
return sb.toString();
} }
return sb.toString();
}
} }

@ -3,6 +3,7 @@
* *
* See the file "LICENSE" for the full license governing this code. * See the file "LICENSE" for the full license governing this code.
*/ */
package com.todoroo.andlib.sql; package com.todoroo.andlib.sql;
import static com.todoroo.andlib.sql.SqlConstants.COMMA; import static com.todoroo.andlib.sql.SqlConstants.COMMA;
@ -12,68 +13,70 @@ import static com.todoroo.andlib.sql.SqlConstants.SPACE;
public class Field extends DBObject<Field> { public class Field extends DBObject<Field> {
protected Field(String expression) { protected Field(String expression) {
super(expression); super(expression);
} }
public static Field field(String expression) { public static Field field(String expression) {
return new Field(expression); return new Field(expression);
} }
public Criterion eq(Object value) { public Criterion eq(Object value) {
if(value == null) { if (value == null) {
return UnaryCriterion.isNull(this); return UnaryCriterion.isNull(this);
}
return UnaryCriterion.eq(this, value);
} }
return UnaryCriterion.eq(this, value);
}
public Criterion neq(Object value) { public Criterion neq(Object value) {
if(value == null) { if (value == null) {
return UnaryCriterion.isNotNull(this); return UnaryCriterion.isNotNull(this);
}
return UnaryCriterion.neq(this, value);
} }
return UnaryCriterion.neq(this, value);
}
public Criterion gt(Object value) { public Criterion gt(Object value) {
return UnaryCriterion.gt(this, value); return UnaryCriterion.gt(this, value);
} }
public Criterion lt(final Object value) { public Criterion lt(final Object value) {
return UnaryCriterion.lt(this, value); return UnaryCriterion.lt(this, value);
} }
public Criterion lte(final Object value) { public Criterion lte(final Object value) {
return UnaryCriterion.lte(this, value); return UnaryCriterion.lte(this, value);
} }
public Criterion like(final String value) { public Criterion like(final String value) {
return UnaryCriterion.like(this, value); return UnaryCriterion.like(this, value);
} }
public <T> Criterion in(final Iterable<T> value) { public <T> Criterion in(final Iterable<T> value) {
final Field field = this; final Field field = this;
return new Criterion(Operator.in) { return new Criterion(Operator.in) {
@Override @Override
protected void populate(StringBuilder sb) { protected void populate(StringBuilder sb) {
sb.append(field).append(SPACE).append(Operator.in).append(SPACE).append(LEFT_PARENTHESIS).append(SPACE); sb.append(field).append(SPACE).append(Operator.in).append(SPACE).append(LEFT_PARENTHESIS)
for (T t : value) { .append(SPACE);
sb.append(t.toString()).append(COMMA); for (T t : value) {
} sb.append(t.toString()).append(COMMA);
sb.deleteCharAt(sb.length() - 1).append(RIGHT_PARENTHESIS); }
} sb.deleteCharAt(sb.length() - 1).append(RIGHT_PARENTHESIS);
}; }
} };
}
public Criterion in(final Query query) { public Criterion in(final Query query) {
final Field field = this; final Field field = this;
return new Criterion(Operator.in) { return new Criterion(Operator.in) {
@Override @Override
protected void populate(StringBuilder sb) { protected void populate(StringBuilder sb) {
sb.append(field).append(SPACE).append(Operator.in).append(SPACE).append(LEFT_PARENTHESIS).append(query) sb.append(field).append(SPACE).append(Operator.in).append(SPACE).append(LEFT_PARENTHESIS)
.append(RIGHT_PARENTHESIS); .append(query)
} .append(RIGHT_PARENTHESIS);
}; }
} };
}
} }

@ -3,18 +3,19 @@
* *
* See the file "LICENSE" for the full license governing this code. * See the file "LICENSE" for the full license governing this code.
*/ */
package com.todoroo.andlib.sql; package com.todoroo.andlib.sql;
public final class Functions { public final class Functions {
public static Field upper(Field title) { public static Field upper(Field title) {
return new Field("UPPER(" + title.toString() + ")"); return new Field("UPPER(" + title.toString() + ")");
} }
/** /**
* @return SQL now (in milliseconds) * @return SQL now (in milliseconds)
*/ */
public static Field now() { public static Field now() {
return new Field("(strftime('%s','now')*1000)"); return new Field("(strftime('%s','now')*1000)");
} }
} }

@ -3,6 +3,7 @@
* *
* See the file "LICENSE" for the full license governing this code. * See the file "LICENSE" for the full license governing this code.
*/ */
package com.todoroo.andlib.sql; package com.todoroo.andlib.sql;
import static com.todoroo.andlib.sql.SqlConstants.AND; import static com.todoroo.andlib.sql.SqlConstants.AND;
@ -11,35 +12,37 @@ import static com.todoroo.andlib.sql.SqlConstants.ON;
import static com.todoroo.andlib.sql.SqlConstants.SPACE; import static com.todoroo.andlib.sql.SqlConstants.SPACE;
public class Join { public class Join {
private final SqlTable joinTable;
private final JoinType joinType;
private final Criterion[] criterions;
private Join(SqlTable table, JoinType joinType, Criterion... criterions) {
joinTable = table;
this.joinType = joinType;
this.criterions = criterions;
}
public static Join inner(SqlTable expression, Criterion... criterions) { private final SqlTable joinTable;
return new Join(expression, JoinType.INNER, criterions); private final JoinType joinType;
} private final Criterion[] criterions;
public static Join left(SqlTable table, Criterion... criterions) { private Join(SqlTable table, JoinType joinType, Criterion... criterions) {
return new Join(table, JoinType.LEFT, criterions); joinTable = table;
} this.joinType = joinType;
this.criterions = criterions;
}
public static Join inner(SqlTable expression, Criterion... criterions) {
return new Join(expression, JoinType.INNER, criterions);
}
public static Join left(SqlTable table, Criterion... criterions) {
return new Join(table, JoinType.LEFT, criterions);
}
@Override @Override
public String toString() { public String toString() {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
sb.append(joinType).append(SPACE).append(JOIN).append(SPACE).append(joinTable).append(SPACE).append(ON).append(SPACE).append("("); sb.append(joinType).append(SPACE).append(JOIN).append(SPACE).append(joinTable).append(SPACE)
for (int i = 0; i < criterions.length; i++) { .append(ON).append(SPACE).append("(");
sb.append(criterions[i]); for (int i = 0; i < criterions.length; i++) {
if (i < criterions.length - 1) { sb.append(criterions[i]);
sb.append(SPACE).append(AND).append(SPACE); if (i < criterions.length - 1) {
} sb.append(SPACE).append(AND).append(SPACE);
} }
sb.append(")");
return sb.toString();
} }
sb.append(")");
return sb.toString();
}
} }

@ -3,8 +3,9 @@
* *
* See the file "LICENSE" for the full license governing this code. * See the file "LICENSE" for the full license governing this code.
*/ */
package com.todoroo.andlib.sql; package com.todoroo.andlib.sql;
public enum JoinType { public enum JoinType {
INNER, LEFT INNER, LEFT
} }

@ -3,31 +3,32 @@
* *
* See the file "LICENSE" for the full license governing this code. * See the file "LICENSE" for the full license governing this code.
*/ */
package com.todoroo.andlib.sql; package com.todoroo.andlib.sql;
public final class Operator { public final class Operator {
private final String operator; public static final Operator eq = new Operator("=");
public static final Operator eq = new Operator("="); public static final Operator isNull = new Operator("IS NULL");
static final Operator neq = new Operator("<>"); public static final Operator and = new Operator("AND");
public static final Operator isNull = new Operator("IS NULL"); public static final Operator or = new Operator("OR");
static final Operator isNotNull = new Operator("IS NOT NULL"); public static final Operator not = new Operator("NOT");
static final Operator gt = new Operator(">"); public static final Operator exists = new Operator("EXISTS");
static final Operator lt = new Operator("<"); public static final Operator like = new Operator("LIKE");
static final Operator lte = new Operator("<="); public static final Operator in = new Operator("IN");
public static final Operator and = new Operator("AND"); static final Operator neq = new Operator("<>");
public static final Operator or = new Operator("OR"); static final Operator isNotNull = new Operator("IS NOT NULL");
public static final Operator not = new Operator("NOT"); static final Operator gt = new Operator(">");
public static final Operator exists = new Operator("EXISTS"); static final Operator lt = new Operator("<");
public static final Operator like = new Operator("LIKE"); static final Operator lte = new Operator("<=");
public static final Operator in = new Operator("IN"); private final String operator;
private Operator(String operator) { private Operator(String operator) {
this.operator = operator; this.operator = operator;
} }
@Override @Override
public String toString() { public String toString() {
return this.operator; return this.operator;
} }
} }

@ -3,59 +3,61 @@
* *
* See the file "LICENSE" for the full license governing this code. * See the file "LICENSE" for the full license governing this code.
*/ */
package com.todoroo.andlib.sql; package com.todoroo.andlib.sql;
import static com.todoroo.andlib.sql.SqlConstants.SPACE;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import static com.todoroo.andlib.sql.SqlConstants.SPACE;
public class Order { public class Order {
private final Object expression;
private final List<Order> secondaryExpressions;
private final OrderType orderType;
private Order(Object expression) {
this(expression, OrderType.ASC);
}
private Order(Object expression, OrderType orderType) {
this.expression = expression;
this.orderType = orderType;
this.secondaryExpressions = new ArrayList<>();
}
public static Order asc(Object expression) {
return new Order(expression);
}
public static Order desc(Object expression) {
return new Order(expression, OrderType.DESC);
}
public void addSecondaryExpression(Order secondary) { private final Object expression;
secondaryExpressions.add(secondary); private final List<Order> secondaryExpressions;
private final OrderType orderType;
private Order(Object expression) {
this(expression, OrderType.ASC);
}
private Order(Object expression, OrderType orderType) {
this.expression = expression;
this.orderType = orderType;
this.secondaryExpressions = new ArrayList<>();
}
public static Order asc(Object expression) {
return new Order(expression);
}
public static Order desc(Object expression) {
return new Order(expression, OrderType.DESC);
}
public void addSecondaryExpression(Order secondary) {
secondaryExpressions.add(secondary);
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(expression.toString())
.append(SPACE)
.append(orderType.toString());
for (Order secondary : secondaryExpressions) {
sb.append(", ").append(secondary.toString()); //$NON-NLS-1$
} }
@Override return sb.toString();
public String toString() { }
StringBuilder sb = new StringBuilder();
sb.append(expression.toString())
.append(SPACE)
.append(orderType.toString());
for (Order secondary : secondaryExpressions) {
sb.append(", ").append(secondary.toString()); //$NON-NLS-1$
}
return sb.toString();
}
public Order reverse() { public Order reverse() {
if(orderType == OrderType.ASC) { if (orderType == OrderType.ASC) {
return new Order(expression, OrderType.DESC); return new Order(expression, OrderType.DESC);
} else { } else {
return new Order(expression, OrderType.ASC); return new Order(expression, OrderType.ASC);
}
} }
}
} }

@ -3,8 +3,9 @@
* *
* See the file "LICENSE" for the full license governing this code. * See the file "LICENSE" for the full license governing this code.
*/ */
package com.todoroo.andlib.sql; package com.todoroo.andlib.sql;
public enum OrderType { public enum OrderType {
DESC, ASC DESC, ASC
} }

@ -3,11 +3,8 @@
* *
* See the file "LICENSE" for the full license governing this code. * See the file "LICENSE" for the full license governing this code.
*/ */
package com.todoroo.andlib.sql;
import com.todoroo.astrid.data.Task;
import java.util.ArrayList; package com.todoroo.andlib.sql;
import static com.todoroo.andlib.sql.SqlConstants.ALL; import static com.todoroo.andlib.sql.SqlConstants.ALL;
import static com.todoroo.andlib.sql.SqlConstants.COMMA; import static com.todoroo.andlib.sql.SqlConstants.COMMA;
@ -19,142 +16,147 @@ import static com.todoroo.andlib.sql.SqlConstants.SPACE;
import static com.todoroo.andlib.sql.SqlConstants.WHERE; import static com.todoroo.andlib.sql.SqlConstants.WHERE;
import static java.util.Arrays.asList; import static java.util.Arrays.asList;
public final class Query { import com.todoroo.astrid.data.Task;
import java.util.ArrayList;
private SqlTable table;
private String queryTemplate = null;
private final ArrayList<Criterion> criterions = new ArrayList<>();
private final ArrayList<Field> fields = new ArrayList<>();
private final ArrayList<Join> joins = new ArrayList<>();
private final ArrayList<Order> orders = new ArrayList<>();
private int limits = -1;
private Query(Field... fields) {
this.fields.addAll(asList(fields));
}
public static Query select() {
return new Query(Task.PROPERTIES);
}
public static Query select(Field... fields) {
return new Query(fields);
}
public Query from(SqlTable fromTable) {
this.table = fromTable;
return this;
}
public Query join(Join... join) {
joins.addAll(asList(join));
return this;
}
public Query where(Criterion criterion) {
criterions.add(criterion);
return this;
}
public Query orderBy(Order... order) {
orders.addAll(asList(order));
return this;
}
public Query limit(int limit) {
limits = limit;
return this;
}
@Override
public boolean equals(Object o) {
return this == o || !(o == null || getClass() != o.getClass()) && this.toString().equals(o.toString());
}
@Override
public int hashCode() {
return toString().hashCode();
}
@Override
public String toString() {
StringBuilder sql = new StringBuilder();
visitSelectClause(sql);
visitFromClause(sql);
visitJoinClause(sql);
if(queryTemplate == null) {
visitWhereClause(sql);
visitOrderByClause(sql);
visitLimitClause(sql);
} else {
if(orders.size() > 0) {
throw new IllegalStateException("Can't have extras AND query template"); //$NON-NLS-1$
}
sql.append(queryTemplate);
}
return sql.toString();
}
private void visitOrderByClause(StringBuilder sql) {
if (orders.isEmpty()) {
return;
}
sql.append(ORDER_BY);
for (Order order : orders) {
sql.append(SPACE).append(order).append(COMMA);
}
sql.deleteCharAt(sql.length() - 1).append(SPACE);
}
private void visitWhereClause(StringBuilder sql) {
if (criterions.isEmpty()) {
return;
}
sql.append(WHERE);
for (Criterion criterion : criterions) {
sql.append(SPACE).append(criterion).append(SPACE);
}
}
private void visitJoinClause(StringBuilder sql) {
for (Join join : joins) {
sql.append(join).append(SPACE);
}
}
private void visitFromClause(StringBuilder sql) {
if (table == null) {
return;
}
sql.append(FROM).append(SPACE).append(table).append(SPACE);
}
private void visitSelectClause(StringBuilder sql) {
sql.append(SELECT).append(SPACE);
if (fields.isEmpty()) {
sql.append(ALL).append(SPACE);
return;
}
for (Field field : fields) {
sql.append(field.toStringInSelect()).append(COMMA);
}
sql.deleteCharAt(sql.length() - 1).append(SPACE);
}
private void visitLimitClause(StringBuilder sql) { public final class Query {
if(limits > -1) {
sql.append(LIMIT).append(SPACE).append(limits).append(SPACE);
}
}
/** private final ArrayList<Criterion> criterions = new ArrayList<>();
* Add the SQL query template (comes after the "from") private final ArrayList<Field> fields = new ArrayList<>();
* @return query private final ArrayList<Join> joins = new ArrayList<>();
*/ private final ArrayList<Order> orders = new ArrayList<>();
public Query withQueryTemplate(String template) { private SqlTable table;
queryTemplate = template; private String queryTemplate = null;
return this; private int limits = -1;
}
private Query(Field... fields) {
this.fields.addAll(asList(fields));
}
public static Query select() {
return new Query(Task.PROPERTIES);
}
public static Query select(Field... fields) {
return new Query(fields);
}
public Query from(SqlTable fromTable) {
this.table = fromTable;
return this;
}
public Query join(Join... join) {
joins.addAll(asList(join));
return this;
}
public Query where(Criterion criterion) {
criterions.add(criterion);
return this;
}
public Query orderBy(Order... order) {
orders.addAll(asList(order));
return this;
}
public Query limit(int limit) {
limits = limit;
return this;
}
@Override
public boolean equals(Object o) {
return this == o || !(o == null || getClass() != o.getClass()) && this.toString()
.equals(o.toString());
}
@Override
public int hashCode() {
return toString().hashCode();
}
@Override
public String toString() {
StringBuilder sql = new StringBuilder();
visitSelectClause(sql);
visitFromClause(sql);
visitJoinClause(sql);
if (queryTemplate == null) {
visitWhereClause(sql);
visitOrderByClause(sql);
visitLimitClause(sql);
} else {
if (orders.size() > 0) {
throw new IllegalStateException("Can't have extras AND query template"); //$NON-NLS-1$
}
sql.append(queryTemplate);
}
return sql.toString();
}
private void visitOrderByClause(StringBuilder sql) {
if (orders.isEmpty()) {
return;
}
sql.append(ORDER_BY);
for (Order order : orders) {
sql.append(SPACE).append(order).append(COMMA);
}
sql.deleteCharAt(sql.length() - 1).append(SPACE);
}
private void visitWhereClause(StringBuilder sql) {
if (criterions.isEmpty()) {
return;
}
sql.append(WHERE);
for (Criterion criterion : criterions) {
sql.append(SPACE).append(criterion).append(SPACE);
}
}
private void visitJoinClause(StringBuilder sql) {
for (Join join : joins) {
sql.append(join).append(SPACE);
}
}
private void visitFromClause(StringBuilder sql) {
if (table == null) {
return;
}
sql.append(FROM).append(SPACE).append(table).append(SPACE);
}
private void visitSelectClause(StringBuilder sql) {
sql.append(SELECT).append(SPACE);
if (fields.isEmpty()) {
sql.append(ALL).append(SPACE);
return;
}
for (Field field : fields) {
sql.append(field.toStringInSelect()).append(COMMA);
}
sql.deleteCharAt(sql.length() - 1).append(SPACE);
}
private void visitLimitClause(StringBuilder sql) {
if (limits > -1) {
sql.append(LIMIT).append(SPACE).append(limits).append(SPACE);
}
}
/**
* Add the SQL query template (comes after the "from")
*
* @return query
*/
public Query withQueryTemplate(String template) {
queryTemplate = template;
return this;
}
} }

@ -3,9 +3,8 @@
* *
* See the file "LICENSE" for the full license governing this code. * See the file "LICENSE" for the full license governing this code.
*/ */
package com.todoroo.andlib.sql;
import java.util.ArrayList; package com.todoroo.andlib.sql;
import static com.todoroo.andlib.sql.SqlConstants.COMMA; import static com.todoroo.andlib.sql.SqlConstants.COMMA;
import static com.todoroo.andlib.sql.SqlConstants.LIMIT; import static com.todoroo.andlib.sql.SqlConstants.LIMIT;
@ -14,76 +13,77 @@ import static com.todoroo.andlib.sql.SqlConstants.SPACE;
import static com.todoroo.andlib.sql.SqlConstants.WHERE; import static com.todoroo.andlib.sql.SqlConstants.WHERE;
import static java.util.Arrays.asList; import static java.util.Arrays.asList;
import java.util.ArrayList;
/** /**
* Query Template returns a bunch of criteria that allows a query to be * Query Template returns a bunch of criteria that allows a query to be
* constructed * constructed
* *
* @author Tim Su <tim@todoroo.com> * @author Tim Su <tim@todoroo.com>
*
*/ */
public final class QueryTemplate { public final class QueryTemplate {
private final ArrayList<Criterion> criterions = new ArrayList<>(); private final ArrayList<Criterion> criterions = new ArrayList<>();
private final ArrayList<Join> joins = new ArrayList<>(); private final ArrayList<Join> joins = new ArrayList<>();
private final ArrayList<Order> orders = new ArrayList<>(); private final ArrayList<Order> orders = new ArrayList<>();
private Integer limit = null; private Integer limit = null;
public QueryTemplate join(Join... join) { public QueryTemplate join(Join... join) {
joins.addAll(asList(join)); joins.addAll(asList(join));
return this; return this;
} }
public QueryTemplate where(Criterion criterion) { public QueryTemplate where(Criterion criterion) {
criterions.add(criterion); criterions.add(criterion);
return this; return this;
} }
public QueryTemplate orderBy(Order... order) { public QueryTemplate orderBy(Order... order) {
orders.addAll(asList(order)); orders.addAll(asList(order));
return this; return this;
} }
@Override @Override
public String toString() { public String toString() {
StringBuilder sql = new StringBuilder(); StringBuilder sql = new StringBuilder();
visitJoinClause(sql); visitJoinClause(sql);
visitWhereClause(sql); visitWhereClause(sql);
visitOrderByClause(sql); visitOrderByClause(sql);
if(limit != null) { if (limit != null) {
sql.append(LIMIT).append(SPACE).append(limit); sql.append(LIMIT).append(SPACE).append(limit);
}
return sql.toString();
} }
return sql.toString();
}
private void visitOrderByClause(StringBuilder sql) { private void visitOrderByClause(StringBuilder sql) {
if (orders.isEmpty()) { if (orders.isEmpty()) {
return; return;
}
sql.append(ORDER_BY);
for (Order order : orders) {
sql.append(SPACE).append(order).append(COMMA);
}
sql.deleteCharAt(sql.length() - 1).append(SPACE);
} }
sql.append(ORDER_BY);
private void visitWhereClause(StringBuilder sql) { for (Order order : orders) {
if (criterions.isEmpty()) { sql.append(SPACE).append(order).append(COMMA);
return;
}
sql.append(WHERE);
for (Criterion criterion : criterions) {
sql.append(SPACE).append(criterion).append(SPACE);
}
} }
sql.deleteCharAt(sql.length() - 1).append(SPACE);
}
private void visitJoinClause(StringBuilder sql) { private void visitWhereClause(StringBuilder sql) {
for (Join join : joins) { if (criterions.isEmpty()) {
sql.append(join).append(SPACE); return;
} }
sql.append(WHERE);
for (Criterion criterion : criterions) {
sql.append(SPACE).append(criterion).append(SPACE);
} }
}
public QueryTemplate limit(int limitValue) { private void visitJoinClause(StringBuilder sql) {
this.limit = limitValue; for (Join join : joins) {
return this; sql.append(join).append(SPACE);
} }
}
public QueryTemplate limit(int limitValue) {
this.limit = limitValue;
return this;
}
} }

@ -3,23 +3,25 @@
* *
* See the file "LICENSE" for the full license governing this code. * See the file "LICENSE" for the full license governing this code.
*/ */
package com.todoroo.andlib.sql; package com.todoroo.andlib.sql;
public final class SqlConstants { public final class SqlConstants {
public static final String SELECT = "SELECT";
public static final String SPACE = " "; public static final String SELECT = "SELECT";
public static final String AS = "AS"; public static final String SPACE = " ";
static final String COMMA = ","; public static final String AS = "AS";
public static final String FROM = "FROM"; public static final String FROM = "FROM";
public static final String ON = "ON"; public static final String ON = "ON";
static final String JOIN = "JOIN"; public static final String ALL = "*";
public static final String ALL = "*"; public static final String AND = "AND";
static final String LEFT_PARENTHESIS = "("; public static final String OR = "OR";
static final String RIGHT_PARENTHESIS = ")"; public static final String WHERE = "WHERE";
public static final String AND = "AND"; public static final String NOT = "NOT";
public static final String OR = "OR"; public static final String LIMIT = "LIMIT";
static final String ORDER_BY = "ORDER BY"; static final String COMMA = ",";
public static final String WHERE = "WHERE"; static final String JOIN = "JOIN";
public static final String NOT = "NOT"; static final String LEFT_PARENTHESIS = "(";
public static final String LIMIT = "LIMIT"; static final String RIGHT_PARENTHESIS = ")";
static final String ORDER_BY = "ORDER BY";
} }

@ -3,11 +3,12 @@
* *
* See the file "LICENSE" for the full license governing this code. * See the file "LICENSE" for the full license governing this code.
*/ */
package com.todoroo.andlib.sql; package com.todoroo.andlib.sql;
public class SqlTable extends DBObject<SqlTable> { public class SqlTable extends DBObject<SqlTable> {
protected SqlTable(String expression) { protected SqlTable(String expression) {
super(expression); super(expression);
} }
} }

@ -3,101 +3,103 @@
* *
* See the file "LICENSE" for the full license governing this code. * See the file "LICENSE" for the full license governing this code.
*/ */
package com.todoroo.andlib.sql; package com.todoroo.andlib.sql;
import static com.todoroo.andlib.sql.SqlConstants.SPACE; import static com.todoroo.andlib.sql.SqlConstants.SPACE;
public class UnaryCriterion extends Criterion { public class UnaryCriterion extends Criterion {
private final Field expression;
private final Object value;
private UnaryCriterion(Field expression, Operator operator, Object value) {
super(operator);
this.expression = expression;
this.value = value;
}
@Override
protected void populate(StringBuilder sb) {
beforePopulateOperator(sb);
populateOperator(sb);
afterPopulateOperator(sb);
}
public static Criterion eq(Field expression, Object value) {
return new UnaryCriterion(expression, Operator.eq, value);
}
@SuppressWarnings("WeakerAccess")
void beforePopulateOperator(StringBuilder sb) {
sb.append(expression);
}
@SuppressWarnings("WeakerAccess")
void populateOperator(StringBuilder sb) {
sb.append(operator);
}
@SuppressWarnings("WeakerAccess")
void afterPopulateOperator(StringBuilder sb) {
if(value == null) {
return;
}
if(value instanceof String) {
sb.append("'").append(sanitize((String) value)).append("'");
} else {
sb.append(value);
}
}
/**
* Sanitize the given input for SQL
*/
public static String sanitize(String input) {
return input.replace("'", "''");
}
static Criterion neq(Field field, Object value) {
return new UnaryCriterion(field, Operator.neq, value);
}
static Criterion gt(Field field, Object value) {
return new UnaryCriterion(field, Operator.gt, value);
}
static Criterion lt(Field field, Object value) {
return new UnaryCriterion(field, Operator.lt, value);
}
static Criterion lte(Field field, Object value) {
return new UnaryCriterion(field, Operator.lte, value);
}
public static Criterion isNull(Field field) {
return new UnaryCriterion(field, Operator.isNull, null) {
@Override
protected void populateOperator(StringBuilder sb) {
sb.append(SPACE).append(operator);
}
};
}
static Criterion isNotNull(Field field) { private final Field expression;
return new UnaryCriterion(field, Operator.isNotNull, null) { private final Object value;
@Override
protected void populateOperator(StringBuilder sb) { private UnaryCriterion(Field expression, Operator operator, Object value) {
sb.append(SPACE).append(operator); super(operator);
} this.expression = expression;
}; this.value = value;
}
public static Criterion eq(Field expression, Object value) {
return new UnaryCriterion(expression, Operator.eq, value);
}
/**
* Sanitize the given input for SQL
*/
public static String sanitize(String input) {
return input.replace("'", "''");
}
static Criterion neq(Field field, Object value) {
return new UnaryCriterion(field, Operator.neq, value);
}
static Criterion gt(Field field, Object value) {
return new UnaryCriterion(field, Operator.gt, value);
}
static Criterion lt(Field field, Object value) {
return new UnaryCriterion(field, Operator.lt, value);
}
static Criterion lte(Field field, Object value) {
return new UnaryCriterion(field, Operator.lte, value);
}
public static Criterion isNull(Field field) {
return new UnaryCriterion(field, Operator.isNull, null) {
@Override
protected void populateOperator(StringBuilder sb) {
sb.append(SPACE).append(operator);
}
};
}
static Criterion isNotNull(Field field) {
return new UnaryCriterion(field, Operator.isNotNull, null) {
@Override
protected void populateOperator(StringBuilder sb) {
sb.append(SPACE).append(operator);
}
};
}
public static Criterion like(Field field, String value) {
return new UnaryCriterion(field, Operator.like, value) {
@Override
protected void populateOperator(StringBuilder sb) {
sb.append(SPACE).append(operator).append(SPACE);
}
};
}
@Override
protected void populate(StringBuilder sb) {
beforePopulateOperator(sb);
populateOperator(sb);
afterPopulateOperator(sb);
}
@SuppressWarnings("WeakerAccess")
void beforePopulateOperator(StringBuilder sb) {
sb.append(expression);
}
@SuppressWarnings("WeakerAccess")
void populateOperator(StringBuilder sb) {
sb.append(operator);
}
@SuppressWarnings("WeakerAccess")
void afterPopulateOperator(StringBuilder sb) {
if (value == null) {
return;
} }
public static Criterion like(Field field, String value) { if (value instanceof String) {
return new UnaryCriterion(field, Operator.like, value) { sb.append("'").append(sanitize((String) value)).append("'");
@Override } else {
protected void populateOperator(StringBuilder sb) { sb.append(value);
sb.append(SPACE).append(operator).append(SPACE);
}
};
} }
}
} }

@ -3,6 +3,7 @@
* *
* See the file "LICENSE" for the full license governing this code. * See the file "LICENSE" for the full license governing this code.
*/ */
package com.todoroo.andlib.utility; package com.todoroo.andlib.utility;
import android.app.Activity; import android.app.Activity;
@ -13,7 +14,6 @@ import android.util.DisplayMetrics;
import android.view.View; import android.view.View;
import android.view.inputmethod.InputMethodManager; import android.view.inputmethod.InputMethodManager;
import android.widget.TextView; import android.widget.TextView;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.FileOutputStream; import java.io.FileOutputStream;
@ -23,257 +23,262 @@ import java.io.OutputStream;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import timber.log.Timber; import timber.log.Timber;
/** /**
* Android Utility Classes * Android Utility Classes
* *
* @author Tim Su <tim@todoroo.com> * @author Tim Su <tim@todoroo.com>
*
*/ */
public class AndroidUtilities { public class AndroidUtilities {
public static final String SEPARATOR_ESCAPE = "!PIPE!"; //$NON-NLS-1$ public static final String SEPARATOR_ESCAPE = "!PIPE!"; //$NON-NLS-1$
public static final String SERIALIZATION_SEPARATOR = "|"; //$NON-NLS-1$ public static final String SERIALIZATION_SEPARATOR = "|"; //$NON-NLS-1$
// --- utility methods // --- utility methods
/** Suppress virtual keyboard until user's first tap */ /**
public static void suppressVirtualKeyboard(final TextView editor) { * Suppress virtual keyboard until user's first tap
final int inputType = editor.getInputType(); */
editor.setInputType(InputType.TYPE_NULL); public static void suppressVirtualKeyboard(final TextView editor) {
editor.setOnTouchListener((v, event) -> { final int inputType = editor.getInputType();
editor.setInputType(inputType); editor.setInputType(InputType.TYPE_NULL);
editor.setOnTouchListener(null); editor.setOnTouchListener((v, event) -> {
return false; editor.setInputType(inputType);
}); editor.setOnTouchListener(null);
} return false;
});
// --- serialization }
/** // --- serialization
* Serializes a content value into a string
*/ /**
public static String mapToSerializedString(Map<String, Object> source) { * Serializes a content value into a string
StringBuilder result = new StringBuilder(); */
for(Entry<String, Object> entry : source.entrySet()) { public static String mapToSerializedString(Map<String, Object> source) {
addSerialized(result, entry.getKey(), entry.getValue()); StringBuilder result = new StringBuilder();
} for (Entry<String, Object> entry : source.entrySet()) {
return result.toString(); addSerialized(result, entry.getKey(), entry.getValue());
}
/** add serialized helper */
private static void addSerialized(StringBuilder result,
String key, Object value) {
result.append(key.replace(SERIALIZATION_SEPARATOR, SEPARATOR_ESCAPE)).append(
SERIALIZATION_SEPARATOR);
if(value instanceof Integer) {
result.append('i').append(value);
} else if(value instanceof Double) {
result.append('d').append(value);
} else if(value instanceof Long) {
result.append('l').append(value);
} else if(value instanceof String) {
result.append('s').append(value.toString().replace(SERIALIZATION_SEPARATOR, SEPARATOR_ESCAPE));
} else if (value instanceof Boolean) {
result.append('b').append(value);
} else {
throw new UnsupportedOperationException(value.getClass().toString());
}
result.append(SERIALIZATION_SEPARATOR);
}
public static Map<String, Object> mapFromSerializedString(String string) {
if (string == null) {
return new HashMap<>();
}
Map<String, Object> result = new HashMap<>();
fromSerialized(string, result, (object, key, type, value) -> {
switch (type) {
case 'i':
object.put(key, Integer.parseInt(value));
break;
case 'd':
object.put(key, Double.parseDouble(value));
break;
case 'l':
object.put(key, Long.parseLong(value));
break;
case 's':
object.put(key, value.replace(SEPARATOR_ESCAPE, SERIALIZATION_SEPARATOR));
break;
case 'b':
object.put(key, Boolean.parseBoolean(value));
break;
}
});
return result;
} }
return result.toString();
public interface SerializedPut<T> { }
void put(T object, String key, char type, String value) throws NumberFormatException;
/**
* add serialized helper
*/
private static void addSerialized(StringBuilder result,
String key, Object value) {
result.append(key.replace(SERIALIZATION_SEPARATOR, SEPARATOR_ESCAPE)).append(
SERIALIZATION_SEPARATOR);
if (value instanceof Integer) {
result.append('i').append(value);
} else if (value instanceof Double) {
result.append('d').append(value);
} else if (value instanceof Long) {
result.append('l').append(value);
} else if (value instanceof String) {
result.append('s')
.append(value.toString().replace(SERIALIZATION_SEPARATOR, SEPARATOR_ESCAPE));
} else if (value instanceof Boolean) {
result.append('b').append(value);
} else {
throw new UnsupportedOperationException(value.getClass().toString());
} }
result.append(SERIALIZATION_SEPARATOR);
}
private static <T> void fromSerialized(String string, T object, SerializedPut<T> putter) { public static Map<String, Object> mapFromSerializedString(String string) {
String[] pairs = string.split("\\" + SERIALIZATION_SEPARATOR); //$NON-NLS-1$ if (string == null) {
for(int i = 0; i < pairs.length; i += 2) { return new HashMap<>();
try {
String key = pairs[i].replaceAll(SEPARATOR_ESCAPE, SERIALIZATION_SEPARATOR);
String value = pairs[i+1].substring(1);
try {
putter.put(object, key, pairs[i+1].charAt(0), value);
} catch (NumberFormatException e) {
// failed parse to number
putter.put(object, key, 's', value);
Timber.e(e, e.getMessage());
}
} catch (IndexOutOfBoundsException e) {
Timber.e(e, e.getMessage());
}
}
} }
/** Map<String, Object> result = new HashMap<>();
* Copy a file from one place to another fromSerialized(string, result, (object, key, type, value) -> {
* @throws Exception switch (type) {
*/ case 'i':
public static void copyFile(File in, File out) throws Exception { object.put(key, Integer.parseInt(value));
FileInputStream fis = new FileInputStream(in); break;
FileOutputStream fos = new FileOutputStream(out); case 'd':
object.put(key, Double.parseDouble(value));
break;
case 'l':
object.put(key, Long.parseLong(value));
break;
case 's':
object.put(key, value.replace(SEPARATOR_ESCAPE, SERIALIZATION_SEPARATOR));
break;
case 'b':
object.put(key, Boolean.parseBoolean(value));
break;
}
});
return result;
}
private static <T> void fromSerialized(String string, T object, SerializedPut<T> putter) {
String[] pairs = string.split("\\" + SERIALIZATION_SEPARATOR); //$NON-NLS-1$
for (int i = 0; i < pairs.length; i += 2) {
try {
String key = pairs[i].replaceAll(SEPARATOR_ESCAPE, SERIALIZATION_SEPARATOR);
String value = pairs[i + 1].substring(1);
try { try {
copyStream(fis, fos); putter.put(object, key, pairs[i + 1].charAt(0), value);
} finally { } catch (NumberFormatException e) {
fis.close(); // failed parse to number
fos.close(); putter.put(object, key, 's', value);
Timber.e(e, e.getMessage());
} }
} catch (IndexOutOfBoundsException e) {
Timber.e(e, e.getMessage());
}
} }
}
/**
* Copy stream from source to destination /**
* @throws IOException * Copy a file from one place to another
*/ */
private static void copyStream(InputStream source, OutputStream dest) throws IOException { public static void copyFile(File in, File out) throws Exception {
int bytes; FileInputStream fis = new FileInputStream(in);
byte[] buffer; FileOutputStream fos = new FileOutputStream(out);
int BUFFER_SIZE = 1024; try {
buffer = new byte[BUFFER_SIZE]; copyStream(fis, fos);
while ((bytes = source.read(buffer)) != -1) { } finally {
if (bytes == 0) { fis.close();
bytes = source.read(); fos.close();
if (bytes < 0) {
break;
}
dest.write(bytes);
dest.flush();
continue;
}
dest.write(buffer, 0, bytes);
dest.flush();
}
}
public static int convertDpToPixels(DisplayMetrics displayMetrics, int dp) {
// developer.android.com/guide/practices/screens_support.html#dips-pels
return (int) (dp * displayMetrics.density + 0.5f);
}
public static boolean preLollipop() {
return !atLeastLollipop();
}
public static boolean preJellybean() {
return !atLeastJellybean();
}
public static boolean preOreo() {
return !atLeastOreo();
}
public static boolean atLeastJellybeanMR1() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1;
}
public static boolean atLeastJellybean() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN;
}
public static boolean atLeastKitKat() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
}
public static boolean atLeastLollipop() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP;
}
public static boolean atLeastMarshmallow() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M;
} }
}
/**
* Copy stream from source to destination
*/
private static void copyStream(InputStream source, OutputStream dest) throws IOException {
int bytes;
byte[] buffer;
int BUFFER_SIZE = 1024;
buffer = new byte[BUFFER_SIZE];
while ((bytes = source.read(buffer)) != -1) {
if (bytes == 0) {
bytes = source.read();
if (bytes < 0) {
break;
}
dest.write(bytes);
dest.flush();
continue;
}
public static boolean atLeastNougat() { dest.write(buffer, 0, bytes);
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N; dest.flush();
} }
}
public static boolean atLeastOreo() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O; public static int convertDpToPixels(DisplayMetrics displayMetrics, int dp) {
// developer.android.com/guide/practices/screens_support.html#dips-pels
return (int) (dp * displayMetrics.density + 0.5f);
}
public static boolean preLollipop() {
return !atLeastLollipop();
}
public static boolean preJellybean() {
return !atLeastJellybean();
}
public static boolean preOreo() {
return !atLeastOreo();
}
public static boolean atLeastJellybeanMR1() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1;
}
public static boolean atLeastJellybean() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN;
}
public static boolean atLeastKitKat() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
}
public static boolean atLeastLollipop() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP;
}
public static boolean atLeastMarshmallow() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M;
}
public static boolean atLeastNougat() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N;
}
public static boolean atLeastOreo() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O;
}
/**
* Sleep, ignoring interruption. Before using this method, think carefully
* about why you are ignoring interruptions.
*/
public static void sleepDeep(long l) {
try {
Thread.sleep(l);
} catch (InterruptedException e) {
// ignore
} }
}
/**
* Sleep, ignoring interruption. Before using this method, think carefully /**
* about why you are ignoring interruptions. * Capitalize the first character
*/ */
public static void sleepDeep(long l) { public static String capitalize(String string) {
try { return string.substring(0, 1).toUpperCase() + string.substring(1);
Thread.sleep(l); }
} catch (InterruptedException e) {
// ignore public static void hideKeyboard(Activity activity) {
} try {
View currentFocus = activity.getCurrentFocus();
if (currentFocus != null) {
InputMethodManager inputMethodManager = (InputMethodManager) activity
.getSystemService(Context.INPUT_METHOD_SERVICE);
inputMethodManager.hideSoftInputFromWindow(currentFocus.getWindowToken(), 0);
}
} catch (Exception e) {
Timber.e(e, e.getMessage());
} }
}
/**
* Capitalize the first character /**
*/ * Dismiss the keyboard if it is displayed by any of the listed views
public static String capitalize(String string) { *
return string.substring(0, 1).toUpperCase() + string.substring(1); * @param views - a list of views that might potentially be displaying the keyboard
*/
public static void hideSoftInputForViews(Context context, View... views) {
InputMethodManager imm = (InputMethodManager) context
.getSystemService(Context.INPUT_METHOD_SERVICE);
for (View v : views) {
imm.hideSoftInputFromWindow(v.getWindowToken(), 0);
} }
}
public static void hideKeyboard(Activity activity) {
try { /**
View currentFocus = activity.getCurrentFocus(); * Returns the final word characters after the last '.'
if (currentFocus != null) { */
InputMethodManager inputMethodManager = (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE); public static String getFileExtension(String file) {
inputMethodManager.hideSoftInputFromWindow(currentFocus.getWindowToken(), 0); int index = file.lastIndexOf('.');
} String extension = "";
} catch (Exception e) { if (index > 0) {
Timber.e(e, e.getMessage()); extension = file.substring(index + 1);
} if (!extension.matches("\\w+")) {
extension = "";
}
} }
return extension;
}
/** public interface SerializedPut<T> {
* Dismiss the keyboard if it is displayed by any of the listed views
* @param views - a list of views that might potentially be displaying the keyboard
*/
public static void hideSoftInputForViews(Context context, View...views) {
InputMethodManager imm = (InputMethodManager)context.getSystemService(Context.INPUT_METHOD_SERVICE);
for (View v : views) {
imm.hideSoftInputFromWindow(v.getWindowToken(), 0);
}
}
/** void put(T object, String key, char type, String value) throws NumberFormatException;
* Returns the final word characters after the last '.' }
*/
public static String getFileExtension(String file) {
int index = file.lastIndexOf('.');
String extension = "";
if (index > 0) {
extension = file.substring(index + 1);
if (!extension.matches("\\w+")) {
extension = "";
}
}
return extension;
}
} }

@ -3,206 +3,212 @@
* *
* See the file "LICENSE" for the full license governing this code. * See the file "LICENSE" for the full license governing this code.
*/ */
package com.todoroo.andlib.utility; package com.todoroo.andlib.utility;
import static org.tasks.date.DateTimeUtils.newDateTime;
import static org.tasks.time.DateTimeUtils.currentTimeMillis;
import android.content.Context; import android.content.Context;
import android.text.format.DateFormat; import android.text.format.DateFormat;
import com.todoroo.astrid.data.Task; import com.todoroo.astrid.data.Task;
import org.tasks.R; import org.tasks.R;
import org.tasks.locale.Locale; import org.tasks.locale.Locale;
import org.tasks.time.DateTime; import org.tasks.time.DateTime;
import static org.tasks.date.DateTimeUtils.newDateTime;
import static org.tasks.time.DateTimeUtils.currentTimeMillis;
public class DateUtilities { public class DateUtilities {
private static final long abbreviationLimit = DateUtilities.ONE_DAY * 6; /**
* Represents a single hour
/** */
* Add the specified amount of months to the given time.<br/> public static final long ONE_HOUR = 3600000L;
* The day of month will stay the same.<br/> /**
* * Represents a single day
* @param time the base-time (in milliseconds) to which the amount of months is added */
* @param interval the amount of months to be added public static final long ONE_DAY = 24 * ONE_HOUR;
* @return the calculated time in milliseconds /**
*/ * Represents a single week
public static long addCalendarMonthsToUnixtime(long time, int interval) { */
DateTime dt = new DateTime(time); public static final long ONE_WEEK = 7 * ONE_DAY;
DateTime result = dt.plusMonths(interval); /**
// preserving java.util.date behavior * Represents a single minute
int diff = dt.getDayOfMonth() - result.getDayOfMonth(); */
if(diff > 0) { public static final long ONE_MINUTE = 60000L;
result = result.plusDays(diff); private static final long abbreviationLimit = DateUtilities.ONE_DAY * 6;
} private static final String JA = "MMM d\u65E5";
return result.getMillis(); private static final String JA_YEAR = "yy\u5E74 " + JA;
} private static final String KO = "MMM d\uC77C";
private static final String KO_YEAR = "yy\uB144 " + KO;
/** Returns unixtime for current time */ private static final String ZH = "MMM d\u65E5";
public static long now() { private static final String ZH_YEAR = "yy\u5E74 " + ZH;
return currentTimeMillis(); static Boolean is24HourOverride = null;
}
/**
/** Returns unixtime one month from now */ * Add the specified amount of months to the given time.<br/>
public static long oneMonthFromNow() { * The day of month will stay the same.<br/>
return addCalendarMonthsToUnixtime(currentTimeMillis(), 1); *
} * @param time the base-time (in milliseconds) to which the amount of months is added
* @param interval the amount of months to be added
/** Represents a single hour */ * @return the calculated time in milliseconds
public static final long ONE_HOUR = 3600000L; */
public static long addCalendarMonthsToUnixtime(long time, int interval) {
/** Represents a single day */ DateTime dt = new DateTime(time);
public static final long ONE_DAY = 24 * ONE_HOUR; DateTime result = dt.plusMonths(interval);
// preserving java.util.date behavior
/** Represents a single week */ int diff = dt.getDayOfMonth() - result.getDayOfMonth();
public static final long ONE_WEEK = 7 * ONE_DAY; if (diff > 0) {
result = result.plusDays(diff);
/** Represents a single minute */ }
public static final long ONE_MINUTE = 60000L; return result.getMillis();
}
private static final String JA = "MMM d\u65E5";
private static final String JA_YEAR = "yy\u5E74 " + JA; /**
private static final String KO = "MMM d\uC77C"; * Returns unixtime for current time
private static final String KO_YEAR = "yy\uB144 " + KO; */
private static final String ZH = "MMM d\u65E5"; public static long now() {
private static final String ZH_YEAR = "yy\u5E74 " + ZH; return currentTimeMillis();
}
/* ====================================================================== /* ======================================================================
* =========================================================== formatters * =========================================================== formatters
* ====================================================================== */ * ====================================================================== */
static Boolean is24HourOverride = null; /**
* Returns unixtime one month from now
public static boolean is24HourFormat(Context context) { */
if(is24HourOverride != null) { public static long oneMonthFromNow() {
return is24HourOverride; return addCalendarMonthsToUnixtime(currentTimeMillis(), 1);
} }
return DateFormat.is24HourFormat(context); public static boolean is24HourFormat(Context context) {
} if (is24HourOverride != null) {
return is24HourOverride;
public static String getTimeString(Context context, long timestamp) { }
return getTimeString(context, newDateTime(timestamp));
} return DateFormat.is24HourFormat(context);
}
public static String getTimeString(Context context, DateTime date) {
String value; public static String getTimeString(Context context, long timestamp) {
if (is24HourFormat(context)) { return getTimeString(context, newDateTime(timestamp));
value = "HH:mm"; }
} else if (date.getMinuteOfHour() == 0){
value = "h a"; public static String getTimeString(Context context, DateTime date) {
} else { String value;
value = "h:mm a"; if (is24HourFormat(context)) {
} value = "HH:mm";
return date.toString(value); } else if (date.getMinuteOfHour() == 0) {
} value = "h a";
} else {
public static String getLongDateString(DateTime date) { value = "h:mm a";
return getDateString("MMMM", date); }
} return date.toString(value);
}
/**
* @param date date to format public static String getLongDateString(DateTime date) {
* @return date, with month, day, and year return getDateString("MMMM", date);
*/ }
public static String getDateString(DateTime date) {
return getDateString("MMM", date); /**
} * @param date date to format
* @return date, with month, day, and year
private static String getDateString(String simpleDateFormat, DateTime date) { */
boolean includeYear = date.getYear() != newDateTime().getYear(); public static String getDateString(DateTime date) {
String format = getFormat(Locale.getInstance(), simpleDateFormat, includeYear); return getDateString("MMM", date);
return date.toString(format); }
}
private static String getDateString(String simpleDateFormat, DateTime date) {
private static String getFormat(Locale locale, String monthFormat, boolean includeYear) { boolean includeYear = date.getYear() != newDateTime().getYear();
switch(locale.getLanguage()) { String format = getFormat(Locale.getInstance(), simpleDateFormat, includeYear);
case "ja": return date.toString(format);
return includeYear ? JA_YEAR : JA; }
case "ko":
return includeYear ? KO_YEAR : KO; private static String getFormat(Locale locale, String monthFormat, boolean includeYear) {
case "zh": switch (locale.getLanguage()) {
return includeYear ? ZH_YEAR : ZH; case "ja":
} return includeYear ? JA_YEAR : JA;
switch (locale.getCountry()) { case "ko":
case "BZ": return includeYear ? KO_YEAR : KO;
case "CA": case "zh":
case "KE": return includeYear ? ZH_YEAR : ZH;
case "MN": }
case "US": switch (locale.getCountry()) {
return includeYear case "BZ":
? monthFormat + " d ''yy" case "CA":
: monthFormat + " d"; case "KE":
default: case "MN":
return includeYear case "US":
? "d " + monthFormat + " ''yy" return includeYear
: "d " + monthFormat; ? monthFormat + " d ''yy"
} : monthFormat + " d";
} default:
return includeYear
/** ? "d " + monthFormat + " ''yy"
* @return weekday : "d " + monthFormat;
*/ }
public static String getWeekday(DateTime date) { }
return date.toString("EEEE");
} /**
* @return weekday
/** */
* @return weekday public static String getWeekday(DateTime date) {
*/ return date.toString("EEEE");
public static String getWeekdayShort(DateTime date) { }
return date.toString("EEE");
} /**
* @return weekday
public static String getLongDateStringWithTime(Context context, long timestamp) { */
DateTime date = newDateTime(timestamp); public static String getWeekdayShort(DateTime date) {
return getLongDateString(date) + ", " + getTimeString(context, date); return date.toString("EEE");
} }
public static String getDateStringWithTime(Context context, long timestamp) { public static String getLongDateStringWithTime(Context context, long timestamp) {
DateTime date = newDateTime(timestamp); DateTime date = newDateTime(timestamp);
return getDateString(date) + ", " + getTimeString(context, date); return getLongDateString(date) + ", " + getTimeString(context, date);
} }
public static String getRelativeDateStringWithTime(Context context, long timestamp) { public static String getDateStringWithTime(Context context, long timestamp) {
String string = DateUtilities.getRelativeDay(context, timestamp, false); DateTime date = newDateTime(timestamp);
if (Task.hasDueTime(timestamp)) { return getDateString(date) + ", " + getTimeString(context, date);
string = String.format("%s %s", string, //$NON-NLS-1$ }
DateUtilities.getTimeString(context, timestamp));
} public static String getRelativeDateStringWithTime(Context context, long timestamp) {
return string; String string = DateUtilities.getRelativeDay(context, timestamp, false);
} if (Task.hasDueTime(timestamp)) {
string = String.format("%s %s", string, //$NON-NLS-1$
/** DateUtilities.getTimeString(context, timestamp));
* @return yesterday, today, tomorrow, or null }
*/ return string;
public static String getRelativeDay(Context context, long date, boolean abbreviated) { }
long today = getStartOfDay(currentTimeMillis());
long input = getStartOfDay(date); /**
* @return yesterday, today, tomorrow, or null
if(today == input) { */
return context.getString(R.string.today); public static String getRelativeDay(Context context, long date, boolean abbreviated) {
} long today = getStartOfDay(currentTimeMillis());
long input = getStartOfDay(date);
if(today + ONE_DAY == input) {
return context.getString(abbreviated ? R.string.tmrw : R.string.tomorrow); if (today == input) {
} return context.getString(R.string.today);
}
if(today == input + ONE_DAY) {
return context.getString(abbreviated ? R.string.yest : R.string.yesterday); if (today + ONE_DAY == input) {
} return context.getString(abbreviated ? R.string.tmrw : R.string.tomorrow);
}
if(today + abbreviationLimit >= input && today - abbreviationLimit <= input) {
return abbreviated ? DateUtilities.getWeekdayShort(newDateTime(date)) : DateUtilities.getWeekday(newDateTime(date)); if (today == input + ONE_DAY) {
} return context.getString(abbreviated ? R.string.yest : R.string.yesterday);
}
return getDateString(newDateTime(date));
} if (today + abbreviationLimit >= input && today - abbreviationLimit <= input) {
return abbreviated ? DateUtilities.getWeekdayShort(newDateTime(date))
public static long getStartOfDay(long time) { : DateUtilities.getWeekday(newDateTime(date));
return newDateTime(time).startOfDay().getMillis(); }
}
return getDateString(newDateTime(date));
}
public static long getStartOfDay(long time) {
return newDateTime(time).startOfDay().getMillis();
}
} }

@ -3,30 +3,30 @@
* *
* See the file "LICENSE" for the full license governing this code. * See the file "LICENSE" for the full license governing this code.
*/ */
package com.todoroo.andlib.utility; package com.todoroo.andlib.utility;
import android.app.Activity; import android.app.Activity;
import android.app.Dialog; import android.app.Dialog;
import timber.log.Timber; import timber.log.Timber;
public class DialogUtilities { public class DialogUtilities {
/** /**
* Dismiss a dialog off the UI thread * Dismiss a dialog off the UI thread
*/ */
@Deprecated @Deprecated
public static void dismissDialog(Activity activity, final Dialog dialog) { public static void dismissDialog(Activity activity, final Dialog dialog) {
if(dialog == null) { if (dialog == null) {
return; return;
}
activity.runOnUiThread(() -> {
try {
dialog.dismiss();
} catch(Exception e) {
Timber.e(e, e.getMessage());
}
});
} }
activity.runOnUiThread(() -> {
try {
dialog.dismiss();
} catch (Exception e) {
Timber.e(e, e.getMessage());
}
});
}
} }

@ -3,8 +3,11 @@
* *
* See the file "LICENSE" for the full license governing this code. * See the file "LICENSE" for the full license governing this code.
*/ */
package com.todoroo.astrid.activity; package com.todoroo.astrid.activity;
import static java.util.Arrays.asList;
import android.content.Context; import android.content.Context;
import android.os.Bundle; import android.os.Bundle;
import android.support.v4.content.ContextCompat; import android.support.v4.content.ContextCompat;
@ -12,9 +15,13 @@ import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.Toolbar; import android.support.v7.widget.Toolbar;
import android.view.MenuItem; import android.view.MenuItem;
import butterknife.BindView;
import butterknife.ButterKnife;
import com.google.common.base.Strings; import com.google.common.base.Strings;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.inject.Inject;
import org.tasks.R; import org.tasks.R;
import org.tasks.injection.ActivityComponent; import org.tasks.injection.ActivityComponent;
import org.tasks.injection.ThemedInjectingAppCompatActivity; import org.tasks.injection.ThemedInjectingAppCompatActivity;
@ -22,119 +29,106 @@ import org.tasks.preferences.Preferences;
import org.tasks.preferences.beast.BeastModeRecyclerAdapter; import org.tasks.preferences.beast.BeastModeRecyclerAdapter;
import org.tasks.ui.MenuColorizer; import org.tasks.ui.MenuColorizer;
import java.util.ArrayList; public class BeastModePreferences extends ThemedInjectingAppCompatActivity implements
import java.util.Collections; Toolbar.OnMenuItemClickListener {
import java.util.List;
import javax.inject.Inject;
import butterknife.BindView;
import butterknife.ButterKnife;
import static java.util.Arrays.asList;
public class BeastModePreferences extends ThemedInjectingAppCompatActivity implements Toolbar.OnMenuItemClickListener {
@BindView(R.id.toolbar) Toolbar toolbar;
@BindView(R.id.recycler_view) RecyclerView recyclerView;
private static final String BEAST_MODE_ORDER_PREF = "beast_mode_order_v3"; //$NON-NLS-1$
private static final String BEAST_MODE_PREF_ITEM_SEPARATOR = ";";
private BeastModeRecyclerAdapter adapter;
@Inject Preferences preferences; private static final String BEAST_MODE_ORDER_PREF = "beast_mode_order_v3"; //$NON-NLS-1$
private static final String BEAST_MODE_PREF_ITEM_SEPARATOR = ";";
@BindView(R.id.toolbar) Toolbar toolbar;
@BindView(R.id.recycler_view) RecyclerView recyclerView;
@Inject Preferences preferences;
private BeastModeRecyclerAdapter adapter;
@Override public static void setDefaultOrder(Preferences preferences, Context context) {
protected void onCreate(Bundle savedInstanceState) { if (preferences.getStringValue(BEAST_MODE_ORDER_PREF) != null) {
super.onCreate(savedInstanceState); return;
setContentView(R.layout.beast_mode_pref_activity);
ButterKnife.bind(this);
toolbar.setNavigationIcon(ContextCompat.getDrawable(this, R.drawable.ic_arrow_back_24dp));
toolbar.setNavigationOnClickListener(v -> finish());
toolbar.inflateMenu(R.menu.beast_mode);
toolbar.setOnMenuItemClickListener(this);
MenuColorizer.colorToolbar(this, toolbar);
adapter = new BeastModeRecyclerAdapter(this, constructOrderedControlList(preferences, this));
recyclerView.setHasFixedSize(true);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
adapter.applyToRecyclerView(recyclerView);
} }
@Override ArrayList<String> list = constructOrderedControlList(preferences, context);
public void inject(ActivityComponent component) { StringBuilder newSetting = new StringBuilder();
component.inject(this); for (String item : list) {
newSetting.append(item);
newSetting.append(BEAST_MODE_PREF_ITEM_SEPARATOR);
} }
preferences.setString(BEAST_MODE_ORDER_PREF, newSetting.toString());
@Override }
public boolean onMenuItemClick(MenuItem item) {
switch(item.getItemId()) { public static ArrayList<String> constructOrderedControlList(Preferences preferences,
case R.id.menu_reset_to_defaults: Context context) {
String[] prefsArray = getResources().getStringArray(R.array.TEA_control_sets_prefs); String order = preferences.getStringValue(BEAST_MODE_ORDER_PREF);
adapter.setItems(asList(prefsArray)); ArrayList<String> list = new ArrayList<>();
return true; String[] itemsArray;
default: if (order == null) {
return super.onOptionsItemSelected(item); itemsArray = context.getResources().getStringArray(R.array.TEA_control_sets_prefs);
} } else {
itemsArray = order.split(BEAST_MODE_PREF_ITEM_SEPARATOR);
} }
@Override Collections.addAll(list, itemsArray);
public void finish() {
List<String> items = adapter.getItems();
StringBuilder newSetting = new StringBuilder();
for (int i = 0; i < items.size(); i++) {
newSetting.append(items.get(i));
newSetting.append(BEAST_MODE_PREF_ITEM_SEPARATOR);
}
String oldValue = preferences.getStringValue(BEAST_MODE_ORDER_PREF);
String newValue = newSetting.toString();
if (Strings.isNullOrEmpty(oldValue) || !oldValue.equals(newValue)) {
preferences.setString(BEAST_MODE_ORDER_PREF, newSetting.toString());
setResult(RESULT_OK);
}
super.finish();
}
public static void setDefaultOrder(Preferences preferences, Context context) { if (order == null) {
if (preferences.getStringValue(BEAST_MODE_ORDER_PREF) != null) { return list;
return;
}
ArrayList<String> list = constructOrderedControlList(preferences, context);
StringBuilder newSetting = new StringBuilder();
for (String item : list) {
newSetting.append(item);
newSetting.append(BEAST_MODE_PREF_ITEM_SEPARATOR);
}
preferences.setString(BEAST_MODE_ORDER_PREF, newSetting.toString());
} }
public static ArrayList<String> constructOrderedControlList(Preferences preferences, Context context) { itemsArray = context.getResources().getStringArray(R.array.TEA_control_sets_prefs);
String order = preferences.getStringValue(BEAST_MODE_ORDER_PREF); for (int i = 0; i < itemsArray.length; i++) {
ArrayList<String> list = new ArrayList<>(); if (!list.contains(itemsArray[i])) {
String[] itemsArray; list.add(i, itemsArray[i]);
if (order == null) { }
itemsArray = context.getResources().getStringArray(R.array.TEA_control_sets_prefs); }
} else { return list;
itemsArray = order.split(BEAST_MODE_PREF_ITEM_SEPARATOR); }
}
@Override
Collections.addAll(list, itemsArray); protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (order == null) {
return list; setContentView(R.layout.beast_mode_pref_activity);
} ButterKnife.bind(this);
itemsArray = context.getResources().getStringArray(R.array.TEA_control_sets_prefs); toolbar.setNavigationIcon(ContextCompat.getDrawable(this, R.drawable.ic_arrow_back_24dp));
for (int i = 0; i < itemsArray.length; i++) { toolbar.setNavigationOnClickListener(v -> finish());
if (!list.contains(itemsArray[i])) { toolbar.inflateMenu(R.menu.beast_mode);
list.add(i, itemsArray[i]); toolbar.setOnMenuItemClickListener(this);
} MenuColorizer.colorToolbar(this, toolbar);
}
return list; adapter = new BeastModeRecyclerAdapter(this, constructOrderedControlList(preferences, this));
recyclerView.setHasFixedSize(true);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
adapter.applyToRecyclerView(recyclerView);
}
@Override
public void inject(ActivityComponent component) {
component.inject(this);
}
@Override
public boolean onMenuItemClick(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_reset_to_defaults:
String[] prefsArray = getResources().getStringArray(R.array.TEA_control_sets_prefs);
adapter.setItems(asList(prefsArray));
return true;
default:
return super.onOptionsItemSelected(item);
}
}
@Override
public void finish() {
List<String> items = adapter.getItems();
StringBuilder newSetting = new StringBuilder();
for (int i = 0; i < items.size(); i++) {
newSetting.append(items.get(i));
newSetting.append(BEAST_MODE_PREF_ITEM_SEPARATOR);
}
String oldValue = preferences.getStringValue(BEAST_MODE_ORDER_PREF);
String newValue = newSetting.toString();
if (Strings.isNullOrEmpty(oldValue) || !oldValue.equals(newValue)) {
preferences.setString(BEAST_MODE_ORDER_PREF, newSetting.toString());
setResult(RESULT_OK);
} }
super.finish();
}
} }

@ -1,65 +1,63 @@
/** /**
* TODO: make this lightweight, don't extend the entire TaskListActivity * TODO: make this lightweight, don't extend the entire TaskListActivity
*/ */
package com.todoroo.astrid.activity; package com.todoroo.astrid.activity;
import static org.tasks.intents.TaskIntents.getEditTaskStack;
import android.content.Intent; import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import com.todoroo.astrid.dao.TaskDao; import com.todoroo.astrid.dao.TaskDao;
import com.todoroo.astrid.data.Task; import com.todoroo.astrid.data.Task;
import com.todoroo.astrid.service.TaskCreator; import com.todoroo.astrid.service.TaskCreator;
import javax.inject.Inject;
import org.tasks.injection.ActivityComponent; import org.tasks.injection.ActivityComponent;
import org.tasks.injection.InjectingAppCompatActivity; import org.tasks.injection.InjectingAppCompatActivity;
import javax.inject.Inject;
import static org.tasks.intents.TaskIntents.getEditTaskStack;
/** /**
* @author joshuagross * @author joshuagross
* *
* Create a new task based on incoming links from the "share" menu * Create a new task based on incoming links from the "share" menu
*/ */
public final class ShareLinkActivity extends InjectingAppCompatActivity { public final class ShareLinkActivity extends InjectingAppCompatActivity {
@Inject TaskCreator taskCreator; @Inject TaskCreator taskCreator;
@Inject TaskDao taskDao; @Inject TaskDao taskDao;
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
readIntent(); readIntent();
} }
@Override @Override
public void inject(ActivityComponent component) { public void inject(ActivityComponent component) {
component.inject(this); component.inject(this);
} }
@Override @Override
protected void onNewIntent(Intent intent) { protected void onNewIntent(Intent intent) {
super.onNewIntent(intent); super.onNewIntent(intent);
setIntent(intent); setIntent(intent);
readIntent(); readIntent();
} }
private void readIntent() { private void readIntent() {
Intent intent = getIntent(); Intent intent = getIntent();
String subject = intent.getStringExtra(Intent.EXTRA_SUBJECT); String subject = intent.getStringExtra(Intent.EXTRA_SUBJECT);
if (subject == null) { if (subject == null) {
subject = ""; subject = "";
} }
Task task = taskCreator.createWithValues(null, subject); Task task = taskCreator.createWithValues(null, subject);
if (task != null) { if (task != null) {
task.setNotes(intent.getStringExtra(Intent.EXTRA_TEXT)); task.setNotes(intent.getStringExtra(Intent.EXTRA_TEXT));
getEditTaskStack(this, null, task).startActivities(); getEditTaskStack(this, null, task).startActivities();
}
finish();
} }
finish();
}
} }

@ -6,18 +6,18 @@ import android.os.Bundle;
public class TaskEditActivity extends Activity { public class TaskEditActivity extends Activity {
private static final String TOKEN_ID = "id"; private static final String TOKEN_ID = "id";
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
final long taskId = getIntent().getLongExtra(TOKEN_ID, 0); final long taskId = getIntent().getLongExtra(TOKEN_ID, 0);
Intent intent = new Intent(this, TaskListActivity.class); Intent intent = new Intent(this, TaskListActivity.class);
intent.putExtra(TaskListActivity.OPEN_TASK, taskId); intent.putExtra(TaskListActivity.OPEN_TASK, taskId);
startActivity(intent); startActivity(intent);
finish(); finish();
} }
} }

@ -3,8 +3,11 @@
* *
* See the file "LICENSE" for the full license governing this code. * See the file "LICENSE" for the full license governing this code.
*/ */
package com.todoroo.astrid.activity; package com.todoroo.astrid.activity;
import static org.tasks.date.DateTimeUtils.newDateTime;
import android.app.Activity; import android.app.Activity;
import android.content.Context; import android.content.Context;
import android.os.Bundle; import android.os.Bundle;
@ -18,7 +21,8 @@ import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.LinearLayout; import android.widget.LinearLayout;
import butterknife.BindView;
import butterknife.ButterKnife;
import com.todoroo.andlib.utility.AndroidUtilities; import com.todoroo.andlib.utility.AndroidUtilities;
import com.todoroo.andlib.utility.DateUtilities; import com.todoroo.andlib.utility.DateUtilities;
import com.todoroo.astrid.api.Filter; import com.todoroo.astrid.api.Filter;
@ -31,7 +35,8 @@ import com.todoroo.astrid.service.TaskDeleter;
import com.todoroo.astrid.timers.TimerPlugin; import com.todoroo.astrid.timers.TimerPlugin;
import com.todoroo.astrid.ui.EditTitleControlSet; import com.todoroo.astrid.ui.EditTitleControlSet;
import com.todoroo.astrid.utility.Flags; import com.todoroo.astrid.utility.Flags;
import java.util.List;
import javax.inject.Inject;
import org.tasks.LocalBroadcastManager; import org.tasks.LocalBroadcastManager;
import org.tasks.R; import org.tasks.R;
import org.tasks.analytics.Tracker; import org.tasks.analytics.Tracker;
@ -48,146 +53,164 @@ import org.tasks.ui.MenuColorizer;
import org.tasks.ui.RemoteListFragment; import org.tasks.ui.RemoteListFragment;
import org.tasks.ui.TaskEditControlFragment; import org.tasks.ui.TaskEditControlFragment;
import java.util.List; public final class TaskEditFragment extends InjectingFragment implements
Toolbar.OnMenuItemClickListener {
import javax.inject.Inject;
public static final String TAG_TASKEDIT_FRAGMENT = "taskedit_fragment";
import butterknife.BindView; private static final String EXTRA_TASK = "extra_task";
import butterknife.ButterKnife; @Inject TaskDao taskDao;
@Inject UserActivityDao userActivityDao;
import static org.tasks.date.DateTimeUtils.newDateTime; @Inject TaskDeleter taskDeleter;
@Inject NotificationManager notificationManager;
public final class TaskEditFragment extends InjectingFragment implements Toolbar.OnMenuItemClickListener { @Inject DialogBuilder dialogBuilder;
@Inject @ForActivity
public interface TaskEditFragmentCallbackHandler { Context context;
void taskEditFinished(); @Inject TaskEditControlSetFragmentManager taskEditControlSetFragmentManager;
@Inject CommentsController commentsController;
@Inject Preferences preferences;
@Inject Tracker tracker;
@Inject TimerPlugin timerPlugin;
@Inject LocalBroadcastManager localBroadcastManager;
@BindView(R.id.toolbar) Toolbar toolbar;
@BindView(R.id.comments) LinearLayout comments;
@BindView(R.id.control_sets) LinearLayout controlSets;
Task model = null;
private TaskEditFragmentCallbackHandler callback;
public static TaskEditFragment newTaskEditFragment(Task task) {
TaskEditFragment taskEditFragment = new TaskEditFragment();
Bundle arguments = new Bundle();
arguments.putParcelable(EXTRA_TASK, task);
taskEditFragment.setArguments(arguments);
return taskEditFragment;
}
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
callback = (TaskEditFragmentCallbackHandler) activity;
}
@Override
protected void inject(FragmentComponent component) {
component.inject(this);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_task_edit, container, false);
ButterKnife.bind(this, view);
Bundle arguments = getArguments();
model = arguments.getParcelable(EXTRA_TASK);
final boolean backButtonSavesTask = preferences.backButtonSavesTask();
toolbar.setNavigationIcon(ContextCompat.getDrawable(context,
backButtonSavesTask ? R.drawable.ic_close_24dp : R.drawable.ic_save_24dp));
toolbar.setNavigationOnClickListener(v -> {
if (backButtonSavesTask) {
discardButtonClick();
} else {
save();
}
});
toolbar.inflateMenu(R.menu.menu_task_edit_fragment);
toolbar.setOnMenuItemClickListener(this);
MenuColorizer.colorToolbar(context, toolbar);
if (!model.isNew()) {
notificationManager.cancel(model.getId());
} }
public static TaskEditFragment newTaskEditFragment(Task task) { commentsController.initialize(model, comments);
TaskEditFragment taskEditFragment = new TaskEditFragment(); commentsController.reloadView();
Bundle arguments = new Bundle();
arguments.putParcelable(EXTRA_TASK, task);
taskEditFragment.setArguments(arguments);
return taskEditFragment;
}
public static final String TAG_TASKEDIT_FRAGMENT = "taskedit_fragment";
private static final String EXTRA_TASK = "extra_task";
@Inject TaskDao taskDao;
@Inject UserActivityDao userActivityDao;
@Inject TaskDeleter taskDeleter;
@Inject NotificationManager notificationManager;
@Inject DialogBuilder dialogBuilder;
@Inject @ForActivity Context context;
@Inject TaskEditControlSetFragmentManager taskEditControlSetFragmentManager;
@Inject CommentsController commentsController;
@Inject Preferences preferences;
@Inject Tracker tracker;
@Inject TimerPlugin timerPlugin;
@Inject LocalBroadcastManager localBroadcastManager;
@BindView(R.id.toolbar) Toolbar toolbar; FragmentManager fragmentManager = getChildFragmentManager();
@BindView(R.id.comments) LinearLayout comments; List<TaskEditControlFragment> taskEditControlFragments = taskEditControlSetFragmentManager
@BindView(R.id.control_sets) LinearLayout controlSets; .getOrCreateFragments(fragmentManager, model);
Task model = null; FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
for (int i = 0; i < taskEditControlFragments.size(); i++) {
private TaskEditFragmentCallbackHandler callback; TaskEditControlFragment taskEditControlFragment = taskEditControlFragments.get(i);
String tag = getString(taskEditControlFragment.controlId());
@Override fragmentTransaction
public void onAttach(Activity activity) { .replace(TaskEditControlSetFragmentManager.TASK_EDIT_CONTROL_FRAGMENT_ROWS[i],
super.onAttach(activity); taskEditControlFragment, tag);
callback = (TaskEditFragmentCallbackHandler) activity;
} }
fragmentTransaction.commit();
@Override for (int i = taskEditControlFragments.size() - 2; i > 1; i--) {
protected void inject(FragmentComponent component) { controlSets.addView(inflater.inflate(R.layout.task_edit_row_divider, controlSets, false), i);
component.inject(this);
} }
@Override return view;
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { }
View view = inflater.inflate(R.layout.fragment_task_edit, container, false);
ButterKnife.bind(this, view);
Bundle arguments = getArguments();
model = arguments.getParcelable(EXTRA_TASK);
final boolean backButtonSavesTask = preferences.backButtonSavesTask();
toolbar.setNavigationIcon(ContextCompat.getDrawable(context,
backButtonSavesTask ? R.drawable.ic_close_24dp : R.drawable.ic_save_24dp));
toolbar.setNavigationOnClickListener(v -> {
if (backButtonSavesTask) {
discardButtonClick();
} else {
save();
}
});
toolbar.inflateMenu(R.menu.menu_task_edit_fragment);
toolbar.setOnMenuItemClickListener(this);
MenuColorizer.colorToolbar(context, toolbar);
if (!model.isNew()) {
notificationManager.cancel(model.getId());
}
commentsController.initialize(model, comments); @Override
commentsController.reloadView(); public boolean onMenuItemClick(MenuItem item) {
AndroidUtilities.hideKeyboard(getActivity());
FragmentManager fragmentManager = getChildFragmentManager();
List<TaskEditControlFragment> taskEditControlFragments = taskEditControlSetFragmentManager.getOrCreateFragments(fragmentManager, model);
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
for (int i = 0 ; i < taskEditControlFragments.size() ; i++) {
TaskEditControlFragment taskEditControlFragment = taskEditControlFragments.get(i);
String tag = getString(taskEditControlFragment.controlId());
fragmentTransaction.replace(TaskEditControlSetFragmentManager.TASK_EDIT_CONTROL_FRAGMENT_ROWS[i], taskEditControlFragment, tag);
}
fragmentTransaction.commit();
switch (item.getItemId()) {
for (int i = taskEditControlFragments.size() - 2; i > 1 ; i--) { case R.id.menu_delete:
controlSets.addView(inflater.inflate(R.layout.task_edit_row_divider, controlSets, false), i); deleteButtonClick();
} return true;
return view;
}
@Override
public boolean onMenuItemClick(MenuItem item) {
AndroidUtilities.hideKeyboard(getActivity());
switch (item.getItemId()) {
case R.id.menu_delete:
deleteButtonClick();
return true;
}
return false;
} }
public Task stopTimer() { return false;
timerPlugin.stopTimer(model); }
String elapsedTime = DateUtils.formatElapsedTime(model.getElapsedSeconds());
addComment(String.format("%s %s\n%s %s", //$NON-NLS-1$ public Task stopTimer() {
getString(R.string.TEA_timer_comment_stopped), timerPlugin.stopTimer(model);
DateUtilities.getTimeString(getActivity(), newDateTime()), String elapsedTime = DateUtils.formatElapsedTime(model.getElapsedSeconds());
getString(R.string.TEA_timer_comment_spent), addComment(String.format("%s %s\n%s %s", //$NON-NLS-1$
elapsedTime), null); getString(R.string.TEA_timer_comment_stopped),
return model; DateUtilities.getTimeString(getActivity(), newDateTime()),
} getString(R.string.TEA_timer_comment_spent),
elapsedTime), null);
public Task startTimer() { return model;
timerPlugin.startTimer(model); }
addComment(String.format("%s %s",
getString(R.string.TEA_timer_comment_started), public Task startTimer() {
DateUtilities.getTimeString(getActivity(), newDateTime())), timerPlugin.startTimer(model);
null); addComment(String.format("%s %s",
return model; getString(R.string.TEA_timer_comment_started),
DateUtilities.getTimeString(getActivity(), newDateTime())),
null);
return model;
}
/**
* Save task model from values in UI components
*/
public void save() {
List<TaskEditControlFragment> fragments = taskEditControlSetFragmentManager
.getFragmentsInPersistOrder(getChildFragmentManager());
if (hasChanges(fragments)) {
boolean isNewTask = model.isNew();
if (isNewTask) {
taskDao.createNew(model);
}
for (TaskEditControlFragment fragment : fragments) {
fragment.apply(model);
}
taskDao.save(model, null);
if (Flags.checkAndClear(Flags.TAGS_CHANGED)) {
localBroadcastManager.broadcastRefreshList();
}
if (isNewTask) {
((TaskListActivity) getActivity())
.getTaskListFragment()
.onTaskCreated(model.getUuid());
}
callback.taskEditFinished();
} else {
discard();
} }
}
/* /*
* ====================================================================== * ======================================================================
@ -195,54 +218,39 @@ public final class TaskEditFragment extends InjectingFragment implements Toolbar
* ====================================================================== * ======================================================================
*/ */
/** Save task model from values in UI components */ private EditTitleControlSet getEditTitleControlSet() {
public void save() { return getFragment(EditTitleControlSet.TAG);
List<TaskEditControlFragment> fragments = taskEditControlSetFragmentManager.getFragmentsInPersistOrder(getChildFragmentManager()); }
if (hasChanges(fragments)) {
boolean isNewTask = model.isNew();
if (isNewTask) {
taskDao.createNew(model);
}
for (TaskEditControlFragment fragment : fragments) {
fragment.apply(model);
}
taskDao.save(model, null);
if (Flags.checkAndClear(Flags.TAGS_CHANGED)) {
localBroadcastManager.broadcastRefreshList();
}
if (isNewTask) {
((TaskListActivity) getActivity())
.getTaskListFragment()
.onTaskCreated(model.getUuid());
}
callback.taskEditFinished();
} else {
discard();
}
}
private EditTitleControlSet getEditTitleControlSet() { private RemoteListFragment getRemoteListFragment() {
return getFragment(EditTitleControlSet.TAG); return getFragment(RemoteListFragment.TAG);
} }
private RemoteListFragment getRemoteListFragment() { private RepeatControlSet getRepeatControlSet() {
return getFragment(RemoteListFragment.TAG); return getFragment(RepeatControlSet.TAG);
} }
private RepeatControlSet getRepeatControlSet() { private FilesControlSet getFilesControlSet() {
return getFragment(RepeatControlSet.TAG); return getFragment(FilesControlSet.TAG);
} }
private FilesControlSet getFilesControlSet() { @SuppressWarnings("unchecked")
return getFragment(FilesControlSet.TAG); private <T extends TaskEditControlFragment> T getFragment(int tag) {
} return (T) getChildFragmentManager().findFragmentByTag(getString(tag));
}
@SuppressWarnings("unchecked") private boolean hasChanges(List<TaskEditControlFragment> fragments) {
private <T extends TaskEditControlFragment> T getFragment(int tag) { try {
return (T) getChildFragmentManager().findFragmentByTag(getString(tag)); for (TaskEditControlFragment fragment : fragments) {
if (fragment.hasChanges(model)) {
return true;
}
}
} catch (Exception e) {
tracker.reportException(e);
} }
return false;
}
/* /*
* ====================================================================== * ======================================================================
@ -250,48 +258,40 @@ public final class TaskEditFragment extends InjectingFragment implements Toolbar
* ====================================================================== * ======================================================================
*/ */
private boolean hasChanges(List<TaskEditControlFragment> fragments) { public void discardButtonClick() {
try { if (hasChanges(
for (TaskEditControlFragment fragment : fragments) { taskEditControlSetFragmentManager.getFragmentsInPersistOrder(getChildFragmentManager()))) {
if (fragment.hasChanges(model)) { dialogBuilder.newMessageDialog(R.string.discard_confirmation)
return true; .setPositiveButton(R.string.keep_editing, null)
} .setNegativeButton(R.string.discard, (dialog, which) -> discard())
} .show();
} catch(Exception e) { } else {
tracker.reportException(e); discard();
}
return false;
} }
}
public void discardButtonClick() { public void discard() {
if (hasChanges(taskEditControlSetFragmentManager.getFragmentsInPersistOrder(getChildFragmentManager()))) { if (model != null && model.isNew()) {
dialogBuilder.newMessageDialog(R.string.discard_confirmation) timerPlugin.stopTimer(model);
.setPositiveButton(R.string.keep_editing, null)
.setNegativeButton(R.string.discard, (dialog, which) -> discard())
.show();
} else {
discard();
}
} }
public void discard() { callback.taskEditFinished();
if (model != null && model.isNew()) { }
timerPlugin.stopTimer(model);
}
callback.taskEditFinished(); private void deleteButtonClick() {
} dialogBuilder.newMessageDialog(R.string.DLG_delete_this_task_question)
.setPositiveButton(android.R.string.ok, (dialog, which) -> {
timerPlugin.stopTimer(model);
taskDeleter.markDeleted(model);
callback.taskEditFinished();
})
.setNegativeButton(android.R.string.cancel, null)
.show();
}
private void deleteButtonClick() { public void onPriorityChange(int priority) {
dialogBuilder.newMessageDialog(R.string.DLG_delete_this_task_question) getEditTitleControlSet().setPriority(priority);
.setPositiveButton(android.R.string.ok, (dialog, which) -> { }
timerPlugin.stopTimer(model);
taskDeleter.markDeleted(model);
callback.taskEditFinished();
})
.setNegativeButton(android.R.string.cancel, null)
.show();
}
/* /*
* ====================================================================== * ======================================================================
@ -299,34 +299,35 @@ public final class TaskEditFragment extends InjectingFragment implements Toolbar
* ====================================================================== * ======================================================================
*/ */
public void onPriorityChange(int priority) { public void onRepeatChanged(boolean repeat) {
getEditTitleControlSet().setPriority(priority); getEditTitleControlSet().repeatChanged(repeat);
} }
public void onRepeatChanged(boolean repeat) { public void onRemoteListChanged(Filter list) {
getEditTitleControlSet().repeatChanged(repeat); getRemoteListFragment().setList(list);
} }
public void onRemoteListChanged(Filter list) { public void onDueDateChanged(long dueDate) {
getRemoteListFragment().setList(list); RepeatControlSet repeatControlSet = getRepeatControlSet();
if (repeatControlSet != null) {
repeatControlSet.onDueDateChanged(dueDate);
} }
}
public void onDueDateChanged(long dueDate) {
RepeatControlSet repeatControlSet = getRepeatControlSet(); public void addComment(String message, String picture) {
if (repeatControlSet != null) { UserActivity userActivity = new UserActivity();
repeatControlSet.onDueDateChanged(dueDate); userActivity.setMessage(message);
} userActivity.setTargetId(model.getUuid());
userActivity.setCreated(DateUtilities.now());
if (picture != null) {
userActivity.setPicture(picture);
} }
userActivityDao.createNew(userActivity);
commentsController.reloadView();
}
public void addComment(String message, String picture) { public interface TaskEditFragmentCallbackHandler {
UserActivity userActivity = new UserActivity();
userActivity.setMessage(message); void taskEditFinished();
userActivity.setTargetId(model.getUuid()); }
userActivity.setCreated(DateUtilities.now());
if (picture != null) {
userActivity.setPicture(picture);
}
userActivityDao.createNew(userActivity);
commentsController.reloadView();
}
} }

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save