Support Android Marshmallow permission changes

pull/384/head
Alex Baker 9 years ago
parent 2181132b7a
commit 65492648c4

@ -3,12 +3,12 @@ sudo: false
jdk: oraclejdk7 jdk: oraclejdk7
env: env:
matrix: matrix:
- ANDROID_SDKS=android-22,sysimg-19 ANDROID_TARGET=android-19 ANDROID_ABI=armeabi-v7a - ANDROID_SDKS=android-23,sysimg-19 ANDROID_TARGET=android-19 ANDROID_ABI=armeabi-v7a
android: android:
components: components:
- android-22 - android-23
- platform-tools-23.0.1 - platform-tools-23.0.1
- build-tools-22.0.1 - build-tools-23.0.2
- extra-android-m2repository - extra-android-m2repository
- extra-google-m2repository - extra-google-m2repository
licenses: licenses:

@ -6,7 +6,6 @@ task wrapper(type: Wrapper) {
buildscript { buildscript {
repositories { repositories {
mavenCentral()
jcenter() jcenter()
} }
@ -16,7 +15,7 @@ buildscript {
} }
repositories { repositories {
mavenCentral() jcenter()
} }
android { android {
@ -24,14 +23,14 @@ android {
lintConfig file("lint.xml") lintConfig file("lint.xml")
} }
compileSdkVersion 22 compileSdkVersion 23
buildToolsVersion "22.0.1" buildToolsVersion "23.0.2"
defaultConfig { defaultConfig {
versionCode 379 versionCode 379
versionName "4.7.22" versionName "4.7.22"
minSdkVersion 9 minSdkVersion 9
targetSdkVersion 22 targetSdkVersion 23
} }
signingConfigs { signingConfigs {
@ -94,7 +93,7 @@ dependencies {
compile group: 'com.jakewharton', name: 'butterknife', version: '7.0.1' compile group: 'com.jakewharton', name: 'butterknife', version: '7.0.1'
compile group: 'com.android.support', name: 'design', version: '22.2.1' compile group: 'com.android.support', name: 'design', version: '23.1.1'
compile group: 'org.slf4j', name: 'slf4j-api', version: '1.7.7' compile group: 'org.slf4j', name: 'slf4j-api', version: '1.7.7'
@ -113,6 +112,7 @@ dependencies {
googleplayCompile(group: 'com.google.api-client', name: 'google-api-client-android', version: '1.21.0') { googleplayCompile(group: 'com.google.api-client', name: 'google-api-client-android', version: '1.21.0') {
exclude group: 'org.apache.httpcomponents', module: 'httpclient' exclude group: 'org.apache.httpcomponents', module: 'httpclient'
} }
compile group: 'com.google.guava', name: 'guava-jdk5', version: '13.0' compile group: 'com.google.guava', name: 'guava-jdk5', version: '13.0'
compile group: 'com.github.tony19', name: 'logback-android-classic', version: '1.1.1-2' compile group: 'com.github.tony19', name: 'logback-android-classic', version: '1.1.1-2'

@ -34,7 +34,7 @@ public class NotifyAtDeadlineTest extends AndroidTestCase {
@Override @Override
public void setUp() { public void setUp() {
preferences = new Preferences(getContext(), null); preferences = new Preferences(getContext(), null, null);
reminderService = new ReminderService(getContext(), preferences, mock(AlarmManager.class)); reminderService = new ReminderService(getContext(), preferences, mock(AlarmManager.class));
freezeAt(new DateTime(2014, 1, 24, 17, 23, 37)); freezeAt(new DateTime(2014, 1, 24, 17, 23, 37));
} }

@ -23,7 +23,7 @@ public class NotifierTests extends AndroidTestCase {
@Override @Override
public void setUp() { public void setUp() {
preferences = new Preferences(getContext(), null); preferences = new Preferences(getContext(), null, null);
preferences.clear(); preferences.clear();
preferences.setBoolean(R.string.p_rmd_enable_quiet, true); preferences.setBoolean(R.string.p_rmd_enable_quiet, true);
freezeAt(now); freezeAt(now);

@ -29,7 +29,7 @@ public class GtasksMetadataServiceTest extends DatabaseTestCase {
private final GtasksTestPreferenceService service; private final GtasksTestPreferenceService service;
public GtasksMetadataServiceTestModule(Context context) { public GtasksMetadataServiceTestModule(Context context) {
service = new GtasksTestPreferenceService(context, new Preferences(context, null)); service = new GtasksTestPreferenceService(context, new Preferences(context, null, null));
} }
@Provides @Provides

@ -6,6 +6,7 @@
package com.todoroo.astrid.gtasks; package com.todoroo.astrid.gtasks;
import android.content.Intent; import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Bundle; import android.os.Bundle;
import android.preference.CheckBoxPreference; import android.preference.CheckBoxPreference;
import android.preference.Preference; import android.preference.Preference;
@ -16,6 +17,7 @@ import com.todoroo.astrid.gtasks.auth.GtasksLoginActivity;
import org.tasks.R; import org.tasks.R;
import org.tasks.activities.ClearGtaskDataActivity; import org.tasks.activities.ClearGtaskDataActivity;
import org.tasks.injection.InjectingPreferenceActivity; import org.tasks.injection.InjectingPreferenceActivity;
import org.tasks.preferences.PermissionRequestor;
import org.tasks.scheduling.BackgroundScheduler; import org.tasks.scheduling.BackgroundScheduler;
import javax.inject.Inject; import javax.inject.Inject;
@ -27,6 +29,7 @@ public class GtasksPreferences extends InjectingPreferenceActivity {
@Inject GtasksPreferenceService gtasksPreferenceService; @Inject GtasksPreferenceService gtasksPreferenceService;
@Inject BackgroundScheduler backgroundScheduler; @Inject BackgroundScheduler backgroundScheduler;
@Inject PermissionRequestor permissionRequestor;
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
@ -39,12 +42,18 @@ public class GtasksPreferences extends InjectingPreferenceActivity {
gtaskPreference.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { gtaskPreference.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override @Override
public boolean onPreferenceChange(Preference preference, Object newValue) { public boolean onPreferenceChange(Preference preference, Object newValue) {
if ((boolean) newValue && !gtasksPreferenceService.isLoggedIn()) { if ((boolean) newValue) {
startActivityForResult(new Intent(GtasksPreferences.this, GtasksLoginActivity.class), REQUEST_LOGIN); if (gtasksPreferenceService.isLoggedIn()) {
return true;
}
if (permissionRequestor.requestAccountPermissions()) {
requestLogin();
}
return false;
} else { } else {
gtasksPreferenceService.stopOngoing(); gtasksPreferenceService.stopOngoing();
return true;
} }
return true;
} }
}); });
if (gtasksPreferenceService.getLastSyncDate() > 0) { if (gtasksPreferenceService.getLastSyncDate() > 0) {
@ -61,6 +70,10 @@ public class GtasksPreferences extends InjectingPreferenceActivity {
}); });
} }
private void requestLogin() {
startActivityForResult(new Intent(GtasksPreferences.this, GtasksLoginActivity.class), REQUEST_LOGIN);
}
@Override @Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) { protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_LOGIN) { if (requestCode == REQUEST_LOGIN) {
@ -74,6 +87,17 @@ public class GtasksPreferences extends InjectingPreferenceActivity {
} }
} }
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
if (requestCode == PermissionRequestor.REQUEST_ACCOUNTS) {
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
requestLogin();
}
} else {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
}
@Override @Override
protected void onPause() { protected void onPause() {
super.onPause(); super.onPause();

@ -49,6 +49,7 @@ public class LocationPickerDialog extends InjectingDialogFragment implements Goo
private OnLocationPickedHandler onLocationPickedHandler; private OnLocationPickedHandler onLocationPickedHandler;
private DialogInterface.OnCancelListener onCancelListener; private DialogInterface.OnCancelListener onCancelListener;
private boolean resolvingError;
public void setOnLocationPickedHandler(OnLocationPickedHandler onLocationPickedHandler) { public void setOnLocationPickedHandler(OnLocationPickedHandler onLocationPickedHandler) {
this.onLocationPickedHandler = onLocationPickedHandler; this.onLocationPickedHandler = onLocationPickedHandler;
@ -137,13 +138,18 @@ public class LocationPickerDialog extends InjectingDialogFragment implements Goo
@Override @Override
public void onConnectionFailed(ConnectionResult connectionResult) { public void onConnectionFailed(ConnectionResult connectionResult) {
if (connectionResult.hasResolution()) { if (resolvingError) {
log.info("Ignoring {}, already resolving error", connectionResult);
} else if (connectionResult.hasResolution()) {
try { try {
resolvingError = true;
connectionResult.startResolutionForResult(fragmentActivity, RC_RESOLVE_GPS_ISSUE); connectionResult.startResolutionForResult(fragmentActivity, RC_RESOLVE_GPS_ISSUE);
} catch (IntentSender.SendIntentException e) { } catch (IntentSender.SendIntentException e) {
log.error(e.getMessage(), e); log.error(e.getMessage(), e);
googleApi.connect(this);
} }
} else { } else {
resolvingError = true;
GooglePlayServicesUtil GooglePlayServicesUtil
.getErrorDialog(connectionResult.getErrorCode(), fragmentActivity, RC_RESOLVE_GPS_ISSUE) .getErrorDialog(connectionResult.getErrorCode(), fragmentActivity, RC_RESOLVE_GPS_ISSUE)
.show(); .show();

@ -16,6 +16,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.tasks.R; import org.tasks.R;
import org.tasks.injection.ForApplication; import org.tasks.injection.ForApplication;
import org.tasks.preferences.PermissionChecker;
import org.tasks.preferences.Preferences; import org.tasks.preferences.Preferences;
import java.util.List; import java.util.List;
@ -35,15 +36,17 @@ public class GeofenceApi {
private Context context; private Context context;
private Preferences preferences; private Preferences preferences;
private PermissionChecker permissionChecker;
@Inject @Inject
public GeofenceApi(@ForApplication Context context, Preferences preferences) { public GeofenceApi(@ForApplication Context context, Preferences preferences, PermissionChecker permissionChecker) {
this.context = context; this.context = context;
this.preferences = preferences; this.preferences = preferences;
this.permissionChecker = permissionChecker;
} }
public void register(final List<Geofence> geofences) { public void register(final List<Geofence> geofences) {
if (geofences.isEmpty() || !preferences.geofencesEnabled()) { if (geofences.isEmpty() || !preferences.geofencesEnabled() || !permissionChecker.canAccessLocation()) {
return; return;
} }
@ -75,7 +78,7 @@ public class GeofenceApi {
} }
public void cancel(final List<Geofence> geofences) { public void cancel(final List<Geofence> geofences) {
if (geofences.isEmpty()) { if (geofences.isEmpty() || !permissionChecker.canAccessLocation()) {
return; return;
} }

@ -379,17 +379,6 @@
</intent-filter> </intent-filter>
</receiver> </receiver>
<receiver android:name="com.todoroo.astrid.gcal.CalendarStartupReceiver">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<intent-filter>
<action android:name="org.tasks.SCHEDULE_CAL_REMINDERS" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</receiver>
<activity <activity
android:name=".activities.CalendarSelectionActivity" android:name=".activities.CalendarSelectionActivity"
android:theme="@style/TranslucentDialog" /> android:theme="@style/TranslucentDialog" />
@ -515,6 +504,9 @@
<service <service
android:name=".scheduling.ReminderSchedulerIntentService" android:name=".scheduling.ReminderSchedulerIntentService"
android:exported="false" /> android:exported="false" />
<service
android:name=".scheduling.CalendarNotificationIntentService"
android:exported="false" />
<!-- Uses Library --> <!-- Uses Library -->
<uses-library <uses-library

@ -81,6 +81,7 @@ import org.tasks.location.Geofence;
import org.tasks.location.GeofenceService; import org.tasks.location.GeofenceService;
import org.tasks.notifications.NotificationManager; import org.tasks.notifications.NotificationManager;
import org.tasks.preferences.ActivityPreferences; import org.tasks.preferences.ActivityPreferences;
import org.tasks.preferences.PermissionRequestor;
import org.tasks.ui.DeadlineControlSet; import org.tasks.ui.DeadlineControlSet;
import org.tasks.ui.MenuColorizer; import org.tasks.ui.MenuColorizer;
@ -172,6 +173,7 @@ ViewPager.OnPageChangeListener, EditNoteActivity.UpdatesChangedListener {
@Inject ActFmCameraModule actFmCameraModule; @Inject ActFmCameraModule actFmCameraModule;
@Inject GeofenceService geofenceService; @Inject GeofenceService geofenceService;
@Inject DialogBuilder dialogBuilder; @Inject DialogBuilder dialogBuilder;
@Inject PermissionRequestor permissionRequestor;
// --- UI components // --- UI components
@ -354,7 +356,7 @@ ViewPager.OnPageChangeListener, EditNoteActivity.UpdatesChangedListener {
RepeatControlSet repeatControls = new RepeatControlSet(preferences, getActivity(), dialogBuilder); RepeatControlSet repeatControls = new RepeatControlSet(preferences, getActivity(), dialogBuilder);
controlSetMap.put(getString(R.string.TEA_ctrl_repeat_pref), repeatControls); controlSetMap.put(getString(R.string.TEA_ctrl_repeat_pref), repeatControls);
gcalControl = new GCalControlSet(gcalHelper, preferences, this); gcalControl = new GCalControlSet(gcalHelper, preferences, this, permissionRequestor);
controlSetMap.put(getString(R.string.TEA_ctrl_gcal), gcalControl); controlSetMap.put(getString(R.string.TEA_ctrl_gcal), gcalControl);
// The deadline control set contains the repeat controls and the // The deadline control set contains the repeat controls and the
@ -382,7 +384,7 @@ ViewPager.OnPageChangeListener, EditNoteActivity.UpdatesChangedListener {
controlSetMap.put(getString(R.string.TEA_ctrl_notes_pref), controlSetMap.put(getString(R.string.TEA_ctrl_notes_pref),
notesControlSet); notesControlSet);
reminderControlSet = new ReminderControlSet(alarmService, geofenceService, this, preferences); reminderControlSet = new ReminderControlSet(alarmService, geofenceService, this, preferences, permissionRequestor);
controls.add(reminderControlSet); controls.add(reminderControlSet);
controlSetMap.put(getString(R.string.TEA_ctrl_reminders_pref), reminderControlSet); controlSetMap.put(getString(R.string.TEA_ctrl_reminders_pref), reminderControlSet);

@ -6,6 +6,7 @@
package com.todoroo.astrid.core; package com.todoroo.astrid.core;
import android.content.Intent; import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Bundle; import android.os.Bundle;
import android.preference.Preference; import android.preference.Preference;
@ -15,6 +16,8 @@ import com.todoroo.astrid.gcal.GCalHelper;
import org.tasks.R; import org.tasks.R;
import org.tasks.activities.CalendarSelectionActivity; import org.tasks.activities.CalendarSelectionActivity;
import org.tasks.injection.InjectingPreferenceActivity; import org.tasks.injection.InjectingPreferenceActivity;
import org.tasks.preferences.PermissionChecker;
import org.tasks.preferences.PermissionRequestor;
import org.tasks.preferences.Preferences; import org.tasks.preferences.Preferences;
import java.util.List; import java.util.List;
@ -33,6 +36,8 @@ public class DefaultsPreferences extends InjectingPreferenceActivity {
@Inject Preferences preferences; @Inject Preferences preferences;
@Inject GCalHelper calendarHelper; @Inject GCalHelper calendarHelper;
@Inject PermissionChecker permissionChecker;
@Inject PermissionRequestor permissionRequester;
private Preference defaultCalendarPref; private Preference defaultCalendarPref;
@Override @Override
@ -45,7 +50,9 @@ public class DefaultsPreferences extends InjectingPreferenceActivity {
defaultCalendarPref.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { defaultCalendarPref.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
@Override @Override
public boolean onPreferenceClick(Preference preference) { public boolean onPreferenceClick(Preference preference) {
startActivityForResult(new Intent(DefaultsPreferences.this, CalendarSelectionActivity.class), REQUEST_CALENDAR_SELECTION); if (permissionRequester.requestCalendarPermissions()) {
startCalendarSelectionActivity();
}
return false; return false;
} }
}); });
@ -53,16 +60,33 @@ public class DefaultsPreferences extends InjectingPreferenceActivity {
} }
private void setCalendarSummary(String calendarId) { private void setCalendarSummary(String calendarId) {
List<AndroidCalendar> calendars = calendarHelper.getCalendars(); if (permissionChecker.canAccessCalendars()) {
for (AndroidCalendar calendar : calendars) { List<AndroidCalendar> calendars = calendarHelper.getCalendars();
if (calendar.getId().equals(calendarId)) { for (AndroidCalendar calendar : calendars) {
defaultCalendarPref.setSummary(calendar.getName()); if (calendar.getId().equals(calendarId)) {
return; defaultCalendarPref.setSummary(calendar.getName());
return;
}
} }
} }
defaultCalendarPref.setSummary(getString(R.string.none)); defaultCalendarPref.setSummary(getString(R.string.none));
} }
private void startCalendarSelectionActivity() {
startActivityForResult(new Intent(DefaultsPreferences.this, CalendarSelectionActivity.class), REQUEST_CALENDAR_SELECTION);
}
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
if (requestCode == PermissionRequestor.REQUEST_CALENDAR) {
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
startCalendarSelectionActivity();
}
} else {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
}
@Override @Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) { protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_CALENDAR_SELECTION && resultCode == RESULT_OK) { if (requestCode == REQUEST_CALENDAR_SELECTION && resultCode == RESULT_OK) {

@ -6,9 +6,8 @@
package com.todoroo.astrid.files; package com.todoroo.astrid.files;
import android.content.Intent; import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Bundle; import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Chronometer; import android.widget.Chronometer;
import com.todoroo.astrid.voice.AACRecorder; import com.todoroo.astrid.voice.AACRecorder;
@ -16,67 +15,66 @@ import com.todoroo.astrid.voice.AACRecorder.AACRecorderCallbacks;
import org.tasks.R; import org.tasks.R;
import org.tasks.injection.InjectingAppCompatActivity; import org.tasks.injection.InjectingAppCompatActivity;
import org.tasks.preferences.PermissionRequestor;
import org.tasks.preferences.Preferences; import org.tasks.preferences.Preferences;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
import javax.inject.Inject; import javax.inject.Inject;
import butterknife.Bind;
import butterknife.ButterKnife;
import butterknife.OnClick;
public class AACRecordingActivity extends InjectingAppCompatActivity implements AACRecorderCallbacks { public class AACRecordingActivity extends InjectingAppCompatActivity implements AACRecorderCallbacks {
public static final String RESULT_OUTFILE = "outfile"; //$NON-NLS-1$ public static final String RESULT_OUTFILE = "outfile"; //$NON-NLS-1$
public static final String RESULT_FILENAME = "filename"; //$NON-NLS-1$ public static final String RESULT_FILENAME = "filename"; //$NON-NLS-1$
private final AtomicReference<String> nameRef = new AtomicReference<>();
private AACRecorder recorder; private AACRecorder recorder;
private Chronometer timer;
private AtomicReference<String> nameRef;
private String tempFile; private String tempFile;
@Inject Preferences preferences; @Inject Preferences preferences;
@Inject PermissionRequestor permissionRequestor;
@Bind(R.id.timer) Chronometer timer;
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setContentView(R.layout.aac_record_activity); if (permissionRequestor.requestMic()) {
startRecording();
}
}
setupUi(); private void startRecording() {
setContentView(R.layout.aac_record_activity);
ButterKnife.bind(this);
nameRef = new AtomicReference<>();
tempFile = preferences.getNewAudioAttachmentPath(nameRef); tempFile = preferences.getNewAudioAttachmentPath(nameRef);
recorder = new AACRecorder(); recorder = new AACRecorder();
recorder.setListener(this); recorder.setListener(this);
recorder.startRecording(tempFile); recorder.startRecording(tempFile);
timer.start(); timer.start();
} }
private void setupUi() { @OnClick(R.id.stop_recording)
View stopRecording = findViewById(R.id.stop_recording); void stopRecording() {
if (recorder != null) {
stopRecording.setOnClickListener(new OnClickListener() { recorder.stopRecording();
@Override timer.stop();
public void onClick(View v) { }
stopRecording();
}
});
View dismiss = findViewById(R.id.dismiss);
dismiss.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
recorder.setListener(null);
recorder.stopRecording();
finish();
}
});
timer = (Chronometer) findViewById(R.id.timer);
} }
private void stopRecording() { @OnClick(R.id.dismiss)
recorder.stopRecording(); void dismiss() {
timer.stop(); if (recorder != null) {
recorder.setListener(null);
recorder.stopRecording();
}
finish();
} }
@Override @Override
@ -94,4 +92,17 @@ public class AACRecordingActivity extends InjectingAppCompatActivity implements
setResult(RESULT_OK, result); setResult(RESULT_OK, result);
finish(); finish();
} }
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
if (requestCode == PermissionRequestor.REQUEST_MIC) {
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
startRecording();
} else {
finish();
}
} else {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
}
} }

@ -9,6 +9,7 @@ import android.app.Dialog;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.DialogInterface.OnCancelListener; import android.content.DialogInterface.OnCancelListener;
import android.content.Intent; import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.os.Bundle; import android.os.Bundle;
import android.os.Environment; import android.os.Environment;
@ -27,6 +28,7 @@ import org.tasks.R;
import org.tasks.dialogs.DialogBuilder; import org.tasks.dialogs.DialogBuilder;
import org.tasks.injection.InjectingAppCompatActivity; import org.tasks.injection.InjectingAppCompatActivity;
import org.tasks.preferences.ActivityPreferences; import org.tasks.preferences.ActivityPreferences;
import org.tasks.preferences.PermissionRequestor;
import java.io.File; import java.io.File;
import java.io.FilenameFilter; import java.io.FilenameFilter;
@ -54,6 +56,7 @@ public class FileExplore extends InjectingAppCompatActivity {
@Inject DialogBuilder dialogBuilder; @Inject DialogBuilder dialogBuilder;
@Inject ActivityPreferences activityPreferences; @Inject ActivityPreferences activityPreferences;
@Inject PermissionRequestor permissionRequestor;
private Item[] fileList; private Item[] fileList;
private File path; private File path;
@ -66,13 +69,19 @@ public class FileExplore extends InjectingAppCompatActivity {
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
if (permissionRequestor.requestFileWritePermission()) {
showDialog();
}
}
private void showDialog() {
activityPreferences.applyDialogTheme(); activityPreferences.applyDialogTheme();
if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) { if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
path = new File(Environment.getExternalStorageDirectory().toString()); path = new File(Environment.getExternalStorageDirectory().toString());
} else { } else {
path = Environment.getRootDirectory(); path = Environment.getRootDirectory();
} }
loadFileList(); loadFileList();
@ -83,6 +92,19 @@ public class FileExplore extends InjectingAppCompatActivity {
log.debug(path.getAbsolutePath()); log.debug(path.getAbsolutePath());
} }
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
if (requestCode == PermissionRequestor.REQUEST_FILE_WRITE) {
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
showDialog();
} else {
finish();
}
} else {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
}
private void loadFileList() { private void loadFileList() {
try { try {
path.mkdirs(); path.mkdirs();

@ -7,6 +7,7 @@ package com.todoroo.astrid.files;
import android.app.Activity; import android.app.Activity;
import android.content.ActivityNotFoundException; import android.content.ActivityNotFoundException;
import android.content.ClipData;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
import android.media.MediaPlayer; import android.media.MediaPlayer;
@ -40,6 +41,8 @@ import org.tasks.preferences.ActivityPreferences;
import java.io.File; import java.io.File;
import java.util.ArrayList; import java.util.ArrayList;
import static com.todoroo.andlib.utility.AndroidUtilities.atLeastLollipop;
public class FilesControlSet extends TaskEditControlSetBase { public class FilesControlSet extends TaskEditControlSetBase {
private static final Logger log = LoggerFactory.getLogger(FilesControlSet.class); private static final Logger log = LoggerFactory.getLogger(FilesControlSet.class);
@ -201,9 +204,15 @@ public class FilesControlSet extends TaskEditControlSetBase {
}); });
} else if (fileType.startsWith(TaskAttachment.FILE_TYPE_IMAGE)) { } else if (fileType.startsWith(TaskAttachment.FILE_TYPE_IMAGE)) {
try { try {
activity.startActivity(new Intent(Intent.ACTION_VIEW) {{ Intent intent = new Intent(Intent.ACTION_VIEW);
setDataAndType(Uri.fromFile(new File(filePath)), fileType); Uri uri = Uri.fromFile(new File(filePath));
}}); intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.setFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
intent.setDataAndType(uri, TaskAttachment.FILE_TYPE_IMAGE + "*");
if (atLeastLollipop()) {
intent.setClipData(ClipData.newRawUri(null, uri));
}
activity.startActivity(intent);
} catch(ActivityNotFoundException e) { } catch(ActivityNotFoundException e) {
log.error(e.getMessage(), e); log.error(e.getMessage(), e);
Toast.makeText(activity, R.string.no_application_found, Toast.LENGTH_SHORT).show(); Toast.makeText(activity, R.string.no_application_found, Toast.LENGTH_SHORT).show();
@ -232,6 +241,7 @@ public class FilesControlSet extends TaskEditControlSetBase {
try { try {
Intent intent = new Intent(Intent.ACTION_VIEW); Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(Uri.fromFile(new File(file)), type); intent.setDataAndType(Uri.fromFile(new File(file)), type);
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
activity.startActivity(intent); activity.startActivity(intent);
} catch (ActivityNotFoundException e) { } catch (ActivityNotFoundException e) {
log.error(e.getMessage(), e); log.error(e.getMessage(), e);

@ -16,7 +16,9 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.tasks.R; import org.tasks.R;
import org.tasks.injection.InjectingBroadcastReceiver; import org.tasks.injection.InjectingBroadcastReceiver;
import org.tasks.preferences.PermissionChecker;
import org.tasks.preferences.Preferences; import org.tasks.preferences.Preferences;
import org.tasks.scheduling.CalendarNotificationIntentService;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashSet; import java.util.HashSet;
@ -43,6 +45,7 @@ public class CalendarAlarmReceiver extends InjectingBroadcastReceiver {
}; };
@Inject Preferences preferences; @Inject Preferences preferences;
@Inject PermissionChecker permissionChecker;
@Override @Override
public void onReceive(Context context, Intent intent) { public void onReceive(Context context, Intent intent) {
@ -51,6 +54,11 @@ public class CalendarAlarmReceiver extends InjectingBroadcastReceiver {
if (!preferences.getBoolean(R.string.p_calendar_reminders, true)) { if (!preferences.getBoolean(R.string.p_calendar_reminders, true)) {
return; return;
} }
if (!permissionChecker.canAccessCalendars()) {
return;
}
try { try {
Uri data = intent.getData(); Uri data = intent.getData();
if (data == null) { if (data == null) {
@ -65,7 +73,7 @@ public class CalendarAlarmReceiver extends InjectingBroadcastReceiver {
return; return;
} }
long eventId = Long.parseLong(uriString.substring(pathIndex)); long eventId = Long.parseLong(uriString.substring(pathIndex));
boolean fromPostpone = CalendarAlarmScheduler.URI_PREFIX_POSTPONE.equals(data.getScheme()); boolean fromPostpone = CalendarNotificationIntentService.URI_PREFIX_POSTPONE.equals(data.getScheme());
if (eventId > 0) { if (eventId > 0) {
showCalReminder(context, eventId, fromPostpone); showCalReminder(context, eventId, fromPostpone);
} }

@ -26,6 +26,7 @@ import org.tasks.preferences.ActivityPreferences;
import org.tasks.preferences.BasicPreferences; import org.tasks.preferences.BasicPreferences;
import org.tasks.preferences.ResourceResolver; import org.tasks.preferences.ResourceResolver;
import org.tasks.scheduling.AlarmManager; import org.tasks.scheduling.AlarmManager;
import org.tasks.scheduling.CalendarNotificationIntentService;
import javax.inject.Inject; import javax.inject.Inject;
@ -225,7 +226,7 @@ public class CalendarReminderActivity extends InjectingAppCompatActivity {
private void postpone() { private void postpone() {
Intent eventAlarm = new Intent(this, CalendarAlarmReceiver.class); Intent eventAlarm = new Intent(this, CalendarAlarmReceiver.class);
eventAlarm.setAction(CalendarAlarmReceiver.BROADCAST_CALENDAR_REMINDER); eventAlarm.setAction(CalendarAlarmReceiver.BROADCAST_CALENDAR_REMINDER);
eventAlarm.setData(Uri.parse(CalendarAlarmScheduler.URI_PREFIX_POSTPONE + "://" + eventId)); eventAlarm.setData(Uri.parse(CalendarNotificationIntentService.URI_PREFIX_POSTPONE + "://" + eventId));
PendingIntent pendingIntent = PendingIntent.getBroadcast(this, PendingIntent pendingIntent = PendingIntent.getBroadcast(this,
CalendarAlarmReceiver.REQUEST_CODE_CAL_REMINDER, eventAlarm, 0); CalendarAlarmReceiver.REQUEST_CODE_CAL_REMINDER, eventAlarm, 0);

@ -1,23 +0,0 @@
package com.todoroo.astrid.gcal;
import android.content.Context;
import android.content.Intent;
import org.tasks.BuildConfig;
import org.tasks.injection.InjectingBroadcastReceiver;
import javax.inject.Inject;
public class CalendarStartupReceiver extends InjectingBroadcastReceiver {
public static final String BROADCAST_RESCHEDULE_CAL_ALARMS = BuildConfig.APPLICATION_ID + ".SCHEDULE_CAL_REMINDERS"; //$NON-NLS-1$
@Inject CalendarAlarmScheduler calendarAlarmScheduler;
@Override
public void onReceive(Context context, Intent intent) {
super.onReceive(context, intent);
calendarAlarmScheduler.scheduleCalendarAlarms(context, false);
}
}

@ -26,6 +26,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.tasks.R; import org.tasks.R;
import org.tasks.activities.CalendarSelectionDialog; import org.tasks.activities.CalendarSelectionDialog;
import org.tasks.preferences.PermissionRequestor;
import org.tasks.preferences.Preferences; import org.tasks.preferences.Preferences;
import org.tasks.reminders.SnoozeDialog; import org.tasks.reminders.SnoozeDialog;
@ -45,6 +46,7 @@ public class GCalControlSet extends TaskEditControlSetBase implements CalendarSe
private final GCalHelper gcal; private final GCalHelper gcal;
private Preferences preferences; private Preferences preferences;
private final TaskEditFragment taskEditFragment; private final TaskEditFragment taskEditFragment;
private PermissionRequestor permissionRequestor;
private Uri calendarUri = null; private Uri calendarUri = null;
@ -54,11 +56,13 @@ public class GCalControlSet extends TaskEditControlSetBase implements CalendarSe
private String calendarId; private String calendarId;
private String calendarName; private String calendarName;
public GCalControlSet(GCalHelper gcal, Preferences preferences, TaskEditFragment taskEditFragment) { public GCalControlSet(GCalHelper gcal, Preferences preferences,
TaskEditFragment taskEditFragment, PermissionRequestor permissionRequestor) {
super(taskEditFragment.getActivity(), R.layout.control_set_gcal_display); super(taskEditFragment.getActivity(), R.layout.control_set_gcal_display);
this.gcal = gcal; this.gcal = gcal;
this.preferences = preferences; this.preferences = preferences;
this.taskEditFragment = taskEditFragment; this.taskEditFragment = taskEditFragment;
this.permissionRequestor = permissionRequestor;
} }
@Override @Override
@ -72,13 +76,12 @@ public class GCalControlSet extends TaskEditControlSetBase implements CalendarSe
if (hasEvent) { if (hasEvent) {
viewCalendarEvent(); viewCalendarEvent();
} else { } else {
FragmentManager fragmentManager = taskEditFragment.getFragmentManager(); // TODO: show calendar selection if permission has just been granted
CalendarSelectionDialog fragmentByTag = (CalendarSelectionDialog) fragmentManager.findFragmentByTag(FRAG_TAG_CALENDAR_SELECTION); // can't do this now because the app saves state when TEA is paused,
if (fragmentByTag == null) { // which triggers calendar creation if there is a default add to calendar.
fragmentByTag = new CalendarSelectionDialog(); if (permissionRequestor.requestCalendarPermissions()) {
fragmentByTag.show(fragmentManager, FRAG_TAG_CALENDAR_SELECTION); showCalendarSelectionDialog();
} }
fragmentByTag.setCalendarSelectionHandler(GCalControlSet.this);
} }
} }
}); });
@ -91,6 +94,16 @@ public class GCalControlSet extends TaskEditControlSetBase implements CalendarSe
}); });
} }
public void showCalendarSelectionDialog() {
FragmentManager fragmentManager = taskEditFragment.getFragmentManager();
CalendarSelectionDialog fragmentByTag = (CalendarSelectionDialog) fragmentManager.findFragmentByTag(FRAG_TAG_CALENDAR_SELECTION);
if (fragmentByTag == null) {
fragmentByTag = new CalendarSelectionDialog();
fragmentByTag.show(fragmentManager, FRAG_TAG_CALENDAR_SELECTION);
}
fragmentByTag.setCalendarSelectionHandler(GCalControlSet.this);
}
@Override @Override
protected void readFromTaskOnInitialize() { protected void readFromTaskOnInitialize() {
String uri = gcal.getTaskEventUri(model); String uri = gcal.getTaskEventUri(model);

@ -6,14 +6,18 @@
package com.todoroo.astrid.reminders; package com.todoroo.astrid.reminders;
import android.content.Intent; import android.content.Intent;
import android.content.pm.PackageManager;
import android.media.Ringtone; import android.media.Ringtone;
import android.media.RingtoneManager; import android.media.RingtoneManager;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.preference.CheckBoxPreference;
import android.preference.Preference; import android.preference.Preference;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import android.preference.PreferenceScreen; import android.preference.PreferenceScreen;
import org.tasks.preferences.PermissionChecker;
import org.tasks.preferences.PermissionRequestor;
import org.tasks.time.DateTime; import org.tasks.time.DateTime;
import org.tasks.R; import org.tasks.R;
import org.tasks.activities.TimePickerActivity; import org.tasks.activities.TimePickerActivity;
@ -37,6 +41,10 @@ public class ReminderPreferences extends InjectingPreferenceActivity {
private Bundle result; private Bundle result;
@Inject DeviceInfo deviceInfo; @Inject DeviceInfo deviceInfo;
@Inject PermissionRequestor permissionRequestor;
@Inject PermissionChecker permissionChecker;
private CheckBoxPreference fieldMissedCalls;
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
@ -60,12 +68,35 @@ public class ReminderPreferences extends InjectingPreferenceActivity {
preferenceScreen.removePreference(findPreference(getString(R.string.geolocation_reminders))); preferenceScreen.removePreference(findPreference(getString(R.string.geolocation_reminders)));
} }
fieldMissedCalls = (CheckBoxPreference) findPreference(getString(R.string.p_field_missed_calls));
fieldMissedCalls.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
return newValue != null && (!(boolean) newValue || permissionRequestor.requestMissedCallPermissions());
}
});
fieldMissedCalls.setChecked(fieldMissedCalls.isChecked() && permissionChecker.canAccessMissedCallPermissions());
initializeRingtonePreference(); initializeRingtonePreference();
initializeTimePreference(getDefaultRemindTimePreference(), REQUEST_DEFAULT_REMIND); initializeTimePreference(getDefaultRemindTimePreference(), REQUEST_DEFAULT_REMIND);
initializeTimePreference(getQuietStartPreference(), REQUEST_QUIET_START); initializeTimePreference(getQuietStartPreference(), REQUEST_QUIET_START);
initializeTimePreference(getQuietEndPreference(), REQUEST_QUIET_END); initializeTimePreference(getQuietEndPreference(), REQUEST_QUIET_END);
} }
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
if (requestCode == PermissionRequestor.REQUEST_CONTACTS) {
for (int grantResult : grantResults) {
if (grantResult != PackageManager.PERMISSION_GRANTED) {
return;
}
}
fieldMissedCalls.setChecked(true);
} else {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
}
@Override @Override
protected void onSaveInstanceState(Bundle outState) { protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState); super.onSaveInstanceState(outState);

@ -8,8 +8,6 @@ package com.todoroo.astrid.service;
import android.app.Activity; import android.app.Activity;
import android.content.ContentValues; import android.content.ContentValues;
import android.content.Context; import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.database.sqlite.SQLiteException; import android.database.sqlite.SQLiteException;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
@ -24,17 +22,16 @@ import com.todoroo.astrid.dao.MetadataDao.MetadataCriteria;
import com.todoroo.astrid.dao.TagDataDao; import com.todoroo.astrid.dao.TagDataDao;
import com.todoroo.astrid.data.Metadata; import com.todoroo.astrid.data.Metadata;
import com.todoroo.astrid.data.TagData; import com.todoroo.astrid.data.TagData;
import com.todoroo.astrid.gcal.CalendarAlarmScheduler;
import com.todoroo.astrid.gtasks.GtasksPreferenceService; import com.todoroo.astrid.gtasks.GtasksPreferenceService;
import com.todoroo.astrid.gtasks.sync.GtasksSyncService; import com.todoroo.astrid.gtasks.sync.GtasksSyncService;
import com.todoroo.astrid.provider.Astrid2TaskProvider; import com.todoroo.astrid.provider.Astrid2TaskProvider;
import com.todoroo.astrid.provider.Astrid3ContentProvider; import com.todoroo.astrid.provider.Astrid3ContentProvider;
import com.todoroo.astrid.tags.TaskToTagMetadata; import com.todoroo.astrid.tags.TaskToTagMetadata;
import com.todoroo.astrid.utility.Constants;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.tasks.Broadcaster; import org.tasks.Broadcaster;
import org.tasks.BuildConfig;
import org.tasks.R; import org.tasks.R;
import org.tasks.dialogs.DialogBuilder; import org.tasks.dialogs.DialogBuilder;
import org.tasks.preferences.Preferences; import org.tasks.preferences.Preferences;
@ -65,7 +62,6 @@ public class StartupService {
private final MetadataDao metadataDao; private final MetadataDao metadataDao;
private final Preferences preferences; private final Preferences preferences;
private final TasksXmlImporter xmlImporter; private final TasksXmlImporter xmlImporter;
private final CalendarAlarmScheduler calendarAlarmScheduler;
private final TaskDeleter taskDeleter; private final TaskDeleter taskDeleter;
private Broadcaster broadcaster; private Broadcaster broadcaster;
private DialogBuilder dialogBuilder; private DialogBuilder dialogBuilder;
@ -75,8 +71,7 @@ public class StartupService {
GtasksPreferenceService gtasksPreferenceService, GtasksPreferenceService gtasksPreferenceService,
GtasksSyncService gtasksSyncService, MetadataDao metadataDao, GtasksSyncService gtasksSyncService, MetadataDao metadataDao,
Preferences preferences, TasksXmlImporter xmlImporter, Preferences preferences, TasksXmlImporter xmlImporter,
CalendarAlarmScheduler calendarAlarmScheduler, TaskDeleter taskDeleter, TaskDeleter taskDeleter, Broadcaster broadcaster, DialogBuilder dialogBuilder) {
Broadcaster broadcaster, DialogBuilder dialogBuilder) {
this.upgradeService = upgradeService; this.upgradeService = upgradeService;
this.tagDataDao = tagDataDao; this.tagDataDao = tagDataDao;
this.database = database; this.database = database;
@ -85,7 +80,6 @@ public class StartupService {
this.metadataDao = metadataDao; this.metadataDao = metadataDao;
this.preferences = preferences; this.preferences = preferences;
this.xmlImporter = xmlImporter; this.xmlImporter = xmlImporter;
this.calendarAlarmScheduler = calendarAlarmScheduler;
this.taskDeleter = taskDeleter; this.taskDeleter = taskDeleter;
this.broadcaster = broadcaster; this.broadcaster = broadcaster;
this.dialogBuilder = dialogBuilder; this.dialogBuilder = dialogBuilder;
@ -128,19 +122,10 @@ public class StartupService {
log.error(e.getMessage(), e); log.error(e.getMessage(), e);
} }
int version = 0; int version = BuildConfig.VERSION_CODE;
String versionName = "0"; //$NON-NLS-1$ String versionName = BuildConfig.VERSION_NAME;
try {
PackageManager pm = activity.getPackageManager();
PackageInfo pi = pm.getPackageInfo(Constants.PACKAGE, PackageManager.GET_META_DATA);
version = pi.versionCode;
versionName = pi.versionName;
} catch (Exception e) {
log.error(e.getMessage(), e);
}
log.info("astrid", "Astrid Startup. " + latestSetVersion + //$NON-NLS-1$ //$NON-NLS-2$ log.info("Astrid Startup. {} => {}", latestSetVersion, version);
" => " + version); //$NON-NLS-1$
databaseRestoreIfEmpty(activity); databaseRestoreIfEmpty(activity);
@ -177,8 +162,6 @@ public class StartupService {
broadcaster.firstLaunch(); broadcaster.firstLaunch();
} }
calendarAlarmScheduler.scheduleCalendarAlarms(activity, false); // This needs to be after set preference defaults for the purposes of ab testing
hasStartedUp = true; hasStartedUp = true;
} }

@ -9,14 +9,20 @@ import android.app.Activity;
import android.app.ProgressDialog; import android.app.ProgressDialog;
import android.content.Intent; import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import android.os.Environment;
import com.todoroo.andlib.utility.DialogUtilities; import com.todoroo.andlib.utility.DialogUtilities;
import com.todoroo.astrid.activity.AstridActivity; import com.todoroo.astrid.activity.AstridActivity;
import com.todoroo.astrid.api.AstridApiConstants; import com.todoroo.astrid.api.AstridApiConstants;
import com.todoroo.astrid.data.TaskAttachment;
import org.tasks.BuildConfig;
import org.tasks.R; import org.tasks.R;
import org.tasks.dialogs.DialogBuilder; import org.tasks.dialogs.DialogBuilder;
import org.tasks.injection.InjectingAppCompatActivity; import org.tasks.injection.InjectingAppCompatActivity;
import org.tasks.preferences.Preferences;
import java.io.File;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Singleton; import javax.inject.Singleton;
@ -24,7 +30,8 @@ import javax.inject.Singleton;
@Singleton @Singleton
public final class UpgradeService { public final class UpgradeService {
public static final int V4_6_5 = 306; public static final int CURRENT = BuildConfig.VERSION_CODE;
public static final int V4_8_0 = 376;
public static final int V3_0_0 = 136; public static final int V3_0_0 = 136;
@Inject @Inject
@ -37,9 +44,7 @@ public final class UpgradeService {
* show users a change log. * show users a change log.
*/ */
public void performUpgrade(final Activity context, final int from) { public void performUpgrade(final Activity context, final int from) {
int maxWithUpgrade = V4_6_5; if(from < CURRENT) {
if(from < maxWithUpgrade) {
Intent upgrade = new Intent(context, UpgradeActivity.class); Intent upgrade = new Intent(context, UpgradeActivity.class);
upgrade.putExtra(UpgradeActivity.TOKEN_FROM_VERSION, from); upgrade.putExtra(UpgradeActivity.TOKEN_FROM_VERSION, from);
context.startActivityForResult(upgrade, 0); context.startActivityForResult(upgrade, 0);
@ -54,6 +59,7 @@ public final class UpgradeService {
private boolean finished = false; private boolean finished = false;
@Inject DialogBuilder dialogBuilder; @Inject DialogBuilder dialogBuilder;
@Inject Preferences preferences;
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
@ -66,7 +72,9 @@ public final class UpgradeService {
public void run() { public void run() {
//noinspection EmptyTryBlock //noinspection EmptyTryBlock
try { try {
if (from < V4_8_0) {
performMarshmallowMigration();
}
} finally { } finally {
finished = true; finished = true;
DialogUtilities.dismissDialog(UpgradeActivity.this, dialog); DialogUtilities.dismissDialog(UpgradeActivity.this, dialog);
@ -89,5 +97,30 @@ public final class UpgradeService {
super.onBackPressed(); super.onBackPressed();
} }
} }
private void performMarshmallowMigration() {
// preserve pre-marshmallow default attachment and backup locations
if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
if (!preferences.isStringValueSet(R.string.p_backup_dir)) {
String directory = String.format("%s/astrid",
Environment.getExternalStorageDirectory());
File file = new File(directory);
if (file.exists() && file.isDirectory()) {
preferences.setString(R.string.p_backup_dir, directory);
}
}
if (!preferences.isStringValueSet(R.string.p_attachment_dir)) {
String directory = String.format("%s/Android/data/%s/files/%s",
Environment.getExternalStorageDirectory(),
BuildConfig.APPLICATION_ID,
TaskAttachment.FILES_DIRECTORY_DEFAULT);
File file = new File(directory);
if (file.exists() && file.isDirectory()) {
preferences.setString(R.string.p_attachment_dir, directory);
}
}
}
}
} }
} }

@ -33,6 +33,7 @@ import org.tasks.activities.DateAndTimePickerActivity;
import org.tasks.activities.LocationPickerActivity; import org.tasks.activities.LocationPickerActivity;
import org.tasks.location.Geofence; import org.tasks.location.Geofence;
import org.tasks.location.GeofenceService; import org.tasks.location.GeofenceService;
import org.tasks.preferences.PermissionRequestor;
import org.tasks.preferences.Preferences; import org.tasks.preferences.Preferences;
import org.tasks.time.DateTime; import org.tasks.time.DateTime;
@ -68,17 +69,20 @@ public class ReminderControlSet extends TaskEditControlSetBase implements Adapte
private GeofenceService geofenceService; private GeofenceService geofenceService;
private TaskEditFragment taskEditFragment; private TaskEditFragment taskEditFragment;
private Preferences preferences; private Preferences preferences;
private PermissionRequestor permissionRequestor;
private List<String> spinnerOptions = new ArrayList<>(); private List<String> spinnerOptions = new ArrayList<>();
private ArrayAdapter<String> remindAdapter; private ArrayAdapter<String> remindAdapter;
public ReminderControlSet(AlarmService alarmService, GeofenceService geofenceService, public ReminderControlSet(AlarmService alarmService, GeofenceService geofenceService,
TaskEditFragment taskEditFragment, Preferences preferences) { TaskEditFragment taskEditFragment, Preferences preferences,
PermissionRequestor permissionRequestor) {
super(taskEditFragment.getActivity(), R.layout.control_set_reminders); super(taskEditFragment.getActivity(), R.layout.control_set_reminders);
this.alarmService = alarmService; this.alarmService = alarmService;
this.geofenceService = geofenceService; this.geofenceService = geofenceService;
this.taskEditFragment = taskEditFragment; this.taskEditFragment = taskEditFragment;
this.preferences = preferences; this.preferences = preferences;
this.permissionRequestor = permissionRequestor;
} }
public int getValue() { public int getValue() {
@ -240,7 +244,7 @@ public class ReminderControlSet extends TaskEditControlSetBase implements Adapte
@Override @Override
public void onItemSelected(AdapterView<?> parent, View view, public void onItemSelected(AdapterView<?> parent, View view,
int position, long id) { int position, long id) {
modeDisplay.setText(modeAdapter.getItem(position)); modeDisplay.setText(modeAdapter.getItem(position));
} }
@ -379,13 +383,19 @@ public class ReminderControlSet extends TaskEditControlSetBase implements Adapte
} else if (selected.equals(taskEditFragment.getString(R.string.pick_a_date_and_time))) { } else if (selected.equals(taskEditFragment.getString(R.string.pick_a_date_and_time))) {
addNewAlarm(); addNewAlarm();
} else if (selected.equals(taskEditFragment.getString(R.string.pick_a_location))) { } else if (selected.equals(taskEditFragment.getString(R.string.pick_a_location))) {
taskEditFragment.startActivityForResult(new Intent(taskEditFragment.getActivity(), LocationPickerActivity.class), REQUEST_LOCATION_REMINDER); if (permissionRequestor.requestFineLocation()) {
pickLocation();
}
} }
if (position != 0) { if (position != 0) {
updateSpinner(); updateSpinner();
} }
} }
public void pickLocation() {
taskEditFragment.startActivityForResult(new Intent(taskEditFragment.getActivity(), LocationPickerActivity.class), REQUEST_LOCATION_REMINDER);
}
@Override @Override
public void onNothingSelected(AdapterView<?> parent) { public void onNothingSelected(AdapterView<?> parent) {
} }

@ -1,5 +1,6 @@
package org.tasks.activities; package org.tasks.activities;
import android.content.ClipData;
import android.content.ContentResolver; import android.content.ContentResolver;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
@ -7,12 +8,10 @@ import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.provider.MediaStore; import android.provider.MediaStore;
import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentManager;
import android.text.TextUtils;
import android.webkit.MimeTypeMap; import android.webkit.MimeTypeMap;
import android.widget.Toast; import android.widget.Toast;
import com.todoroo.andlib.utility.AndroidUtilities; import com.todoroo.andlib.utility.AndroidUtilities;
import com.todoroo.astrid.data.TaskAttachment;
import com.todoroo.astrid.files.FileExplore; import com.todoroo.astrid.files.FileExplore;
import org.slf4j.Logger; import org.slf4j.Logger;
@ -30,6 +29,8 @@ import java.util.concurrent.atomic.AtomicReference;
import javax.inject.Inject; import javax.inject.Inject;
import static com.todoroo.andlib.utility.AndroidUtilities.atLeastLollipop;
public class AddAttachmentActivity extends InjectingAppCompatActivity implements DialogInterface.OnCancelListener, AddAttachmentDialog.AddAttachmentCallback { public class AddAttachmentActivity extends InjectingAppCompatActivity implements DialogInterface.OnCancelListener, AddAttachmentDialog.AddAttachmentCallback {
private static final Logger log = LoggerFactory.getLogger(AddAttachmentActivity.class); private static final Logger log = LoggerFactory.getLogger(AddAttachmentActivity.class);
@ -69,11 +70,15 @@ public class AddAttachmentActivity extends InjectingAppCompatActivity implements
if (lastTempFile == null) { if (lastTempFile == null) {
Toast.makeText(this, R.string.external_storage_unavailable, Toast.LENGTH_LONG).show(); Toast.makeText(this, R.string.external_storage_unavailable, Toast.LENGTH_LONG).show();
} else { } else {
startActivityForResult(new Intent(MediaStore.ACTION_IMAGE_CAPTURE) {{ Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION); Uri uri = Uri.fromFile(lastTempFile);
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); intent.setFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(lastTempFile)); intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
}}, REQUEST_CAMERA); intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
if (atLeastLollipop()) {
intent.setClipData(ClipData.newRawUri(null, uri));
}
startActivityForResult(intent, REQUEST_CAMERA);
} }
} }

@ -3,7 +3,6 @@ package org.tasks.injection;
import com.todoroo.astrid.alarms.AlarmTaskRepeatListener; import com.todoroo.astrid.alarms.AlarmTaskRepeatListener;
import com.todoroo.astrid.calls.PhoneStateChangedReceiver; import com.todoroo.astrid.calls.PhoneStateChangedReceiver;
import com.todoroo.astrid.gcal.CalendarAlarmReceiver; import com.todoroo.astrid.gcal.CalendarAlarmReceiver;
import com.todoroo.astrid.gcal.CalendarStartupReceiver;
import com.todoroo.astrid.gcal.GCalTaskCompleteListener; import com.todoroo.astrid.gcal.GCalTaskCompleteListener;
import com.todoroo.astrid.repeats.RepeatTaskCompleteListener; import com.todoroo.astrid.repeats.RepeatTaskCompleteListener;
import com.todoroo.astrid.timers.TimerTaskCompleteListener; import com.todoroo.astrid.timers.TimerTaskCompleteListener;
@ -32,7 +31,6 @@ import dagger.Module;
AlarmTaskRepeatListener.class, AlarmTaskRepeatListener.class,
PhoneStateChangedReceiver.class, PhoneStateChangedReceiver.class,
CalendarAlarmReceiver.class, CalendarAlarmReceiver.class,
CalendarStartupReceiver.class,
BootCompletedReceiver.class, BootCompletedReceiver.class,
FirstLaunchReceiver.class, FirstLaunchReceiver.class,
MyPackageReplacedReceiver.class, MyPackageReplacedReceiver.class,

@ -13,7 +13,8 @@ import dagger.Module;
MidnightRefreshService.class, MidnightRefreshService.class,
RefreshSchedulerIntentService.class, RefreshSchedulerIntentService.class,
ReminderSchedulerIntentService.class, ReminderSchedulerIntentService.class,
GeofenceTransitionsIntentService.class GeofenceTransitionsIntentService.class,
CalendarNotificationIntentService.class
}) })
public class IntentServiceModule { public class IntentServiceModule {
} }

@ -24,8 +24,8 @@ public class ActivityPreferences extends Preferences {
private final Activity activity; private final Activity activity;
@Inject @Inject
public ActivityPreferences(Activity activity, DeviceInfo deviceInfo) { public ActivityPreferences(Activity activity, DeviceInfo deviceInfo, PermissionChecker permissionChecker) {
super(activity, deviceInfo); super(activity, deviceInfo, permissionChecker);
this.activity = activity; this.activity = activity;
} }

@ -1,8 +1,11 @@
package org.tasks.preferences; package org.tasks.preferences;
import android.content.Intent; import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.preference.CheckBoxPreference;
import android.preference.Preference;
import org.tasks.R; import org.tasks.R;
import org.tasks.injection.InjectingPreferenceActivity; import org.tasks.injection.InjectingPreferenceActivity;
@ -12,6 +15,11 @@ import javax.inject.Inject;
public class HelpAndFeedbackActivity extends InjectingPreferenceActivity { public class HelpAndFeedbackActivity extends InjectingPreferenceActivity {
@Inject DeviceInfo deviceInfo; @Inject DeviceInfo deviceInfo;
@Inject Preferences preferences;
@Inject PermissionChecker permissionChecker;
@Inject PermissionRequestor permissionRequestor;
private CheckBoxPreference debugLogging;
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
@ -27,6 +35,38 @@ public class HelpAndFeedbackActivity extends InjectingPreferenceActivity {
if (!deviceInfo.isPlayStoreAvailable()) { if (!deviceInfo.isPlayStoreAvailable()) {
remove(R.string.rate_tasks); remove(R.string.rate_tasks);
} }
debugLogging = (CheckBoxPreference) findPreference(getString(R.string.p_debug_logging));
debugLogging.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
if (newValue != null && (boolean) newValue) {
if (permissionRequestor.requestFileWritePermission()) {
enableDebugLogging(true);
}
} else {
enableDebugLogging(false);
}
return true;
}
});
enableDebugLogging(
preferences.getBoolean(R.string.p_debug_logging, false) &&
permissionChecker.canWriteToExternalStorage());
}
private void enableDebugLogging(boolean enabled) {
debugLogging.setChecked(enabled);
preferences.setupLogger(enabled);
}
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
if (requestCode == PermissionRequestor.REQUEST_FILE_WRITE) {
enableDebugLogging(grantResults[0] == PackageManager.PERMISSION_GRANTED);
} else {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
} }
private void remove(int resId) { private void remove(int resId) {

@ -1,18 +1,20 @@
package org.tasks.preferences; package org.tasks.preferences;
import android.content.Intent; import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Bundle; import android.os.Bundle;
import android.preference.CheckBoxPreference;
import android.preference.Preference; import android.preference.Preference;
import android.speech.tts.TextToSpeech; import android.speech.tts.TextToSpeech;
import com.todoroo.astrid.files.FileExplore; import com.todoroo.astrid.files.FileExplore;
import com.todoroo.astrid.gcal.CalendarAlarmScheduler;
import com.todoroo.astrid.voice.VoiceOutputAssistant; import com.todoroo.astrid.voice.VoiceOutputAssistant;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.tasks.R; import org.tasks.R;
import org.tasks.injection.InjectingPreferenceActivity; import org.tasks.injection.InjectingPreferenceActivity;
import org.tasks.scheduling.BackgroundScheduler;
import java.io.File; import java.io.File;
@ -25,8 +27,12 @@ public class MiscellaneousPreferences extends InjectingPreferenceActivity {
private static final int REQUEST_CODE_TTS_CHECK = 2534; private static final int REQUEST_CODE_TTS_CHECK = 2534;
@Inject Preferences preferences; @Inject Preferences preferences;
@Inject CalendarAlarmScheduler calendarAlarmScheduler;
@Inject VoiceOutputAssistant voiceOutputAssistant; @Inject VoiceOutputAssistant voiceOutputAssistant;
@Inject PermissionRequestor permissionRequestor;
@Inject PermissionChecker permissionChecker;
@Inject BackgroundScheduler backgroundScheduler;
private CheckBoxPreference calendarReminderPreference;
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
@ -34,13 +40,7 @@ public class MiscellaneousPreferences extends InjectingPreferenceActivity {
addPreferencesFromResource(R.xml.preferences_misc); addPreferencesFromResource(R.xml.preferences_misc);
findPreference(getString(R.string.p_debug_logging)).setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { calendarReminderPreference = (CheckBoxPreference) findPreference(getString(R.string.p_calendar_reminders));
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
preferences.setupLogger((boolean) newValue);
return true;
}
});
initializeAttachmentDirectoryPreference(); initializeAttachmentDirectoryPreference();
initializeCalendarReminderPreference(); initializeCalendarReminderPreference();
@ -103,16 +103,24 @@ public class MiscellaneousPreferences extends InjectingPreferenceActivity {
} }
private void initializeCalendarReminderPreference() { private void initializeCalendarReminderPreference() {
Preference calendarReminderPreference = findPreference(getString(R.string.p_calendar_reminders)); CheckBoxPreference calendarReminderPreference = (CheckBoxPreference) findPreference(getString(R.string.p_calendar_reminders));
calendarReminderPreference.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { calendarReminderPreference.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override @Override
public boolean onPreferenceChange(Preference preference, Object newValue) { public boolean onPreferenceChange(Preference preference, Object newValue) {
if (newValue != null && ((Boolean) newValue)) { if (newValue == null) {
calendarAlarmScheduler.scheduleCalendarAlarms(MiscellaneousPreferences.this, true); return false;
} }
return true; if (!(Boolean) newValue) {
return true;
}
if (permissionRequestor.requestCalendarPermissions()) {
backgroundScheduler.scheduleCalendarNotifications();
return true;
}
return false;
} }
}); });
calendarReminderPreference.setChecked(calendarReminderPreference.isChecked() && permissionChecker.canAccessCalendars());
} }
private void initializeVoiceReminderPreference() { private void initializeVoiceReminderPreference() {
@ -137,4 +145,15 @@ public class MiscellaneousPreferences extends InjectingPreferenceActivity {
} }
}); });
} }
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
if (requestCode == PermissionRequestor.REQUEST_CALENDAR) {
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
calendarReminderPreference.setChecked(true);
}
} else {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
}
} }

