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
env:
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:
components:
- android-22
- android-23
- platform-tools-23.0.1
- build-tools-22.0.1
- build-tools-23.0.2
- extra-android-m2repository
- extra-google-m2repository
licenses:

@ -6,7 +6,6 @@ task wrapper(type: Wrapper) {
buildscript {
repositories {
mavenCentral()
jcenter()
}
@ -16,7 +15,7 @@ buildscript {
}
repositories {
mavenCentral()
jcenter()
}
android {
@ -24,14 +23,14 @@ android {
lintConfig file("lint.xml")
}
compileSdkVersion 22
buildToolsVersion "22.0.1"
compileSdkVersion 23
buildToolsVersion "23.0.2"
defaultConfig {
versionCode 379
versionName "4.7.22"
minSdkVersion 9
targetSdkVersion 22
targetSdkVersion 23
}
signingConfigs {
@ -94,7 +93,7 @@ dependencies {
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'
@ -113,6 +112,7 @@ dependencies {
googleplayCompile(group: 'com.google.api-client', name: 'google-api-client-android', version: '1.21.0') {
exclude group: 'org.apache.httpcomponents', module: 'httpclient'
}
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'

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

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

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

@ -6,6 +6,7 @@
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;
@ -16,6 +17,7 @@ import com.todoroo.astrid.gtasks.auth.GtasksLoginActivity;
import org.tasks.R;
import org.tasks.activities.ClearGtaskDataActivity;
import org.tasks.injection.InjectingPreferenceActivity;
import org.tasks.preferences.PermissionRequestor;
import org.tasks.scheduling.BackgroundScheduler;
import javax.inject.Inject;
@ -27,6 +29,7 @@ public class GtasksPreferences extends InjectingPreferenceActivity {
@Inject GtasksPreferenceService gtasksPreferenceService;
@Inject BackgroundScheduler backgroundScheduler;
@Inject PermissionRequestor permissionRequestor;
@Override
public void onCreate(Bundle savedInstanceState) {
@ -39,12 +42,18 @@ public class GtasksPreferences extends InjectingPreferenceActivity {
gtaskPreference.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
if ((boolean) newValue && !gtasksPreferenceService.isLoggedIn()) {
startActivityForResult(new Intent(GtasksPreferences.this, GtasksLoginActivity.class), REQUEST_LOGIN);
if ((boolean) newValue) {
if (gtasksPreferenceService.isLoggedIn()) {
return true;
}
if (permissionRequestor.requestAccountPermissions()) {
requestLogin();
}
return false;
} else {
gtasksPreferenceService.stopOngoing();
return true;
}
return true;
}
});
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
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
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
protected void onPause() {
super.onPause();

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

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

@ -379,17 +379,6 @@
</intent-filter>
</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
android:name=".activities.CalendarSelectionActivity"
android:theme="@style/TranslucentDialog" />
@ -515,6 +504,9 @@
<service
android:name=".scheduling.ReminderSchedulerIntentService"
android:exported="false" />
<service
android:name=".scheduling.CalendarNotificationIntentService"
android:exported="false" />
<!-- Uses Library -->
<uses-library

@ -81,6 +81,7 @@ import org.tasks.location.Geofence;
import org.tasks.location.GeofenceService;
import org.tasks.notifications.NotificationManager;
import org.tasks.preferences.ActivityPreferences;
import org.tasks.preferences.PermissionRequestor;
import org.tasks.ui.DeadlineControlSet;
import org.tasks.ui.MenuColorizer;
@ -172,6 +173,7 @@ ViewPager.OnPageChangeListener, EditNoteActivity.UpdatesChangedListener {
@Inject ActFmCameraModule actFmCameraModule;
@Inject GeofenceService geofenceService;
@Inject DialogBuilder dialogBuilder;
@Inject PermissionRequestor permissionRequestor;
// --- UI components
@ -354,7 +356,7 @@ ViewPager.OnPageChangeListener, EditNoteActivity.UpdatesChangedListener {
RepeatControlSet repeatControls = new RepeatControlSet(preferences, getActivity(), dialogBuilder);
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);
// 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),
notesControlSet);
reminderControlSet = new ReminderControlSet(alarmService, geofenceService, this, preferences);
reminderControlSet = new ReminderControlSet(alarmService, geofenceService, this, preferences, permissionRequestor);
controls.add(reminderControlSet);
controlSetMap.put(getString(R.string.TEA_ctrl_reminders_pref), reminderControlSet);

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

@ -6,9 +6,8 @@
package com.todoroo.astrid.files;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Chronometer;
import com.todoroo.astrid.voice.AACRecorder;
@ -16,67 +15,66 @@ import com.todoroo.astrid.voice.AACRecorder.AACRecorderCallbacks;
import org.tasks.R;
import org.tasks.injection.InjectingAppCompatActivity;
import org.tasks.preferences.PermissionRequestor;
import org.tasks.preferences.Preferences;
import java.util.concurrent.atomic.AtomicReference;
import javax.inject.Inject;
import butterknife.Bind;
import butterknife.ButterKnife;
import butterknife.OnClick;
public class AACRecordingActivity extends InjectingAppCompatActivity implements AACRecorderCallbacks {
public static final String RESULT_OUTFILE = "outfile"; //$NON-NLS-1$
public static final String RESULT_FILENAME = "filename"; //$NON-NLS-1$
private final AtomicReference<String> nameRef = new AtomicReference<>();
private AACRecorder recorder;
private Chronometer timer;
private AtomicReference<String> nameRef;
private String tempFile;
@Inject Preferences preferences;
@Inject PermissionRequestor permissionRequestor;
@Bind(R.id.timer) Chronometer timer;
@Override
protected void onCreate(Bundle 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);
recorder = new AACRecorder();
recorder.setListener(this);
recorder.startRecording(tempFile);
timer.start();
}
private void setupUi() {
View stopRecording = findViewById(R.id.stop_recording);
stopRecording.setOnClickListener(new OnClickListener() {
@Override
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);
@OnClick(R.id.stop_recording)
void stopRecording() {
if (recorder != null) {
recorder.stopRecording();
timer.stop();
}
}
private void stopRecording() {
recorder.stopRecording();
timer.stop();
@OnClick(R.id.dismiss)
void dismiss() {
if (recorder != null) {
recorder.setListener(null);
recorder.stopRecording();
}
finish();
}
@Override
@ -94,4 +92,17 @@ public class AACRecordingActivity extends InjectingAppCompatActivity implements
setResult(RESULT_OK, result);
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.OnCancelListener;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.Environment;
@ -27,6 +28,7 @@ import org.tasks.R;
import org.tasks.dialogs.DialogBuilder;
import org.tasks.injection.InjectingAppCompatActivity;
import org.tasks.preferences.ActivityPreferences;
import org.tasks.preferences.PermissionRequestor;
import java.io.File;
import java.io.FilenameFilter;
@ -54,6 +56,7 @@ public class FileExplore extends InjectingAppCompatActivity {
@Inject DialogBuilder dialogBuilder;
@Inject ActivityPreferences activityPreferences;
@Inject PermissionRequestor permissionRequestor;
private Item[] fileList;
private File path;
@ -66,13 +69,19 @@ public class FileExplore extends InjectingAppCompatActivity {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (permissionRequestor.requestFileWritePermission()) {
showDialog();
}
}
private void showDialog() {
activityPreferences.applyDialogTheme();
if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
path = new File(Environment.getExternalStorageDirectory().toString());
} else {
path = Environment.getRootDirectory();
}
path = new File(Environment.getExternalStorageDirectory().toString());
} else {
path = Environment.getRootDirectory();
}
loadFileList();
@ -83,6 +92,19 @@ public class FileExplore extends InjectingAppCompatActivity {
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() {
try {
path.mkdirs();

@ -7,6 +7,7 @@ package com.todoroo.astrid.files;
import android.app.Activity;
import android.content.ActivityNotFoundException;
import android.content.ClipData;
import android.content.DialogInterface;
import android.content.Intent;
import android.media.MediaPlayer;
@ -40,6 +41,8 @@ import org.tasks.preferences.ActivityPreferences;
import java.io.File;
import java.util.ArrayList;
import static com.todoroo.andlib.utility.AndroidUtilities.atLeastLollipop;
public class FilesControlSet extends TaskEditControlSetBase {
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)) {
try {
activity.startActivity(new Intent(Intent.ACTION_VIEW) {{
setDataAndType(Uri.fromFile(new File(filePath)), fileType);
}});
Intent intent = new Intent(Intent.ACTION_VIEW);
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) {
log.error(e.getMessage(), e);
Toast.makeText(activity, R.string.no_application_found, Toast.LENGTH_SHORT).show();
@ -232,6 +241,7 @@ public class FilesControlSet extends TaskEditControlSetBase {
try {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(Uri.fromFile(new File(file)), type);
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
activity.startActivity(intent);
} catch (ActivityNotFoundException e) {
log.error(e.getMessage(), e);

@ -16,7 +16,9 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.tasks.R;
import org.tasks.injection.InjectingBroadcastReceiver;
import org.tasks.preferences.PermissionChecker;
import org.tasks.preferences.Preferences;
import org.tasks.scheduling.CalendarNotificationIntentService;
import java.util.ArrayList;
import java.util.HashSet;
@ -43,6 +45,7 @@ public class CalendarAlarmReceiver extends InjectingBroadcastReceiver {
};
@Inject Preferences preferences;
@Inject PermissionChecker permissionChecker;
@Override
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)) {
return;
}
if (!permissionChecker.canAccessCalendars()) {
return;
}
try {
Uri data = intent.getData();
if (data == null) {
@ -65,7 +73,7 @@ public class CalendarAlarmReceiver extends InjectingBroadcastReceiver {
return;
}
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) {
showCalReminder(context, eventId, fromPostpone);
}

@ -26,6 +26,7 @@ import org.tasks.preferences.ActivityPreferences;
import org.tasks.preferences.BasicPreferences;
import org.tasks.preferences.ResourceResolver;
import org.tasks.scheduling.AlarmManager;
import org.tasks.scheduling.CalendarNotificationIntentService;
import javax.inject.Inject;
@ -225,7 +226,7 @@ public class CalendarReminderActivity extends InjectingAppCompatActivity {
private void postpone() {
Intent eventAlarm = new Intent(this, CalendarAlarmReceiver.class);
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,
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.tasks.R;
import org.tasks.activities.CalendarSelectionDialog;
import org.tasks.preferences.PermissionRequestor;
import org.tasks.preferences.Preferences;
import org.tasks.reminders.SnoozeDialog;
@ -45,6 +46,7 @@ public class GCalControlSet extends TaskEditControlSetBase implements CalendarSe
private final GCalHelper gcal;
private Preferences preferences;
private final TaskEditFragment taskEditFragment;
private PermissionRequestor permissionRequestor;
private Uri calendarUri = null;
@ -54,11 +56,13 @@ public class GCalControlSet extends TaskEditControlSetBase implements CalendarSe
private String calendarId;
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);
this.gcal = gcal;
this.preferences = preferences;
this.taskEditFragment = taskEditFragment;
this.permissionRequestor = permissionRequestor;
}
@Override
@ -72,13 +76,12 @@ public class GCalControlSet extends TaskEditControlSetBase implements CalendarSe
if (hasEvent) {
viewCalendarEvent();
} else {
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);
// TODO: show calendar selection if permission has just been granted
// can't do this now because the app saves state when TEA is paused,
// which triggers calendar creation if there is a default add to calendar.
if (permissionRequestor.requestCalendarPermissions()) {
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
protected void readFromTaskOnInitialize() {
String uri = gcal.getTaskEventUri(model);

@ -6,14 +6,18 @@
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;
import android.os.Bundle;
import android.preference.CheckBoxPreference;
import android.preference.Preference;
import android.preference.PreferenceManager;
import android.preference.PreferenceScreen;
import org.tasks.preferences.PermissionChecker;
import org.tasks.preferences.PermissionRequestor;
import org.tasks.time.DateTime;
import org.tasks.R;
import org.tasks.activities.TimePickerActivity;
@ -37,6 +41,10 @@ public class ReminderPreferences extends InjectingPreferenceActivity {
private Bundle result;
@Inject DeviceInfo deviceInfo;
@Inject PermissionRequestor permissionRequestor;
@Inject PermissionChecker permissionChecker;
private CheckBoxPreference fieldMissedCalls;
@Override
public void onCreate(Bundle savedInstanceState) {
@ -60,12 +68,35 @@ public class ReminderPreferences extends InjectingPreferenceActivity {
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();
initializeTimePreference(getDefaultRemindTimePreference(), REQUEST_DEFAULT_REMIND);
initializeTimePreference(getQuietStartPreference(), REQUEST_QUIET_START);
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
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);

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

@ -9,14 +9,20 @@ import android.app.Activity;
import android.app.ProgressDialog;
import android.content.Intent;
import android.os.Bundle;
import android.os.Environment;
import com.todoroo.andlib.utility.DialogUtilities;
import com.todoroo.astrid.activity.AstridActivity;
import com.todoroo.astrid.api.AstridApiConstants;
import com.todoroo.astrid.data.TaskAttachment;
import org.tasks.BuildConfig;
import org.tasks.R;
import org.tasks.dialogs.DialogBuilder;
import org.tasks.injection.InjectingAppCompatActivity;
import org.tasks.preferences.Preferences;
import java.io.File;
import javax.inject.Inject;
import javax.inject.Singleton;
@ -24,7 +30,8 @@ import javax.inject.Singleton;
@Singleton
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;
@Inject
@ -37,9 +44,7 @@ public final class UpgradeService {
* show users a change log.
*/
public void performUpgrade(final Activity context, final int from) {
int maxWithUpgrade = V4_6_5;
if(from < maxWithUpgrade) {
if(from < CURRENT) {
Intent upgrade = new Intent(context, UpgradeActivity.class);
upgrade.putExtra(UpgradeActivity.TOKEN_FROM_VERSION, from);
context.startActivityForResult(upgrade, 0);
@ -54,6 +59,7 @@ public final class UpgradeService {
private boolean finished = false;
@Inject DialogBuilder dialogBuilder;
@Inject Preferences preferences;
@Override
protected void onCreate(Bundle savedInstanceState) {
@ -66,7 +72,9 @@ public final class UpgradeService {
public void run() {
//noinspection EmptyTryBlock
try {
if (from < V4_8_0) {
performMarshmallowMigration();
}
} finally {
finished = true;
DialogUtilities.dismissDialog(UpgradeActivity.this, dialog);
@ -89,5 +97,30 @@ public final class UpgradeService {
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.location.Geofence;
import org.tasks.location.GeofenceService;
import org.tasks.preferences.PermissionRequestor;
import org.tasks.preferences.Preferences;
import org.tasks.time.DateTime;
@ -68,17 +69,20 @@ public class ReminderControlSet extends TaskEditControlSetBase implements Adapte
private GeofenceService geofenceService;
private TaskEditFragment taskEditFragment;
private Preferences preferences;
private PermissionRequestor permissionRequestor;
private List<String> spinnerOptions = new ArrayList<>();
private ArrayAdapter<String> remindAdapter;
public ReminderControlSet(AlarmService alarmService, GeofenceService geofenceService,
TaskEditFragment taskEditFragment, Preferences preferences) {
TaskEditFragment taskEditFragment, Preferences preferences,
PermissionRequestor permissionRequestor) {
super(taskEditFragment.getActivity(), R.layout.control_set_reminders);
this.alarmService = alarmService;
this.geofenceService = geofenceService;
this.taskEditFragment = taskEditFragment;
this.preferences = preferences;
this.permissionRequestor = permissionRequestor;
}
public int getValue() {
@ -240,7 +244,7 @@ public class ReminderControlSet extends TaskEditControlSetBase implements Adapte
@Override
public void onItemSelected(AdapterView<?> parent, View view,
int position, long id) {
int position, long id) {
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))) {
addNewAlarm();
} 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) {
updateSpinner();
}
}
public void pickLocation() {
taskEditFragment.startActivityForResult(new Intent(taskEditFragment.getActivity(), LocationPickerActivity.class), REQUEST_LOCATION_REMINDER);
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
}

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

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

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

@ -1,8 +1,11 @@
package org.tasks.preferences;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Bundle;
import android.preference.CheckBoxPreference;
import android.preference.Preference;
import org.tasks.R;
import org.tasks.injection.InjectingPreferenceActivity;
@ -12,6 +15,11 @@ import javax.inject.Inject;
public class HelpAndFeedbackActivity extends InjectingPreferenceActivity {
@Inject DeviceInfo deviceInfo;
@Inject Preferences preferences;
@Inject PermissionChecker permissionChecker;
@Inject PermissionRequestor permissionRequestor;
private CheckBoxPreference debugLogging;
@Override
public void onCreate(Bundle savedInstanceState) {
@ -27,6 +35,38 @@ public class HelpAndFeedbackActivity extends InjectingPreferenceActivity {
if (!deviceInfo.isPlayStoreAvailable()) {
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) {

@ -1,18 +1,20 @@
package org.tasks.preferences;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.preference.CheckBoxPreference;
import android.preference.Preference;
import android.speech.tts.TextToSpeech;
import com.todoroo.astrid.files.FileExplore;
import com.todoroo.astrid.gcal.CalendarAlarmScheduler;
import com.todoroo.astrid.voice.VoiceOutputAssistant;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.tasks.R;
import org.tasks.injection.InjectingPreferenceActivity;
import org.tasks.scheduling.BackgroundScheduler;
import java.io.File;
@ -25,8 +27,12 @@ public class MiscellaneousPreferences extends InjectingPreferenceActivity {
private static final int REQUEST_CODE_TTS_CHECK = 2534;
@Inject Preferences preferences;
@Inject CalendarAlarmScheduler calendarAlarmScheduler;
@Inject VoiceOutputAssistant voiceOutputAssistant;
@Inject PermissionRequestor permissionRequestor;
@Inject PermissionChecker permissionChecker;
@Inject BackgroundScheduler backgroundScheduler;
private CheckBoxPreference calendarReminderPreference;
@Override
public void onCreate(Bundle savedInstanceState) {
@ -34,13 +40,7 @@ public class MiscellaneousPreferences extends InjectingPreferenceActivity {
addPreferencesFromResource(R.xml.preferences_misc);
findPreference(getString(R.string.p_debug_logging)).setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
preferences.setupLogger((boolean) newValue);
return true;
}
});
calendarReminderPreference = (CheckBoxPreference) findPreference(getString(R.string.p_calendar_reminders));
initializeAttachmentDirectoryPreference();
initializeCalendarReminderPreference();
@ -103,16 +103,24 @@ public class MiscellaneousPreferences extends InjectingPreferenceActivity {
}
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() {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
if (newValue != null && ((Boolean) newValue)) {
calendarAlarmScheduler.scheduleCalendarAlarms(MiscellaneousPreferences.this, true);
if (newValue == null) {
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() {
@ -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.widget.WidgetConfigActivity;
import org.tasks.time.DateTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.tasks.R;
import org.tasks.injection.ForApplication;
import org.tasks.time.DateTime;
import java.io.File;
import java.util.ArrayList;
@ -51,14 +51,16 @@ public class Preferences {
private static final String FILE_APPENDER_NAME = "FILE";
protected final Context context;
private DeviceInfo deviceInfo;
private final DeviceInfo deviceInfo;
private final PermissionChecker permissionChecker;
private final SharedPreferences prefs;
private final SharedPreferences publicPrefs;
@Inject
public Preferences(@ForApplication Context context, DeviceInfo deviceInfo) {
public Preferences(@ForApplication Context context, DeviceInfo deviceInfo, PermissionChecker permissionChecker) {
this.context = context;
this.deviceInfo = deviceInfo;
this.permissionChecker = permissionChecker;
prefs = PreferenceManager.getDefaultSharedPreferences(context);
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);
}
public boolean isStringValueSet(int keyResource) {
return !TextUtils.isEmpty(getStringValue(keyResource));
}
public int getDefaultReminders() {
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() {
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) {
@ -278,7 +286,9 @@ public class Preferences {
}
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) {
@ -335,27 +345,27 @@ public class Preferences {
public File getAttachmentsDirectory() {
File directory = null;
String customDir = getStringValue(R.string.p_attachment_dir);
if (!TextUtils.isEmpty(customDir)) {
if (permissionChecker.canWriteToExternalStorage() && !TextUtils.isEmpty(customDir)) {
directory = new File(customDir);
}
if (directory == null || !directory.exists()) {
directory = getExternalFilesDir(TaskAttachment.FILES_DIRECTORY_DEFAULT);
directory = getDefaultFileLocation(TaskAttachment.FILES_DIRECTORY_DEFAULT);
}
return directory;
}
private File getExternalFilesDir(String type) {
if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
String directory = String.format("%s/Android/data/%s/files/%s", Environment.getExternalStorageDirectory(), context.getPackageName(), type);
File file = new File(directory);
if (file.isDirectory() || file.mkdirs()) {
return file;
}
private File getDefaultFileLocation(String type) {
File externalFilesDir = context.getExternalFilesDir(null);
if (externalFilesDir == null) {
return null;
}
return null;
String path = String.format("%s/%s",
externalFilesDir.getAbsolutePath(),
type);
File file = new File(path);
return file.isDirectory() || file.mkdirs() ? file : null;
}
public String getNewAudioAttachmentPath(AtomicReference<String> nameReference) {
@ -389,12 +399,12 @@ public class Preferences {
public File getBackupDirectory() {
File directory = null;
String customDir = getStringValue(R.string.p_backup_dir);
if (!TextUtils.isEmpty(customDir)) {
if (permissionChecker.canWriteToExternalStorage() && !TextUtils.isEmpty(customDir)) {
directory = new File(customDir);
}
if (directory == null || !directory.exists()) {
directory = defaultExportDirectory();
directory = getDefaultFileLocation("backups");
}
return directory;

@ -22,7 +22,7 @@ public class BackgroundScheduler {
context.startService(new Intent(context, ReminderSchedulerIntentService.class));
scheduleBackupService();
scheduleMidnightRefresh();
scheduleCalendarNotifications();
if (context.getResources().getBoolean(R.bool.sync_enabled)) {
scheduleGtaskSync();
}
@ -39,4 +39,8 @@ public class BackgroundScheduler {
public void scheduleGtaskSync() {
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.content.ContentResolver;
@ -8,46 +8,34 @@ import android.database.Cursor;
import android.net.Uri;
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.injection.ForApplication;
import org.tasks.preferences.PermissionChecker;
import org.tasks.preferences.Preferences;
import org.tasks.scheduling.AlarmManager;
import java.util.concurrent.TimeUnit;
import javax.inject.Inject;
import javax.inject.Singleton;
@Singleton
public class CalendarAlarmScheduler {
public class CalendarNotificationIntentService extends RecurringIntervalIntentService {
public static final String URI_PREFIX = "cal-reminder";
public static final String URI_PREFIX_POSTPONE = "cal-postpone";
private final Preferences preferences;
private AlarmManager alarmManager;
@Inject
public CalendarAlarmScheduler(Preferences preferences, AlarmManager alarmManager) {
this.preferences = preferences;
this.alarmManager = alarmManager;
}
@Inject Preferences preferences;
@Inject PermissionChecker permissionChecker;
@Inject @ForApplication Context context;
@Inject AlarmManager alarmManager;
public void scheduleCalendarAlarms(final Context context, boolean force) {
if (!preferences.getBoolean(R.string.p_calendar_reminders, true) && !force) {
return;
}
new Thread(new Runnable() {
@Override
public void run() {
scheduleAllCalendarAlarms(context);
}
}).start();
public CalendarNotificationIntentService() {
super(CalendarNotificationIntentService.class.getSimpleName());
}
private void scheduleAllCalendarAlarms(Context context) {
if (!preferences.getBoolean(R.string.p_calendar_reminders, true)) {
return;
}
@Override
void run() {
ContentResolver cr = context.getContentResolver();
long now = DateUtilities.now();
@ -80,17 +68,22 @@ public class CalendarAlarmScheduler {
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 {
if (events != null) {
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;
}
long lastRun = preferences.getLong(getLastRunPreference(), 0);
String lastRunPreference = getLastRunPreference();
long lastRun = lastRunPreference != null ? preferences.getLong(lastRunPreference, 0) : 0;
long now = currentTimeMillis();
long nextRun = lastRun + interval;
if (nextRun < now + PADDING) {
if (lastRunPreference == null || nextRun < now + PADDING) {
nextRun = now + interval;
log.debug("running now [nextRun={}]", printTimestamp(nextRun));
preferences.setLong(getLastRunPreference(), now);
if (lastRunPreference != null) {
preferences.setLong(lastRunPreference, now);
}
run();
} else {
log.debug("will run at {} [lastRun={}]", printTimestamp(nextRun), printTimestamp(lastRun));

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

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

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

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

Loading…
Cancel
Save