Merge remote-tracking branch 'upstream/master'

# Conflicts:
#	src/main/res/values/keys.xml
#	src/main/res/xml/preferences_gtasks.xml
gtask_note_sync
Bernhard Mähr 7 years ago
commit 0b194a6cf9

@ -10,7 +10,7 @@ android:
- tools # https://github.com/travis-ci/travis-ci/issues/6040
- android-25
- platform-tools
- build-tools-25.0.2
- build-tools-25.0.3
- extra-android-m2repository
- extra-google-m2repository
licenses:
@ -23,5 +23,5 @@ before_install:
- adb shell input keyevent 82 &
script:
- ./gradlew :lintGoogleplayProdDebug
- ./gradlew :connectedGoogleplayProdDebugAndroidTest
- ./gradlew :lintGoogleplayDebug
- ./gradlew :connectedGoogleplayDebugAndroidTest

@ -7,10 +7,13 @@ task wrapper(type: Wrapper) {
buildscript {
repositories {
jcenter()
maven {
url 'https://maven.google.com'
}
}
dependencies {
classpath 'com.android.tools.build:gradle:2.3.0'
classpath 'com.android.tools.build:gradle:3.0.0-alpha3'
}
}
@ -26,18 +29,15 @@ android {
}
compileSdkVersion 25
buildToolsVersion "25.0.2"
buildToolsVersion "25.0.3"
defaultConfig {
applicationId "org.tasks"
versionCode 448
versionName "4.9.10"
minSdkVersion 15
versionCode 457
versionName "4.9.14"
targetSdkVersion 25
minSdkVersion 15
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
jackOptions {
enabled true
}
}
signingConfigs {
@ -47,7 +47,6 @@ android {
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
incremental false
}
buildTypes {
@ -61,7 +60,7 @@ android {
}
}
flavorDimensions 'store', 'env'
flavorDimensions 'store'
productFlavors {
generic {
@ -73,13 +72,6 @@ android {
amazon {
dimension 'store'
}
dev {
minSdkVersion 21
dimension 'env'
}
prod {
dimension 'env'
}
}
if (project.hasProperty('keyAlias') &&
@ -101,9 +93,10 @@ configurations {
}
final DAGGER_VERSION = '2.9'
final BUTTERKNIFE_VERSION = '8.5.1'
final GPS_VERSION = '10.0.1'
final SUPPORT_VERSION = '25.2.0'
final BUTTERKNIFE_VERSION = '8.6.0'
final GPS_VERSION = '10.2.6'
final SUPPORT_VERSION = '25.3.1'
final SUPPORT_ANNOTATIONS_VERSION = '26.0.0-alpha1'
final STETHO_VERSION = '1.4.2'
final TESTING_SUPPORT_VERSION = '0.5'
@ -114,7 +107,9 @@ dependencies {
annotationProcessor "com.jakewharton:butterknife-compiler:${BUTTERKNIFE_VERSION}"
compile "com.jakewharton:butterknife:${BUTTERKNIFE_VERSION}"
debugCompile "com.facebook.stetho:stetho:${STETHO_VERSION}"
debugCompile ("com.facebook.stetho:stetho:${STETHO_VERSION}") {
exclude group: 'com.google.code.findbugs', module: 'jsr305'
}
debugCompile "com.facebook.stetho:stetho-timber:${STETHO_VERSION}@aar"
debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5'
//noinspection GradleCompatible
@ -128,6 +123,7 @@ dependencies {
compile "com.android.support:cardview-v7:${SUPPORT_VERSION}"
compile 'com.jakewharton.timber:timber:4.5.1'
compile 'com.jakewharton.threetenabp:threetenabp:1.0.5'
//noinspection GradleDependency
compile 'com.google.guava:guava:20.0'
compile 'com.jakewharton:process-phoenix:1.1.1'
compile 'com.google.android.apps.dashclock:dashclock-api:2.0.0'
@ -158,5 +154,5 @@ dependencies {
androidTestCompile 'com.natpryce:make-it-easy:4.0.1'
androidTestCompile "com.android.support.test:runner:${TESTING_SUPPORT_VERSION}"
androidTestCompile "com.android.support.test:rules:${TESTING_SUPPORT_VERSION}"
androidTestCompile "com.android.support:support-annotations:${SUPPORT_VERSION}"
androidTestCompile "com.android.support:support-annotations:${SUPPORT_ANNOTATIONS_VERSION}"
}

@ -1,6 +1,6 @@
#Thu Jan 26 17:04:55 CST 2017
#Tue May 23 14:22:03 CDT 2017
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-4.0-milestone-1-all.zip

13
proguard.pro vendored

@ -24,16 +24,3 @@
-dontwarn javax.inject.**
-dontwarn com.google.j2objc.annotations.**
-dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement
# https://github.com/square/leakcanary/blob/457175960e341658fc908ee12a9bf803bca63a23/leakcanary-android/consumer-proguard-rules.pro
-dontwarn com.squareup.haha.guava.**
-dontwarn com.squareup.haha.perflib.**
-dontwarn com.squareup.haha.trove.**
-dontwarn com.squareup.leakcanary.**
-keep class com.squareup.haha.** { *; }
-keep class com.squareup.leakcanary.** { *; }
-dontwarn android.app.Notification
# https://github.com/facebook/stetho/blob/2807d4248c6fa06cdd3626b6afb9bfc42ba50d55/stetho/proguard-consumer.pro
-keep class com.facebook.stetho.** { *; }
-dontwarn com.facebook.stetho.**

@ -74,17 +74,21 @@ public class Tracker {
}
public void reportEvent(Tracking.Events event, String label) {
reportEvent(event.category, event.action, 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, String action, String label) {
reportEvent(event.category, action, label);
}
private void reportEvent(int category, int action, String label) {
private void reportEvent(int category, String action, String label) {
HitBuilders.EventBuilder eventBuilder = new HitBuilders.EventBuilder()
.setCategory(context.getString(category))
.setAction(context.getString(action));
.setAction(action);
if (!Strings.isNullOrEmpty(label)) {
eventBuilder.setLabel(label);
}

@ -12,8 +12,6 @@ import org.tasks.receivers.BootCompletedReceiver;
import org.tasks.receivers.CompleteTaskReceiver;
import org.tasks.receivers.ListNotificationReceiver;
import org.tasks.receivers.MyPackageReplacedReceiver;
import org.tasks.receivers.RefreshReceiver;
import org.tasks.receivers.TaskNotificationReceiver;
import org.tasks.receivers.TeslaUnreadReceiver;
import org.tasks.widget.TasksWidget;
@ -37,10 +35,6 @@ public interface BroadcastComponent {
void inject(MyPackageReplacedReceiver myPackageReplacedReceiver);
void inject(RefreshReceiver refreshReceiver);
void inject(TaskNotificationReceiver taskNotificationReceiver);
void inject(CompleteTaskReceiver completeTaskReceiver);
void inject(ListNotificationReceiver listNotificationReceiver);

@ -1,72 +0,0 @@
package com.todoroo.astrid.reminders;
import android.support.test.runner.AndroidJUnit4;
import com.todoroo.astrid.data.Task;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.tasks.preferences.Preferences;
import org.tasks.time.DateTime;
import static android.support.test.InstrumentationRegistry.getContext;
import static android.support.test.InstrumentationRegistry.getTargetContext;
import static com.natpryce.makeiteasy.MakeItEasy.with;
import static com.todoroo.astrid.data.Task.NOTIFY_AT_DEADLINE;
import static com.todoroo.astrid.reminders.ReminderService.NO_ALARM;
import static junit.framework.Assert.assertEquals;
import static org.tasks.makers.TaskMaker.DUE_DATE;
import static org.tasks.makers.TaskMaker.DUE_TIME;
import static org.tasks.makers.TaskMaker.REMINDERS;
import static org.tasks.makers.TaskMaker.REMINDER_LAST;
import static org.tasks.makers.TaskMaker.newTask;
@RunWith(AndroidJUnit4.class)
public class NotifyAtDeadlineTest {
private ReminderService reminderService;
@Before
public void setUp() {
Preferences preferences = new Preferences(getTargetContext(), null);
reminderService = new ReminderService(getContext(), preferences, null);
}
@Test
public void testNoReminderWhenNoDueDate() {
Task task = newTask(with(REMINDERS, NOTIFY_AT_DEADLINE));
assertEquals(NO_ALARM, reminderService.calculateNextDueDateReminder(task));
}
@Test
public void testNoReminderWhenNotifyAtDeadlineFlagNotSet() {
Task task = newTask(with(DUE_TIME, new DateTime(2014, 1, 24, 19, 23)));
assertEquals(NO_ALARM, reminderService.calculateNextDueDateReminder(task));
}
@Test
public void testScheduleReminderAtDueTime() {
final DateTime dueDate = new DateTime(2014, 1, 24, 19, 23);
Task task = newTask(with(DUE_TIME, dueDate), with(REMINDERS, NOTIFY_AT_DEADLINE));
assertEquals(dueDate.plusSeconds(1).getMillis(), reminderService.calculateNextDueDateReminder(task));
}
@Test
public void testScheduleReminderAtDefaultDueTime() {
final DateTime dueDate = new DateTime(2015, 12, 29, 12, 0);
Task task = newTask(with(DUE_DATE, dueDate), with(REMINDERS, NOTIFY_AT_DEADLINE));
assertEquals(dueDate.withHourOfDay(18).getMillis(),
reminderService.calculateNextDueDateReminder(task));
}
@Test
public void testNoReminderIfAlreadyRemindedPastDueDate() {
final DateTime dueDate = new DateTime(2015, 12, 29, 19, 23);
Task task = newTask(
with(DUE_TIME, dueDate),
with(REMINDER_LAST, dueDate.plusSeconds(1)),
with(REMINDERS, NOTIFY_AT_DEADLINE));
assertEquals(NO_ALARM, reminderService.calculateNextDueDateReminder(task));
}
}

@ -1,45 +1,72 @@
/**
* Copyright (c) 2012 Todoroo Inc
*
* See the file "LICENSE" for the full license governing this code.
*/
package com.todoroo.astrid.reminders;
import android.content.Context;
import android.support.test.runner.AndroidJUnit4;
import com.todoroo.andlib.utility.DateUtilities;
import com.todoroo.astrid.dao.TaskDao;
import com.todoroo.astrid.data.Task;
import com.todoroo.astrid.reminders.ReminderService.AlarmScheduler;
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InOrder;
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 javax.inject.Inject;
import static android.support.test.InstrumentationRegistry.getContext;
import static android.support.test.InstrumentationRegistry.getTargetContext;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertTrue;
import static junit.framework.Assert.fail;
import static com.natpryce.makeiteasy.MakeItEasy.with;
import static com.todoroo.andlib.utility.DateUtilities.ONE_HOUR;
import static com.todoroo.andlib.utility.DateUtilities.ONE_WEEK;
import static com.todoroo.astrid.data.Task.NOTIFY_AFTER_DEADLINE;
import static com.todoroo.astrid.data.Task.NOTIFY_AT_DEADLINE;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import static org.tasks.Freeze.freezeClock;
import static org.tasks.Freeze.thaw;
import static org.tasks.date.DateTimeUtils.newDateTime;
import static org.tasks.makers.TaskMaker.COMPLETION_TIME;
import static org.tasks.makers.TaskMaker.CREATION_TIME;
import static org.tasks.makers.TaskMaker.DELETION_TIME;
import static org.tasks.makers.TaskMaker.DUE_DATE;
import static org.tasks.makers.TaskMaker.DUE_TIME;
import static org.tasks.makers.TaskMaker.ID;
import static org.tasks.makers.TaskMaker.PRIORITY;
import static org.tasks.makers.TaskMaker.RANDOM_REMINDER_PERIOD;
import static org.tasks.makers.TaskMaker.REMINDERS;
import static org.tasks.makers.TaskMaker.REMINDER_LAST;
import static org.tasks.makers.TaskMaker.SNOOZE_TIME;
import static org.tasks.makers.TaskMaker.newTask;
import static org.tasks.time.DateTimeUtils.currentTimeMillis;
@RunWith(AndroidJUnit4.class)
public class ReminderServiceTest extends InjectingTestCase {
@Inject TaskDao taskDao;
@Inject ReminderService reminderService;
@Inject Preferences preferences;
@Override
public void setUp() {
super.setUp();
freezeClock();
private ReminderService service;
private Random random;
private JobQueue<Reminder> jobs;
@Before
public void before() {
jobs = mock(JobQueue.class);
random = mock(Random.class);
when(random.nextFloat()).thenReturn(1.0f);
service = new ReminderService(preferences, jobs, random);
}
@After
public void after() {
verifyNoMoreInteractions(jobs);
}
@Override
@ -47,222 +74,280 @@ public class ReminderServiceTest extends InjectingTestCase {
component.inject(this);
}
@After
public void tearDown() {
thaw();
@Test
public void dontScheduleDueDateReminderWhenFlagNotSet() {
service.scheduleAlarm(null, newTask(with(ID, 1L), with(DUE_TIME, newDateTime())));
verify(jobs).cancel(1);
}
@Test
public void testNoReminders() {
reminderService.setScheduler(new NoAlarmExpected());
Task task = new Task();
task.setTitle("water");
task.setReminderFlags(0);
task.setReminderPeriod(0L);
taskDao.save(task);
reminderService.scheduleAlarm(taskDao, task);
public void dontScheduleDueDateReminderWhenTimeNotSet() {
service.scheduleAlarm(null, newTask(with(ID, 1L), with(REMINDERS, NOTIFY_AT_DEADLINE)));
verify(jobs).cancel(1);
}
@Test
public void testDueDates() {
reminderService.setScheduler(new AlarmExpected() {
@Override
public void createAlarm(Context context, Task task, long time, int type) {
if (time == ReminderService.NO_ALARM)
return;
super.createAlarm(getTargetContext(), task, time, type);
assertEquals((long) task.getDueDate(), time);
assertEquals(type, ReminderService.TYPE_DUE);
}
});
// test due date in the past
final Task task = new Task();
task.setTitle("water");
task.setDueDate(DateUtilities.now() - DateUtilities.ONE_DAY);
task.setReminderFlags(Task.NOTIFY_AT_DEADLINE);
taskDao.save(task);
// test due date in the future
task.setDueDate(DateUtilities.now() + DateUtilities.ONE_DAY);
taskDao.save(task);
assertTrue(((AlarmExpected) reminderService.getScheduler()).alarmCreated);
public void schedulePastDueDate() {
Task task = newTask(
with(ID, 1L),
with(DUE_TIME, newDateTime().minusDays(1)),
with(REMINDERS, NOTIFY_AT_DEADLINE));
service.scheduleAlarm(null, task);
InOrder order = inOrder(jobs);
order.verify(jobs).cancel(1);
order.verify(jobs).add(new Reminder(1, task.getDueDate(), ReminderService.TYPE_DUE));
}
@Test
public void testRandom() {
// test random
final Task task = new Task();
task.setTitle("water");
task.setReminderPeriod(DateUtilities.ONE_WEEK);
reminderService.setScheduler(new AlarmExpected() {
@Override
public void createAlarm(Context context, Task task, long time, int type) {
if (time == ReminderService.NO_ALARM)
return;
super.createAlarm(getContext(), task, time, type);
assertTrue(time > DateUtilities.now());
assertTrue(time < DateUtilities.now() + 1.2 * DateUtilities.ONE_WEEK);
assertEquals(type, ReminderService.TYPE_RANDOM);
}
});
taskDao.save(task);
assertTrue(((AlarmExpected) reminderService.getScheduler()).alarmCreated);
public void scheduleFutureDueDate() {
Task task = newTask(
with(ID, 1L),
with(DUE_TIME, newDateTime().plusDays(1)),
with(REMINDERS, NOTIFY_AT_DEADLINE));
service.scheduleAlarm(null, task);
InOrder order = inOrder(jobs);
order.verify(jobs).cancel(1);
order.verify(jobs).add(new Reminder(1, task.getDueDate(), ReminderService.TYPE_DUE));
}
@Test
public void testOverdue() {
// test due date in the future
reminderService.setScheduler(new AlarmExpected() {
@Override
public void createAlarm(Context context, Task task, long time, int type) {
if (time == ReminderService.NO_ALARM)
return;
super.createAlarm(getContext(), task, time, type);
assertTrue(time > task.getDueDate());
assertTrue(time < task.getDueDate() + DateUtilities.ONE_DAY);
assertEquals(type, ReminderService.TYPE_OVERDUE);
}
});
final Task task = new Task();
task.setTitle("water");
task.setDueDate(DateUtilities.now() + DateUtilities.ONE_DAY);
task.setReminderFlags(Task.NOTIFY_AFTER_DEADLINE);
taskDao.save(task);
// test due date in the past
task.setDueDate(DateUtilities.now() - DateUtilities.ONE_DAY);
reminderService.setScheduler(new AlarmExpected() {
@Override
public void createAlarm(Context context, Task task, long time, int type) {
if (time == ReminderService.NO_ALARM)
return;
super.createAlarm(getContext(), task, time, type);
assertTrue(time > DateUtilities.now() - 1000L);
assertTrue(time < DateUtilities.now() + 2 * DateUtilities.ONE_DAY);
assertEquals(type, ReminderService.TYPE_OVERDUE);
}
});
taskDao.save(task);
assertTrue(((AlarmExpected) reminderService.getScheduler()).alarmCreated);
// test due date in the past, but recently notified
task.setReminderLast(DateUtilities.now());
reminderService.setScheduler(new AlarmExpected() {
@Override
public void createAlarm(Context context, Task task, long time, int type) {
if (time == ReminderService.NO_ALARM)
return;
super.createAlarm(getContext(), task, time, type);
assertTrue(time > DateUtilities.now() + DateUtilities.ONE_HOUR);
assertTrue(time < DateUtilities.now() + DateUtilities.ONE_DAY);
assertEquals(type, ReminderService.TYPE_OVERDUE);
}
});
taskDao.save(task);
assertTrue(((AlarmExpected) reminderService.getScheduler()).alarmCreated);
public void scheduleReminderAtDefaultDueTime() {
DateTime now = newDateTime();
Task task = newTask(
with(ID, 1L),
with(DUE_DATE, now),
with(REMINDERS, NOTIFY_AT_DEADLINE));
service.scheduleAlarm(null, task);
InOrder order = inOrder(jobs);
order.verify(jobs).cancel(1);
order.verify(jobs).add(new Reminder(1, now.startOfDay().withHourOfDay(18).getMillis(), ReminderService.TYPE_DUE));
}
@Test
public void testMultipleReminders() {
// test due date in the future, enable random
final Task task = new Task();
task.setTitle("water");
task.setDueDate(DateUtilities.now() + DateUtilities.ONE_WEEK);
task.setReminderFlags(Task.NOTIFY_AT_DEADLINE);
task.setReminderPeriod(DateUtilities.ONE_HOUR);
reminderService.setScheduler(new AlarmExpected() {
@Override
public void createAlarm(Context context, Task task, long time, int type) {
if (time == ReminderService.NO_ALARM)
return;
super.createAlarm(getContext(), task, time, type);
assertTrue(time > DateUtilities.now());
assertTrue(time < DateUtilities.now() + DateUtilities.ONE_DAY);
assertEquals(type, ReminderService.TYPE_RANDOM);
}
});
taskDao.save(task);
assertTrue(((AlarmExpected) reminderService.getScheduler()).alarmCreated);
// now set the due date in the past
task.setDueDate(DateUtilities.now() - DateUtilities.ONE_WEEK);
((AlarmExpected) reminderService.getScheduler()).alarmCreated = false;
reminderService.scheduleAlarm(taskDao, task);
assertTrue(((AlarmExpected) reminderService.getScheduler()).alarmCreated);
// now set the due date before the random
task.setDueDate(DateUtilities.now() + DateUtilities.ONE_HOUR);
reminderService.setScheduler(new AlarmExpected() {
@Override
public void createAlarm(Context context, Task task, long time, int type) {
if (time == ReminderService.NO_ALARM)
return;
super.createAlarm(getContext(), task, time, type);
assertEquals((long) task.getDueDate(), time);
assertEquals(type, ReminderService.TYPE_DUE);
}
});
taskDao.save(task);
assertTrue(((AlarmExpected) reminderService.getScheduler()).alarmCreated);
public void dontScheduleReminderForCompletedTask() {
Task task = newTask(
with(ID, 1L),
with(DUE_TIME, newDateTime().plusDays(1)),
with(COMPLETION_TIME, newDateTime()),
with(REMINDERS, NOTIFY_AT_DEADLINE));
service.scheduleAlarm(null, task);
verify(jobs).cancel(1);
}
@Test
public void testSnoozeReminders() {
thaw(); // TODO: get rid of this
// test due date and snooze in the future
final Task task = new Task();
task.setTitle("spacemen");
task.setDueDate(DateUtilities.now() + 5000L);
task.setReminderFlags(Task.NOTIFY_AT_DEADLINE);
task.setReminderSnooze(DateUtilities.now() + DateUtilities.ONE_WEEK);
reminderService.setScheduler(new AlarmExpected() {
@Override
public void createAlarm(Context context, Task task, long time, int type) {
if (time == ReminderService.NO_ALARM)
return;
super.createAlarm(getContext(), task, time, type);
assertTrue(time > DateUtilities.now() + DateUtilities.ONE_WEEK - 1000L);
assertTrue(time < DateUtilities.now() + DateUtilities.ONE_WEEK + 1000L);
assertEquals(type, ReminderService.TYPE_SNOOZE);
}
});
taskDao.save(task);
assertTrue(((AlarmExpected) reminderService.getScheduler()).alarmCreated);
// snooze in the past
task.setReminderSnooze(DateUtilities.now() - DateUtilities.ONE_WEEK);
reminderService.setScheduler(new AlarmExpected() {
@Override
public void createAlarm(Context context, Task task, long time, int type) {
if (time == ReminderService.NO_ALARM)
return;
super.createAlarm(getContext(), task, time, type);
assertTrue(time > DateUtilities.now() - 1000L);
assertTrue(time < DateUtilities.now() + 5000L);
assertEquals(type, ReminderService.TYPE_DUE);
}
});
taskDao.save(task);
assertTrue(((AlarmExpected) reminderService.getScheduler()).alarmCreated);
public void dontScheduleReminderForDeletedTask() {
Task task = newTask(
with(ID, 1L),
with(DUE_TIME, newDateTime().plusDays(1)),
with(DELETION_TIME, newDateTime()),
with(REMINDERS, NOTIFY_AT_DEADLINE));
service.scheduleAlarm(null, task);
verify(jobs).cancel(1);
}
// --- helper classes
@Test
public void dontScheduleDueDateReminderWhenAlreadyReminded() {
DateTime now = newDateTime();
Task task = newTask(
with(ID, 1L),
with(DUE_TIME, now),
with(REMINDER_LAST, now.plusSeconds(1)),
with(REMINDERS, NOTIFY_AT_DEADLINE));
service.scheduleAlarm(null, task);
public class NoAlarmExpected implements AlarmScheduler {
public void createAlarm(Context context, Task task, long time, int type) {
if(time == 0 || time == Long.MAX_VALUE)
return;
fail("created alarm, no alarm expected (" + type + ": " + newDateTime(time));
}
verify(jobs).cancel(1);
}
public class AlarmExpected implements AlarmScheduler {
public boolean alarmCreated = false;
public void createAlarm(Context context, Task task, long time, int type) {
alarmCreated = true;
}
@Test
public void ignoreLapsedSnoozeTime() {
Task task = newTask(
with(ID, 1L),
with(DUE_TIME, newDateTime()),
with(SNOOZE_TIME, newDateTime().minusMinutes(5)),
with(REMINDERS, NOTIFY_AT_DEADLINE));
service.scheduleAlarm(null, task);
InOrder order = inOrder(jobs);
order.verify(jobs).cancel(1);
order.verify(jobs).add(new Reminder(1, task.getDueDate(), ReminderService.TYPE_DUE));
}
@Test
public void scheduleInitialRandomReminder() {
freezeClock().thawAfter(new Snippet() {{
DateTime now = newDateTime();
when(random.nextFloat()).thenReturn(0.3865f);
Task task = newTask(
with(ID, 1L),
with(REMINDER_LAST, (DateTime) null),
with(CREATION_TIME, now.minusDays(1)),
with(RANDOM_REMINDER_PERIOD, ONE_WEEK));
service.scheduleAlarm(null, task);
InOrder order = inOrder(jobs);
order.verify(jobs).cancel(1);
order.verify(jobs).add(new Reminder(1L, now.minusDays(1).getMillis() + 584206592, ReminderService.TYPE_RANDOM));
}});
}
@Test
public void scheduleNextRandomReminder() {
freezeClock().thawAfter(new Snippet() {{
DateTime now = newDateTime();
when(random.nextFloat()).thenReturn(0.3865f);
Task task = newTask(
with(ID, 1L),
with(REMINDER_LAST, now.minusDays(1)),
with(CREATION_TIME, now.minusDays(30)),
with(RANDOM_REMINDER_PERIOD, ONE_WEEK));
service.scheduleAlarm(null, task);
InOrder order = inOrder(jobs);
order.verify(jobs).cancel(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();
when(random.nextFloat()).thenReturn(0.3865f);
Task task = newTask(
with(ID, 1L),
with(REMINDER_LAST, now.minusDays(14)),
with(CREATION_TIME, now.minusDays(30)),
with(RANDOM_REMINDER_PERIOD, ONE_WEEK));
service.scheduleAlarm(null, task);
InOrder order = inOrder(jobs);
order.verify(jobs).cancel(1);
order.verify(jobs).add(new Reminder(1L, now.getMillis() + 10148400, ReminderService.TYPE_RANDOM));
}});
}
@Test
public void scheduleOverdueForFutureDueDate() {
freezeClock().thawAfter(new Snippet() {{
when(random.nextFloat()).thenReturn(0.3865f);
Task task = newTask(
with(ID, 1L),
with(DUE_TIME, newDateTime().plusMinutes(5)),
with(REMINDERS, NOTIFY_AFTER_DEADLINE));
service.scheduleAlarm(null, task);
InOrder order = inOrder(jobs);
order.verify(jobs).cancel(1);
order.verify(jobs).add(new Reminder(1L, task.getDueDate() + 4582800, ReminderService.TYPE_OVERDUE));
}});
}
@Test
public void scheduleOverdueForPastDueDateWithNoReminderPastDueDate() {
freezeClock().thawAfter(new Snippet() {{
DateTime now = newDateTime();
Task task = newTask(
with(ID, 1L),
with(DUE_TIME, now.minusMinutes(5)),
with(REMINDERS, NOTIFY_AFTER_DEADLINE));
service.scheduleAlarm(null, task);
InOrder order = inOrder(jobs);
order.verify(jobs).cancel(1);
order.verify(jobs).add(new Reminder(1L, currentTimeMillis(), ReminderService.TYPE_OVERDUE));
}});
}
@Test
public void scheduleOverdueForPastDueDateLastReminderSixHoursAgo() {
freezeClock().thawAfter(new Snippet() {{
Task task = newTask(
with(ID, 1L),
with(DUE_TIME, newDateTime().minusHours(12)),
with(REMINDER_LAST, newDateTime().minusHours(6)),
with(REMINDERS, NOTIFY_AFTER_DEADLINE));
service.scheduleAlarm(null, task);
InOrder order = inOrder(jobs);
order.verify(jobs).cancel(1);
order.verify(jobs).add(new Reminder(1L, currentTimeMillis(), ReminderService.TYPE_OVERDUE));
}});
}
@Test
public void scheduleOverdueForPastDueDateLastReminderWithinSixHours() {
freezeClock().thawAfter(new Snippet() {{
when(random.nextFloat()).thenReturn(0.3865f);
Task task = newTask(
with(ID, 1L),
with(DUE_TIME, newDateTime().minusHours(12)),
with(PRIORITY, 2),
with(REMINDER_LAST, newDateTime().minusHours(3)),
with(REMINDERS, NOTIFY_AFTER_DEADLINE));
service.scheduleAlarm(null, task);
InOrder order = inOrder(jobs);
order.verify(jobs).cancel(1);
order.verify(jobs).add(new Reminder(1L, currentTimeMillis() + 22748400, ReminderService.TYPE_OVERDUE));
}});
}
@Test
public void snoozeOverridesAll() {
DateTime now = newDateTime();
Task task = newTask(
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(null, task);
InOrder order = inOrder(jobs);
order.verify(jobs).cancel(1);
order.verify(jobs).add(new Reminder(1, now.plusMonths(12).getMillis(), ReminderService.TYPE_SNOOZE));
}
@Test
@Ignore
public void randomReminderBeforeDueAndOverdue() {
}
@Test
@Ignore
public void randomReminderAfterDue() {
}
@Test
@Ignore
public void randomReminderAfterOverdue() {
}
@Test
@Ignore
public void dueDateBeforeOverdue() {
}
}

@ -3,7 +3,7 @@
*
* See the file "LICENSE" for the full license governing this code.
*/
package org.tasks.scheduling;
package org.tasks.jobs;
import android.support.test.runner.AndroidJUnit4;
@ -19,6 +19,7 @@ import org.junit.runner.RunWith;
import org.tasks.R;
import org.tasks.injection.TestComponent;
import org.tasks.preferences.Preferences;
import org.tasks.scheduling.AlarmManager;
import java.io.File;
import java.io.IOException;
@ -29,6 +30,7 @@ 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;
@ -90,15 +92,15 @@ public class BackupServiceTests extends DatabaseTestCase {
preferences.setLong(TasksXmlExporter.PREF_BACKUP_LAST_DATE, 0);
// create a backup
BackupIntentService service = new BackupIntentService();
service.testBackup(xmlExporter, preferences, getTargetContext());
BackupJob service = new BackupJob(getTargetContext(), new JobManager(getTargetContext(), mock(AlarmManager.class)), xmlExporter, preferences);
service.startBackup(getTargetContext());
AndroidUtilities.sleepDeep(BACKUP_WAIT_TIME);
// assert file created
File[] files = temporaryDirectory.listFiles();
assertEquals(1, files.length);
assertTrue(files[0].getName().matches(BackupIntentService.BACKUP_FILE_NAME_REGEX));
assertTrue(files[0].getName().matches(BackupJob.BACKUP_FILE_NAME_REGEX));
// assert summary updated
assertTrue(preferences.getLong(TasksXmlExporter.PREF_BACKUP_LAST_DATE, 0) > 0);
@ -128,8 +130,8 @@ public class BackupServiceTests extends DatabaseTestCase {
assertEquals(11, files.length);
// backup
BackupIntentService service = new BackupIntentService();
service.testBackup(xmlExporter, preferences, getTargetContext());
BackupJob service = new BackupJob(getTargetContext(), new JobManager(getTargetContext(), mock(AlarmManager.class)), xmlExporter, preferences);
service.startBackup(getTargetContext());
AndroidUtilities.sleepDeep(BACKUP_WAIT_TIME);
@ -138,7 +140,7 @@ public class BackupServiceTests extends DatabaseTestCase {
assertFalse(files[4].exists());
// assert user file still exists
service.testBackup(xmlExporter, preferences, getTargetContext());
service.startBackup(getTargetContext());
assertTrue(myFile.exists());
}
}

@ -0,0 +1,240 @@
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 java.util.concurrent.TimeUnit;
import static com.todoroo.astrid.reminders.ReminderService.TYPE_DUE;
import static java.util.Collections.singletonList;
import static junit.framework.Assert.assertEquals;
import static org.mockito.AdditionalAnswers.returnsFirstArg;
import static org.mockito.Matchers.anyLong;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import static org.tasks.time.DateTimeUtils.currentTimeMillis;
@RunWith(AndroidJUnit4.class)
public class JobQueueTest {
private static final long ONE_MINUTE = TimeUnit.MINUTES.toMillis(1);
private static final String TAG = "test";
private JobQueue<Reminder> 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, TAG);
}
@After
public void after() {
verifyNoMoreInteractions(jobManager);
}
@Test
public void twoJobsAtSameTime() {
queue.add(new Reminder(1, 1, 0));
queue.add(new Reminder(2, 1, 0));
verify(jobManager).schedule(TAG, 1);
assertEquals(2, queue.size());
}
@Test
public void rescheduleForFirstJob() {
queue.add(new Reminder(1, 1, 0));
verify(jobManager).schedule(TAG, 1);
}
@Test
public void dontRescheduleForLaterJobs() {
queue.add(new Reminder(1, 1, 0));
queue.add(new Reminder(2, 2, 0));
verify(jobManager).schedule(TAG, 1);
}
@Test
public void rescheduleForNewerJob() {
queue.add(new Reminder(1, 2, 0));
queue.add(new Reminder(1, 1, 0));
InOrder order = inOrder(jobManager);
order.verify(jobManager).schedule(TAG, 2);
order.verify(jobManager).schedule(TAG, 1);
}
@Test
public void rescheduleWhenCancelingOnlyJob() {
queue.add(new Reminder(1, 2, 0));
queue.cancel(1);
InOrder order = inOrder(jobManager);
order.verify(jobManager).schedule(TAG, 2);
order.verify(jobManager).cancel(TAG);
}
@Test
public void rescheduleWhenCancelingFirstJob() {
queue.add(new Reminder(1, 1, 0));
queue.add(new Reminder(2, 2, 0));
queue.cancel(1);
InOrder order = inOrder(jobManager);
order.verify(jobManager).schedule(TAG, 1);
order.verify(jobManager).schedule(TAG, 2);
}
@Test
public void dontRescheduleWhenCancelingLaterJob() {
queue.add(new Reminder(1, 1, 0));
queue.add(new Reminder(2, 2, 0));
queue.cancel(2);
verify(jobManager).schedule(TAG, 1);
}
@Test
public void nextScheduledTimeIsZeroWhenQueueIsEmpty() {
when(preferences.adjustForQuietHours(anyLong())).thenReturn(1234L);
assertEquals(0, queue.nextScheduledTime());
}
@Test
public void adjustNextScheduledTimeForQuietHours() {
when(preferences.adjustForQuietHours(anyLong())).thenReturn(1234L);
queue.add(new Reminder(1, 1, 1));
verify(jobManager).schedule(TAG, 1234);
}
@Test
public void overdueJobsAreReturned() {
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).thawAfter(new Snippet() {{
assertEquals(
singletonList(new Reminder(1, now, TYPE_DUE)),
queue.getOverdueJobs());
}});
}
@Test
public void twoOverdueJobsAtSameTimeReturned() {
long now = currentTimeMillis();
queue.add(new Reminder(1, now, TYPE_DUE));
queue.add(new Reminder(2, now, TYPE_DUE));
verify(jobManager).schedule(TAG, now);
Freeze.freezeAt(now).thawAfter(new Snippet() {{
assertEquals(
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);
Freeze.freezeAt(now).thawAfter(new Snippet() {{
queue.remove(new Reminder(1, now, TYPE_DUE));
}});
assertEquals(
singletonList(new Reminder(2, now + ONE_MINUTE, TYPE_DUE)),
queue.getJobs());
}
@Test
public void multipleOverduePeriodsLapsed() {
long now = currentTimeMillis();
queue.add(new Reminder(1, now, TYPE_DUE));
queue.add(new Reminder(2, now + ONE_MINUTE, TYPE_DUE));
queue.add(new Reminder(3, now + 2 * ONE_MINUTE, TYPE_DUE));
verify(jobManager).schedule(TAG, now);
Freeze.freezeAt(now + ONE_MINUTE).thawAfter(new Snippet() {{
queue.remove(new Reminder(1, now, TYPE_DUE));
queue.remove(new Reminder(2, now + ONE_MINUTE, TYPE_DUE));
}});
assertEquals(
singletonList(new Reminder(3, now + 2 * ONE_MINUTE, TYPE_DUE)),
queue.getJobs());
}
@Test
public void clearShouldCancelExisting() {
queue.add(new Reminder(1, 1, 0));
queue.clear();
InOrder order = inOrder(jobManager);
order.verify(jobManager).schedule(TAG, 1);
order.verify(jobManager).cancel(TAG);
assertEquals(0, queue.size());
}
@Test
public void ignoreInvalidCancel() {
long now = currentTimeMillis();
queue.add(new Reminder(1, now, TYPE_DUE));
queue.cancel(2);
verify(jobManager).schedule(TAG, now);
}
}

@ -1,5 +1,6 @@
package org.tasks.makers;
import com.google.common.base.Strings;
import com.natpryce.makeiteasy.Instantiator;
import com.natpryce.makeiteasy.Property;
import com.natpryce.makeiteasy.PropertyValue;
@ -8,15 +9,24 @@ import com.todoroo.astrid.data.Task;
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 static Property<Task, Long> ID = newProperty();
public static Property<Task, String> TITLE = newProperty();
public static Property<Task, DateTime> DUE_DATE = newProperty();
public static Property<Task, DateTime> DUE_TIME = newProperty();
public static Property<Task, Integer> PRIORITY = newProperty();
public static Property<Task, DateTime> REMINDER_LAST = newProperty();
public static Property<Task, Long> RANDOM_REMINDER_PERIOD = newProperty();
public static Property<Task, Integer> HIDE_TYPE = newProperty();
public static Property<Task, Integer> REMINDERS = newProperty();
public static Property<Task, DateTime> CREATION_TIME = newProperty();
public static Property<Task, DateTime> COMPLETION_TIME = newProperty();
public static Property<Task, DateTime> DELETION_TIME = newProperty();
public static Property<Task, DateTime> SNOOZE_TIME = newProperty();
@SafeVarargs
public static Task newTask(PropertyValue<? super Task, ?>... properties) {
@ -26,6 +36,21 @@ public class TaskMaker {
private static final Instantiator<Task> instantiator = lookup -> {
Task task = new Task();
String title = lookup.valueOf(TITLE, (String) null);
if (!Strings.isNullOrEmpty(title)) {
task.setTitle(title);
}
long id = lookup.valueOf(ID, Task.NO_ID);
if (id != Task.NO_ID) {
task.setId(id);
}
int priority = lookup.valueOf(PRIORITY, -1);
if (priority >= 0) {
task.setImportance(priority);
}
DateTime dueDate = lookup.valueOf(DUE_DATE, (DateTime) null);
if (dueDate != null) {
task.setDueDate(Task.createDueDate(Task.URGENCY_SPECIFIC_DAY, dueDate.getMillis()));
@ -36,6 +61,21 @@ public class TaskMaker {
task.setDueDate(Task.createDueDate(Task.URGENCY_SPECIFIC_DAY_TIME, dueTime.getMillis()));
}
DateTime completionTime = lookup.valueOf(COMPLETION_TIME, (DateTime) null);
if (completionTime != null) {
task.setCompletionDate(completionTime.getMillis());
}
DateTime deletedTime = lookup.valueOf(DELETION_TIME, (DateTime) null);
if (deletedTime != null) {
task.setDeletionDate(deletedTime.getMillis());
}
DateTime snoozeTime = lookup.valueOf(SNOOZE_TIME, (DateTime) null);
if (snoozeTime != null) {
task.setReminderSnooze(snoozeTime.getMillis());
}
int hideType = lookup.valueOf(HIDE_TYPE, -1);
if (hideType >= 0) {
task.setHideUntil(task.createHideUntil(hideType, 0));
@ -51,6 +91,14 @@ public class TaskMaker {
task.setReminderLast(reminderLast.getMillis());
}
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());
return task;
};
}

@ -1,4 +1,4 @@
package org.tasks.scheduling;
package org.tasks.preferences;
import android.annotation.SuppressLint;
import android.support.test.runner.AndroidJUnit4;
@ -7,7 +7,6 @@ import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.tasks.R;
import org.tasks.preferences.Preferences;
import org.tasks.time.DateTime;
import java.util.concurrent.TimeUnit;
@ -16,20 +15,18 @@ import static android.support.test.InstrumentationRegistry.getTargetContext;
import static junit.framework.Assert.assertEquals;
@RunWith(AndroidJUnit4.class)
public class AlarmManagerTests {
public class PreferenceTests {
@SuppressLint("NewApi")
private static final int MILLIS_PER_HOUR = (int) TimeUnit.HOURS.toMillis(1);
private Preferences preferences;
private AlarmManager alarmManager;
@Before
public void setUp() {
preferences = new Preferences(getTargetContext(), null);
preferences.clear();
preferences.setBoolean(R.string.p_rmd_enable_quiet, true);
alarmManager = new AlarmManager(getTargetContext(), preferences);
}
@Test
@ -40,7 +37,7 @@ public class AlarmManagerTests {
long dueDate = new DateTime(2015, 12, 29, 8, 0, 1).getMillis();
assertEquals(dueDate, alarmManager.adjustForQuietHours(dueDate));
assertEquals(dueDate, preferences.adjustForQuietHours(dueDate));
}
@Test
@ -51,7 +48,7 @@ public class AlarmManagerTests {
long dueDate = new DateTime(2015, 12, 29, 18, 0, 1).getMillis();
assertEquals(new DateTime(2015, 12, 29, 19, 0).getMillis(),
alarmManager.adjustForQuietHours(dueDate));
preferences.adjustForQuietHours(dueDate));
}
@Test
@ -62,7 +59,7 @@ public class AlarmManagerTests {
long dueDate = new DateTime(2015, 12, 29, 22, 0, 1).getMillis();
assertEquals(new DateTime(2015, 12, 30, 10, 0).getMillis(),
alarmManager.adjustForQuietHours(dueDate));
preferences.adjustForQuietHours(dueDate));
}
@Test
@ -73,7 +70,7 @@ public class AlarmManagerTests {
long dueDate = new DateTime(2015, 12, 29, 23, 30).getMillis();
assertEquals(new DateTime(2015, 12, 30, 10, 0).getMillis(),
alarmManager.adjustForQuietHours(dueDate));
preferences.adjustForQuietHours(dueDate));
}
@Test
@ -84,7 +81,7 @@ public class AlarmManagerTests {
long dueDate = new DateTime(2015, 12, 30, 7, 15).getMillis();
assertEquals(new DateTime(2015, 12, 30, 10, 0).getMillis(),
alarmManager.adjustForQuietHours(dueDate));
preferences.adjustForQuietHours(dueDate));
}
@Test
@ -94,7 +91,7 @@ public class AlarmManagerTests {
long dueDate = new DateTime(2015, 12, 29, 18, 0, 0).getMillis();
assertEquals(dueDate, alarmManager.adjustForQuietHours(dueDate));
assertEquals(dueDate, preferences.adjustForQuietHours(dueDate));
}
@Test
@ -104,7 +101,7 @@ public class AlarmManagerTests {
long dueDate = new DateTime(2015, 12, 29, 18, 0).getMillis();
assertEquals(dueDate, alarmManager.adjustForQuietHours(dueDate));
assertEquals(dueDate, preferences.adjustForQuietHours(dueDate));
}
@Test
@ -114,7 +111,7 @@ public class AlarmManagerTests {
long dueDate = new DateTime(2015, 12, 29, 10, 0).getMillis();
assertEquals(dueDate, alarmManager.adjustForQuietHours(dueDate));
assertEquals(dueDate, preferences.adjustForQuietHours(dueDate));
}
@Test
@ -124,7 +121,7 @@ public class AlarmManagerTests {
long dueDate = new DateTime(2015, 12, 29, 11, 30).getMillis();
assertEquals(dueDate, alarmManager.adjustForQuietHours(dueDate));
assertEquals(dueDate, preferences.adjustForQuietHours(dueDate));
}
@Test
@ -134,7 +131,7 @@ public class AlarmManagerTests {
long dueDate = new DateTime(2015, 12, 29, 22, 15).getMillis();
assertEquals(dueDate, alarmManager.adjustForQuietHours(dueDate));
assertEquals(dueDate, preferences.adjustForQuietHours(dueDate));
}
@Test
@ -144,7 +141,7 @@ public class AlarmManagerTests {
long dueDate = new DateTime(2015, 12, 29, 13, 45).getMillis();
assertEquals(dueDate, alarmManager.adjustForQuietHours(dueDate));
assertEquals(dueDate, preferences.adjustForQuietHours(dueDate));
}
private void setQuietHoursStart(int hour) {

@ -14,7 +14,7 @@ import com.todoroo.astrid.subtasks.SubtasksHelperTest;
import com.todoroo.astrid.subtasks.SubtasksTestCase;
import com.todoroo.astrid.sync.NewSyncTestCase;
import org.tasks.scheduling.BackupServiceTests;
import org.tasks.jobs.BackupServiceTests;
import dagger.Component;

@ -14,7 +14,7 @@ import com.todoroo.astrid.subtasks.SubtasksHelperTest;
import com.todoroo.astrid.subtasks.SubtasksTestCase;
import com.todoroo.astrid.sync.NewSyncTestCase;
import org.tasks.scheduling.BackupServiceTests;
import org.tasks.jobs.BackupServiceTests;
import dagger.Component;

@ -19,7 +19,7 @@ import com.todoroo.astrid.subtasks.SubtasksHelperTest;
import com.todoroo.astrid.subtasks.SubtasksTestCase;
import com.todoroo.astrid.sync.NewSyncTestCase;
import org.tasks.scheduling.BackupServiceTests;
import org.tasks.jobs.BackupServiceTests;
import dagger.Component;

@ -34,4 +34,8 @@ public class Tracker {
public void reportEvent(Tracking.Events setPreference, int resId, String s) {
}
public void reportEvent(Tracking.Events category, String action, String label) {
}
}

@ -12,8 +12,6 @@ import org.tasks.receivers.BootCompletedReceiver;
import org.tasks.receivers.CompleteTaskReceiver;
import org.tasks.receivers.ListNotificationReceiver;
import org.tasks.receivers.MyPackageReplacedReceiver;
import org.tasks.receivers.RefreshReceiver;
import org.tasks.receivers.TaskNotificationReceiver;
import org.tasks.receivers.TeslaUnreadReceiver;
import org.tasks.widget.TasksWidget;
@ -37,10 +35,6 @@ public interface BroadcastComponent {
void inject(MyPackageReplacedReceiver myPackageReplacedReceiver);
void inject(RefreshReceiver refreshReceiver);
void inject(TaskNotificationReceiver taskNotificationReceiver);
void inject(CompleteTaskReceiver completeTaskReceiver);
void inject(ListNotificationReceiver listNotificationReceiver);

@ -6,9 +6,9 @@
package com.todoroo.astrid.gtasks;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.preference.CheckBoxPreference;
import android.preference.Preference;
import android.support.annotation.NonNull;
import com.todoroo.andlib.utility.DateUtilities;
@ -31,6 +31,8 @@ import org.tasks.preferences.PermissionRequestor;
import javax.inject.Inject;
import static org.tasks.PermissionUtil.verifyPermissions;
public class GtasksPreferences extends InjectingPreferenceActivity implements GoogleTaskListSelectionHandler {
private static final String FRAG_TAG_GOOGLE_TASK_LIST_SELECTION = "frag_tag_google_task_list_selection";
@ -74,8 +76,8 @@ public class GtasksPreferences extends InjectingPreferenceActivity implements Go
DateUtilities.getDateStringWithTime(GtasksPreferences.this,
gtasksPreferenceService.getLastSyncDate())));
}
findPreference(getString(R.string.gtasks_GPr_interval_key)).setOnPreferenceChangeListener((preference, o) -> {
syncAdapterHelper.setSynchronizationInterval(Integer.parseInt((String) o));
findPreference(getString(R.string.gtask_background_sync)).setOnPreferenceChangeListener((preference, o) -> {
syncAdapterHelper.enableSynchronization((Boolean) o);
return true;
});
findPreference(getString(R.string.sync_SPr_forget_key)).setOnPreferenceClickListener(preference -> {
@ -104,6 +106,19 @@ public class GtasksPreferences extends InjectingPreferenceActivity implements Go
startActivityForResult(new Intent(GtasksPreferences.this, GtasksLoginActivity.class), REQUEST_LOGIN);
}
@Override
protected void onPostResume() {
super.onPostResume();
CheckBoxPreference backgroundSync = (CheckBoxPreference) findPreference(getString(R.string.gtask_background_sync));
backgroundSync.setChecked(syncAdapterHelper.isSyncEnabled());
if (syncAdapterHelper.isMasterSyncEnabled()) {
backgroundSync.setSummary(null);
} else {
backgroundSync.setSummary(R.string.master_sync_warning);
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_LOGIN) {
@ -121,7 +136,7 @@ public class GtasksPreferences extends InjectingPreferenceActivity implements Go
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
if (requestCode == PermissionRequestor.REQUEST_ACCOUNTS) {
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
if (verifyPermissions(grantResults)) {
requestLogin();
}
} else {

@ -19,7 +19,6 @@ package org.tasks.gtasks;
import android.accounts.Account;
import android.app.PendingIntent;
import android.content.ContentProviderClient;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
@ -150,9 +149,6 @@ public class GoogleTaskSyncAdapter extends InjectingAbstractThreadedSyncAdapter
syncResult.stats.numAuthExceptions++;
return;
}
if (!extras.getBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, false)) {
preferences.setBoolean(R.string.p_sync_warning_shown, false);
}
Timber.d("%s: start sync", account);
RecordSyncStatusCallback callback = new RecordSyncStatusCallback(gtasksPreferenceService, broadcaster);
try {

@ -13,6 +13,8 @@ import org.tasks.analytics.Tracker;
import org.tasks.analytics.Tracking;
import org.tasks.preferences.Preferences;
import java.util.concurrent.TimeUnit;
import javax.inject.Inject;
import timber.log.Timber;
@ -76,7 +78,7 @@ public class SyncAdapterHelper {
getAccount() != null;
}
private boolean masterSyncEnabled() {
public boolean isMasterSyncEnabled() {
return ContentResolver.getMasterSyncAutomatically();
}
@ -84,37 +86,23 @@ public class SyncAdapterHelper {
Account account = getAccount();
if (account != null) {
Timber.d("enableSynchronization=%s", enabled);
ContentResolver.setIsSyncable(account, AUTHORITY, 1);
ContentResolver.setSyncAutomatically(account, AUTHORITY, enabled);
if (enabled) {
setSynchronizationInterval(preferences.getIntegerFromString(R.string.gtasks_GPr_interval_key, 0));
ContentResolver.addPeriodicSync(account, AUTHORITY, Bundle.EMPTY, TimeUnit.HOURS.toSeconds(1));
} else {
setSynchronizationInterval(0);
ContentResolver.removePeriodicSync(account, AUTHORITY, Bundle.EMPTY);
}
}
}
public void setSynchronizationInterval(int seconds) {
Account account = getAccount();
if (account != null) {
boolean syncAutomatically = seconds > 0;
ContentResolver.setSyncAutomatically(account, AUTHORITY, syncAutomatically);
Timber.d("syncAutomatically=%s, syncInterval=%s", syncAutomatically, seconds);
if (syncAutomatically) {
ContentResolver.addPeriodicSync(account, AUTHORITY, Bundle.EMPTY, seconds);
} else {
ContentResolver.removePeriodicSync(account, AUTHORITY, Bundle.EMPTY);
}
}
public boolean isSyncEnabled() {
return isEnabled() && ContentResolver.getSyncAutomatically(getAccount(), AUTHORITY);
}
private Account getAccount() {
return accountManager.getAccount(gtasksPreferenceService.getUserName());
}
public boolean shouldShowBackgroundSyncWarning() {
return isEnabled() && !masterSyncEnabled() && !ContentResolver.getPeriodicSyncs(getAccount(), AUTHORITY).isEmpty();
}
public void checkPlayServices(TaskListFragment taskListFragment) {
if (taskListFragment != null &&
preferences.getBoolean(R.string.sync_gtasks, false) &&

@ -13,8 +13,6 @@ import org.tasks.receivers.CompleteTaskReceiver;
import org.tasks.receivers.GoogleTaskPushReceiver;
import org.tasks.receivers.ListNotificationReceiver;
import org.tasks.receivers.MyPackageReplacedReceiver;
import org.tasks.receivers.RefreshReceiver;
import org.tasks.receivers.TaskNotificationReceiver;
import org.tasks.receivers.TeslaUnreadReceiver;
import org.tasks.widget.TasksWidget;
@ -40,10 +38,6 @@ public interface BroadcastComponent {
void inject(MyPackageReplacedReceiver myPackageReplacedReceiver);
void inject(RefreshReceiver refreshReceiver);
void inject(TaskNotificationReceiver taskNotificationReceiver);
void inject(CompleteTaskReceiver completeTaskReceiver);
void inject(ListNotificationReceiver listNotificationReceiver);

@ -11,8 +11,8 @@ public abstract class InjectingAbstractThreadedSyncAdapter extends AbstractThrea
}
private void inject(Context context) {
inject(((InjectingApplication) context.getApplicationContext())
.getComponent()
inject(Dagger.get(context)
.getApplicationComponent()
.plus(new SyncAdapterModule()));
}

@ -48,6 +48,11 @@
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-feature android:name="android.hardware.microphone" android:required="false" />
<!-- *********************************** -->
<!-- keep device awake for notifications -->
<!-- *********************************** -->
<uses-permission android:name="android.permission.WAKE_LOCK" />
<!-- ============================================== Exported Permissions = -->
<!-- for v2 tasks provider -->
@ -207,14 +212,10 @@
<!-- ======================================================= Receivers = -->
<receiver android:name=".receivers.TaskNotificationReceiver" />
<receiver
android:name=".receivers.ListNotificationReceiver"
android:exported="true" />
<receiver android:name=".receivers.RefreshReceiver" />
<!-- widgets -->
<receiver
android:name=".widget.TasksWidget"
@ -441,16 +442,36 @@
android:name=".scheduling.GeofenceSchedulingIntentService"
android:exported="false" />
<service
android:name=".scheduling.BackupIntentService"
android:name=".scheduling.SchedulerIntentService"
android:exported="false" />
<service
android:name=".scheduling.RefreshSchedulerIntentService"
android:name=".scheduling.NotificationSchedulerIntentService"
android:exported="false" />
<service
android:name=".scheduling.ReminderSchedulerIntentService"
android:name=".scheduling.CalendarNotificationIntentService"
android:exported="false" />
<service
android:name=".scheduling.CalendarNotificationIntentService"
android:name=".jobs.AlarmJob"
android:exported="false" />
<service
android:name=".jobs.BackupJob"
android:exported="false" />
<service
android:name=".jobs.MidnightRefreshJob"
android:exported="false" />
<service
android:name=".jobs.RefreshJob"
android:exported="false" />
<service
android:name=".jobs.ReminderJob"
android:exported="false" />
<receiver
android:name=".jobs.AlarmJobBroadcast"
android:exported="false" />
<receiver
android:name=".jobs.ReminderJobBroadcast"
android:exported="false" />
<!-- Uses Library -->

@ -266,26 +266,7 @@ public class TaskListActivity extends InjectingAppCompatActivity implements
repeatConfirmationReceiver,
new IntentFilter(AstridApiConstants.BROADCAST_EVENT_TASK_REPEATED));
TaskListFragment taskListFragment = getTaskListFragment();
if (syncAdapterHelper.shouldShowBackgroundSyncWarning() && !preferences.getBoolean(R.string.p_sync_warning_shown, false)) {
if (taskListFragment != null) {
taskListFragment.makeSnackbar(R.string.master_sync_warning)
.setAction(R.string.TLA_menu_settings, view -> {
Intent intent = new Intent(Settings.ACTION_SYNC_SETTINGS);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
})
.setCallback(new Snackbar.Callback() {
@Override
public void onShown(Snackbar snackbar) {
preferences.setBoolean(R.string.p_sync_warning_shown, true);
}
})
.show();
}
}
syncAdapterHelper.checkPlayServices(taskListFragment);
syncAdapterHelper.checkPlayServices(getTaskListFragment());
}
public void restart() {

@ -361,10 +361,6 @@ public class TaskListFragment extends InjectingFragment implements
refresh();
}
public Snackbar makeSnackbar(int resId) {
return makeSnackbar(getString(resId));
}
public Snackbar makeSnackbar(String text) {
Snackbar snackbar = Snackbar.make(coordinatorLayout, text, 8000)
.setActionTextColor(getColor(context, R.color.snackbar_text_color));

@ -5,10 +5,7 @@
*/
package com.todoroo.astrid.alarms;
import android.app.PendingIntent;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import com.todoroo.andlib.data.Callback;
import com.todoroo.andlib.sql.Criterion;
@ -21,13 +18,13 @@ import com.todoroo.astrid.dao.MetadataDao.MetadataCriteria;
import com.todoroo.astrid.dao.TaskDao.TaskCriteria;
import com.todoroo.astrid.data.Metadata;
import com.todoroo.astrid.data.Task;
import com.todoroo.astrid.reminders.ReminderService;
import com.todoroo.astrid.service.SynchronizeMetadataCallback;
import org.tasks.injection.ApplicationScope;
import org.tasks.injection.ForApplication;
import org.tasks.receivers.TaskNotificationReceiver;
import org.tasks.scheduling.AlarmManager;
import org.tasks.jobs.Alarm;
import org.tasks.jobs.JobManager;
import org.tasks.jobs.JobQueue;
import org.tasks.preferences.Preferences;
import java.util.ArrayList;
import java.util.HashSet;
@ -48,15 +45,14 @@ public class AlarmService {
private static final long NO_ALARM = Long.MAX_VALUE;
private final JobQueue<Alarm> jobs;
private final MetadataDao metadataDao;
private final Context context;
private final AlarmManager alarmManager;
@Inject
public AlarmService(MetadataDao metadataDao, @ForApplication Context context, AlarmManager alarmManager) {
public AlarmService(MetadataDao metadataDao, JobManager jobManager, Preferences preferences) {
this.metadataDao = metadataDao;
this.context = context;
this.alarmManager = alarmManager;
jobs = JobQueue.newAlarmQueue(preferences, jobManager);
}
public void getAlarms(long taskId, Callback<Metadata> callback) {
@ -79,11 +75,7 @@ public class AlarmService {
metadata.add(item);
}
boolean changed = synchronizeMetadata(taskId, metadata, m -> {
// Cancel the alarm before the metadata is deleted
PendingIntent pendingIntent = pendingIntentForAlarm(m, taskId);
alarmManager.cancel(pendingIntent);
});
boolean changed = synchronizeMetadata(taskId, metadata, m -> jobs.cancel(m.getId()));
if(changed) {
scheduleAlarms(taskId);
@ -94,18 +86,23 @@ public class AlarmService {
// --- alarm scheduling
private void getActiveAlarms(Callback<Metadata> callback) {
metadataDao.query(callback, Query.select(Metadata.ID, Metadata.TASK, AlarmFields.TIME).
metadataDao.query(callback, Query.select(Metadata.PROPERTIES).
join(Join.inner(Task.TABLE, Metadata.TASK.eq(Task.ID))).
where(Criterion.and(TaskCriteria.isActive(), MetadataCriteria.withKey(AlarmFields.METADATA_KEY))));
where(Criterion.and(TaskCriteria.isActive(),
MetadataCriteria.withKey(AlarmFields.METADATA_KEY))));
}
private void getActiveAlarmsForTask(long taskId, Callback<Metadata> callback) {
metadataDao.query(callback, Query.select(Metadata.ID, Metadata.TASK, AlarmFields.TIME).
metadataDao.query(callback, Query.select(Metadata.PROPERTIES).
join(Join.inner(Task.TABLE, Metadata.TASK.eq(Task.ID))).
where(Criterion.and(TaskCriteria.isActive(),
MetadataCriteria.byTaskAndwithKey(taskId, AlarmFields.METADATA_KEY))));
}
public void clear() {
jobs.clear();
}
/**
* Schedules all alarms
*/
@ -120,33 +117,20 @@ public class AlarmService {
getActiveAlarmsForTask(taskId, this::scheduleAlarm);
}
private PendingIntent pendingIntentForAlarm(Metadata alarm, long taskId) {
Intent intent = new Intent(context, TaskNotificationReceiver.class);
intent.setAction("ALARM" + alarm.getId()); //$NON-NLS-1$
intent.putExtra(TaskNotificationReceiver.ID_KEY, taskId);
intent.putExtra(TaskNotificationReceiver.EXTRAS_TYPE, ReminderService.TYPE_ALARM);
return PendingIntent.getBroadcast(context, (int)alarm.getId(),
intent, PendingIntent.FLAG_UPDATE_CURRENT);
}
/**
* Schedules alarms for a single task
*/
private void scheduleAlarm(Metadata alarm) {
if(alarm == null) {
private void scheduleAlarm(Metadata metadata) {
if(metadata == null) {
return;
}
long taskId = alarm.getTask();
PendingIntent pendingIntent = pendingIntentForAlarm(alarm, taskId);
long time = alarm.getValue(AlarmFields.TIME);
Alarm alarm = new Alarm(metadata);
long time = alarm.getTime();
if(time == 0 || time == NO_ALARM) {
alarmManager.cancel(pendingIntent);
} else if(time > DateUtilities.now()) {
alarmManager.wakeupAdjustingForQuietHours(time, pendingIntent);
jobs.cancel(alarm.getId());
} else {
jobs.add(alarm);
}
}
@ -200,4 +184,16 @@ public class AlarmService {
return dirty[0];
}
public void scheduleNextJob() {
jobs.scheduleNext();
}
public List<Alarm> getOverdueAlarms() {
return jobs.getOverdueJobs();
}
public boolean remove(Alarm alarm) {
return jobs.remove(alarm);
}
}

@ -9,6 +9,7 @@ import android.app.Activity;
import android.app.ProgressDialog;
import android.content.Context;
import android.os.Handler;
import android.support.annotation.Nullable;
import android.util.Xml;
import android.widget.Toast;
@ -98,7 +99,7 @@ public class TasksXmlExporter {
this.preferences = preferences;
}
public void exportTasks(final Context context, final ExportType exportType, final ProgressDialog progressDialog) {
public void exportTasks(final Context context, final ExportType exportType, @Nullable final ProgressDialog progressDialog) {
this.context = context;
this.exportCount = 0;
this.backupDirectory = preferences.getBackupDirectory();
@ -106,9 +107,6 @@ public class TasksXmlExporter {
this.progressDialog = progressDialog;
handler = exportType == ExportType.EXPORT_TYPE_MANUAL ? new Handler() : null;
if(exportType != ExportType.EXPORT_TYPE_MANUAL) {
this.progressDialog = new ProgressDialog(context);
}
new Thread(() -> {
try {
@ -129,7 +127,7 @@ public class TasksXmlExporter {
Timber.e(e, e.getMessage());
} finally {
post(() -> {
if(progressDialog.isShowing() && context instanceof Activity) {
if(progressDialog != null && progressDialog.isShowing() && context instanceof Activity) {
DialogUtilities.dismissDialog((Activity) context, progressDialog);
}
});

@ -44,7 +44,7 @@ class CustomFilterAdapter extends ArrayAdapter<CriterionInstance> {
public CustomFilterAdapter(CustomFilterActivity activity, DialogBuilder dialogBuilder,
List<CriterionInstance> objects, Locale locale) {
super(activity, R.id.name, objects);
super(activity, 0, objects);
this.activity = activity;
this.dialogBuilder = dialogBuilder;
this.locale = locale;

@ -6,7 +6,6 @@
package com.todoroo.astrid.core;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.preference.Preference;
import android.support.annotation.NonNull;
@ -23,6 +22,8 @@ import org.tasks.preferences.Preferences;
import javax.inject.Inject;
import static org.tasks.PermissionUtil.verifyPermissions;
/**
* Displays the preference screen for users to edit their preferences
*
@ -70,7 +71,7 @@ public class DefaultsPreferences extends InjectingPreferenceActivity {
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
if (requestCode == PermissionRequestor.REQUEST_CALENDAR) {
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
if (verifyPermissions(grantResults)) {
startCalendarSelectionActivity();
}
} else {

@ -6,25 +6,20 @@
package com.todoroo.astrid.files;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.FragmentManager;
import android.widget.Chronometer;
import org.tasks.R;
import org.tasks.dialogs.RecordAudioDialog;
import org.tasks.injection.ActivityComponent;
import org.tasks.injection.InjectingAppCompatActivity;
import org.tasks.preferences.ActivityPermissionRequestor;
import org.tasks.preferences.PermissionRequestor;
import org.tasks.preferences.Preferences;
import org.tasks.themes.Theme;
import javax.inject.Inject;
import butterknife.BindView;
import static org.tasks.PermissionUtil.verifyPermissions;
import static org.tasks.dialogs.RecordAudioDialog.newRecordAudioDialog;
public class AACRecordingActivity extends InjectingAppCompatActivity implements RecordAudioDialog.RecordAudioDialogCallback {
@ -33,12 +28,9 @@ public class AACRecordingActivity extends InjectingAppCompatActivity implements
public static final String RESULT_OUTFILE = "outfile"; //$NON-NLS-1$
@Inject Preferences preferences;
@Inject ActivityPermissionRequestor permissionRequestor;
@Inject Theme theme;
@BindView(R.id.timer) Chronometer timer;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@ -66,7 +58,7 @@ public class AACRecordingActivity extends InjectingAppCompatActivity implements
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
if (requestCode == PermissionRequestor.REQUEST_MIC) {
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
if (verifyPermissions(grantResults)) {
showDialog();
} else {
finish();

@ -6,7 +6,6 @@
package com.todoroo.astrid.reminders;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.media.Ringtone;
import android.media.RingtoneManager;
import android.net.Uri;
@ -14,13 +13,13 @@ import android.os.Bundle;
import android.preference.CheckBoxPreference;
import android.preference.Preference;
import android.preference.PreferenceManager;
import android.provider.Settings;
import android.support.annotation.NonNull;
import org.tasks.R;
import org.tasks.activities.ColorPickerActivity;
import org.tasks.activities.TimePickerActivity;
import org.tasks.dialogs.ColorPickerDialog;
import org.tasks.dialogs.DialogBuilder;
import org.tasks.injection.ActivityComponent;
import org.tasks.injection.InjectingPreferenceActivity;
import org.tasks.preferences.ActivityPermissionRequestor;
@ -29,7 +28,7 @@ import org.tasks.preferences.PermissionChecker;
import org.tasks.preferences.PermissionRequestor;
import org.tasks.preferences.Preferences;
import org.tasks.scheduling.GeofenceSchedulingIntentService;
import org.tasks.scheduling.ReminderSchedulerIntentService;
import org.tasks.scheduling.NotificationSchedulerIntentService;
import org.tasks.themes.LEDColor;
import org.tasks.themes.ThemeCache;
import org.tasks.time.DateTime;
@ -38,7 +37,7 @@ import org.tasks.ui.TimePreference;
import javax.inject.Inject;
import static com.todoroo.andlib.utility.AndroidUtilities.atLeastJellybean;
import static com.todoroo.andlib.utility.AndroidUtilities.atLeastMarshmallow;
import static org.tasks.PermissionUtil.verifyPermissions;
public class ReminderPreferences extends InjectingPreferenceActivity {
@ -50,7 +49,6 @@ public class ReminderPreferences extends InjectingPreferenceActivity {
@Inject Device device;
@Inject ActivityPermissionRequestor permissionRequestor;
@Inject PermissionChecker permissionChecker;
@Inject DialogBuilder dialogBuilder;
@Inject Preferences preferences;
@Inject ThemeCache themeCache;
@ -64,7 +62,6 @@ public class ReminderPreferences extends InjectingPreferenceActivity {
rescheduleNotificationsOnChange(
R.string.p_rmd_time,
R.string.p_doze_notifications,
R.string.p_rmd_enable_quiet,
R.string.p_rmd_quietStart,
R.string.p_rmd_quietEnd);
@ -89,7 +86,6 @@ public class ReminderPreferences extends InjectingPreferenceActivity {
});
requires(R.string.notification_shade, atLeastJellybean(), R.string.p_rmd_notif_actions_enabled, R.string.p_notification_priority, R.string.p_rmd_show_description);
requires(atLeastMarshmallow(), R.string.p_doze_notifications);
requires(device.supportsLocationServices(), R.string.geolocation_reminders);
updateLEDColor();
@ -98,7 +94,7 @@ public class ReminderPreferences extends InjectingPreferenceActivity {
private void rescheduleNotificationsOnChange(int... resIds) {
for (int resId : resIds) {
findPreference(getString(resId)).setOnPreferenceChangeListener((preference, newValue) -> {
startService(new Intent(ReminderPreferences.this, ReminderSchedulerIntentService.class));
startService(new Intent(ReminderPreferences.this, NotificationSchedulerIntentService.class));
return true;
});
}
@ -116,12 +112,9 @@ public class ReminderPreferences extends InjectingPreferenceActivity {
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
if (requestCode == PermissionRequestor.REQUEST_CONTACTS) {
for (int grantResult : grantResults) {
if (grantResult != PackageManager.PERMISSION_GRANTED) {
return;
}
if (verifyPermissions(grantResults)) {
fieldMissedCalls.setChecked(true);
}
fieldMissedCalls.setChecked(true);
} else {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
@ -143,8 +136,7 @@ public class ReminderPreferences extends InjectingPreferenceActivity {
preference.setSummary(R.string.silent);
} else {
Ringtone ringtone = RingtoneManager.getRingtone(ReminderPreferences.this, value == null
? RingtoneManager.getActualDefaultRingtoneUri(getApplicationContext(), RingtoneManager.TYPE_NOTIFICATION)
: Uri.parse((String) value));
? Settings.System.DEFAULT_NOTIFICATION_URI : Uri.parse((String) value));
preference.setSummary(ringtone == null ? "" : ringtone.getTitle(ReminderPreferences.this));
}
return true;

@ -5,11 +5,6 @@
*/
package com.todoroo.astrid.reminders;
import android.annotation.SuppressLint;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import com.todoroo.andlib.data.Property;
import com.todoroo.andlib.sql.Criterion;
import com.todoroo.andlib.sql.Query;
@ -20,26 +15,19 @@ import com.todoroo.astrid.data.Task;
import org.tasks.R;
import org.tasks.injection.ApplicationScope;
import org.tasks.injection.ForApplication;
import org.tasks.jobs.JobManager;
import org.tasks.jobs.JobQueue;
import org.tasks.jobs.Reminder;
import org.tasks.preferences.Preferences;
import org.tasks.receivers.TaskNotificationReceiver;
import org.tasks.scheduling.AlarmManager;
import org.tasks.reminders.Random;
import org.tasks.time.DateTime;
import java.util.Random;
import java.util.List;
import javax.inject.Inject;
import timber.log.Timber;
import static org.tasks.date.DateTimeUtils.newDateTime;
/**
* Data service for reminders
*
* @author Tim Su <tim@todoroo.com>
*
*/
@ApplicationScope
public final class ReminderService {
@ -69,70 +57,68 @@ public final class ReminderService {
/** flag for an alarm reminder */
public static final int TYPE_ALARM = 4;
private static final Random random = new Random();
private static final long NO_ALARM = Long.MAX_VALUE;
// --- instance variables
private AlarmScheduler scheduler;
private final JobQueue<Reminder> jobs;
private final Random random;
private final Preferences preferences;
private long now = -1; // For tracking when reminders might be scheduled all at once
private final Context context;
private final Preferences preferences;
@Inject
ReminderService(@ForApplication Context context, Preferences preferences, AlarmManager alarmManager) {
this.context = context;
ReminderService(Preferences preferences, JobManager jobManager) {
this(preferences, JobQueue.newReminderQueue(preferences, jobManager), new Random());
}
ReminderService(Preferences preferences, JobQueue<Reminder> jobs, Random random) {
this.preferences = preferences;
scheduler = new ReminderAlarmScheduler(alarmManager);
this.jobs = jobs;
this.random = random;
}
private static final int MILLIS_PER_HOUR = 60 * 60 * 1000;
// --- reminder scheduling logic
/**
* Schedules all alarms
*/
public void scheduleAllAlarms(TaskDao taskDao) {
now = DateUtilities.now(); // Before mass scheduling, initialize now variable
Query query = Query.select(NOTIFICATION_PROPERTIES).where(Criterion.and(
TaskCriteria.isActive(),
Criterion.or(Task.REMINDER_FLAGS.gt(0), Task.REMINDER_PERIOD.gt(0))));
taskDao.forEach(query, task -> scheduleAlarm(task, null));
taskDao.forEach(query, task -> scheduleAlarm(null, task));
now = -1; // Signal done with now variable
}
public void clear() {
jobs.clear();
}
private long getNowValue() {
// If we're in the midst of mass scheduling, use the prestored now var
return (now == -1 ? DateUtilities.now() : now);
}
public static final long NO_ALARM = Long.MAX_VALUE;
public List<Reminder> getPastReminders() {
return jobs.getOverdueJobs();
}
/**
* Schedules alarms for a single task
*/
public void scheduleAlarm(TaskDao taskDao, Task task) {
scheduleAlarm(task, taskDao);
public boolean remove(Reminder reminder) {
return jobs.remove(reminder);
}
private void clearAllAlarms(Task task) {
scheduler.createAlarm(context, task, NO_ALARM, TYPE_SNOOZE);
scheduler.createAlarm(context, task, NO_ALARM, TYPE_RANDOM);
scheduler.createAlarm(context, task, NO_ALARM, TYPE_DUE);
scheduler.createAlarm(context, task, NO_ALARM, TYPE_OVERDUE);
public void scheduleNextJob() {
jobs.scheduleNext();
}
private void scheduleAlarm(Task task, TaskDao taskDao) {
public void scheduleAlarm(TaskDao taskDao, Task task) {
if(task == null || !task.isSaved()) {
return;
}
// read data if necessary
long taskId = task.getId();
if(taskDao != null) {
for(Property<?> property : NOTIFICATION_PROPERTIES) {
if(!task.containsValue(property)) {
task = taskDao.fetch(task.getId(), NOTIFICATION_PROPERTIES);
task = taskDao.fetch(taskId, NOTIFICATION_PROPERTIES);
if(task == null) {
return;
}
@ -143,7 +129,8 @@ public final class ReminderService {
// Make sure no alarms are scheduled other than the next one. When that one is shown, it
// will schedule the next one after it, and so on and so forth.
clearAllAlarms(task);
jobs.cancel(taskId);
if(task.isCompleted() || task.isDeleted()) {
return;
}
@ -157,15 +144,15 @@ public final class ReminderService {
// notifications at due date
long whenDueDate = calculateNextDueDateReminder(task);
// notifications after due date
long whenOverdue = calculateNextOverdueReminder(task);
// For alarms around/before now, increment the now value so the next one will be later
if (whenDueDate <= now || whenOverdue <= now) {
if (whenDueDate <= now) {
whenDueDate = now;
}
if (whenOverdue <= now) {
whenOverdue = now;
now += 30 * DateUtilities.ONE_MINUTE; // Prevents overdue tasks from being scheduled all at once
}
// if random reminders are too close to due date, favor due date
@ -175,15 +162,13 @@ public final class ReminderService {
// snooze trumps all
if(whenSnooze != NO_ALARM) {
scheduler.createAlarm(context, task, whenSnooze, TYPE_SNOOZE);
jobs.add(new Reminder(taskId, whenSnooze, TYPE_SNOOZE));
} else if(whenRandom < whenDueDate && whenRandom < whenOverdue) {
scheduler.createAlarm(context, task, whenRandom, TYPE_RANDOM);
jobs.add(new Reminder(taskId, whenRandom, TYPE_RANDOM));
} else if(whenDueDate < whenOverdue) {
scheduler.createAlarm(context, task, whenDueDate, TYPE_DUE);
jobs.add(new Reminder(taskId, whenDueDate, TYPE_DUE));
} else if(whenOverdue != NO_ALARM) {
scheduler.createAlarm(context, task, whenOverdue, TYPE_OVERDUE);
} else {
scheduler.createAlarm(context, task, 0, 0);
jobs.add(new Reminder(taskId, whenOverdue, TYPE_OVERDUE));
}
}
@ -249,7 +234,7 @@ public final class ReminderService {
* If the date was indicated to not have a due time, we read from
* preferences and assign a time.
*/
long calculateNextDueDateReminder(Task task) {
private long calculateNextDueDateReminder(Task task) {
if(task.hasDueDate() && task.isNotifyAtDeadline()) {
long dueDate = task.getDueDate();
long lastReminder = task.getReminderLast();
@ -296,67 +281,4 @@ public final class ReminderService {
}
return NO_ALARM;
}
// --- alarm manager alarm creation
/**
* Interface for testing
*/
public interface AlarmScheduler {
void createAlarm(Context context, Task task, long time, int type);
}
public void setScheduler(AlarmScheduler scheduler) {
this.scheduler = scheduler;
}
public AlarmScheduler getScheduler() {
return scheduler;
}
private static class ReminderAlarmScheduler implements AlarmScheduler {
private final AlarmManager alarmManager;
public ReminderAlarmScheduler(AlarmManager alarmManager) {
this.alarmManager = alarmManager;
}
/**
* Create an alarm for the given task at the given type
*/
@Override
public void createAlarm(Context context, Task task, long time, int type) {
if(task.getId() == Task.NO_ID) {
return;
}
Intent intent = new Intent(context, TaskNotificationReceiver.class);
intent.setType(Long.toString(task.getId()));
intent.setAction(Integer.toString(type));
intent.putExtra(TaskNotificationReceiver.ID_KEY, task.getId());
intent.putExtra(TaskNotificationReceiver.EXTRAS_TYPE, type);
// calculate the unique requestCode as a combination of the task-id and alarm-type:
// concatenate id+type to keep the combo unique
@SuppressLint("DefaultLocale") String rc = String.format("%d%d", task.getId(), type);
int requestCode;
try {
requestCode = Integer.parseInt(rc);
} catch (Exception e) {
Timber.e(e, e.getMessage());
requestCode = type;
}
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, requestCode,
intent, 0);
if (time == 0 || time == NO_ALARM) {
alarmManager.cancel(pendingIntent);
} else {
if(time < DateUtilities.now()) {
time = DateUtilities.now() + 5000L;
}
alarmManager.wakeupAdjustingForQuietHours(time, pendingIntent);
}
}
}
}

@ -8,7 +8,6 @@ package com.todoroo.astrid.ui;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.Parcelable;
import android.support.annotation.NonNull;
@ -58,6 +57,7 @@ import butterknife.OnItemSelected;
import static com.google.common.collect.Lists.newArrayList;
import static com.google.common.collect.Sets.newHashSet;
import static com.todoroo.andlib.utility.DateUtilities.getLongDateStringWithTime;
import static org.tasks.PermissionUtil.verifyPermissions;
import static org.tasks.date.DateTimeUtils.newDateTime;
/**
@ -269,7 +269,7 @@ public class ReminderControlSet extends TaskEditControlFragment {
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
if (requestCode == PermissionRequestor.REQUEST_LOCATION) {
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
if (verifyPermissions(grantResults)) {
pickLocation();
}
} else {

@ -4,7 +4,8 @@ import android.content.pm.PackageManager;
public abstract class PermissionUtil {
public static boolean verifyPermissions(int[] grantResults) {
if(grantResults.length < 1){
if(grantResults.length == 0) {
// request canceled
return false;
}

@ -28,11 +28,12 @@ public class Tasks extends InjectingApplication {
tracker.setTrackingEnabled(preferences.isTrackingEnabled());
AndroidThreeTen.init(this);
if (!buildSetup.setup()) {
return;
}
AndroidThreeTen.init(this);
flavorSetup.setup();
teslaUnreadReceiver.setEnabled(preferences.getBoolean(R.string.p_tesla_unread_enabled, false));

@ -31,6 +31,8 @@ import javax.inject.Inject;
import timber.log.Timber;
import static com.google.common.base.Strings.isNullOrEmpty;
public class AddAttachmentActivity extends InjectingAppCompatActivity implements DialogInterface.OnCancelListener, AddAttachmentDialog.AddAttachmentCallback {
private static final String FRAG_TAG_ATTACHMENT_DIALOG = "frag_tag_attachment_dialog";
@ -159,7 +161,9 @@ public class AddAttachmentActivity extends InjectingAppCompatActivity implements
private File getFilename(String extension) {
AtomicReference<String> nameRef = new AtomicReference<>();
if (!extension.startsWith(".")) {
if (isNullOrEmpty(extension)) {
extension = "";
} else if (!extension.startsWith(".")) {
extension = "." + extension;
}
try {

@ -1,7 +1,6 @@
package org.tasks.activities;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.os.Bundle;
import android.support.annotation.NonNull;
@ -14,7 +13,6 @@ import org.tasks.calendars.CalendarProvider;
import org.tasks.dialogs.AlertDialogBuilder;
import org.tasks.dialogs.DialogBuilder;
import org.tasks.injection.DialogFragmentComponent;
import org.tasks.injection.ForActivity;
import org.tasks.injection.InjectingDialogFragment;
import org.tasks.preferences.FragmentPermissionRequestor;
import org.tasks.preferences.PermissionChecker;
@ -47,7 +45,6 @@ public class CalendarSelectionDialog extends InjectingDialogFragment {
@Inject DialogBuilder dialogBuilder;
@Inject CalendarProvider calendarProvider;
@Inject @ForActivity Context context;
@Inject FragmentPermissionRequestor fragmentPermissionRequestor;
@Inject PermissionChecker permissionChecker;
@Inject Theme theme;
@ -114,7 +111,7 @@ public class CalendarSelectionDialog extends InjectingDialogFragment {
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
if (requestCode == PermissionRequestor.REQUEST_CALENDAR) {
if (grantResults.length > 0 && !verifyPermissions(grantResults)) {
if (!verifyPermissions(grantResults)) {
handler.cancel();
}
} else {

@ -6,11 +6,11 @@ import android.os.Bundle;
import com.wdullaer.materialdatetimepicker.date.DatePickerDialog;
import org.tasks.R;
import org.tasks.dialogs.MyDatePickerDialog;
import org.tasks.dialogs.NativeDatePickerDialog;
import org.tasks.injection.ActivityComponent;
import org.tasks.injection.InjectingAppCompatActivity;
import org.tasks.preferences.Device;
import org.tasks.preferences.Preferences;
import org.tasks.themes.ThemeAccent;
import org.tasks.themes.ThemeBase;
@ -18,8 +18,6 @@ import org.tasks.time.DateTime;
import javax.inject.Inject;
import static com.todoroo.andlib.utility.AndroidUtilities.atLeastLollipop;
import static com.todoroo.andlib.utility.AndroidUtilities.atLeastMarshmallow;
import static org.tasks.dialogs.NativeDatePickerDialog.newNativeDatePickerDialog;
import static org.tasks.time.DateTimeUtils.currentTimeMillis;
@ -31,7 +29,6 @@ public class DatePickerActivity extends InjectingAppCompatActivity implements Da
@Inject ThemeBase themeBase;
@Inject ThemeAccent themeAccent;
@Inject Device device;
@Inject Preferences preferences;
@Override
@ -42,7 +39,8 @@ public class DatePickerActivity extends InjectingAppCompatActivity implements Da
DateTime initial = (timestamp > 0 ? new DateTime(timestamp) : new DateTime()).startOfDay();
FragmentManager fragmentManager = getFragmentManager();
if (atLeastMarshmallow() || (atLeastLollipop() && !device.isBaneOfMyExistence())) {
if (preferences.getBoolean(R.string.p_use_native_datetime_pickers, false)) {
if (fragmentManager.findFragmentByTag(FRAG_TAG_DATE_PICKER) == null) {
newNativeDatePickerDialog(initial)
.show(fragmentManager, FRAG_TAG_DATE_PICKER);

@ -7,17 +7,18 @@ import android.text.format.DateFormat;
import com.wdullaer.materialdatetimepicker.time.TimePickerDialog;
import org.tasks.R;
import org.tasks.dialogs.MyTimePickerDialog;
import org.tasks.dialogs.NativeTimePickerDialog;
import org.tasks.injection.ActivityComponent;
import org.tasks.injection.InjectingAppCompatActivity;
import org.tasks.preferences.Preferences;
import org.tasks.themes.ThemeAccent;
import org.tasks.themes.ThemeBase;
import org.tasks.time.DateTime;
import javax.inject.Inject;
import static com.todoroo.andlib.utility.AndroidUtilities.atLeastLollipop;
import static org.tasks.dialogs.NativeTimePickerDialog.newNativeTimePickerDialog;
import static org.tasks.time.DateTimeUtils.currentTimeMillis;
@ -30,6 +31,7 @@ public class TimePickerActivity extends InjectingAppCompatActivity implements Ti
@Inject ThemeBase themeBase;
@Inject ThemeAccent themeAccent;
@Inject Preferences preferences;
private DateTime initial;
@ -40,7 +42,7 @@ public class TimePickerActivity extends InjectingAppCompatActivity implements Ti
initial = new DateTime(getIntent().getLongExtra(EXTRA_TIMESTAMP, currentTimeMillis()));
FragmentManager fragmentManager = getFragmentManager();
if (atLeastLollipop()) {
if (preferences.getBoolean(R.string.p_use_native_datetime_pickers, false)) {
if (fragmentManager.findFragmentByTag(FRAG_TAG_TIME_PICKER) == null) {
newNativeTimePickerDialog(initial)
.show(fragmentManager, FRAG_TAG_TIME_PICKER);

@ -2,7 +2,6 @@ package org.tasks.files;
import android.app.Activity;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
@ -20,6 +19,8 @@ import java.io.File;
import javax.inject.Inject;
import static org.tasks.PermissionUtil.verifyPermissions;
public class FileExplore extends InjectingAppCompatActivity {
private static final int REQUEST_PICKER = 1000;
@ -79,12 +80,10 @@ public class FileExplore extends InjectingAppCompatActivity {
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
if (requestCode == PermissionRequestor.REQUEST_FILE_WRITE) {
if (grantResults.length > 0) {
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
launchPicker();
} else {
finish();
}
if (verifyPermissions(grantResults)) {
launchPicker();
} else {
finish();
}
} else {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);

@ -0,0 +1,45 @@
package org.tasks.injection;
import android.content.Context;
import org.tasks.locale.Locale;
import timber.log.Timber;
class Dagger {
private static final Object lock = new Object();
private static Dagger instance;
public static Dagger get(Context context) {
if (instance == null) {
synchronized (lock) {
if (instance == null) {
instance = new Dagger(context);
}
}
}
return instance;
}
private ApplicationComponent applicationComponent;
private Dagger(Context context) {
Context localeContext = context.getApplicationContext();
try {
localeContext = Locale.getInstance(localeContext)
.createConfigurationContext(localeContext);
} catch (Exception e) {
Timber.e(e.getMessage(), e);
}
applicationComponent = DaggerApplicationComponent.builder()
.applicationModule(new ApplicationModule(localeContext))
.build();
}
ApplicationComponent getApplicationComponent() {
return applicationComponent;
}
}

@ -15,9 +15,7 @@ public abstract class InjectingApplication extends BaseApplication {
Context context = Locale.getInstance(this).createConfigurationContext(getApplicationContext());
applicationComponent = DaggerApplicationComponent.builder()
.applicationModule(new ApplicationModule(context))
.build();
applicationComponent = Dagger.get(context).getApplicationComponent();
inject(applicationComponent);
}

@ -1,25 +1,37 @@
package org.tasks.injection;
import org.tasks.jobs.AlarmJob;
import org.tasks.jobs.BackupJob;
import org.tasks.jobs.MidnightRefreshJob;
import org.tasks.jobs.RefreshJob;
import org.tasks.jobs.ReminderJob;
import org.tasks.location.GeofenceTransitionsIntentService;
import org.tasks.scheduling.BackupIntentService;
import org.tasks.scheduling.CalendarNotificationIntentService;
import org.tasks.scheduling.GeofenceSchedulingIntentService;
import org.tasks.scheduling.RefreshSchedulerIntentService;
import org.tasks.scheduling.ReminderSchedulerIntentService;
import org.tasks.scheduling.NotificationSchedulerIntentService;
import org.tasks.scheduling.SchedulerIntentService;
import dagger.Subcomponent;
@Subcomponent(modules = IntentServiceModule.class)
public interface IntentServiceComponent {
void inject(ReminderSchedulerIntentService reminderSchedulerIntentService);
void inject(RefreshSchedulerIntentService refreshSchedulerIntentService);
void inject(SchedulerIntentService schedulerIntentService);
void inject(GeofenceSchedulingIntentService geofenceSchedulingIntentService);
void inject(CalendarNotificationIntentService calendarNotificationIntentService);
void inject(BackupIntentService backupIntentService);
void inject(GeofenceTransitionsIntentService geofenceTransitionsIntentService);
void inject(NotificationSchedulerIntentService notificationSchedulerIntentService);
void inject(AlarmJob alarmJob);
void inject(BackupJob backupJob);
void inject(MidnightRefreshJob midnightRefreshJob);
void inject(RefreshJob refreshJob);
void inject(ReminderJob reminderJob);
}

@ -0,0 +1,58 @@
package org.tasks.jobs;
import com.todoroo.astrid.alarms.AlarmFields;
import com.todoroo.astrid.data.Metadata;
public class Alarm implements JobQueueEntry {
private final long alarmId;
private final long taskId;
private final long time;
public Alarm(Metadata metadata) {
this(metadata.getId(), metadata.getTask(), metadata.getValue(AlarmFields.TIME));
}
public Alarm(long alarmId, long taskId, Long time) {
this.alarmId = alarmId;
this.taskId = taskId;
this.time = time;
}
@Override
public long getId() {
return alarmId;
}
public long getTaskId() {
return taskId;
}
@Override
public long getTime() {
return time;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Alarm alarm = (Alarm) o;
return alarmId == alarm.alarmId;
}
@Override
public int hashCode() {
return (int) (alarmId ^ (alarmId >>> 32));
}
@Override
public String toString() {
return "Alarm{" +
"alarmId=" + alarmId +
", taskId=" + taskId +
", time=" + time +
'}';
}
}

@ -0,0 +1,56 @@
package org.tasks.jobs;
import android.content.Intent;
import com.todoroo.astrid.alarms.AlarmService;
import com.todoroo.astrid.dao.TaskDao;
import com.todoroo.astrid.data.Task;
import com.todoroo.astrid.reminders.ReminderService;
import org.tasks.Notifier;
import org.tasks.injection.IntentServiceComponent;
import org.tasks.preferences.Preferences;
import javax.inject.Inject;
public class AlarmJob extends WakefulJob {
public static final String TAG = "job_alarm";
@Inject Preferences preferences;
@Inject AlarmService alarmService;
@Inject Notifier notifier;
@Inject TaskDao taskDao;
public AlarmJob() {
super(AlarmJob.class.getSimpleName());
}
@Override
protected void run() {
if (!preferences.isCurrentlyQuietHours()) {
for (Alarm alarm : alarmService.getOverdueAlarms()) {
Task task = taskDao.fetch(alarm.getTaskId(), Task.REMINDER_LAST);
if (task != null && task.getReminderLast() < alarm.getTime()) {
notifier.triggerTaskNotification(alarm.getTaskId(), ReminderService.TYPE_ALARM);
}
alarmService.remove(alarm);
}
}
}
@Override
protected void scheduleNext() {
alarmService.scheduleNextJob();
}
@Override
protected void inject(IntentServiceComponent component) {
component.inject(this);
}
@Override
protected void completeWakefulIntent(Intent intent) {
AlarmJobBroadcast.completeWakefulIntent(intent);
}
}

@ -0,0 +1,12 @@
package org.tasks.jobs;
import android.content.Context;
import android.content.Intent;
import android.support.v4.content.WakefulBroadcastReceiver;
public class AlarmJobBroadcast extends WakefulBroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
startWakefulService(context, new Intent(context, AlarmJob.class));
}
}

@ -1,9 +1,10 @@
package org.tasks.scheduling;
package org.tasks.jobs;
import android.content.Context;
import com.todoroo.astrid.backup.TasksXmlExporter;
import org.tasks.injection.ForApplication;
import org.tasks.injection.IntentServiceComponent;
import org.tasks.preferences.Preferences;
@ -15,41 +16,42 @@ import javax.inject.Inject;
import timber.log.Timber;
public class BackupIntentService extends MidnightIntentService {
public class BackupJob extends MidnightJob {
public static final String TAG = "job_backup";
public static final String BACKUP_FILE_NAME_REGEX = "auto\\.[-\\d]+\\.xml"; //$NON-NLS-1$
private static final int DAYS_TO_KEEP_BACKUP = 7;
@Inject TasksXmlExporter xmlExporter;
@Inject @ForApplication Context context;
@Inject JobManager jobManager;
@Inject TasksXmlExporter tasksXmlExporter;
@Inject Preferences preferences;
public BackupIntentService() {
super(BackupIntentService.class.getSimpleName());
public BackupJob() {
super(BackupJob.class.getSimpleName());
}
@Override
void run() {
startBackup(this);
BackupJob(Context context, JobManager jobManager, TasksXmlExporter tasksXmlExporter, Preferences preferences) {
this();
this.context = context;
this.jobManager = jobManager;
this.tasksXmlExporter = tasksXmlExporter;
this.preferences = preferences;
}
@Override
protected String getLastRunPreference() {
return TasksXmlExporter.PREF_BACKUP_LAST_DATE;
protected void run() {
startBackup(context);
}
/**
* Test hook for backup
*/
void testBackup(TasksXmlExporter xmlExporter, Preferences preferences, Context context) {
this.xmlExporter = xmlExporter;
this.preferences = preferences;
startBackup(context);
@Override
protected void scheduleNext() {
jobManager.scheduleMidnightBackup();
}
private void startBackup(Context context) {
if (context == null || context.getResources() == null) {
return;
}
void startBackup(Context context) {
try {
deleteOldBackups();
} catch (Exception e) {
@ -57,7 +59,7 @@ public class BackupIntentService extends MidnightIntentService {
}
try {
xmlExporter.exportTasks(context, TasksXmlExporter.ExportType.EXPORT_TYPE_SERVICE, null);
tasksXmlExporter.exportTasks(context, TasksXmlExporter.ExportType.EXPORT_TYPE_SERVICE, null);
} catch (Exception e) {
Timber.e(e, e.getMessage());
}

@ -0,0 +1,39 @@
package org.tasks.jobs;
import android.content.Intent;
import org.tasks.analytics.Tracker;
import org.tasks.injection.InjectingIntentService;
import javax.inject.Inject;
import timber.log.Timber;
public abstract class Job extends InjectingIntentService {
@Inject Tracker tracker;
public Job(String name) {
super(name);
setIntentRedelivery(true);
}
@Override
protected void onHandleIntent(Intent intent) {
super.onHandleIntent(intent);
Timber.d("onHandleIntent(%s)", intent);
try {
run();
} catch (Exception e) {
tracker.reportException(e);
} finally {
scheduleNext();
}
}
protected abstract void run();
protected abstract void scheduleNext();
}

@ -0,0 +1,82 @@
package org.tasks.jobs;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import org.tasks.injection.ApplicationScope;
import org.tasks.injection.ForApplication;
import org.tasks.scheduling.AlarmManager;
import javax.inject.Inject;
import timber.log.Timber;
import static org.tasks.time.DateTimeUtils.currentTimeMillis;
import static org.tasks.time.DateTimeUtils.nextMidnight;
import static org.tasks.time.DateTimeUtils.printTimestamp;
@ApplicationScope
public class JobManager {
private Context context;
private AlarmManager alarmManager;
@Inject
public JobManager(@ForApplication Context context, AlarmManager alarmManager) {
this.context = context;
this.alarmManager = alarmManager;
}
public void schedule(String tag, long time) {
Timber.d("%s: %s", tag, printTimestamp(time));
alarmManager.wakeup(adjust(time), getPendingIntent(tag));
}
public void scheduleRefresh(long time) {
Timber.d("%s: %s", RefreshJob.TAG, printTimestamp(time));
alarmManager.noWakeup(adjust(time), getPendingService(RefreshJob.class));
}
public void scheduleMidnightRefresh() {
long time = nextMidnight();
Timber.d("%s: %s", MidnightRefreshJob.TAG, printTimestamp(time));
alarmManager.noWakeup(adjust(time), getPendingService(MidnightRefreshJob.class));
}
public void scheduleMidnightBackup() {
long time = nextMidnight();
Timber.d("%s: %s", BackupJob.TAG, printTimestamp(time));
alarmManager.noWakeup(adjust(time), getPendingService(BackupJob.class));
}
public void cancel(String tag) {
Timber.d("CXL %s", tag);
alarmManager.cancel(getPendingIntent(tag));
}
private long adjust(long time) {
return Math.max(time, currentTimeMillis() + 5000);
}
private PendingIntent getPendingIntent(String tag) {
switch (tag) {
case ReminderJob.TAG:
return getPendingBroadcast(ReminderJobBroadcast.class);
case AlarmJob.TAG:
return getPendingBroadcast(AlarmJobBroadcast.class);
case RefreshJob.TAG:
return getPendingService(RefreshJob.class);
default:
throw new RuntimeException("Unexpected tag: " + tag);
}
}
private <T> PendingIntent getPendingBroadcast(Class<T> c) {
return PendingIntent.getBroadcast(context, 0, new Intent(context, c), 0);
}
private <T> PendingIntent getPendingService(Class<T> c) {
return PendingIntent.getService(context, 0, new Intent(context, c), 0);
}
}

@ -0,0 +1,107 @@
package org.tasks.jobs;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Ordering;
import com.google.common.collect.TreeMultimap;
import com.google.common.primitives.Longs;
import org.tasks.preferences.Preferences;
import java.util.List;
import java.util.NavigableSet;
import java.util.SortedSet;
import static com.google.common.collect.Iterables.filter;
import static com.google.common.collect.Lists.newArrayList;
import static org.tasks.time.DateTimeUtils.currentTimeMillis;
public class JobQueue<T extends JobQueueEntry> {
private final TreeMultimap<Long, T> jobs = TreeMultimap.create(Ordering.natural(), (l, r) -> Longs.compare(l.getId(), r.getId()));
private final Preferences preferences;
private final JobManager jobManager;
private final String tag;
public static JobQueue<Reminder> newReminderQueue(Preferences preferences, JobManager jobManager) {
return new JobQueue<>(preferences, jobManager, ReminderJob.TAG);
}
public static JobQueue<Alarm> newAlarmQueue(Preferences preferences, JobManager jobManager) {
return new JobQueue<>(preferences, jobManager, AlarmJob.TAG);
}
JobQueue(Preferences preferences, JobManager jobManager, String tag) {
this.preferences = preferences;
this.jobManager = jobManager;
this.tag = tag;
}
public synchronized void add(T entry) {
boolean reschedule = jobs.isEmpty() || entry.getTime() < firstTime();
jobs.put(entry.getTime(), entry);
if (reschedule) {
scheduleNext(true);
}
}
public synchronized void clear() {
jobs.clear();
jobManager.cancel(tag);
}
public synchronized void cancel(long id) {
boolean reschedule = false;
long firstTime = firstTime();
List<T> existing = newArrayList(filter(jobs.values(), r -> r.getId() == id));
for (T entry : existing) {
reschedule |= entry.getTime() == firstTime;
jobs.remove(entry.getTime(), entry);
}
if (reschedule) {
scheduleNext(true);
}
}
public synchronized List<T> getOverdueJobs() {
List<T> result = newArrayList();
for (Long key : jobs.keySet().headSet(currentTimeMillis() + 1)) {
result.addAll(jobs.get(key));
}
return result;
}
public synchronized boolean remove(T entry) {
return jobs.remove(entry.getTime(), entry);
}
public synchronized void scheduleNext() {
scheduleNext(false);
}
private void scheduleNext(boolean cancelCurrent) {
if (jobs.isEmpty()) {
if (cancelCurrent) {
jobManager.cancel(tag);
}
} else {
jobManager.schedule(tag, nextScheduledTime());
}
}
private long firstTime() {
return jobs.isEmpty() ? 0 : jobs.asMap().firstKey();
}
long nextScheduledTime() {
long next = firstTime();
return next > 0 ? preferences.adjustForQuietHours(next) : 0;
}
int size() {
return jobs.size();
}
List<T> getJobs() {
return ImmutableList.copyOf(jobs.values());
}
}

@ -0,0 +1,7 @@
package org.tasks.jobs;
public interface JobQueueEntry {
long getId();
long getTime();
}

@ -0,0 +1,7 @@
package org.tasks.jobs;
public abstract class MidnightJob extends Job {
public MidnightJob(String name) {
super(name);
}
}

@ -0,0 +1,33 @@
package org.tasks.jobs;
import org.tasks.Broadcaster;
import org.tasks.injection.IntentServiceComponent;
import javax.inject.Inject;
public class MidnightRefreshJob extends MidnightJob {
public static final String TAG = "job_midnight_refresh";
@Inject Broadcaster broadcaster;
@Inject JobManager jobManager;
public MidnightRefreshJob() {
super(MidnightRefreshJob.class.getSimpleName());
}
@Override
protected void run() {
broadcaster.refresh();
}
@Override
protected void scheduleNext() {
jobManager.scheduleMidnightRefresh();
}
@Override
protected void inject(IntentServiceComponent component) {
component.inject(this);
}
}

@ -0,0 +1,34 @@
package org.tasks.jobs;
import org.tasks.Broadcaster;
import org.tasks.injection.IntentServiceComponent;
import org.tasks.scheduling.RefreshScheduler;
import javax.inject.Inject;
public class RefreshJob extends Job {
public static final String TAG = "job_refresh";
@Inject RefreshScheduler refreshScheduler;
@Inject Broadcaster broadcaster;
public RefreshJob() {
super(RefreshJob.class.getSimpleName());
}
@Override
protected void inject(IntentServiceComponent component) {
component.inject(this);
}
@Override
protected void run() {
broadcaster.refresh();
}
@Override
protected void scheduleNext() {
refreshScheduler.scheduleNext();
}
}

@ -0,0 +1,57 @@
package org.tasks.jobs;
public class Reminder implements JobQueueEntry {
private final long taskId;
private final long time;
private final int type;
public Reminder(long taskId, long time, int type) {
this.taskId = taskId;
this.time = time;
this.type = type;
}
@Override
public long getId() {
return taskId;
}
@Override
public long getTime() {
return time;
}
public int getType() {
return type;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Reminder reminder = (Reminder) o;
if (taskId != reminder.taskId) return false;
if (time != reminder.time) return false;
return type == reminder.type;
}
@Override
public int hashCode() {
int result = (int) (taskId ^ (taskId >>> 32));
result = 31 * result + (int) (time ^ (time >>> 32));
result = 31 * result + type;
return result;
}
@Override
public String toString() {
return "Reminder{" +
"taskId=" + taskId +
", time=" + time +
", type=" + type +
'}';
}
}

@ -0,0 +1,49 @@
package org.tasks.jobs;
import android.content.Intent;
import com.todoroo.astrid.reminders.ReminderService;
import org.tasks.Notifier;
import org.tasks.injection.IntentServiceComponent;
import org.tasks.preferences.Preferences;
import javax.inject.Inject;
public class ReminderJob extends WakefulJob {
public static final String TAG = "job_reminder";
@Inject Preferences preferences;
@Inject ReminderService reminderService;
@Inject Notifier notifier;
public ReminderJob() {
super(ReminderJob.class.getSimpleName());
}
@Override
protected void inject(IntentServiceComponent component) {
component.inject(this);
}
@Override
protected void run() {
if (!preferences.isCurrentlyQuietHours()) {
for (Reminder reminder : reminderService.getPastReminders()) {
notifier.triggerTaskNotification(reminder.getId(), reminder.getType());
reminderService.remove(reminder);
}
}
}
@Override
protected void scheduleNext() {
reminderService.scheduleNextJob();
}
@Override
protected void completeWakefulIntent(Intent intent) {
ReminderJobBroadcast.completeWakefulIntent(intent);
}
}

@ -0,0 +1,12 @@
package org.tasks.jobs;
import android.content.Context;
import android.content.Intent;
import android.support.v4.content.WakefulBroadcastReceiver;
public class ReminderJobBroadcast extends WakefulBroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
startWakefulService(context, new Intent(context, ReminderJob.class));
}
}

@ -0,0 +1,18 @@
package org.tasks.jobs;
import android.content.Intent;
public abstract class WakefulJob extends Job {
public WakefulJob(String name) {
super(name);
}
@Override
protected void onHandleIntent(Intent intent) {
super.onHandleIntent(intent);
completeWakefulIntent(intent);
}
protected abstract void completeWakefulIntent(Intent intent);
}

@ -28,11 +28,16 @@ public class Locale {
public static Locale getInstance(Context context) {
if (INSTANCE == null) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
String language = prefs.getString(context.getString(R.string.p_language), null);
int directionOverride = Integer.parseInt(prefs.getString(context.getString(R.string.p_layout_direction), "-1"));
INSTANCE = new Locale(DEFAULT.getLocale(), language, directionOverride);
java.util.Locale.setDefault(INSTANCE.getLocale());
synchronized (DEFAULT) {
if (INSTANCE == null) {
Context applicationContext = context.getApplicationContext();
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(applicationContext);
String language = prefs.getString(applicationContext.getString(R.string.p_language), null);
int directionOverride = Integer.parseInt(prefs.getString(applicationContext.getString(R.string.p_layout_direction), "-1"));
INSTANCE = new Locale(DEFAULT.getLocale(), language, directionOverride);
java.util.Locale.setDefault(INSTANCE.getLocale());
}
}
}
return getInstance();

@ -137,7 +137,7 @@ public class BasicPreferences extends InjectingPreferenceActivity implements
});
findPreference(R.string.TLA_menu_donate).setOnPreferenceClickListener(preference -> {
if (BuildConfig.FLAVOR_store.equals("googleplay")) {
if (BuildConfig.FLAVOR.equals("googleplay")) {
newDonationDialog().show(getFragmentManager(), FRAG_TAG_DONATION);
} else {
startActivity(new Intent(Intent.ACTION_VIEW).setData(Uri.parse("http://tasks.org/donate")));
@ -214,7 +214,7 @@ public class BasicPreferences extends InjectingPreferenceActivity implements
requires(R.string.get_plugins, atLeastJellybeanMR1(), R.string.p_purchased_dashclock);
requires(R.string.settings_localization, atLeastJellybeanMR1(), R.string.p_language, R.string.p_layout_direction);
if (!BuildConfig.FLAVOR_store.equals("googleplay")) {
if (!BuildConfig.FLAVOR.equals("googleplay")) {
requires(R.string.settings_general, false, R.string.synchronization);
requires(R.string.privacy, false, R.string.p_collect_statistics);
}

@ -29,10 +29,6 @@ public class Device {
this.locale = locale;
}
public boolean isBaneOfMyExistence() {
return Build.MANUFACTURER.equalsIgnoreCase("samsung");
}
public boolean hasCamera() {
return context.getPackageManager().queryIntentActivities(new Intent(MediaStore.ACTION_IMAGE_CAPTURE), 0).size() > 0;
}

@ -1,7 +1,6 @@
package org.tasks.preferences;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.preference.CheckBoxPreference;
import android.speech.tts.TextToSpeech;
@ -21,6 +20,8 @@ import javax.inject.Inject;
import timber.log.Timber;
import static org.tasks.PermissionUtil.verifyPermissions;
public class MiscellaneousPreferences extends InjectingPreferenceActivity {
private static final int REQUEST_CODE_FILES_DIR = 2;
@ -143,7 +144,7 @@ public class MiscellaneousPreferences extends InjectingPreferenceActivity {
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
if (requestCode == PermissionRequestor.REQUEST_CALENDAR) {
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
if (verifyPermissions(grantResults)) {
calendarReminderPreference.setChecked(true);
}
} else {

@ -28,7 +28,6 @@ import timber.log.Timber;
import static android.content.SharedPreferences.Editor;
import static com.todoroo.andlib.utility.AndroidUtilities.atLeastJellybean;
import static com.todoroo.andlib.utility.AndroidUtilities.atLeastMarshmallow;
public class Preferences {
@ -57,10 +56,52 @@ public class Preferences {
return getBoolean(R.string.p_back_button_saves_task, false);
}
public boolean isCurrentlyQuietHours() {
if (quietHoursEnabled()) {
DateTime dateTime = new DateTime();
DateTime start = dateTime.withMillisOfDay(getQuietHoursStart());
DateTime end = dateTime.withMillisOfDay(getQuietHoursEnd());
if (start.isAfter(end)) {
return dateTime.isBefore(end) || dateTime.isAfter(start);
} else {
return dateTime.isAfter(start) && dateTime.isBefore(end);
}
}
return false;
}
public long adjustForQuietHours(long time) {
if (quietHoursEnabled()) {
DateTime dateTime = new DateTime(time);
DateTime start = dateTime.withMillisOfDay(getQuietHoursStart());
DateTime end = dateTime.withMillisOfDay(getQuietHoursEnd());
if (start.isAfter(end)) {
if (dateTime.isBefore(end)) {
return end.getMillis();
} else if (dateTime.isAfter(start)) {
return end.plusDays(1).getMillis();
}
} else {
if (dateTime.isAfter(start) && dateTime.isBefore(end)) {
return end.getMillis();
}
}
}
return time;
}
public boolean quietHoursEnabled() {
return getBoolean(R.string.p_rmd_enable_quiet, false);
}
public int getQuietHoursStart() {
return getMillisPerDayPref(R.string.p_rmd_quietStart, R.integer.default_quiet_hours_start);
}
public int getQuietHoursEnd() {
return getMillisPerDayPref(R.string.p_rmd_quietEnd, R.integer.default_quiet_hours_end);
}
public int getDateShortcutMorning() {
return getMillisPerDayPref(R.string.p_date_shortcut_morning, R.integer.default_morning);
}
@ -78,9 +119,11 @@ public class Preferences {
}
private int getMillisPerDayPref(int resId, int defResId) {
int defaultValue = context.getResources().getInteger(defResId);
int setting = getInt(resId, defaultValue);
return setting < 0 || setting > DateTime.MAX_MILLIS_PER_DAY ? defaultValue : setting;
int setting = getInt(resId, -1);
if (setting < 0 || setting > DateTime.MAX_MILLIS_PER_DAY) {
return context.getResources().getInteger(defResId);
}
return setting;
}
public boolean isDefaultCalendarSet() {
@ -100,10 +143,6 @@ public class Preferences {
return getStringValue(R.string.gcal_p_default);
}
public boolean isDozeNotificationEnabled() {
return atLeastMarshmallow() && getBoolean(R.string.p_doze_notifications, false);
}
public int getFirstDayOfWeek() {
int firstDayOfWeek = getIntegerFromString(R.string.p_start_of_week, 0);
return firstDayOfWeek < 1 || firstDayOfWeek > 7 ? 0 : firstDayOfWeek;

@ -1,31 +0,0 @@
package org.tasks.receivers;
import android.content.Context;
import android.content.Intent;
import org.tasks.Broadcaster;
import org.tasks.injection.BroadcastComponent;
import org.tasks.injection.InjectingBroadcastReceiver;
import javax.inject.Inject;
import timber.log.Timber;
public class RefreshReceiver extends InjectingBroadcastReceiver {
@Inject Broadcaster broadcaster;
@Override
public void onReceive(Context context, Intent intent) {
super.onReceive(context, intent);
Timber.d("onReceive(context, %s)", intent);
broadcaster.refresh();
}
@Override
protected void inject(BroadcastComponent component) {
component.inject(this);
}
}

@ -1,37 +0,0 @@
package org.tasks.receivers;
import android.content.Context;
import android.content.Intent;
import org.tasks.Notifier;
import org.tasks.injection.BroadcastComponent;
import org.tasks.injection.InjectingBroadcastReceiver;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import javax.inject.Inject;
public class TaskNotificationReceiver extends InjectingBroadcastReceiver {
private static final ExecutorService executorService = Executors.newSingleThreadExecutor();
public static final String ID_KEY = "id"; //$NON-NLS-1$
public static final String EXTRAS_TYPE = "type"; //$NON-NLS-1$
@Inject Notifier notifier;
@Override
public void onReceive(Context context, final Intent intent) {
super.onReceive(context, intent);
executorService.execute(() -> notifier.triggerTaskNotification(
intent.getLongExtra(ID_KEY, 0),
intent.getIntExtra(EXTRAS_TYPE, (byte) 0)));
}
@Override
protected void inject(BroadcastComponent component) {
component.inject(this);
}
}

@ -0,0 +1,10 @@
package org.tasks.reminders;
public class Random {
private static final java.util.Random random = new java.util.Random();
public float nextFloat() {
return random.nextFloat();
}
}

@ -97,7 +97,8 @@ public class SnoozeDialog extends InjectingDialogFragment {
List<SnoozeOption> snoozeOptions = new ArrayList<>();
snoozeOptions.add(new SnoozeOption(R.string.date_shortcut_hour, now.plusHours(1)));
DateTime oneHour = now.plusHours(1).withSecondOfMinute(0).withMillisOfSecond(0);
snoozeOptions.add(new SnoozeOption(R.string.date_shortcut_hour, oneHour));
if (morning.isAfter(hourCutoff)) {
snoozeOptions.add(new SnoozeOption(R.string.date_shortcut_morning, morning));

@ -4,23 +4,19 @@ import android.annotation.SuppressLint;
import android.app.PendingIntent;
import android.content.Context;
import org.tasks.R;
import org.tasks.injection.ForApplication;
import org.tasks.preferences.Preferences;
import org.tasks.time.DateTime;
import javax.inject.Inject;
import static com.todoroo.andlib.utility.AndroidUtilities.atLeastKitKat;
import static com.todoroo.andlib.utility.AndroidUtilities.atLeastMarshmallow;
public class AlarmManager {
private final android.app.AlarmManager alarmManager;
private final Preferences preferences;
@Inject
public AlarmManager(@ForApplication Context context, Preferences preferences) {
this.preferences = preferences;
public AlarmManager(@ForApplication Context context) {
alarmManager = (android.app.AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
}
@ -28,13 +24,9 @@ public class AlarmManager {
alarmManager.cancel(pendingIntent);
}
public void wakeupAdjustingForQuietHours(long time, PendingIntent pendingIntent) {
wakeup(adjustForQuietHours(time), pendingIntent);
}
@SuppressLint("NewApi")
public void wakeup(long time, PendingIntent pendingIntent) {
if (preferences.isDozeNotificationEnabled()) {
if (atLeastMarshmallow()) {
alarmManager.setExactAndAllowWhileIdle(android.app.AlarmManager.RTC_WAKEUP, time, pendingIntent);
} else if (atLeastKitKat()) {
alarmManager.setExact(android.app.AlarmManager.RTC_WAKEUP, time, pendingIntent);
@ -45,7 +37,7 @@ public class AlarmManager {
@SuppressLint("NewApi")
public void noWakeup(long time, PendingIntent pendingIntent) {
if (preferences.isDozeNotificationEnabled()) {
if (atLeastMarshmallow()) {
alarmManager.setExactAndAllowWhileIdle(android.app.AlarmManager.RTC, time, pendingIntent);
} else if (atLeastKitKat()) {
alarmManager.setExact(android.app.AlarmManager.RTC, time, pendingIntent);
@ -53,24 +45,4 @@ public class AlarmManager {
alarmManager.set(android.app.AlarmManager.RTC, time, pendingIntent);
}
}
long adjustForQuietHours(long time) {
if (preferences.quietHoursEnabled()) {
DateTime dateTime = new DateTime(time);
DateTime start = dateTime.withMillisOfDay(preferences.getInt(R.string.p_rmd_quietStart));
DateTime end = dateTime.withMillisOfDay(preferences.getInt(R.string.p_rmd_quietEnd));
if (start.isAfter(end)) {
if (dateTime.isBefore(end)) {
return end.getMillis();
} else if (dateTime.isAfter(start)) {
return end.plusDays(1).getMillis();
}
} else {
if (dateTime.isAfter(start) && dateTime.isBefore(end)) {
return end.getMillis();
}
}
}
return time;
}
}

@ -17,20 +17,11 @@ public class BackgroundScheduler {
public void scheduleEverything() {
context.startService(new Intent(context, GeofenceSchedulingIntentService.class));
context.startService(new Intent(context, ReminderSchedulerIntentService.class));
scheduleBackupService();
scheduleMidnightRefresh();
context.startService(new Intent(context, SchedulerIntentService.class));
context.startService(new Intent(context, NotificationSchedulerIntentService.class));
scheduleCalendarNotifications();
}
public void scheduleBackupService() {
context.startService(new Intent(context, BackupIntentService.class));
}
public void scheduleMidnightRefresh() {
context.startService(new Intent(context, RefreshSchedulerIntentService.class));
}
public void scheduleCalendarNotifications() {
context.startService(new Intent(context, CalendarNotificationIntentService.class));
}

@ -1,61 +0,0 @@
package org.tasks.scheduling;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import org.tasks.injection.InjectingIntentService;
import org.tasks.preferences.Preferences;
import timber.log.Timber;
import static com.google.common.base.Strings.isNullOrEmpty;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.tasks.time.DateTimeUtils.currentTimeMillis;
import static org.tasks.time.DateTimeUtils.nextMidnight;
import static org.tasks.time.DateTimeUtils.printTimestamp;
public abstract class MidnightIntentService extends InjectingIntentService {
private static final long PADDING = SECONDS.toMillis(1);
private final String name;
MidnightIntentService(String name) {
super(name);
this.name = name;
}
@Override
protected void onHandleIntent(Intent intent) {
super.onHandleIntent(intent);
Context context = getApplicationContext();
Preferences preferences = new Preferences(context);
AlarmManager alarmManager = new AlarmManager(context, preferences);
long lastRun = preferences.getLong(getLastRunPreference(), 0);
long nextRun = nextMidnight(lastRun);
long now = currentTimeMillis();
if (nextRun <= now) {
nextRun = nextMidnight(now);
Timber.d("%s running now [nextRun=%s]", name, printTimestamp(nextRun));
if (!isNullOrEmpty(getLastRunPreference())) {
preferences.setLong(getLastRunPreference(), now);
}
run();
} else {
Timber.d("%s will run at %s [lastRun=%s]", name, printTimestamp(nextRun), printTimestamp(lastRun));
}
PendingIntent pendingIntent = PendingIntent.getService(this, 0, new Intent(this, this.getClass()), PendingIntent.FLAG_UPDATE_CURRENT);
alarmManager.noWakeup(nextRun + PADDING, pendingIntent);
}
abstract void run();
String getLastRunPreference() {
return null;
}
}

@ -13,14 +13,14 @@ import javax.inject.Inject;
import timber.log.Timber;
public class ReminderSchedulerIntentService extends InjectingIntentService {
public class NotificationSchedulerIntentService extends InjectingIntentService {
@Inject AlarmService alarmService;
@Inject ReminderService reminderService;
@Inject TaskDao taskDao;
public ReminderSchedulerIntentService() {
super(ReminderSchedulerIntentService.class.getSimpleName());
public NotificationSchedulerIntentService() {
super(NotificationSchedulerIntentService.class.getSimpleName());
}
@Override
@ -29,6 +29,9 @@ public class ReminderSchedulerIntentService extends InjectingIntentService {
Timber.d("onHandleIntent(%s)", intent);
reminderService.clear();
alarmService.clear();
reminderService.scheduleAllAlarms(taskDao);
alarmService.scheduleAllAlarms();
}

@ -1,33 +1,34 @@
package org.tasks.scheduling;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import com.todoroo.astrid.data.Task;
import org.tasks.injection.ForApplication;
import org.tasks.receivers.RefreshReceiver;
import org.tasks.injection.ApplicationScope;
import org.tasks.jobs.JobManager;
import org.tasks.jobs.RefreshJob;
import javax.inject.Inject;
import java.util.SortedSet;
import java.util.TreeSet;
import timber.log.Timber;
import javax.inject.Inject;
import static android.app.PendingIntent.FLAG_UPDATE_CURRENT;
import static com.google.common.collect.Lists.newArrayList;
import static com.todoroo.andlib.utility.DateUtilities.ONE_MINUTE;
import static org.tasks.time.DateTimeUtils.currentTimeMillis;
import static org.tasks.time.DateTimeUtils.nextMidnight;
import static org.tasks.time.DateTimeUtils.printTimestamp;
@ApplicationScope
public class RefreshScheduler {
private final Context context;
private final AlarmManager alarmManager;
private final JobManager jobManager;
private final SortedSet<Long> jobs = new TreeSet<>();
@Inject
public RefreshScheduler(@ForApplication Context context, AlarmManager alarmManager) {
this.context = context;
this.alarmManager = alarmManager;
public RefreshScheduler(JobManager jobManager) {
this.jobManager = jobManager;
}
public void clear() {
jobs.clear();
jobManager.cancel(RefreshJob.TAG);
}
public void scheduleRefresh(Task task) {
@ -43,13 +44,26 @@ public class RefreshScheduler {
private void scheduleRefresh(Long refreshTime) {
long now = currentTimeMillis();
if (now < refreshTime && refreshTime < nextMidnight(now)) {
if (now < refreshTime) {
refreshTime += 1000; // this is ghetto
Timber.d("Scheduling refresh at %s", printTimestamp(refreshTime));
Intent intent = new Intent(context, RefreshReceiver.class);
intent.setAction(Long.toString(refreshTime));
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, intent, FLAG_UPDATE_CURRENT);
alarmManager.noWakeup(refreshTime, pendingIntent);
schedule(refreshTime);
}
}
private void schedule(long timestamp) {
SortedSet<Long> upcoming = jobs.tailSet(currentTimeMillis());
boolean reschedule = upcoming.isEmpty() || timestamp < upcoming.first();
jobs.add(timestamp);
if (reschedule) {
scheduleNext();
}
}
public void scheduleNext() {
long now = currentTimeMillis();
jobs.removeAll(newArrayList(jobs.headSet(now + 1)));
if (!jobs.isEmpty()) {
jobManager.scheduleRefresh(jobs.first());
}
}
}

@ -1,44 +0,0 @@
package org.tasks.scheduling;
import com.todoroo.andlib.sql.Criterion;
import com.todoroo.astrid.dao.TaskDao;
import com.todoroo.astrid.data.Task;
import org.tasks.Broadcaster;
import org.tasks.injection.IntentServiceComponent;
import javax.inject.Inject;
import static org.tasks.time.DateTimeUtils.currentTimeMillis;
import static org.tasks.time.DateTimeUtils.nextMidnight;
public class RefreshSchedulerIntentService extends MidnightIntentService {
@Inject Broadcaster broadcaster;
@Inject RefreshScheduler refreshScheduler;
@Inject TaskDao taskDao;
public RefreshSchedulerIntentService() {
super(RefreshSchedulerIntentService.class.getSimpleName());
}
@Override
void run() {
scheduleApplicationRefreshes();
broadcaster.refresh();
}
public void scheduleApplicationRefreshes() {
long now = currentTimeMillis();
long midnight = nextMidnight(now);
Criterion criterion = Criterion.or(
Criterion.and(Task.HIDE_UNTIL.gt(now), Task.HIDE_UNTIL.lt(midnight)),
Criterion.and(Task.DUE_DATE.gt(now), Task.DUE_DATE.lt(midnight)));
taskDao.selectActive(criterion, refreshScheduler::scheduleRefresh);
}
@Override
protected void inject(IntentServiceComponent component) {
component.inject(this);
}
}

@ -0,0 +1,49 @@
package org.tasks.scheduling;
import android.content.Intent;
import com.todoroo.andlib.sql.Criterion;
import com.todoroo.astrid.dao.TaskDao;
import com.todoroo.astrid.data.Task;
import org.tasks.injection.InjectingIntentService;
import org.tasks.injection.IntentServiceComponent;
import org.tasks.jobs.JobManager;
import javax.inject.Inject;
import timber.log.Timber;
import static java.lang.System.currentTimeMillis;
public class SchedulerIntentService extends InjectingIntentService {
@Inject TaskDao taskDao;
@Inject JobManager jobManager;
@Inject RefreshScheduler refreshScheduler;
public SchedulerIntentService() {
super(SchedulerIntentService.class.getSimpleName());
}
@Override
protected void onHandleIntent(Intent intent) {
super.onHandleIntent(intent);
Timber.d("onHandleIntent(%s)", intent);
jobManager.scheduleMidnightBackup();
jobManager.scheduleMidnightRefresh();
refreshScheduler.clear();
long now = currentTimeMillis();
taskDao.selectActive(
Criterion.or(Task.HIDE_UNTIL.gt(now), Task.DUE_DATE.gt(now)),
refreshScheduler::scheduleRefresh);
}
@Override
protected void inject(IntentServiceComponent component) {
component.inject(this);
}
}

@ -189,6 +189,10 @@ public class DateTime {
return subtract(Calendar.DATE, days);
}
public DateTime minusHours(int hours) {
return subtract(Calendar.HOUR, hours);
}
public DateTime minusMinutes(int minutes) {
return subtract(Calendar.MINUTE, minutes);
}

@ -23,6 +23,10 @@ public class DateTimeUtils {
MILLIS_PROVIDER = SYSTEM_MILLIS_PROVIDER;
}
public static long nextMidnight() {
return nextMidnight(currentTimeMillis());
}
public static long nextMidnight(long timestamp) {
return newDateTime(timestamp).startOfDay().plusDays(1).getMillis();
}

@ -5,7 +5,6 @@ import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
@ -44,6 +43,7 @@ import timber.log.Timber;
import static android.support.v4.content.ContextCompat.getColor;
import static com.google.common.base.Strings.isNullOrEmpty;
import static org.tasks.PermissionUtil.verifyPermissions;
public class CalendarControlSet extends TaskEditControlFragment {
@ -289,11 +289,11 @@ public class CalendarControlSet extends TaskEditControlFragment {
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
if (requestCode == REQUEST_CODE_OPEN_EVENT) {
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
if (verifyPermissions(grantResults)) {
openCalendarEvent();
}
} else if (requestCode == REQUEST_CODE_CLEAR_EVENT) {
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
if (verifyPermissions(grantResults)) {
clear();
}
} else {

@ -17,6 +17,7 @@ import android.widget.ArrayAdapter;
import android.widget.Spinner;
import android.widget.TextView;
import com.todoroo.andlib.utility.AndroidUtilities;
import com.todoroo.andlib.utility.DateUtilities;
import com.todoroo.astrid.data.Task;
@ -37,6 +38,7 @@ import javax.inject.Inject;
import butterknife.BindView;
import butterknife.OnClick;
import butterknife.OnItemSelected;
import butterknife.OnTouch;
import static android.support.v4.content.ContextCompat.getColor;
import static com.google.common.collect.Lists.newArrayList;
@ -196,6 +198,12 @@ public class DeadlineControlSet extends TaskEditControlFragment {
refreshDisplayView();
}
@OnTouch({R.id.due_date, R.id.due_time})
boolean onSpinnersTouched() {
AndroidUtilities.hideKeyboard(getActivity());
return false;
}
@OnItemSelected(R.id.due_date)
void onDateSelected(int position) {
DateTime today = newDateTime().startOfDay();

@ -27,7 +27,7 @@ public class TimePreference extends Preference {
@Override
protected Object onGetDefaultValue(TypedArray a, int index) {
return a.getString(index);
return a.getInteger(index, -1);
}
@Override
@ -36,7 +36,7 @@ public class TimePreference extends Preference {
int noon = new DateTime().startOfDay().withHourOfDay(12).getMillisOfDay();
millisOfDay = getPersistedInt(noon);
} else {
millisOfDay = Integer.parseInt((String) defaultValue);
millisOfDay = (Integer) defaultValue;
}
setMillisOfDay(millisOfDay);

@ -185,19 +185,9 @@
<string name="vibration_count">عدد الإهتزازات</string>
<string name="vibration_duration">إمتداد الهزة</string>
<string name="vibration_pause">التوقف أثناء الهزات</string>
<string name="doze_notifications">وقف نمط دوز من أجل التنبيهات</string>
<string name="doze_notifications_off">أندرويد سيؤخر التنبيهات عندما يكون الهاتف على نمط دوز</string>
<string name="plugin_description">تاسكس هو مشروع مفتوح المصدر مدموع من طرف مطور واحد. بعض الخيارات متوفرة عن الطريق الدفع من داخل التطبيق من أجل دعم التطوير</string>
<string name="opacity">التعتيم</string>
<string name="theme_white">أبيض</string>
<string name="sync_interval_disable">تعطيل</string>
<string name="sync_interval_one_hour">كل ساعة</string>
<string name="sync_interval_three_hours">كل ثلاث ساعات</string>
<string name="sync_interval_six_hours">كل ست ساعات</string>
<string name="sync_interval_twelve_hours">كل 12 ساعة</string>
<string name="sync_interval_one_day">كل يوم</string>
<string name="sync_interval_three_days">كل ثلاث أيام</string>
<string name="sync_interval_one_week">كل أسبوع</string>
<string name="master_sync_warning">المزامنة التلقائية معطلة الآن من طرف أندرويد</string>
<string name="settings_localization">تخصيص اللغة و الجهة</string>
<string name="layout_direction">إتجاه التنسيق</string>

@ -266,6 +266,7 @@
<string name="synchronization">Синхронизация</string>
<string name="enabled">Активирано</string>
<string name="font_size">Размер на шрифта</string>
<string name="row_spacing">Разстояние между редовете</string>
<string name="customize_edit_screen">Настройване на екрана за редактиране</string>
<string name="source_code">Source код</string>
<string name="translations">Подпомагане с преводи</string>
@ -332,9 +333,6 @@
<string name="send_anonymous_statistics_summary">Изпрати анонимна статистика за използването и отчети за грешки за да помогнеш да се подобри Tasks. Няма да бъдат събирани персонални данни.</string>
<string name="tag_already_exists">Този таг вече съществува</string>
<string name="name_cannot_be_empty">Името не може да е празно</string>
<string name="doze_notifications">Прекъсване на Doze режим за уведомления</string>
<string name="doze_notifications_off">Android значително ще забави уведомленията когато устройството е в Doze режим</string>
<string name="doze_notifications_on">Android ще позволява ограничен брой прекъсвания когато устройството е в Doze режим</string>
<string name="no_title">(Без заглавие)</string>
<string name="back_button_saves_task">Бутон \"Назад\" запазва задачата</string>
<string name="default_list">Списък по подразбиране</string>
@ -380,14 +378,6 @@
<string name="theme_wallpaper">Тапет</string>
<string name="theme_day_night">Ден/Нощ</string>
<string name="default_value">По подразбиране</string>
<string name="sync_interval_disable">изключено</string>
<string name="sync_interval_one_hour">на всеки час</string>
<string name="sync_interval_three_hours">на всеки три часа</string>
<string name="sync_interval_six_hours">на всеки шест часа</string>
<string name="sync_interval_twelve_hours">на всеки дванадесет часа</string>
<string name="sync_interval_one_day">всеки ден</string>
<string name="sync_interval_three_days">на всеки три дена</string>
<string name="sync_interval_one_week">всяка седмица</string>
<string name="master_sync_warning">Автоматичната синхронизация в момента е деактивирана от Android</string>
<string name="settings_general">Общи</string>
<string name="language">Език</string>
@ -416,4 +406,8 @@
<string name="delete_multiple_tasks_confirmation">%s изтрити</string>
<string name="delete_selected_tasks">Изтриване на избраните задачи?</string>
<string name="copy_selected_tasks">Копиране на избраните задачи?</string>
<string name="date_and_time">Дата и време</string>
<string name="start_of_week">Начало на седмицата</string>
<string name="use_locale_default">Локализация по подразбиране</string>
<string name="use_native_datetime_pickers">Контроли за избор на дата и време по подразбиране </string>
</resources>

@ -65,6 +65,7 @@
<string name="EPr_manage_delete_completed_gcal_message">Segur que voleu suprimir els esdeveniments de calendari de les tasques completades?</string>
<string name="day_after_tomorrow">Demà passat</string>
<string name="next_week">La setmana que ve</string>
<string name="dont_hide">No amagis</string>
<string name="BFE_Active">Tasques actives</string>
<string name="BFE_Recent">Modificades fa poc</string>
<string name="CFA_universe_all">Tasques actives</string>
@ -106,6 +107,7 @@
<string name="TFE_workingOn">Tasques sent cronometrades</string>
<string name="TEA_timer_comment_started">inici d\'aquesta tasca:</string>
<string name="TEA_timer_comment_stopped">finalització d\'aquesta tasca:</string>
<string name="TEA_timer_comment_spent">Temps invertit</string>
<string name="delete_task">Eliminar tasca</string>
<plurals name="Ntasks">
<item quantity="one">1 tasca</item>
@ -125,21 +127,19 @@
<string name="report_an_issue">Informa d\'un problema</string>
<string name="contact_developer">Contacta amb el desenvolupador</string>
<string name="quiet_hours_summary">Sense recordatoris durant les hores de silenci</string>
<string name="geofence_radius_title">Radi (metres)</string>
<string name="filters">Filtres</string>
<string name="filter_settings">Configuració dels filtres</string>
<string name="show_hidden">Mostra ocultes</string>
<string name="show_completed">Mostra completades</string>
<string name="reverse">Ordenació inversa</string>
<string name="high_priority">Alta</string>
<string name="default_priority">Predeterminat</string>
<string name="low_priority">Baix</string>
<string name="sync_interval_disable">desactivat</string>
<string name="sync_interval_one_hour">cada hora</string>
<string name="sync_interval_three_hours">cada tres hores</string>
<string name="sync_interval_six_hours">cada sis hores</string>
<string name="sync_interval_twelve_hours">cada dotze hores</string>
<string name="sync_interval_one_day">diàriament</string>
<string name="sync_interval_three_days">cada tres dies</string>
<string name="sync_interval_one_week">setmanalment</string>
<string name="theme_white">Blanc</string>
<string name="language">Idioma</string>
<string name="settings_localization">Localització</string>
<string name="layout_direction_left_to_right">D\'esquerra a dreta</string>
<string name="layout_direction_right_to_left">De dreta a esquerra</string>
<string name="led_notification">Led de notificació</string>
</resources>

@ -291,9 +291,6 @@
<string name="send_anonymous_statistics">Vylepšit Úkoly</string>
<string name="send_anonymous_statistics_summary">Odesílat anonymní statistiky využití a zprávy o selhání ke zlepšení Úkolů. Žádné osobní údaje nebudou shromažďovány.</string>
<string name="tag_already_exists">Štítek již existuje</string>
<string name="doze_notifications">Přerušit Doze mód pro oznámení</string>
<string name="doze_notifications_off">Když je zařízení v Doze režimu, Android může významně zpozdit upozornění</string>
<string name="doze_notifications_on">Když je zařízení v Doze režimu, Android může významně zpozdit upozornění</string>
<string name="no_title">(Bez názvu)</string>
<string name="back_button_saves_task">Tlačítko zpět uloží úkol</string>
<string name="default_list">Výchozí seznam</string>
@ -326,14 +323,6 @@
<string name="theme_wallpaper">Tapeta</string>
<string name="theme_day_night">Den/Noc</string>
<string name="default_value">Výchozí</string>
<string name="sync_interval_disable">zakázat</string>
<string name="sync_interval_one_hour">každou hodinu</string>
<string name="sync_interval_three_hours">každé tři hodiny</string>
<string name="sync_interval_six_hours">každých šest hodin</string>
<string name="sync_interval_twelve_hours">každých dvanáct hodin</string>
<string name="sync_interval_one_day">každý den</string>
<string name="sync_interval_three_days">každé tři dny</string>
<string name="sync_interval_one_week">každý týden</string>
<string name="master_sync_warning">Automatická synchronizace je právě zakázána Androidem</string>
<string name="settings_general">Všeobecný</string>
<string name="language">Jazyk</string>

@ -103,11 +103,4 @@
<string name="sync_SPr_forget">Log af</string>
<string name="sync_SPr_forget_description">Sletter al synkroniserings data</string>
<string name="source_code">Kildekode</string>
<string name="sync_interval_one_hour">hver time</string>
<string name="sync_interval_three_hours">hver 3. time</string>
<string name="sync_interval_six_hours">hver 6. time</string>
<string name="sync_interval_twelve_hours">hver 12. time</string>
<string name="sync_interval_one_day">hver dag</string>
<string name="sync_interval_three_days">hver 3. dag</string>
<string name="sync_interval_one_week">hver uge</string>
</resources>

@ -259,6 +259,7 @@
<string name="synchronization">Synchronisierung</string>
<string name="enabled">Aktiviert</string>
<string name="font_size">Schriftgröße</string>
<string name="row_spacing">Reihen-Abstand</string>
<string name="customize_edit_screen">Änderungsbildschirm anpassen</string>
<string name="source_code">Quelltext</string>
<string name="translations">Zur Übersetzung beitragen</string>
@ -325,9 +326,6 @@
<string name="send_anonymous_statistics_summary">Anonyme Nutzungsstatistiken und Absturzberichte zur Verbesserung von Tasks senden. Es werden keine persönlichen Daten gesammelt.</string>
<string name="tag_already_exists">Tag existiert bereits</string>
<string name="name_cannot_be_empty">Name darf nicht leer sein</string>
<string name="doze_notifications">Doze-Modus für Benachrichtigungen unterbrechen</string>
<string name="doze_notifications_off">Im Doze-Modus von Android erfolgen Benachrichtigungen mit deutlicher Verzögerung</string>
<string name="doze_notifications_on">Android erlaubt nur eine begrenzte Anzahl von Unterbrechungen des Doze-Modus</string>
<string name="no_title">(kein Titel)</string>
<string name="back_button_saves_task">Zurück-Button speichert die Aufgabe</string>
<string name="default_list">Standard-Liste</string>
@ -368,14 +366,6 @@
<string name="theme_wallpaper">Hintergrundbild</string>
<string name="theme_day_night">Tag/Nacht</string>
<string name="default_value">Standard</string>
<string name="sync_interval_disable">deaktivieren</string>
<string name="sync_interval_one_hour">stündlich</string>
<string name="sync_interval_three_hours">alle 3 Stunden</string>
<string name="sync_interval_six_hours">alle 6 Stunden</string>
<string name="sync_interval_twelve_hours">alle 12 Stunden</string>
<string name="sync_interval_one_day">täglich</string>
<string name="sync_interval_three_days">jeden dritten Tag</string>
<string name="sync_interval_one_week">wöchentlich</string>
<string name="master_sync_warning">Automatische Syncronisation ist aktuell im System deaktiviert</string>
<string name="settings_general">Allgemein</string>
<string name="language">Sprache</string>
@ -404,4 +394,8 @@
<string name="delete_multiple_tasks_confirmation">%s gelöscht</string>
<string name="delete_selected_tasks">Ausgewählte Aufgaben löschen?</string>
<string name="copy_selected_tasks">Ausgewählte Aufgaben kopieren?</string>
<string name="date_and_time">Tag und Uhrzeit</string>
<string name="start_of_week">Beginn der Woche</string>
<string name="use_locale_default">Aus Systemeinstellungen</string>
<string name="use_native_datetime_pickers">Datum- und Zeitauswahl des Systems benutzen</string>
</resources>

@ -203,11 +203,4 @@
<string name="sync_SPr_interval_title">Συγχρονισμός παρασκηνίου</string>
<string name="sync_SPr_forget">Αποσύνδεση</string>
<string name="TLA_menu_donate">Δωρίστε</string>
<string name="sync_interval_one_hour">κάθε ώρα</string>
<string name="sync_interval_three_hours">κάθε τρεις ώρες</string>
<string name="sync_interval_six_hours">κάθε έξι ώρες</string>
<string name="sync_interval_twelve_hours">κάθε δώδεκα ώρες</string>
<string name="sync_interval_one_day">κάθε μέρα</string>
<string name="sync_interval_three_days">κάθε τρεις ημέρες</string>
<string name="sync_interval_one_week">κάθε εβδομάδα</string>
</resources>

@ -262,6 +262,7 @@
<string name="synchronization">Sincronización</string>
<string name="enabled">Habilitado</string>
<string name="font_size">Tamaño de la fuente</string>
<string name="row_spacing">Espaciado de fila</string>
<string name="customize_edit_screen">Personalizar pantalla de edición</string>
<string name="source_code">Código fuente</string>
<string name="translations">Contribuye con la traducción</string>
@ -325,12 +326,9 @@
<string name="privacy">Privacidad</string>
<string name="privacy_policy">Política de privacidad</string>
<string name="send_anonymous_statistics">Mejorar Tasks</string>
<string name="send_anonymous_statistics_summary">Enviar de forma anónima estadísticas de uso e informes de error para ayudar a mejorar Tasks. No se recogerán datos personales.</string>
<string name="send_anonymous_statistics_summary">Enviar de forma anónima estadísticas de uso e informes de error para ayudar a mejorar Task. No se recogerán datos personales.</string>
<string name="tag_already_exists">La etiqueta ya existe</string>
<string name="name_cannot_be_empty">El nombre no puede estar vacío</string>
<string name="doze_notifications">Interrumpir modo reposo con notificaciones</string>
<string name="doze_notifications_off">Android retrasará las notificaciones si el dispositivo está en modo reposo</string>
<string name="doze_notifications_on">Android permitirá interrupciones limitadas si el dispositivo está en modo reposo</string>
<string name="no_title">(Sin título)</string>
<string name="back_button_saves_task">Botón atrás guarda la tarea</string>
<string name="default_list">Lista por defecto</string>
@ -375,14 +373,6 @@
<string name="theme_wallpaper">Fondo de pantalla</string>
<string name="theme_day_night">Día/Noche</string>
<string name="default_value">Predeterminado</string>
<string name="sync_interval_disable">deshabilitar</string>
<string name="sync_interval_one_hour">cada hora</string>
<string name="sync_interval_three_hours">cada tres horas</string>
<string name="sync_interval_six_hours">cada seis horas</string>
<string name="sync_interval_twelve_hours">cada doce horas</string>
<string name="sync_interval_one_day">cada día</string>
<string name="sync_interval_three_days">cada tres días</string>
<string name="sync_interval_one_week">cada semana</string>
<string name="master_sync_warning">La sincronización automática está actualmente deshabilitada por Android</string>
<string name="language">Idioma</string>
<string name="restart_required">Debe reiniciar Tasks para que los cambios tengan efecto</string>
@ -410,4 +400,8 @@
<string name="delete_multiple_tasks_confirmation">%s borradas</string>
<string name="delete_selected_tasks">¿Borrar tareas seleccionadas?</string>
<string name="copy_selected_tasks">¿Copiar tareas seleccionadas?</string>
<string name="date_and_time">Fecha y hora</string>
<string name="start_of_week">Inicio de semana</string>
<string name="use_locale_default">Usar configuración regional</string>
<string name="use_native_datetime_pickers">Usar fecha y hora nativas</string>
</resources>

@ -252,20 +252,11 @@
<string name="take_a_picture">گرفتن عکس</string>
<string name="send_anonymous_statistics">بهبود وظیفه</string>
<string name="tag_already_exists">این تگ قبلاً ایجاد شده است</string>
<string name="doze_notifications">برای اعلانات، حالت Doze را موقتاً قطع کن</string>
<string name="no_title">(بدون عنوان)</string>
<string name="default_list">لیست پیش فرض</string>
<string name="plugin_description">Tasks پروژه‌ای متن‌باز است که عمدتاً توسط یک‌نفر توسعه داده می‌شود. برای حمایت از این تلاش، برخی ویژگی‌ها به‌صورت خریدهای داخل برنامه ارائه شده‌اند.</string>
<string name="opacity">شفافیت</string>
<string name="theme_white">سفید</string>
<string name="sync_interval_disable">غیرفعال</string>
<string name="sync_interval_one_hour">هر ساعت</string>
<string name="sync_interval_three_hours">هر سه ساعت</string>
<string name="sync_interval_six_hours">هر شش ساعت</string>
<string name="sync_interval_twelve_hours">هر دوازده ساعت</string>
<string name="sync_interval_one_day">هر روز</string>
<string name="sync_interval_three_days">هر سه روز</string>
<string name="sync_interval_one_week">هر هفته</string>
<string name="master_sync_warning">درحال‌حاضر هماهنگ‌سازی خودکار توسط اندروید غیرفعال است</string>
<string name="settings_general">عمومی</string>
<string name="language">زبان</string>

@ -331,9 +331,6 @@
<string name="send_anonymous_statistics_summary">Lähetä nimettömästi käyttäjätilastoja ja virheilmoituksia Tasks -ohjelman parantamiseksi. Mitään henkilökohtaisia tietoja ei kerätä.</string>
<string name="tag_already_exists">Tunniste on jo olemassa</string>
<string name="name_cannot_be_empty">Nimi ei voi olla tyhjä</string>
<string name="doze_notifications">Keskeytä Doze -tila ilmoituksia varten</string>
<string name="doze_notifications_off">Android viivästyttää merkittävästi ilmoituksia kun laite on Doze -tilassa</string>
<string name="doze_notifications_on">Android sallii rajoitetusti keskeytyksiä kun laite on Doze -tilassa</string>
<string name="no_title">(Ei nimikettä)</string>
<string name="back_button_saves_task">Takaisin -painike tallentaa tehtävän</string>
<string name="default_list">Oletuslista</string>
@ -379,14 +376,6 @@
<string name="theme_wallpaper">Taustakuva</string>
<string name="theme_day_night">Päivä/Yö</string>
<string name="default_value">Oletus</string>
<string name="sync_interval_disable">Poistettu</string>
<string name="sync_interval_one_hour">Joka tunti</string>
<string name="sync_interval_three_hours">Joka kolmas tunti</string>
<string name="sync_interval_six_hours">Joka kuudes tunti</string>
<string name="sync_interval_twelve_hours">Joka 12. tunti</string>
<string name="sync_interval_one_day">Joka päivä</string>
<string name="sync_interval_three_days">Joka kolmas päivä</string>
<string name="sync_interval_one_week">Joka viikko</string>
<string name="master_sync_warning">Android on estänyt automaattisen synkronoinnin</string>
<string name="settings_general">Yleinen</string>
<string name="language">Kieli</string>

@ -257,6 +257,7 @@
<string name="synchronization">Synchronisation</string>
<string name="enabled">Activé</string>
<string name="font_size">Taille de police</string>
<string name="row_spacing">Écartement de la ligne</string>
<string name="customize_edit_screen">Personnaliser l\'écran d\'édition</string>
<string name="source_code">Code source</string>
<string name="translations">Contribuer aux traductions</string>
@ -322,14 +323,11 @@
<string name="send_anonymous_statistics_summary">Envoyer des statistiques anonymes d\'usage et les rapports de plantage afin d\'aider à l\'amélioration de Tasks. Aucune donnée personnelle ne sera collectée.</string>
<string name="tag_already_exists">Le tag existe déjà</string>
<string name="name_cannot_be_empty">Le nom ne peut pas être vide</string>
<string name="doze_notifications">Interrompt le Doze mode pour les notifications</string>
<string name="doze_notifications_off">Android va différer les notifications de façon importante lorsque l\'appareil sera en Doze mode</string>
<string name="doze_notifications_on">Android autorisera des interruptions limitées lorsque l\'appareil sera en Doze mode</string>
<string name="no_title">(Sans titre)</string>
<string name="back_button_saves_task">Faire un retour-arrière pour sauvegarder la tâche</string>
<string name="default_list">Liste par défaut</string>
<string name="plugin_description">Tasks est un projet à source ouverte entretenu par un développeur. Quelques contenus payants sont disponible dans l\'application pour supporter le développement.</string>
<string name="tesla_unread_description">Affiche un badge pour le nombre de tâches actives dans votre liste par défaut. TeslaUnread pour Nova Launcher est requis</string>
<string name="tesla_unread_description">Affiche un badge pour le nombre de tâches actives dans votre liste par défaut. TeslaUnread pour Nova Launcher est requis.</string>
<string name="themes_purchase_description">Débloquer tous les thèmes et quelques couleurs à Tasks</string>
<string name="tasker_description">Notifications de la sensibilité du contexte de la liste. Tasker ou Local est requis.</string>
<string name="donate_summary">Les donations sont grandement appréciées.</string>
@ -366,14 +364,6 @@
<string name="theme_wallpaper">Fond d\'écran</string>
<string name="theme_day_night">Jour/Nuit</string>
<string name="default_value">Par défaut</string>
<string name="sync_interval_disable">désactiver</string>
<string name="sync_interval_one_hour">toutes les heures</string>
<string name="sync_interval_three_hours">toutes les trois heures</string>
<string name="sync_interval_six_hours">toutes les six heures</string>
<string name="sync_interval_twelve_hours">toutes les douze heures</string>
<string name="sync_interval_one_day">tous les jours</string>
<string name="sync_interval_three_days">tous les trois jours</string>
<string name="sync_interval_one_week">toutes les semaines</string>
<string name="master_sync_warning">La synchronisation automatique est actuellement déactivé par Android</string>
<string name="settings_general">Général</string>
<string name="language">Langage</string>
@ -402,4 +392,8 @@
<string name="delete_multiple_tasks_confirmation">%s supprimé</string>
<string name="delete_selected_tasks">Supprimer les tâches sélectionnées ?</string>
<string name="copy_selected_tasks">Copier les tâches sélectionnées ?</string>
<string name="date_and_time">Date et heure</string>
<string name="start_of_week">Début de la semaine</string>
<string name="use_locale_default">Par défaut du lieu d\'utilisation</string>
<string name="use_native_datetime_pickers">Utiliser la date et l\'heure native</string>
</resources>

@ -36,11 +36,11 @@
<string name="TAd_hiddenFormat">%s [oculto]</string>
<string name="TAd_completed">Terminado\n%s</string>
<string name="TAd_actionEditTask">Editar</string>
<string name="SSD_sort_drag">Orden manual con subtareas</string>
<string name="SSD_sort_auto">Ordenación inteligente</string>
<string name="SSD_sort_drag">Orde manual con subtarefas</string>
<string name="SSD_sort_auto">Ordenación intelixente</string>
<string name="SSD_sort_alpha">Por título</string>
<string name="SSD_sort_due">Por fecha límite</string>
<string name="SSD_sort_importance">Por prioridad</string>
<string name="SSD_sort_due">Por data límite</string>
<string name="SSD_sort_importance">Por prioridade</string>
<string name="SSD_sort_modified">Por última modificación</string>
<string name="FLA_search_filter">Coincidencias con \'%s\'</string>
<string name="FLA_new_filter">Crear nuevo filtro</string>
@ -133,28 +133,29 @@
<string name="BFE_Active">Mis Tareas</string>
<string name="BFE_Recent">Recién modificadas</string>
<string name="CFA_universe_all">Tareas activas</string>
<string name="CFA_type_add">o</string>
<string name="CFA_type_subtract">no</string>
<string name="CFA_type_intersect">también</string>
<string name="CFA_context_chain">%s cumple el criterio</string>
<string name="CFA_type_add">ou</string>
<string name="CFA_type_subtract">non</string>
<string name="CFA_type_intersect">tamén</string>
<string name="CFA_context_chain">%s cumpre o criterio</string>
<string name="CFA_context_delete">Eliminar Fila</string>
<string name="CFA_help">Mantén pulsado sobre los elementos para opciones adicionales</string>
<string name="CFA_button_add">adir Criterio</string>
<string name="CFC_dueBefore_text">Vence el: ?</string>
<string name="CFA_help">Mantén pulsado sobre os elementos para opcións adicionáis</string>
<string name="CFA_button_add">Engadir Criterio</string>
<string name="CFC_dueBefore_text">Vence o: ?</string>
<string name="CFC_dueBefore_name">Vence el...</string>
<string name="no_due_date">Sin fecha de vencimiento</string>
<string name="next_month">Próximo mes</string>
<string name="CFC_importance_text">¿Prioridad mínima?</string>
<string name="CFC_importance_text">Prioridade mínima?</string>
<string name="CFC_importance_name">Prioridad...</string>
<string name="CFC_tag_text">Etiqueta: ?</string>
<string name="CFC_tag_name">Etiqueta...</string>
<string name="CFC_tag_contains_name">El nombre de la etiqueta contiene...</string>
<string name="CFC_tag_contains_text">El nombre de la etiqueta contiene: ?</string>
<string name="CFC_title_contains_name">Título contiene...</string>
<string name="CFC_title_contains_text">Título contiene: ?</string>
<string name="gcal_TEA_error">¡Ocurrió un error al agregar la tarea al calendario!</string>
<string name="gcal_TEA_addToCalendar_label">Agregar al calendario</string>
<string name="gcal_TEA_showCalendar_label">Abrir evento del calendario</string>
<string name="CFC_title_contains_text">Contén título:?</string>
<string name="gcal_TEA_error">Erro ao engadir a tarefa ao calendario!</string>
<string name="gcal_TEA_addToCalendar_label">Engadir ao calendario</string>
<string name="gcal_TEA_showCalendar_label">Abrir o evento do calendario
</string>
<string name="delete_calendar_event_confirmation">¿Borrar evento de calendario?</string>
<string name="calendar_event_not_found">Evento de calendario no encontrado</string>
<string name="gcal_completed_title">%s (completado)</string>
@ -200,7 +201,7 @@
<string name="default_random_reminder_bi_monthly">bimensualmente</string>
<string name="repeat_every">Cada %d</string>
<string name="repeat_interval_prompt">Intervalo de repetición</string>
<string name="repeat_never">Sin repetir</string>
<string name="repeat_never">Sen repetir</string>
<string name="repeat_interval_day">Día(s)</string>
<string name="repeat_interval_week">Semana(s)</string>
<string name="repeat_interval_month">Mes(es)</string>
@ -228,7 +229,7 @@
<string name="EPr_voiceInputEnabled_title">Ingreso por voz</string>
<string name="EPr_voiceRemindersEnabled_title">Avisos de voz</string>
<string name="EPr_voiceRemindersEnabled_desc_enabled">Tasks dirá los nombres de las tareas durante los avisos</string>
<string name="delete_task">Eliminar tarea</string>
<string name="delete_task">Eliminar tarefa</string>
<string name="voice_command_added_task">Tarea agregada</string>
<string name="external_storage_unavailable">Almacenamiento externo inaccesible</string>
<plurals name="Ntasks">
@ -262,14 +263,15 @@
<string name="synchronization">Sincronización</string>
<string name="enabled">Habilitado</string>
<string name="font_size">Tamaño de la fuente</string>
<string name="row_spacing">Espaciado de fila</string>
<string name="customize_edit_screen">Personalizar pantalla de edición</string>
<string name="source_code">Código fuente</string>
<string name="translations">Contribuye con la traducción</string>
<string name="report_an_issue">Informa de un error</string>
<string name="source_code">Código fonte</string>
<string name="translations">Contribúe coa tradución</string>
<string name="report_an_issue">Informa dun erro</string>
<string name="help_and_feedback">Ayuda y valoraciones</string>
<string name="contact_developer">Contactar con el desarrollador </string>
<string name="contact_developer">Contactar co revelador</string>
<string name="rate_tasks">Valorar Tasks</string>
<string name="quiet_hours_summary">Sin avisos en horario silencioso</string>
<string name="quiet_hours_summary">Sen avisos en horario silencioso</string>
<string name="TLA_menu_donate">Donar</string>
<string name="select_amount">Elegir cantidad</string>
<string name="notification_actions">Acciones de notificación</string>
@ -314,7 +316,7 @@
<string name="add_attachment">Adjuntar archivo</string>
<string name="high_priority">Alta</string>
<string name="default_priority">Por defecto</string>
<string name="low_priority">Baja</string>
<string name="low_priority">Baixa</string>
<string name="notification_priority">Prioridad de la notificación</string>
<string name="vibration_count">Número de vibraciones</string>
<string name="vibration_duration">Duración de cada vibración (milisegundos)</string>
@ -328,9 +330,6 @@
<string name="send_anonymous_statistics_summary">Enviar de forma anónima estadísticas de uso e informes de error para ayudar a mejorar Tasks. No se recogerán datos personales.</string>
<string name="tag_already_exists">La etiqueta ya existe</string>
<string name="name_cannot_be_empty">El nombre no puede estar vacío</string>
<string name="doze_notifications">Interrumpir modo reposo con notificaciones</string>
<string name="doze_notifications_off">Android retrasará las notificaciones si el dispositivo está en modo reposo</string>
<string name="doze_notifications_on">Android permitirá interrupciones limitadas si el dispositivo está en modo reposo</string>
<string name="no_title">(Sin título)</string>
<string name="back_button_saves_task">Botón atrás guarda la tarea</string>
<string name="default_list">Lista por defecto</string>
@ -346,6 +345,7 @@
<string name="filter">Filtro</string>
<string name="opacity">Opacidad</string>
<string name="theme">Tema</string>
<string name="color">Cor</string>
<string name="accent">Acentuado</string>
<string name="themes">Temas adicionales</string>
<string name="theme_red">Rojo</string>
@ -375,22 +375,15 @@
<string name="theme_wallpaper">Fondo de pantalla</string>
<string name="theme_day_night">Día/Noche</string>
<string name="default_value">Predeterminado</string>
<string name="sync_interval_disable">deshabilitar</string>
<string name="sync_interval_one_hour">cada hora</string>
<string name="sync_interval_three_hours">cada tres horas</string>
<string name="sync_interval_six_hours">cada seis horas</string>
<string name="sync_interval_twelve_hours">cada doce horas</string>
<string name="sync_interval_one_day">cada día</string>
<string name="sync_interval_three_days">cada tres días</string>
<string name="sync_interval_one_week">cada semana</string>
<string name="master_sync_warning">La sincronización automática está actualmente deshabilitada por Android</string>
<string name="settings_general">Xeneral</string>
<string name="language">Idioma</string>
<string name="restart_required">Debe reiniciar Tasks para que los cambios tengan efecto</string>
<string name="restart_now">Reiniciar ahora</string>
<string name="restart_later">Más tarde</string>
<string name="settings_localization">Configuración regional</string>
<string name="layout_direction">Sentido de la escritura</string>
<string name="layout_direction_left_to_right">De izquierda a derecha</string>
<string name="settings_localization">Localización</string>
<string name="layout_direction">Sentido da escritura</string>
<string name="layout_direction_left_to_right">De esquerda a dereita</string>
<string name="layout_direction_right_to_left">De derecha a izquierda</string>
<string name="led_notification">Notificación de la LED</string>
<string name="hardware_support_required">Apoyo de hardware requerido</string>
@ -410,4 +403,5 @@
<string name="delete_multiple_tasks_confirmation">%s borradas</string>
<string name="delete_selected_tasks">¿Borrar tareas seleccionadas?</string>
<string name="copy_selected_tasks">¿Copiar tareas seleccionadas?</string>
<string name="use_native_datetime_pickers">Escoller data e hora</string>
</resources>

@ -266,6 +266,7 @@
<string name="synchronization">Szinkronizáció</string>
<string name="enabled">Engedélyezve</string>
<string name="font_size">Karakter méret</string>
<string name="row_spacing">Sorköz</string>
<string name="customize_edit_screen">Szerkesztő képernyő testreszabása</string>
<string name="source_code">Forráskód</string>
<string name="translations">Részvétel a fordításban</string>
@ -332,9 +333,6 @@
<string name="send_anonymous_statistics_summary">Anonim felhasználási statisztikák és hibajelentések küldése a Tasks fejlesztése érdekében. Személyes adatok gyűjtése nem történik.</string>
<string name="tag_already_exists">A címke már létezik</string>
<string name="name_cannot_be_empty">A cím nem lehet üres</string>
<string name="doze_notifications">Értesítéskor kilépés Szundikáló módból</string>
<string name="doze_notifications_off">Az Android jelentősen késlelteti az értesítéseket Szundikáló módban</string>
<string name="doze_notifications_on">Az Android csak bizonyos megszakításokat engedélyez, amennyiben az eszköz Szundikáló módban van</string>
<string name="no_title">(Név nélkül)</string>
<string name="back_button_saves_task">A vissza gomb elmenti a feladatot</string>
<string name="default_list">Alapértelmezett lista</string>
@ -380,14 +378,6 @@
<string name="theme_wallpaper">Háttérkép</string>
<string name="theme_day_night">Nappal/Éjszaka</string>
<string name="default_value">Alapértelmezett</string>
<string name="sync_interval_disable">Letiltás</string>
<string name="sync_interval_one_hour">Óránként</string>
<string name="sync_interval_three_hours">Háromóránként</string>
<string name="sync_interval_six_hours">Hatóránként</string>
<string name="sync_interval_twelve_hours">Tizenkét óránként</string>
<string name="sync_interval_one_day">Naponta</string>
<string name="sync_interval_three_days">Háromnaponta</string>
<string name="sync_interval_one_week">Hetente</string>
<string name="master_sync_warning">Az Android pillanatnyilag letiltotta az automatikus szinkronizálást.</string>
<string name="settings_general">Általános</string>
<string name="language">Nyelv</string>
@ -416,4 +406,8 @@
<string name="delete_multiple_tasks_confirmation">%s törölve</string>
<string name="delete_selected_tasks">Kiválasztott feladatok törlése?</string>
<string name="copy_selected_tasks">Kiválasztott feladatok másolása?</string>
<string name="date_and_time">Dátum és időpont</string>
<string name="start_of_week">Hét kezdőnapja</string>
<string name="use_locale_default">Beállított nyelv alapján</string>
<string name="use_native_datetime_pickers">Natív dátum és időpont kijelölő használata</string>
</resources>

@ -145,7 +145,7 @@
<string name="CFC_dueBefore_name">Entro...</string>
<string name="no_due_date">Nessuna data di scadenza</string>
<string name="next_month">Mese successivo</string>
<string name="CFC_importance_text">Almeno per priorità ?</string>
<string name="CFC_importance_text">Solo per priorità ?</string>
<string name="CFC_importance_name">Priorità...</string>
<string name="CFC_tag_text">Etichetta: ?</string>
<string name="CFC_tag_name">Etichetta...</string>
@ -263,6 +263,7 @@
<string name="synchronization">Sincronizzazione</string>
<string name="enabled">Abilitata</string>
<string name="font_size">Dimensione Carattere</string>
<string name="row_spacing">Interlinea</string>
<string name="customize_edit_screen">Personalizza schermata di modifica</string>
<string name="source_code">Codice sorgente</string>
<string name="translations">Contribuisci alla traduzione</string>
@ -314,7 +315,6 @@
<string name="no_application_found">Nessuna applicazione in grado di aprire l\'allegato </string>
<string name="add_attachment">Aggiungi allegato</string>
<string name="high_priority">Alta</string>
<string name="default_priority">Predefinita</string>
<string name="low_priority">Bassa</string>
<string name="notification_priority">Priorità avvisi</string>
<string name="vibration_count">Numero di vibrazioni</string>
@ -329,9 +329,6 @@
<string name="send_anonymous_statistics_summary">Invio anonimo di statistiche e problemi di funzionamento atti a migliorare \"Tasks\". Non verrà inviato nessun dato personale.</string>
<string name="tag_already_exists">Etichetta già presente</string>
<string name="name_cannot_be_empty">Il nome non può essere omesso</string>
<string name="doze_notifications">Le notifiche interrompono il pisolino</string>
<string name="doze_notifications_off">Android limiterà le notifiche se il dispositivo è in modalità riposo</string>
<string name="doze_notifications_on">Android limiterà le notifiche se il dispositivo è in modalità riposo</string>
<string name="no_title">(nessun titolo)</string>
<string name="back_button_saves_task">Il tasto indietro salva l\'attività</string>
<string name="default_list">Lista predefinita</string>
@ -377,14 +374,6 @@
<string name="theme_wallpaper">Sfondo</string>
<string name="theme_day_night">Giorno/Notte</string>
<string name="default_value">Predefinito</string>
<string name="sync_interval_disable">disabilita</string>
<string name="sync_interval_one_hour">ogni ora</string>
<string name="sync_interval_three_hours">ogni tre ore</string>
<string name="sync_interval_six_hours">ogni sei ore</string>
<string name="sync_interval_twelve_hours">ogni dodici ore</string>
<string name="sync_interval_one_day">ogni giorno</string>
<string name="sync_interval_three_days">ogni tre giorni</string>
<string name="sync_interval_one_week">Ogni settimana</string>
<string name="master_sync_warning">La sincronizzazione automatica è disabilitata da Android</string>
<string name="settings_general">Generale</string>
<string name="language">Lingua</string>
@ -413,4 +402,8 @@
<string name="delete_multiple_tasks_confirmation">%s eliminati</string>
<string name="delete_selected_tasks">Elimino i compiti selezionati?</string>
<string name="copy_selected_tasks">Copio i compiti selezionati?</string>
<string name="date_and_time">Data e ora</string>
<string name="start_of_week">Inizio settimana</string>
<string name="use_locale_default">Usa default locale</string>
<string name="use_native_datetime_pickers">Usa data e ora locali</string>
</resources>

@ -330,9 +330,6 @@
<string name="send_anonymous_statistics_summary">שליחת סטסיטיקות ודיווחי קריסה של האפליקציה באופן אנונימי. מידע אישי לא נאסף כלל.</string>
<string name="tag_already_exists">תגית כבר קיימת</string>
<string name="name_cannot_be_empty">השם לא יכול להיות ריק</string>
<string name="doze_notifications">ניטרול Doze Mode בהתראות</string>
<string name="doze_notifications_off">אנדרואיד יעכב את ההתראות באופן משמעותי כאשר Doze mode פעיל</string>
<string name="doze_notifications_on">מערכת אנדרואיד תאפשר מספר מוגבל של יקיצות כאשר המכשיר במצב Doze</string>
<string name="no_title">(אין כותרת)</string>
<string name="back_button_saves_task">כפתור \"חזרה\" שומר שינויים במשימה</string>
<string name="default_list">רשימת ברירת מחדל</string>
@ -378,14 +375,6 @@
<string name="theme_wallpaper">תמונת רקע</string>
<string name="theme_day_night">יום / לילה</string>
<string name="default_value">ברירת מחדל</string>
<string name="sync_interval_disable">השבת</string>
<string name="sync_interval_one_hour">כל שעה</string>
<string name="sync_interval_three_hours">כל שלוש שעות</string>
<string name="sync_interval_six_hours">כל שש שעות</string>
<string name="sync_interval_twelve_hours">כל שתים עשרה שעות</string>
<string name="sync_interval_one_day">כל יום</string>
<string name="sync_interval_three_days">כל שלושה ימים</string>
<string name="sync_interval_one_week">כל שבוע</string>
<string name="master_sync_warning">סנכרון אוטומטי כרגע מושבת ע\"י Android</string>
<string name="settings_general">כללי</string>
<string name="language">שפה</string>

@ -264,6 +264,7 @@
<string name="synchronization">同期</string>
<string name="enabled">有効</string>
<string name="font_size">フォントサイズ</string>
<string name="row_spacing">行間隔</string>
<string name="customize_edit_screen">編集画面をカスタマイズ</string>
<string name="source_code">ソースコード</string>
<string name="translations">翻訳に貢献する</string>
@ -330,9 +331,6 @@
<string name="send_anonymous_statistics_summary">Tasks を改善するために、匿名で使用状況データとクラッシュレポートを送信します。個人情報は収集されません。</string>
<string name="tag_already_exists">タグは既に存在します</string>
<string name="name_cannot_be_empty">名前は空にできません。</string>
<string name="doze_notifications">通知の Doze モード割り込み</string>
<string name="doze_notifications_off">デバイスが Doze モードの間、Android は通知を大幅に遅らせます</string>
<string name="doze_notifications_on">デバイスが Doze モードの間、Android は限定された割り込みを許可します</string>
<string name="no_title">(タイトルなし)</string>
<string name="back_button_saves_task">戻るボタンでタスクを保存します</string>
<string name="default_list">デフォルトリスト</string>
@ -378,14 +376,6 @@
<string name="theme_wallpaper">壁紙</string>
<string name="theme_day_night">デイナイト</string>
<string name="default_value">デフォルト</string>
<string name="sync_interval_disable">無効</string>
<string name="sync_interval_one_hour">1時間毎</string>
<string name="sync_interval_three_hours">3時間毎</string>
<string name="sync_interval_six_hours">6時間毎</string>
<string name="sync_interval_twelve_hours">12時間毎</string>
<string name="sync_interval_one_day">毎日</string>
<string name="sync_interval_three_days">3日に一度</string>
<string name="sync_interval_one_week">毎週</string>
<string name="master_sync_warning">自動同期は、現在 Android によって無効にされています</string>
<string name="settings_general">全般</string>
<string name="language">言語</string>
@ -414,4 +404,8 @@
<string name="delete_multiple_tasks_confirmation">%s 削除済</string>
<string name="delete_selected_tasks">選択したタスクを削除しますか?</string>
<string name="copy_selected_tasks">選択したタスクをコピーしますか?</string>
<string name="date_and_time">日付と時刻</string>
<string name="start_of_week">週の始まり</string>
<string name="use_locale_default">ロケールのデフォルトを使用する</string>
<string name="use_native_datetime_pickers">ネイティブの日付と時刻選択を使用する</string>
</resources>

@ -12,34 +12,34 @@
<string name="enter_filter_name">필터 이름 입력</string>
<string name="choose_google_account">구글 계정 선택</string>
<string name="backup_BPr_header">백업</string>
<string name="backup_BAc_import"> 가져오기</string>
<string name="backup_BAc_export"> 내보내기</string>
<string name="backup_BAc_import">일 가져오기</string>
<string name="backup_BAc_export">일 내보내기</string>
<string name="export_toast">%1$s 를 %2$s 로 백업함.</string>
<string name="export_toast_no_tasks">내보낼 일 없음.</string>
<string name="export_toast_no_tasks">내보낼 일 없음.</string>
<string name="import_summary_title">간편 복원</string>
<string name="import_summary_message">파일 %1$s: %2$s 개의 일정 포함.\n\n
%3$s 개 가져오기 성공,\n
%4$s 개 이미 존재,\n
%5$s 개 에러 발생\n</string>
<string name="import_progress_read"> %d 읽는 중...</string>
<string name="import_progress_read">일 %d 읽는 중...</string>
<string name="DLG_error_sdcard">폴더에 접근 불가: %s</string>
<string name="DLG_error_sdcard_general">SD 카드에 접근할 수 없습니다!</string>
<string name="read_permission_label">Tasks 권한</string>
<string name="write_permission_label">Tasks 권한</string>
<string name="discard_confirmation">변경 사항을 버리시겠습니까?</string>
<string name="keep_editing">계속 편집</string>
<string name="DLG_delete_this_task_question">이 일을 삭제할까요?</string>
<string name="DLG_delete_this_task_question">일을 삭제할까요?</string>
<string name="DLG_hour_minutes">시간 (시 : 분)</string>
<string name="DLG_undo">실행 취소</string>
<string name="WID_dateButtonUnset">누르세요</string>
<string name="TLA_no_items">이 없습니다.</string>
<string name="TLA_no_items">일이 없습니다.</string>
<string name="TLA_menu_sort">정렬</string>
<string name="TLA_menu_search">검색</string>
<string name="TLA_menu_settings">설정</string>
<string name="TAd_hiddenFormat">%s [숨김]</string>
<string name="TAd_completed">일정 완료\n%s</string>
<string name="TAd_actionEditTask">편집</string>
<string name="SSD_sort_drag">수동 정렬 (하위일 포함)</string>
<string name="SSD_sort_drag">수동 정렬 (하위일 포함)</string>
<string name="SSD_sort_auto">Tasks 스마트 정렬</string>
<string name="SSD_sort_alpha">제목 순서</string>
<string name="SSD_sort_due">마감일 순서</string>
@ -47,7 +47,7 @@
<string name="SSD_sort_modified">최종 수정일 순서</string>
<string name="FLA_search_filter">\'%s\' 검색 중</string>
<string name="FLA_new_filter">새 필터 만들기</string>
<string name="TEA_title_hint">일정 이름</string>
<string name="TEA_title_hint">할일 제목</string>
<string name="TEA_importance_label">우선순위</string>
<string name="TEA_no_date">기한 없음</string>
<string name="TEA_hideUntil_label">숨기기 기간:</string>
@ -69,6 +69,7 @@
<string name="TEA_control_importance">우선순위</string>
<string name="TEA_control_notes">설명</string>
<string name="TEA_control_files">파일</string>
<string name="TEA_control_google_task_list">구글 할일 목록</string>
<string name="TEA_control_reminders">일정 알림</string>
<string name="TEA_control_timer">타이머 조절</string>
<string name="TEA_control_hidden_section">----항상 숨김----</string>
@ -96,27 +97,28 @@
<string name="EPr_filters_to_show_title">표시할 필터</string>
<string name="EPr_edit_screen_options">화면 설정 편집하기</string>
<string name="EPr_beastMode_reset">기본값으로 초기화하기</string>
<string name="EPr_fullTask_title">전체 일정 제목 보기</string>
<string name="EPr_show_task_edit_comments"> 편집에서 댓글 보기</string>
<string name="task_list_options"> 목록 옵션</string>
<string name="EPr_fullTask_title">할일 제목 전체 보기</string>
<string name="EPr_show_task_edit_comments">일 편집에서 댓글 보기</string>
<string name="task_list_options">일 목록 옵션</string>
<string name="EPr_cal_end_or_start_at_due_time">달력 이벤트 시간</string>
<string name="EPr_cal_end_at_due_time">설정한 시간에 달력 이벤트 종료</string>
<string name="EPr_cal_start_at_due_time">설정한 시간에 달력 이벤트 시작</string>
<string name="EPr_manage_header">오래된 일정 관리하기</string>
<string name="EPr_manage_purge_deleted">삭제한 일정을 비우기</string>
<string name="EPr_manage_purge_deleted_message">삭제한 일정을 모두 비울까요?\n\n비운 일정은 다시 살릴 수 없습니다!</string>
<string name="EPr_manage_purge_deleted_status">%d 일정을 비웠습니다!</string>
<string name="EPr_manage_header">오래된 할일 관리하기</string>
<string name="EPr_manage_purge_deleted">삭제한 할일을 비우기</string>
<string name="EPr_manage_purge_deleted_message">삭제한 할일을 모두 비울까요?
비운 할일은 되돌릴 수 없습니다!</string>
<string name="EPr_manage_purge_deleted_status">%d 할일을 비웠습니다!</string>
<string name="EPr_reset_preferences">설정 초기화</string>
<string name="EPr_reset_preferences_warning">설정이 기본값으로 초기화될 것입니다</string>
<string name="EPr_delete_task_data"> 데이터 지우기</string>
<string name="EPr_delete_task_data_warning">모든 일이 영구적으로 삭제될 것입니다</string>
<string name="EPr_manage_delete_completed_gcal">완료한 일의 달력 이벤트를 삭제하기</string>
<string name="EPr_manage_delete_completed_gcal_message">완료한 일의 모든 이벤트를 정말 삭제할까요?</string>
<string name="EPr_delete_task_data">일 데이터 지우기</string>
<string name="EPr_delete_task_data_warning">모든 일이 영구적으로 삭제될 것입니다</string>
<string name="EPr_manage_delete_completed_gcal">완료한 일의 달력 이벤트를 삭제하기</string>
<string name="EPr_manage_delete_completed_gcal_message">완료한 일의 모든 이벤트를 정말 삭제할까요?</string>
<string name="EPr_manage_delete_completed_gcal_status">%d 달력 이벤트를 삭제했습니다!</string>
<string name="EPr_manage_delete_all_gcal">의 모든 달력 이벤트 삭제하기</string>
<string name="EPr_manage_delete_all_gcal_message">의 모든 이벤트를 정말 삭제할까요?</string>
<string name="EPr_manage_delete_all_gcal">일의 모든 달력 이벤트 삭제하기</string>
<string name="EPr_manage_delete_all_gcal_message">일의 모든 이벤트를 정말 삭제할까요?</string>
<string name="EPr_manage_delete_all_gcal_status">%d 달력 이벤트를 삭제했습니다!</string>
<string name="task_defaults"> 기본값</string>
<string name="task_defaults">일 기본값</string>
<string name="EPr_default_urgency_title">기본 중요도</string>
<string name="EPr_default_importance_title">기본 우선순위</string>
<string name="EPr_default_hideUntil_title">기본 숨기기 기간:</string>
@ -133,9 +135,9 @@
<string name="no_deadline_reminder">기한 알림 없음</string>
<string name="at_deadline">완료일에</string>
<string name="at_deadline_or_overdue">마감일이나 그 이후</string>
<string name="BFE_Active">나의 일</string>
<string name="BFE_Active">나의 </string>
<string name="BFE_Recent">최근에 수정한 일정</string>
<string name="CFA_universe_all">실행중인 일</string>
<string name="CFA_universe_all">실행중인 </string>
<string name="CFA_type_add">또는</string>
<string name="CFA_type_subtract">제외</string>
<string name="CFA_type_intersect">또한</string>
@ -155,7 +157,7 @@
<string name="CFC_tag_contains_text">태그 이름이 다음을 포함: ?</string>
<string name="CFC_title_contains_name">제목이 다음을 포함...</string>
<string name="CFC_title_contains_text">제목이 다음을 포함: ?</string>
<string name="gcal_TEA_error">달력에 일 추가 실패!</string>
<string name="gcal_TEA_error">달력에 일 추가 실패!</string>
<string name="gcal_TEA_addToCalendar_label">달력에 일정 추가하기</string>
<string name="gcal_TEA_showCalendar_label">달력 이벤트 열기</string>
<string name="delete_calendar_event_confirmation">달력 일정을 지울까요?</string>
@ -167,8 +169,8 @@
<string name="gtasks_GLA_noaccounts">동기화 가능한 구글 계정이 없음</string>
<string name="gtasks_GLA_authenticating">인증 중...</string>
<string name="gtasks_GLA_errorIOAuth">죄송합니다, 구글 서버와 통신하는 데 문제가 있습니다. 잠시 후 다시 시도하세요.</string>
<string name="gtasks_GPr_header">구글 일정</string>
<string name="gtasks_error_accountNotFound">%s 계정을 찾을 수 없습니다 - 로그아웃하고 구글 일정 설정에서 다시 로그인해 보세요.</string>
<string name="gtasks_GPr_header">구글 할일 목록 (Google Tasks)</string>
<string name="gtasks_error_accountNotFound">%s 계정을 찾을 수 없습니다 - 로그아웃하고 구글 할일목록 (Google Tasks) 설정에서 다시 로그인해 보세요.</string>
<string name="premium_record_audio">노트 기록</string>
<string name="premium_remove_file_confirm">정말입니까? 되돌릴 수 없습니다</string>
<string name="audio_recording_title">오디오 녹음 중</string>
@ -189,11 +191,11 @@
<string name="rmd_EPr_quiet_hours_start_title">알림 꺼짐 시작 시간</string>
<string name="rmd_EPr_quiet_hours_end_title">알림 꺼짐 종료 시간</string>
<string name="rmd_EPr_rmd_time_title">기본 알림 설정</string>
<string name="rmd_EPr_rmd_time_desc">마감일이 없는 일 알림은 %s 에 나타날 것입니다.</string>
<string name="rmd_EPr_rmd_time_desc">마감일이 없는 일 알림은 %s 에 나타날 것입니다.</string>
<string name="persistent_notifications">항구적 알림</string>
<string name="persistent_notifications_description">항구적 알림은 지울 수 없습니다</string>
<string name="rmd_EPr_multiple_maxvolume_title">다중-링 알림의 최대 볼륨</string>
<string name="rmd_EPr_multiple_maxvolume_desc_true">Tasks는 다중-링 알림을 최대 볼륨으로 출력합니다</string>
<string name="rmd_EPr_multiple_maxvolume_desc_true">Tasks는 다중 소리 알림을 최대 볼륨으로 출력합니다</string>
<string name="rmd_EPr_defaultRemind_title">랜덤 알림</string>
<string name="default_random_reminder_disabled">사용안함</string>
<string name="default_random_reminder_hourly">매시간</string>
@ -219,24 +221,25 @@
<string name="repeat_until">%s 까지 반복</string>
<string name="repeat_snackbar">%1$s 이 %2$s 로 변경되었습니다</string>
<string name="new_tag">새 태그 만들기</string>
<string name="new_list">새 목록 만들기</string>
<string name="tag_FEx_untagged">미분류 일정</string>
<string name="delete_tag_confirmation">%s 삭제할까요?</string>
<string name="TPl_notification">%s 동안 타이머 작동함!</string>
<string name="TFE_workingOn">기한이 정해진 일</string>
<string name="TFE_workingOn">기한이 정해진 </string>
<string name="TEA_timer_controls">타이머</string>
<string name="TEA_timer_comment_started">이 일 시작:</string>
<string name="TEA_timer_comment_stopped">이 일 중지:</string>
<string name="TEA_timer_comment_started">일 시작:</string>
<string name="TEA_timer_comment_stopped">일 중지:</string>
<string name="TEA_timer_comment_spent">소요 시간:</string>
<string name="voice_create_prompt">을 만들려면 말하세요</string>
<string name="voice_create_prompt">일을 만들려면 말하세요</string>
<string name="EPr_voiceInputEnabled_title">음성 입력</string>
<string name="EPr_voiceRemindersEnabled_title">음성 알림</string>
<string name="EPr_voiceRemindersEnabled_desc_enabled">일정 알림기간 동안 일정 이름을 직접 말해 줍니다</string>
<string name="delete_task"> 지우기</string>
<string name="voice_command_added_task">추가된 일</string>
<string name="EPr_voiceRemindersEnabled_desc_enabled">할일 알림 시 할일 제목을 소리내어 읽어줍니다</string>
<string name="delete_task">일 지우기</string>
<string name="voice_command_added_task">추가된 </string>
<string name="external_storage_unavailable">외부 저장소에 접근할 수 없음</string>
<plurals name="Ntasks">
<item quantity="one">1 일</item>
<item quantity="other">%d 일</item>
<item quantity="one">1 </item>
<item quantity="other">%d </item>
</plurals>
<string name="today">오늘</string>
<string name="tomorrow">내일</string>
@ -250,6 +253,10 @@
<string name="sync_SPr_forget">로그아웃</string>
<string name="sync_SPr_forget_description">모든 동기화 자료 삭제</string>
<string name="sync_forget_confirm">로그아웃 / 모든 동기화 데이터 삭제?</string>
<string name="widget_show_due_date">완료 예정일 표시</string>
<string name="widget_show_checkboxes">체크박스 표시</string>
<string name="widget_show_header">헤더 표시</string>
<string name="widget_show_settings">설정버튼 표시</string>
<string name="notifications">알림</string>
<string name="silent">무음</string>
<string name="sound">소리</string>
@ -261,8 +268,10 @@
<string name="synchronization">동기화</string>
<string name="enabled">활성화됨</string>
<string name="font_size">글자 크기</string>
<string name="row_spacing">줄 간격</string>
<string name="customize_edit_screen">일정 편집화면 레이아웃 설정하기</string>
<string name="source_code">소스 코드</string>
<string name="translations">번역 참여하기</string>
<string name="report_an_issue">문제점 보고하기</string>
<string name="help_and_feedback">도움말 &amp; 피드백</string>
<string name="contact_developer">개발자와 연락</string>
@ -299,11 +308,14 @@
<string name="discard_changes">변경 사항을 취소할까요?</string>
<string name="discard">버리기</string>
<string name="tag_settings">태그 설정</string>
<string name="list_settings">목록 설정</string>
<string name="delete">삭제</string>
<string name="copy">복사</string>
<string name="filter_settings">필터 설정</string>
<string name="show_hidden">숨겨진 일정 표시</string>
<string name="show_completed">완료된 일 표시</string>
<string name="show_completed">완료된 일 표시</string>
<string name="reverse">반전</string>
<string name="task_count">%d 할일</string>
<string name="get_plugins">인앱 결제</string>
<string name="no_application_found">첨부 파일을 열 수 있는 앱이 발견되지 않았습니다</string>
<string name="add_attachment">첨부파일 추가</string>
@ -322,24 +334,24 @@
<string name="send_anonymous_statistics">Tasks 향상</string>
<string name="send_anonymous_statistics_summary">Tasks를 향상시키기 위해 사용 기록과 충돌 보고서를 익명으로 전송합니다. 개인 정보는 수집되지 않습니다.</string>
<string name="tag_already_exists">태그가 이미 존재합니다</string>
<string name="doze_notifications">알림을 위해 Doze Mode 방해하기</string>
<string name="doze_notifications_off">Android는 기기가 Doze Mode에 있을 때 알림을 매우 늦춥니다</string>
<string name="doze_notifications_on">Android는 기기가 Doze Mode에 있을 때 제한된 수준에서 이 모드를 방해합니다</string>
<string name="name_cannot_be_empty">이름은 필수 입력항목입니다.</string>
<string name="no_title">(제목 없음)</string>
<string name="back_button_saves_task">뒤로가기 버튼으로 일 저장</string>
<string name="back_button_saves_task">뒤로가기 버튼으로 할일 저장</string>
<string name="default_list">기본 목록</string>
<string name="plugin_description">Tasks는 한 명의 개발자에 의해 유지되고 있는 오픈 소스 프로젝트입니다. 개발을 지속하기 위해 몇몇 기능들은 인앱 결제를 통해 제공되고 있습니다.</string>
<string name="tesla_unread_description">기본 목록에 있는 활성화된 일의 숫자 뱃지를 표시합니다. Nova Launcher용 TeslaUnread가 필요합니다.</string>
<string name="tesla_unread_description">기본 목록에 있는 활성화된 일의 숫자 뱃지를 표시합니다. Nova Launcher용 TeslaUnread가 필요합니다.</string>
<string name="themes_purchase_description">모든 테마 잠금 해제 및 색상 추가</string>
<string name="tasker_description">컨텍스트 기반의 리스트 알림. Tasker 또는 Locale이 필요합니다.</string>
<string name="donate_summary">기부를 해주시면 감사하겠습니다</string>
<string name="dashclock_purchase_description">활성화된 일의 개수를 표시합니다. DashClock Widget을 필요로 합니다.</string>
<string name="dashclock_purchase_description">활성화된 일의 개수를 표시합니다. DashClock Widget을 필요로 합니다.</string>
<string name="buy">구매</string>
<string name="buy_dashclock_extension">확장팩 구매</string>
<string name="billing_service_busy">인앱 결제 서비스가 혼잡합니다, 나중에 다시 시도하세요</string>
<string name="filter">필터</string>
<string name="opacity">불투명도</string>
<string name="theme">테마</string>
<string name="color">색상</string>
<string name="accent">강조</string>
<string name="themes">추가적인 테마</string>
<string name="theme_red">빨강</string>
<string name="theme_pink">분홍</string>
@ -361,12 +373,43 @@
<string name="theme_grey">회색</string>
<string name="theme_blue_grey">회청색</string>
<string name="theme_black">검정</string>
<string name="sync_interval_disable">사용안함</string>
<string name="sync_interval_one_hour">매시간</string>
<string name="sync_interval_three_hours">3시간마다</string>
<string name="sync_interval_six_hours">6시간마다</string>
<string name="sync_interval_twelve_hours">12시간마다</string>
<string name="sync_interval_one_day">매일</string>
<string name="sync_interval_three_days">3일마다</string>
<string name="sync_interval_one_week">일주일마다</string>
<string name="theme_dark_grey">어두운 회색</string>
<string name="theme_white">흰색</string>
<string name="theme_light">밝게</string>
<string name="theme_dark">어둡게</string>
<string name="theme_wallpaper">바탕화면</string>
<string name="theme_day_night">주간/야간</string>
<string name="default_value">기본값</string>
<string name="master_sync_warning">현재 자동 동기화는 안드로이드에 의해 사용이 불가합니다. </string>
<string name="settings_general">일반</string>
<string name="language">언어</string>
<string name="restart_required">이 변경을 적용하려면 Tasks 앱을 재시작해야 합니다. </string>
<string name="restart_now">지금 재시작하기</string>
<string name="restart_later">나중에</string>
<string name="settings_localization">현지화</string>
<string name="layout_direction">레이아웃 정렬 방향</string>
<string name="layout_direction_left_to_right">왼쪽 정렬</string>
<string name="layout_direction_right_to_left">오른쪽 정렬</string>
<string name="led_notification">LED 알림</string>
<string name="hardware_support_required">하드웨어 지원을 필요로 합니다. </string>
<string name="led_color">LED 색상</string>
<string name="no_calendars_found">달력 없음</string>
<string name="widget_settings">위젯 설정</string>
<string name="widget_header_settings">헤더 설정</string>
<string name="widget_row_settings">줄 설정</string>
<string name="notification_shade">상단바</string>
<string name="notification_description">할일 설명 보기</string>
<string name="sync_error_permissions">Tasks는 권한이 필요합니다. </string>
<string name="creating_new_list">새 목록 만들기</string>
<string name="deleting_list">목록 삭제하기</string>
<string name="renaming_list">목록 이름 변경하기</string>
<string name="clear_completed_tasks_confirmation">완료한 할일을 지울까요?</string>
<string name="copy_multiple_tasks_confirmation">%s 복사 완료</string>
<string name="delete_multiple_tasks_confirmation">%s 삭제 완료</string>
<string name="delete_selected_tasks">선택한 할일을 삭제할까요?</string>
<string name="copy_selected_tasks">선택한 할일을 복사할까요?</string>
<string name="date_and_time">날짜와 시간</string>
<string name="start_of_week">한 주의 시작</string>
<string name="use_locale_default">기본으로 설정된 언어 사용하기</string>
<string name="use_native_datetime_pickers">시스템 날짜와 시간 입력창 사용하기</string>
</resources>

@ -96,12 +96,4 @@
<string name="sync_SPr_forget">Logg ut</string>
<string name="sync_SPr_forget_description">Sletter all synkroniseringsdata</string>
<string name="sync_forget_confirm">Logg ut / slett synkroniseringsdata?</string>
<string name="sync_interval_disable">deaktiver</string>
<string name="sync_interval_one_hour">hver time</string>
<string name="sync_interval_three_hours">hver tredje time</string>
<string name="sync_interval_six_hours">hver sjette time</string>
<string name="sync_interval_twelve_hours">hver tolvte time</string>
<string name="sync_interval_one_day">daglig</string>
<string name="sync_interval_three_days">hver tredje dag</string>
<string name="sync_interval_one_week">hver uke</string>
</resources>

@ -324,9 +324,6 @@
<string name="send_anonymous_statistics_summary">Verstuur anoniem gebruikersstatistieken en crash rapporten om Tasks te verbeteren. Er worden geen persoonlijke gegevens verzameld.</string>
<string name="tag_already_exists">Label bestaat reeds</string>
<string name="name_cannot_be_empty">Naam mag niet leeg zijn</string>
<string name="doze_notifications">Onderbreek Snooze mode voor notificaties</string>
<string name="doze_notifications_off">Android zal vertraagd notificaties weergeven in Snooze mode</string>
<string name="doze_notifications_on">Android zal gelimiteerd onderbreken in Snooze mode</string>
<string name="no_title">(geen titel)</string>
<string name="back_button_saves_task">Terug knop slaat taak op</string>
<string name="default_list">Standaard lijst</string>
@ -369,14 +366,6 @@
<string name="theme_wallpaper">Achtergrond</string>
<string name="theme_day_night">Dag/Nacht</string>
<string name="default_value">Standaard</string>
<string name="sync_interval_disable">uitschakelen</string>
<string name="sync_interval_one_hour">elk uur</string>
<string name="sync_interval_three_hours">elke 3 uur</string>
<string name="sync_interval_six_hours">elke 6 uur</string>
<string name="sync_interval_twelve_hours">elke 12 uur</string>
<string name="sync_interval_one_day">elke dag</string>
<string name="sync_interval_three_days">elke 3 dagen</string>
<string name="sync_interval_one_week">elke week</string>
<string name="master_sync_warning">Automatische synchronisatie is momenteel uitgezet door Android</string>
<string name="settings_general">Globaal</string>
<string name="language">Taal</string>

@ -263,6 +263,7 @@
<string name="synchronization">Synchronizacja</string>
<string name="enabled">Włączone</string>
<string name="font_size">Rozmiar czcionki</string>
<string name="row_spacing">Odstęp wierszy</string>
<string name="customize_edit_screen">Dostosuj ekran edycji</string>
<string name="source_code">Kod źródłowy</string>
<string name="translations">Wspomóż tłumaczenie</string>
@ -329,9 +330,6 @@
<string name="send_anonymous_statistics_summary">Wyślij anonimowe statyki użycia i raporty o awariach celem ulepszenia Tasks. Żadne prywatne dane nie będą gromadzone.</string>
<string name="tag_already_exists">Tag już istnieje</string>
<string name="name_cannot_be_empty">Nazwa nie może być pusta</string>
<string name="doze_notifications">Przerwij tryb Doze dla powiadomień</string>
<string name="doze_notifications_off">Android będzie znacznie opóźniał powiadomienia w czasie trybu drzemki.</string>
<string name="doze_notifications_on">Android będzie pozwalał na ograniczone przerwania kiedy urządzenie jest w trybie drzemki.</string>
<string name="no_title">(Bez tytułu)</string>
<string name="back_button_saves_task">Przycisk Cofnij zapisuje zadanie</string>
<string name="default_list">Domyślna lista</string>
@ -376,14 +374,6 @@
<string name="theme_wallpaper">Tapeta</string>
<string name="theme_day_night">Dzień/noc</string>
<string name="default_value">Domyślny</string>
<string name="sync_interval_disable">Wyłączone</string>
<string name="sync_interval_one_hour">co godzinę</string>
<string name="sync_interval_three_hours">co 3 godziny</string>
<string name="sync_interval_six_hours">co 6 godzin</string>
<string name="sync_interval_twelve_hours">co 12 godzin</string>
<string name="sync_interval_one_day">raz dziennie</string>
<string name="sync_interval_three_days">co 3 dni</string>
<string name="sync_interval_one_week">co tydzień</string>
<string name="master_sync_warning">Automatyczna synchronizacja jest obecnie wyłączona przez Androida</string>
<string name="settings_general">Podstawowe</string>
<string name="language">Język</string>
@ -410,4 +400,9 @@
<string name="clear_completed_tasks_confirmation">Wyczyścić ukończone zadania?</string>
<string name="copy_multiple_tasks_confirmation">%s skopiowanych</string>
<string name="delete_multiple_tasks_confirmation">%s usuniętych</string>
<string name="delete_selected_tasks">Usunąć zaznaczone zadania?</string>
<string name="copy_selected_tasks">Skopiować zaznaczone zadania?</string>
<string name="date_and_time">Data i czas</string>
<string name="start_of_week">Początek tygodnia</string>
<string name="use_locale_default">Użyj domyślnych</string>
</resources>

@ -8,6 +8,9 @@
<resources>
<string name="actfm_picture_clear">Limpar imagem</string>
<string name="TVA_add_comment">Comentar...</string>
<string name="name">Nome</string>
<string name="enter_filter_name">Digite o nome do filtro</string>
<string name="choose_google_account">Selecione a Conta do Google</string>
<string name="backup_BAc_import">Importar tarefas</string>
<string name="backup_BAc_export">Exportar tarefas</string>
<string name="export_toast">Backups feitos: de %1$s para %2$s.</string>
@ -48,6 +51,7 @@
<string name="TEA_note_label">Descrição</string>
<string name="TEA_estimatedDuration_label">Quanto tempo isto vai levar?</string>
<string name="TEA_elapsedDuration_label">Tempo já gasto na tarefa</string>
<string name="save">Salvar</string>
<string name="TEA_timer_elap">Decorrido %s</string>
<string name="TEA_no_time">Sem horário</string>
<string name="due_date">Data de vencimento</string>
@ -55,9 +59,12 @@
<string name="day_before_due">Dias antes do vencimento</string>
<string name="week_before_due">Semanas antes do vencimento</string>
<string name="TEA_control_when">Quando</string>
<string name="TEA_control_repeat">Repetir</string>
<string name="TEA_control_gcal">Calendário</string>
<string name="TEA_control_importance">Prioridade</string>
<string name="TEA_control_notes">Descrição</string>
<string name="TEA_control_files">Arquivos</string>
<string name="TEA_control_google_task_list">Lista de Tarefas do Google</string>
<string name="TEA_control_reminders">Lembretes</string>
<string name="TEA_control_timer">Temporizador</string>
<string name="TEA_control_hidden_section">----Esconder Sempre---</string>
@ -124,7 +131,7 @@
<string name="BFE_Active">Minhas Tarefas</string>
<string name="BFE_Recent">Modificadas recentemente</string>
<string name="CFA_universe_all">Tarefas ativas</string>
<string name="CFA_type_add">OU</string>
<string name="CFA_type_add">ou</string>
<string name="CFA_type_subtract">NÃO</string>
<string name="CFA_type_intersect">E TAMBÉM</string>
<string name="CFA_context_chain">%s tem</string>
@ -137,11 +144,16 @@
<string name="next_month">Próximo mês</string>
<string name="CFC_importance_text">Pelo menos por prioridade?</string>
<string name="CFC_importance_name">Prioridade...</string>
<string name="CFC_tag_name">Tag...</string>
<string name="CFC_tag_contains_name">Nome da tag contém...</string>
<string name="CFC_tag_contains_text">Nome da tag contém: ?</string>
<string name="CFC_title_contains_name">Título...</string>
<string name="CFC_title_contains_text">Título contêm: ?</string>
<string name="gcal_TEA_error">Erro ao inserir a tarefa no calendário!</string>
<string name="gcal_TEA_addToCalendar_label">Inserir no calendário</string>
<string name="gcal_TEA_showCalendar_label">Abrir evento no calendário</string>
<string name="delete_calendar_event_confirmation">Excluir evento no calendário?</string>
<string name="calendar_event_not_found">Evento no calendário não encontrado</string>
<string name="gcal_completed_title">%s (concluído)</string>
<string name="CFC_gtasks_list_text">Na lista: ?</string>
<string name="CFC_gtasks_list_name">No Google Tasks...</string>
@ -157,6 +169,9 @@
<string name="audio_stop_recording">Parar Gravação</string>
<string name="file_type_unhandled">Desculpa! Nenhuma aplicação para manipular este tipo de arquivo foi encontrada.</string>
<string name="file_err_copy">Erro ao copiar o arquivo para o anexo</string>
<string name="ring_once">Tocar uma vez</string>
<string name="ring_five_times">Tocar cinco vezes</string>
<string name="ring_nonstop">Tocar continuamente</string>
<string name="random_reminder_hour">por hora</string>
<string name="random_reminder_day">por dia</string>
<string name="random_reminder_week">por semana</string>
@ -196,8 +211,11 @@
<string name="repeat_detail_duedate_until">Todo %1$s\naté %2$s</string>
<string name="repeat_forever">Repetir para sempre</string>
<string name="repeat_until">Repetir até %s</string>
<string name="repeat_snackbar">%1$s remarcada para %2$s</string>
<string name="new_tag">Criar nova etiqueta</string>
<string name="new_list">Criar nova lista</string>
<string name="tag_FEx_untagged">Sem categoria</string>
<string name="delete_tag_confirmation">Excluir %s?</string>
<string name="TPl_notification">Temporizador ativado para %s!</string>
<string name="TFE_workingOn">Tarefas com contagem de tempo</string>
<string name="TEA_timer_controls">Temporizador</string>
@ -217,6 +235,7 @@
</plurals>
<string name="today">Hoje</string>
<string name="tomorrow">Amanhã</string>
<string name="next">Próximo %s</string>
<string name="yesterday">Ontem</string>
<string name="tmrw">amanhã</string>
<string name="yest">ontem</string>
@ -226,10 +245,20 @@
<string name="sync_SPr_forget">Desconectar</string>
<string name="sync_SPr_forget_description">Limpar todos os dados de sincronização</string>
<string name="sync_forget_confirm">Desconectar / limpar dados de sincronização?</string>
<string name="widget_show_due_date">Mostrar vencimentos</string>
<string name="widget_show_checkboxes">Mostrar tarefas ocultas</string>
<string name="widget_show_header">Mostrar ocultas</string>
<string name="widget_show_settings">Configurações</string>
<string name="notifications">Notificações</string>
<string name="silent">Silencioso</string>
<string name="sound">Som</string>
<string name="vibrations">Vibrações</string>
<string name="quiet_hours">Horas calmas</string>
<string name="attachment_directory">Diretório para anexos</string>
<string name="backup_directory">Diretório de backup</string>
<string name="synchronization">Sincronização</string>
<string name="enabled">Ativado</string>
<string name="font_size">Tamanho do texto</string>
<string name="customize_edit_screen">Personalizar tela de edição</string>
<string name="source_code">Código fonte</string>
<string name="translations">Ajude com as traduções</string>
@ -239,18 +268,43 @@
<string name="rate_tasks">Avaliar o Tasks</string>
<string name="quiet_hours_summary">Sem lembretes durante as horas calmas</string>
<string name="TLA_menu_donate">Doar</string>
<string name="select_amount">Selecionar quantia</string>
<string name="notification_actions">Ações na notificação</string>
<string name="notification_actions_summary">Mostrar as ações \"Adiar\" e \"Concluída\" na notificação</string>
<string name="add_reminder">Adicionar lembrete</string>
<string name="remove">Remover</string>
<string name="randomly_once">Aleatoriamente uma vez</string>
<string name="randomly">Aleatoriamente</string>
<string name="pick_a_date">Escolha uma data</string>
<string name="pick_a_time">Escolha uma hora</string>
<string name="pick_a_date_and_time">Escolha data e horário</string>
<string name="pick_a_location">Escolher lugar</string>
<string name="when_overdue">Quando vencida</string>
<string name="when_due">Quando vencer</string>
<string name="geofence_radius_title">Raio (metros)</string>
<string name="geofence_responsiveness_title">Tempo de resposta (valores maiores conservam bateria)</string>
<string name="geolocation_reminders">Lembretes com Geolocalização</string>
<string name="tags">Etiquetas</string>
<string name="filters">Filtros</string>
<string name="date_shortcut_hour">Por uma hora</string>
<string name="date_shortcut_morning">Manhã</string>
<string name="date_shortcut_afternoon">Tarde</string>
<string name="date_shortcut_evening">Noite</string>
<string name="date_shortcut_night">Noite</string>
<string name="date_shortcut_tomorrow_morning">Amanhã de manhã</string>
<string name="date_shortcut_tomorrow_afternoon">Amanhã de tarde</string>
<string name="date_shortcut_must_come_before">%1$s deve vir antes de %2$s</string>
<string name="date_shortcut_must_come_after">%1$s deve vir depois de %2$s</string>
<string name="discard_changes">Descartar alterações?</string>
<string name="discard">Descartar</string>
<string name="tag_settings">Config. Tags</string>
<string name="list_settings">Configurações da lista</string>
<string name="delete">Excluir</string>
<string name="copy">Copiar</string>
<string name="filter_settings">Configurações de filtro</string>
<string name="show_hidden">Mostrar ocultas</string>
<string name="show_completed">Mostrar completas</string>
<string name="task_count">%d tarefas</string>
<string name="get_plugins">Compras no app</string>
<string name="no_application_found">Nenhuma aplicação encontrada para abrir o anexo</string>
<string name="add_attachment">Adicionar anexo</string>
@ -261,14 +315,15 @@
<string name="vibration_count">Número de vibrações</string>
<string name="vibration_duration">Duração de cada vibração (milissegundos)</string>
<string name="vibration_pause">Pausa entre vibrações (milissegundos)</string>
<string name="take_a_picture">Tirar uma foto</string>
<string name="pick_from_gallery">Selecionar da galeria</string>
<string name="privacy">Privacidade</string>
<string name="privacy_policy">Política de privacidade</string>
<string name="send_anonymous_statistics">Melhorar o Tasks</string>
<string name="send_anonymous_statistics_summary">Enviar estatísticas de uso e relatórios de falha anonimamente para ajudar a melhorar o Tasks. Nenhuma informação pessoal será coletada.</string>
<string name="tag_already_exists">Etiqueta já existe</string>
<string name="doze_notifications">Interromper modo Doze para notificações</string>
<string name="doze_notifications_off">O Android irá adiar as notificações significativamente enquanto o dispositivo estiver no modo Doze</string>
<string name="doze_notifications_on">O Android irá permitir interrupções limitadas enquanto o dispositivo estiver no modo Doze</string>
<string name="name_cannot_be_empty">Nome não pode ser vazio</string>
<string name="no_title">(Sem título)</string>
<string name="back_button_saves_task">Botão voltar salva a tarefa</string>
<string name="default_list">Lista padrão</string>
<string name="plugin_description">Tasks é um projeto de código aberto mantido por um desenvolvedor. Algumas funções são oferecidas como compras dentro do app a fim de apoiar o desenvolvimento.</string>
@ -277,19 +332,24 @@
<string name="tasker_description">Notificações contextuadas de listas. Necessita Tasker ou Locale.</string>
<string name="donate_summary">Doações são muito valiosas</string>
<string name="dashclock_purchase_description">Mostra um contador de tarefas ativas. Necessita DashClock Widget.</string>
<string name="buy">Comprar</string>
<string name="buy_dashclock_extension">Comprar extensão</string>
<string name="billing_service_busy">O serviço de compras no app está ocupado. Tente novamente mais tarde.</string>
<string name="filter">Filtrar</string>
<string name="opacity">Opacidade</string>
<string name="theme">Tema</string>
<string name="color">Cor</string>
<string name="accent">Cor de realce</string>
<string name="themes">Temas adicionais</string>
<string name="theme_red">Vermelho</string>
<string name="theme_pink">Rosa</string>
<string name="theme_purple">Roxo</string>
<string name="theme_deep_purple">Roxo Profundo</string>
<string name="theme_indigo">Azul-escuro</string>
<string name="theme_blue">Azul</string>
<string name="theme_light_blue">Azul Claro</string>
<string name="theme_cyan">Ciano</string>
<string name="theme_teal">Verde-azulado</string>
<string name="theme_green">Verde</string>
<string name="theme_light_green">Verde Claro</string>
<string name="theme_lime">Limão</string>
@ -300,19 +360,43 @@
<string name="theme_brown">Marrom</string>
<string name="theme_grey">Cinza</string>
<string name="theme_blue_grey">Azul Acinzentado</string>
<string name="theme_black">Preto</string>
<string name="theme_dark_grey">Cinza escuro</string>
<string name="theme_white">Branco</string>
<string name="theme_light">Claro</string>
<string name="theme_dark">Escuro</string>
<string name="sync_interval_disable">desabilitar</string>
<string name="sync_interval_one_hour">a cada hora</string>
<string name="sync_interval_three_hours">a cada três horas</string>
<string name="sync_interval_six_hours">a cada seis horas</string>
<string name="sync_interval_twelve_hours">a cada doze horas</string>
<string name="sync_interval_one_day">diariamente</string>
<string name="sync_interval_three_days">a cada três dias</string>
<string name="sync_interval_one_week">semanalmente</string>
<string name="theme_wallpaper">Plano de fundo</string>
<string name="theme_day_night">Dia/Noite</string>
<string name="default_value">Padrão</string>
<string name="master_sync_warning">Sincronização automática está atualmente desabilitada pelo Android</string>
<string name="settings_general">Geral</string>
<string name="language">Idioma</string>
<string name="restart_required">Você deve reiniciar seu aplicativo para que as alterações sejam aplicadas.</string>
<string name="restart_now">Reiniciar agora</string>
<string name="restart_later">Depois</string>
<string name="settings_localization">Localização</string>
<string name="layout_direction">Direção do Layout</string>
<string name="layout_direction_left_to_right">Esquerda para direita</string>
<string name="layout_direction_right_to_left">Direita para esquerda</string>
<string name="led_notification">LED de notificações</string>
<string name="led_color">Cor do LED</string>
<string name="no_calendars_found">Não foi encontrado nenhum calendário</string>
<string name="widget_settings">Configurações de Widget</string>
<string name="widget_header_settings">Config. cabeçalho</string>
<string name="widget_row_settings">Congif. entrada</string>
<string name="notification_shade">Painel de notificações</string>
<string name="notification_description">Mostrar descrição</string>
<string name="sync_error_permissions">Tasks precisa de permissão</string>
<string name="creating_new_list">Criando uma nova lista</string>
<string name="deleting_list">Excluindo lista</string>
<string name="renaming_list">Renomeando uma lista</string>
<string name="clear_completed_tasks_confirmation">Limpar tarefas concluídas?</string>
<string name="copy_multiple_tasks_confirmation">%s copiadas</string>
<string name="delete_multiple_tasks_confirmation">%s excluídas</string>
<string name="delete_selected_tasks">Excluir tarefas selecionadas?</string>
<string name="copy_selected_tasks">Copiar tarefas selecionadas?</string>
<string name="date_and_time">Hora e data</string>
<string name="start_of_week">Começo da semana</string>
<string name="use_locale_default">Usar padrão do aparelho</string>
<string name="use_native_datetime_pickers">Usar calendário nativo</string>
</resources>

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

Loading…
Cancel
Save