@ -0,0 +1,67 @@
package org.tasks.preferences;
import android.Manifest;
import android.content.Context;
import android.content.pm.PackageManager;
import android.support.v4.app.ActivityCompat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.tasks.injection.ForApplication;
import java.util.List;
import javax.inject.Inject;
import static java.util.Arrays.asList;
public class PermissionChecker {
private static final Logger log = LoggerFactory.getLogger(PermissionChecker.class);
private final Context context;
@Inject
public PermissionChecker(@ForApplication Context context) {
this.context = context;
}
public boolean canAccessCalendars() {
return checkPermissions(asList(Manifest.permission.READ_CALENDAR, Manifest.permission.WRITE_CALENDAR));
}
public boolean canWriteToExternalStorage() {
return checkPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE);
}
public boolean canAccessAccounts() {
return checkPermission(Manifest.permission.GET_ACCOUNTS);
}
public boolean canAccessLocation() {
return checkPermission(Manifest.permission.ACCESS_FINE_LOCATION);
}
public boolean canAccessMic() {
return checkPermission(Manifest.permission.RECORD_AUDIO);
}
public boolean canAccessMissedCallPermissions() {
return checkPermission(Manifest.permission.READ_CONTACTS) &&
checkPermission(Manifest.permission.READ_PHONE_STATE);
}
private boolean checkPermission(String permission) {
return checkPermissions(asList(permission));
}
private boolean checkPermissions(List<String> permissions) {
for (String permission : permissions) {
if (ActivityCompat.checkSelfPermission(context, permission) != PackageManager.PERMISSION_GRANTED) {
log.warn("Request for {} denied", permission);
return false;
}
}
return true;
}
}

@ -0,0 +1,84 @@
package org.tasks.preferences;
import android.Manifest;
import android.app.Activity;
import android.support.v4.app.ActivityCompat;
import javax.inject.Inject;
public class PermissionRequestor {
public static final int REQUEST_FILE_WRITE = 50;
public static final int REQUEST_CALENDAR = 51;
public static final int REQUEST_MIC = 52;
public static final int REQUEST_ACCOUNTS = 53;
public static final int REQUEST_LOCATION = 54;
public static final int REQUEST_CONTACTS = 55;
private final Activity activity;
private final PermissionChecker permissionChecker;
@Inject
public PermissionRequestor(Activity activity, PermissionChecker permissionChecker) {
this.activity = activity;
this.permissionChecker = permissionChecker;
}
public boolean requestMic() {
if (permissionChecker.canAccessMic()) {
return true;
}
requestPermission(Manifest.permission.RECORD_AUDIO, REQUEST_MIC);
return false;
}
public boolean requestFileWritePermission() {
if (permissionChecker.canWriteToExternalStorage()) {
return true;
}
requestPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, REQUEST_FILE_WRITE);
return false;
}
public boolean requestCalendarPermissions() {
if (permissionChecker.canAccessCalendars()) {
return true;
}
requestPermissions(
new String[]{Manifest.permission.READ_CALENDAR, Manifest.permission.WRITE_CALENDAR},
REQUEST_CALENDAR);
return false;
}
public boolean requestAccountPermissions() {
if (permissionChecker.canAccessAccounts()) {
return true;
}
requestPermission(Manifest.permission.GET_ACCOUNTS, REQUEST_ACCOUNTS);
return false;
}
public boolean requestFineLocation() {
if (permissionChecker.canAccessLocation()) {
return true;
}
requestPermission(Manifest.permission.ACCESS_FINE_LOCATION, REQUEST_LOCATION);
return false;
}
public boolean requestMissedCallPermissions() {
if (permissionChecker.canAccessMissedCallPermissions()) {
return true;
}
requestPermissions(new String[]{Manifest.permission.READ_CONTACTS, Manifest.permission.READ_PHONE_STATE}, REQUEST_CONTACTS);
return false;
}
private void requestPermission(String permission, int rc) {
requestPermissions(new String[] {permission}, rc);
}
private void requestPermissions(String[] permissions, int rc) {
ActivityCompat.requestPermissions(activity, permissions, rc);
}
}

@ -15,11 +15,11 @@ import com.todoroo.astrid.data.Task;
import com.todoroo.astrid.data.TaskAttachment; import com.todoroo.astrid.data.TaskAttachment;
import com.todoroo.astrid.widget.WidgetConfigActivity; import com.todoroo.astrid.widget.WidgetConfigActivity;
import org.tasks.time.DateTime;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.tasks.R; import org.tasks.R;
import org.tasks.injection.ForApplication; import org.tasks.injection.ForApplication;
import org.tasks.time.DateTime;
import java.io.File; import java.io.File;
import java.util.ArrayList; import java.util.ArrayList;
@ -51,14 +51,16 @@ public class Preferences {
private static final String FILE_APPENDER_NAME = "FILE"; private static final String FILE_APPENDER_NAME = "FILE";
protected final Context context; protected final Context context;
private DeviceInfo deviceInfo; private final DeviceInfo deviceInfo;
private final PermissionChecker permissionChecker;
private final SharedPreferences prefs; private final SharedPreferences prefs;
private final SharedPreferences publicPrefs; private final SharedPreferences publicPrefs;
@Inject @Inject
public Preferences(@ForApplication Context context, DeviceInfo deviceInfo) { public Preferences(@ForApplication Context context, DeviceInfo deviceInfo, PermissionChecker permissionChecker) {
this.context = context; this.context = context;
this.deviceInfo = deviceInfo; this.deviceInfo = deviceInfo;
this.permissionChecker = permissionChecker;
prefs = PreferenceManager.getDefaultSharedPreferences(context); prefs = PreferenceManager.getDefaultSharedPreferences(context);
publicPrefs = context.getSharedPreferences(AstridApiConstants.PUBLIC_PREFS, Context.MODE_WORLD_READABLE); publicPrefs = context.getSharedPreferences(AstridApiConstants.PUBLIC_PREFS, Context.MODE_WORLD_READABLE);
} }
@ -140,6 +142,10 @@ public class Preferences {
return prefs.getString(context.getResources().getString(keyResource), null); return prefs.getString(context.getResources().getString(keyResource), null);
} }
public boolean isStringValueSet(int keyResource) {
return !TextUtils.isEmpty(getStringValue(keyResource));
}
public int getDefaultReminders() { public int getDefaultReminders() {
return getIntegerFromString(R.string.p_default_reminders_key, Task.NOTIFY_AT_DEADLINE | Task.NOTIFY_AFTER_DEADLINE); return getIntegerFromString(R.string.p_default_reminders_key, Task.NOTIFY_AT_DEADLINE | Task.NOTIFY_AFTER_DEADLINE);
} }
@ -193,7 +199,9 @@ public class Preferences {
} }
public boolean fieldMissedPhoneCalls() { public boolean fieldMissedPhoneCalls() {
return getBoolean(R.string.p_field_missed_calls, true) && notificationsEnabled(); return getBoolean(R.string.p_field_missed_calls, true) &&
notificationsEnabled() &&
permissionChecker.canAccessMissedCallPermissions();
} }
public boolean getBoolean(int keyResources, boolean defValue) { public boolean getBoolean(int keyResources, boolean defValue) {
@ -278,7 +286,9 @@ public class Preferences {
} }
public void setupLogger() { public void setupLogger() {
setupLogger(getBoolean(R.string.p_debug_logging, false)); if (permissionChecker.canWriteToExternalStorage()) {
setupLogger(getBoolean(R.string.p_debug_logging, false));
}
} }
public void setupLogger(boolean enableDebugLogging) { public void setupLogger(boolean enableDebugLogging) {
@ -335,27 +345,27 @@ public class Preferences {
public File getAttachmentsDirectory() { public File getAttachmentsDirectory() {
File directory = null; File directory = null;
String customDir = getStringValue(R.string.p_attachment_dir); String customDir = getStringValue(R.string.p_attachment_dir);
if (!TextUtils.isEmpty(customDir)) { if (permissionChecker.canWriteToExternalStorage() && !TextUtils.isEmpty(customDir)) {
directory = new File(customDir); directory = new File(customDir);
} }
if (directory == null || !directory.exists()) { if (directory == null || !directory.exists()) {
directory = getExternalFilesDir(TaskAttachment.FILES_DIRECTORY_DEFAULT); directory = getDefaultFileLocation(TaskAttachment.FILES_DIRECTORY_DEFAULT);
} }
return directory; return directory;
} }
private File getExternalFilesDir(String type) { private File getDefaultFileLocation(String type) {
if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { File externalFilesDir = context.getExternalFilesDir(null);
String directory = String.format("%s/Android/data/%s/files/%s", Environment.getExternalStorageDirectory(), context.getPackageName(), type); if (externalFilesDir == null) {
File file = new File(directory); return null;
if (file.isDirectory() || file.mkdirs()) {
return file;
}
} }
String path = String.format("%s/%s",
return null; externalFilesDir.getAbsolutePath(),
type);
File file = new File(path);
return file.isDirectory() || file.mkdirs() ? file : null;
} }
public String getNewAudioAttachmentPath(AtomicReference<String> nameReference) { public String getNewAudioAttachmentPath(AtomicReference<String> nameReference) {
@ -389,12 +399,12 @@ public class Preferences {
public File getBackupDirectory() { public File getBackupDirectory() {
File directory = null; File directory = null;
String customDir = getStringValue(R.string.p_backup_dir); String customDir = getStringValue(R.string.p_backup_dir);
if (!TextUtils.isEmpty(customDir)) { if (permissionChecker.canWriteToExternalStorage() && !TextUtils.isEmpty(customDir)) {
directory = new File(customDir); directory = new File(customDir);
} }
if (directory == null || !directory.exists()) { if (directory == null || !directory.exists()) {
directory = defaultExportDirectory(); directory = getDefaultFileLocation("backups");
} }
return directory; return directory;

@ -22,7 +22,7 @@ public class BackgroundScheduler {
context.startService(new Intent(context, ReminderSchedulerIntentService.class)); context.startService(new Intent(context, ReminderSchedulerIntentService.class));
scheduleBackupService(); scheduleBackupService();
scheduleMidnightRefresh(); scheduleMidnightRefresh();
scheduleCalendarNotifications();
if (context.getResources().getBoolean(R.bool.sync_enabled)) { if (context.getResources().getBoolean(R.bool.sync_enabled)) {
scheduleGtaskSync(); scheduleGtaskSync();
} }
@ -39,4 +39,8 @@ public class BackgroundScheduler {
public void scheduleGtaskSync() { public void scheduleGtaskSync() {
context.startService(new Intent(context, GtasksBackgroundService.class)); context.startService(new Intent(context, GtasksBackgroundService.class));
} }
public void scheduleCalendarNotifications() {
context.startService(new Intent(context, CalendarNotificationIntentService.class));
}
} }

@ -1,4 +1,4 @@
package com.todoroo.astrid.gcal; package org.tasks.scheduling;
import android.app.PendingIntent; import android.app.PendingIntent;
import android.content.ContentResolver; import android.content.ContentResolver;
@ -8,46 +8,34 @@ import android.database.Cursor;
import android.net.Uri; import android.net.Uri;
import com.todoroo.andlib.utility.DateUtilities; import com.todoroo.andlib.utility.DateUtilities;
import com.todoroo.astrid.gcal.CalendarAlarmReceiver;
import com.todoroo.astrid.gcal.Calendars;
import org.tasks.R; import org.tasks.R;
import org.tasks.injection.ForApplication;
import org.tasks.preferences.PermissionChecker;
import org.tasks.preferences.Preferences; import org.tasks.preferences.Preferences;
import org.tasks.scheduling.AlarmManager;
import java.util.concurrent.TimeUnit;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Singleton;
@Singleton public class CalendarNotificationIntentService extends RecurringIntervalIntentService {
public class CalendarAlarmScheduler {
public static final String URI_PREFIX = "cal-reminder"; public static final String URI_PREFIX = "cal-reminder";
public static final String URI_PREFIX_POSTPONE = "cal-postpone"; public static final String URI_PREFIX_POSTPONE = "cal-postpone";
private final Preferences preferences; @Inject Preferences preferences;
private AlarmManager alarmManager; @Inject PermissionChecker permissionChecker;
@Inject @ForApplication Context context;
@Inject @Inject AlarmManager alarmManager;
public CalendarAlarmScheduler(Preferences preferences, AlarmManager alarmManager) {
this.preferences = preferences;
this.alarmManager = alarmManager;
}
public void scheduleCalendarAlarms(final Context context, boolean force) { public CalendarNotificationIntentService() {
if (!preferences.getBoolean(R.string.p_calendar_reminders, true) && !force) { super(CalendarNotificationIntentService.class.getSimpleName());
return;
}
new Thread(new Runnable() {
@Override
public void run() {
scheduleAllCalendarAlarms(context);
}
}).start();
} }
private void scheduleAllCalendarAlarms(Context context) { @Override
if (!preferences.getBoolean(R.string.p_calendar_reminders, true)) { void run() {
return;
}
ContentResolver cr = context.getContentResolver(); ContentResolver cr = context.getContentResolver();
long now = DateUtilities.now(); long now = DateUtilities.now();
@ -80,17 +68,22 @@ public class CalendarAlarmScheduler {
alarmManager.wakeup(alarmTime, pendingIntent); alarmManager.wakeup(alarmTime, pendingIntent);
} }
} }
// Schedule alarm to recheck and reschedule calendar alarms in 12 hours
Intent rescheduleAlarm = new Intent(CalendarStartupReceiver.BROADCAST_RESCHEDULE_CAL_ALARMS);
PendingIntent pendingReschedule = PendingIntent.getBroadcast(context, 0,
rescheduleAlarm, 0);
alarmManager.cancel(pendingReschedule);
alarmManager.noWakeup(DateUtilities.now() + DateUtilities.ONE_HOUR * 12, pendingReschedule);
} finally { } finally {
if (events != null) { if (events != null) {
events.close(); events.close();
} }
} }
} }
@Override
long intervalMillis() {
return preferences.getBoolean(R.string.p_calendar_reminders, false) && permissionChecker.canAccessCalendars()
? TimeUnit.HOURS.toMillis(12)
: 0;
}
@Override
String getLastRunPreference() {
return null;
}
} }

@ -38,14 +38,17 @@ public abstract class RecurringIntervalIntentService extends InjectingIntentServ
return; return;
} }
long lastRun = preferences.getLong(getLastRunPreference(), 0); String lastRunPreference = getLastRunPreference();
long lastRun = lastRunPreference != null ? preferences.getLong(lastRunPreference, 0) : 0;
long now = currentTimeMillis(); long now = currentTimeMillis();
long nextRun = lastRun + interval; long nextRun = lastRun + interval;
if (nextRun < now + PADDING) { if (lastRunPreference == null || nextRun < now + PADDING) {
nextRun = now + interval; nextRun = now + interval;
log.debug("running now [nextRun={}]", printTimestamp(nextRun)); log.debug("running now [nextRun={}]", printTimestamp(nextRun));
preferences.setLong(getLastRunPreference(), now); if (lastRunPreference != null) {
preferences.setLong(lastRunPreference, now);
}
run(); run();
} else { } else {
log.debug("will run at {} [lastRun={}]", printTimestamp(nextRun), printTimestamp(lastRun)); log.debug("will run at {} [lastRun={}]", printTimestamp(nextRun), printTimestamp(lastRun));

@ -152,9 +152,7 @@ public class DateTime {
} }
public DateTime plusMonths(int interval) { public DateTime plusMonths(int interval) {
Calendar calendar = getCalendar(); return add(Calendar.MONTH, interval);
calendar.add(Calendar.MONTH, interval);
return new DateTime(calendar);
} }
public DateTime plusWeeks(int weeks) { public DateTime plusWeeks(int weeks) {

@ -31,4 +31,8 @@
android:key="@string/contact_developer" android:key="@string/contact_developer"
android:title="@string/contact_developer" /> android:title="@string/contact_developer" />
<CheckBoxPreference
android:key="@string/p_debug_logging"
android:title="@string/debug_logging" />
</PreferenceScreen> </PreferenceScreen>

@ -16,7 +16,7 @@
android:key="@string/p_calendar_reminders" android:key="@string/p_calendar_reminders"
android:summary="@string/CRA_calendar_reminders_pref_desc_enabled" android:summary="@string/CRA_calendar_reminders_pref_desc_enabled"
android:title="@string/CRA_calendar_reminders_pref_title" android:title="@string/CRA_calendar_reminders_pref_title"
android:defaultValue="true"/> android:defaultValue="false"/>
<CheckBoxPreference <CheckBoxPreference
android:key="@string/p_end_at_deadline" android:key="@string/p_end_at_deadline"
@ -25,7 +25,4 @@
android:title="@string/EPr_cal_end_or_start_at_due_time" android:title="@string/EPr_cal_end_or_start_at_due_time"
android:defaultValue="true"/> android:defaultValue="true"/>
<CheckBoxPreference
android:key="@string/p_debug_logging"
android:title="@string/debug_logging" />
</PreferenceScreen> </PreferenceScreen>

@ -41,7 +41,7 @@
android:summaryOn="@string/rmd_EPr_persistent_desc_true" android:summaryOn="@string/rmd_EPr_persistent_desc_true"
android:title="@string/rmd_EPr_persistent_title" /> android:title="@string/rmd_EPr_persistent_title" />
<CheckBoxPreference <CheckBoxPreference
android:defaultValue="true" android:defaultValue="false"
android:dependency="@string/p_rmd_enabled" android:dependency="@string/p_rmd_enabled"
android:key="@string/p_field_missed_calls" android:key="@string/p_field_missed_calls"
android:title="@string/missed_calls" /> android:title="@string/missed_calls" />

Loading…
Cancel
Save