diff --git a/MeditationAssistant/build.gradle b/MeditationAssistant/build.gradle index 1d5666a..0cf4dfa 100644 --- a/MeditationAssistant/build.gradle +++ b/MeditationAssistant/build.gradle @@ -2,7 +2,7 @@ apply plugin: 'com.android.application' android { compileSdkVersion 29 - buildToolsVersion '30.0.2' + buildToolsVersion '30.0.3' defaultConfig { minSdkVersion 16 @@ -19,9 +19,9 @@ android { multiDexEnabled true } - lintOptions { - checkReleaseBuilds false + lint { abortOnError false + checkReleaseBuilds false } compileOptions { diff --git a/MeditationAssistant/src/main/AndroidManifest.xml b/MeditationAssistant/src/main/AndroidManifest.xml index bbb266e..efeed63 100644 --- a/MeditationAssistant/src/main/AndroidManifest.xml +++ b/MeditationAssistant/src/main/AndroidManifest.xml @@ -3,55 +3,74 @@ xmlns:tools="http://schemas.android.com/tools" package="sh.ftp.rocketninelabs.meditationassistant"> + + + + + + + + + - + android:requestLegacyExternalStorage="true" + android:theme="@style/MeditationDarkTheme"> + - + - - - + - - - + - - - + + + --> @@ -74,17 +94,15 @@ android:value="sh.ftp.rocketninelabs.meditationassistant.MainActivity" /> - - - + @@ -113,9 +131,11 @@ android:theme="@style/FilePickerTheme"> + + + + android:name=".DailyReminderReceiver" + android:exported="true" + android:directBootAware="true" + android:permission="android.permission.RECEIVE_BOOT_COMPLETED"> - - + + + + @@ -144,6 +169,13 @@ android:scheme="package" /> + + @@ -199,33 +231,9 @@ android:name="android.appwidget.provider" android:resource="@xml/widget_streak_3" /> - - - - - - - - - - - + + + - + \ No newline at end of file diff --git a/MeditationAssistant/src/main/java/sh/ftp/rocketninelabs/meditationassistant/DailyReminderReceiver.java b/MeditationAssistant/src/main/java/sh/ftp/rocketninelabs/meditationassistant/DailyReminderReceiver.java index 871b36b..6b7de8a 100644 --- a/MeditationAssistant/src/main/java/sh/ftp/rocketninelabs/meditationassistant/DailyReminderReceiver.java +++ b/MeditationAssistant/src/main/java/sh/ftp/rocketninelabs/meditationassistant/DailyReminderReceiver.java @@ -22,9 +22,6 @@ public class DailyReminderReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - return; - } try { ma = (MeditationAssistant) context.getApplicationContext(); } catch (Exception e) { @@ -33,24 +30,26 @@ public class DailyReminderReceiver extends BroadcastReceiver { } if (!getMeditationAssistant().getPrefs().getBoolean("pref_daily_reminder", false)) { - cancelReminder(context); + getMeditationAssistant().cancelDailyReminder(context); return; // The user has not enabled the daily reminder } Log.d("MeditationAssistant", "onReceive in DailyReminderReceiver"); if (intent != null && intent.getAction() != null && intent.getAction().equals(MeditationAssistant.ACTION_REMINDER)) { // otherwise, it was just an update - Log.d("MeditationAssistant", "Daily notification intent!"); + Log.d("MeditationAssistant", "Received daily reminder notification intent"); SimpleDateFormat sdf = new SimpleDateFormat("d-M-yyyy", Locale.US); if (getMeditationAssistant().getTimeToStopMeditate() != 0) { - Log.d("MeditationAssistant", "Skipping daily notification today, session in progress..."); + Log.d("MeditationAssistant", "Skipping daily reminder notification today, session in progress..."); } else if (getMeditationAssistant().db.numSessionsByDate(Calendar.getInstance()) > 0) { - Log.d("MeditationAssistant", "Skipping daily notification today, there has already been a session recorded..."); + Log.d("MeditationAssistant", "Skipping daily reminder notification today, there has already been a session recorded..."); } else { long last_reminder = getMeditationAssistant().getPrefs().getLong("last_reminder", 0); if (last_reminder == 0 || getMeditationAssistant().getTimestamp() - last_reminder > 120) { getMeditationAssistant().getPrefs().edit().putLong("last_reminder", getMeditationAssistant().getTimestamp()).apply(); + Log.d("MeditationAssistant", "Showing daily reminder notification"); + String reminderText = getMeditationAssistant().getPrefs().getString("pref_daily_reminder_text", "").trim(); if (reminderText.equals("")) { reminderText = context.getString(R.string.reminderText); @@ -64,6 +63,10 @@ public class DailyReminderReceiver extends BroadcastReceiver { .setTicker(reminderText) .setAutoCancel(true); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + notificationBuilder.setChannelId("reminder"); + } + if (getMeditationAssistant().getPrefs().getBoolean("pref_vibrate_reminder", true)) { long[] vibrationPattern = {0, 200, 500, 200, 500}; notificationBuilder.setVibrate(vibrationPattern); @@ -80,76 +83,20 @@ public class DailyReminderReceiver extends BroadcastReceiver { TaskStackBuilder stackBuilder = TaskStackBuilder.create(context); stackBuilder.addParentStack(MainActivity.class); stackBuilder.addNextIntent(notificationIntent); - PendingIntent resultPendingIntent = - stackBuilder.getPendingIntent( - 0, - PendingIntent.FLAG_UPDATE_CURRENT - ); - - //Intent launchMain = new Intent(context, MainActivity.class); - //PendingIntent launchNotification = PendingIntent.getActivity(context, 1008, launchMain, PendingIntent.FLAG_UPDATE_CURRENT); + PendingIntent resultPendingIntent = stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT); notificationBuilder.setContentIntent(resultPendingIntent); Notification notification = notificationBuilder.build(); - NotificationManager notificationManager = - (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); - notificationManager.notify(1946, notification); + NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); + notificationManager.notify(MeditationAssistant.dailyReminderNotificationID, notification); + } else { + Log.d("MeditationAssistant", "Skipping daily reminder notification today, a daily notification was recently shown..."); } } } - String reminderTime = ma.getPrefs().getString("pref_daily_reminder_time", "19:00"); - String[] reminderTimeSplit = ((reminderTime != null && reminderTime != "") ? reminderTime : "19:00").split(":"); - Integer reminderHour = Integer.valueOf(reminderTimeSplit[0]); - Integer reminderMinute = Integer.valueOf(reminderTimeSplit[1]); - - Calendar calendar = Calendar.getInstance(); - calendar.set(Calendar.HOUR_OF_DAY, reminderHour); - calendar.set(Calendar.MINUTE, reminderMinute); - calendar.set(Calendar.SECOND, 0); - - if (Calendar.getInstance().getTimeInMillis() > calendar.getTimeInMillis()) { - calendar.add(Calendar.DATE, 1); // Tomorrow - } - - cancelReminder(context); - - getMeditationAssistant().reminderPendingIntent = PendingIntent - .getBroadcast( - context, - 1946, - new Intent( - MeditationAssistant.ACTION_REMINDER), - PendingIntent.FLAG_CANCEL_CURRENT - ); - - /* Don't use setAlarmClock here as it will always place an alarm icon in the status bar */ - getMeditationAssistant().setAlarm(false, calendar.getTimeInMillis(), getMeditationAssistant().reminderPendingIntent); - Log.d("MeditationAssistant", "Set daily reminder alarm for " + calendar.toString()); - } - - private void cancelReminder(Context context) { - if (getMeditationAssistant().reminderPendingIntent != null) { - try { - getMeditationAssistant().getAlarmManager().cancel(getMeditationAssistant().reminderPendingIntent); - } catch (Exception e) { - Log.e("MeditationAssistant", "AlarmManager update was not canceled. " + e.toString()); - } - try { - PendingIntent.getBroadcast(context, 0, new Intent( - MeditationAssistant.ACTION_REMINDER), - PendingIntent.FLAG_CANCEL_CURRENT - ).cancel(); - } catch (Exception e) { - Log.e("MeditationAssistant", "PendingIntent broadcast was not canceled. " + e.toString()); - } - try { - getMeditationAssistant().reminderPendingIntent.cancel(); - } catch (Exception e) { - Log.e("MeditationAssistant", "PendingIntent was not canceled. " + e.toString()); - } - } + getMeditationAssistant().setDailyReminder(context); } public MeditationAssistant getMeditationAssistant() { diff --git a/MeditationAssistant/src/main/java/sh/ftp/rocketninelabs/meditationassistant/DailyReminderService.java b/MeditationAssistant/src/main/java/sh/ftp/rocketninelabs/meditationassistant/DailyReminderService.java new file mode 100644 index 0000000..0d6331d --- /dev/null +++ b/MeditationAssistant/src/main/java/sh/ftp/rocketninelabs/meditationassistant/DailyReminderService.java @@ -0,0 +1,27 @@ +package sh.ftp.rocketninelabs.meditationassistant; + +import android.app.job.JobParameters; +import android.app.job.JobService; +import android.content.Intent; +import android.os.Build; +import android.util.Log; + +import androidx.annotation.RequiresApi; + +@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) +public class DailyReminderService extends JobService { + @Override + public boolean onStartJob(JobParameters params) { + Log.d(MeditationAssistant.LOG_TAG, "Daily reminder job starting"); + + Intent actionReminder = new Intent(MeditationAssistant.ACTION_REMINDER); + sendBroadcast(actionReminder); + return true; + } + + @Override + public boolean onStopJob(JobParameters params) { + Log.d(MeditationAssistant.LOG_TAG, "Daily reminder job stopped"); + return true; + } +} \ No newline at end of file diff --git a/MeditationAssistant/src/main/java/sh/ftp/rocketninelabs/meditationassistant/MeditationAssistant.java b/MeditationAssistant/src/main/java/sh/ftp/rocketninelabs/meditationassistant/MeditationAssistant.java index 0178c47..91d3df0 100644 --- a/MeditationAssistant/src/main/java/sh/ftp/rocketninelabs/meditationassistant/MeditationAssistant.java +++ b/MeditationAssistant/src/main/java/sh/ftp/rocketninelabs/meditationassistant/MeditationAssistant.java @@ -9,11 +9,14 @@ import android.app.DatePickerDialog; import android.app.NotificationChannel; import android.app.NotificationManager; import android.app.PendingIntent; +import android.app.job.JobInfo; +import android.app.job.JobScheduler; import android.appwidget.AppWidgetManager; import android.content.ComponentName; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; +import android.content.IntentFilter; import android.content.SharedPreferences; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; @@ -98,8 +101,12 @@ public class MeditationAssistant extends Application { public static String ACTION_REMINDER = "sh.ftp.rocketninelabs.meditationassistant.DAILY_NOTIFICATION"; public static String ACTION_UPDATED = "sh.ftp.rocketninelabs.meditationassistant.DAILY_NOTIFICATION_UPDATED"; + public static String LOG_TAG = "MeditationAssistant"; + public static int CSV_COLUMN_COUNT = 5; + public static int dailyReminderJobID = 108; + public static int dailyReminderNotificationID = 1946; // Terence McKenna's year of birth public static int sessionNotificationID = 1990; public static int bellNotificationID = 1991; @@ -169,6 +176,8 @@ public class MeditationAssistant extends Application { private Button sessionDialogCompletedTimeButton = null; private Button sessionDialogLengthButton = null; private EditText sessionDialogMessage = null; + private DailyReminderReceiver dailyReminderReceiver = null; + private JobScheduler jobScheduler = null; private DatePickerDialog.OnDateSetListener sessionDialogDateSetListener = new DatePickerDialog.OnDateSetListener() { @Override @@ -1201,6 +1210,16 @@ public class MeditationAssistant extends Application { + Build.VERSION.SDK_INT ); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + jobScheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE); + + dailyReminderReceiver = new DailyReminderReceiver(); + IntentFilter reminderFilter = new IntentFilter(); + reminderFilter.addAction(ACTION_REMINDER); + reminderFilter.addAction(ACTION_UPDATED); + registerReceiver(dailyReminderReceiver, reminderFilter); + } + if (Build.VERSION.SDK_INT >= 23) { PackageManager pm = getPackageManager(); pm.setComponentEnabledSetting(new ComponentName(this, FilePickerActivity.class), @@ -1251,15 +1270,87 @@ public class MeditationAssistant extends Application { db = DatabaseHandler.getInstance(getApplicationContext()); + setDailyReminder(getApplicationContext()); + } + + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + public JobInfo.Builder buildDailyReminderJob(Context context, Calendar calendar) { + long delay = calendar.getTimeInMillis() - System.currentTimeMillis(); + + JobInfo.Builder builder = new JobInfo.Builder(dailyReminderJobID, new ComponentName(context, DailyReminderService.class)); + builder.setPersisted(true); + builder.setMinimumLatency(delay); + builder.setOverrideDeadline(delay); + return builder; + } + + public void setDailyReminder(Context context) { + String reminderTime = getPrefs().getString("pref_daily_reminder_time", "19:00"); + String[] reminderTimeSplit = ((reminderTime != null && reminderTime != "") ? reminderTime : "19:00").split(":"); + Integer reminderHour = Integer.valueOf(reminderTimeSplit[0]); + Integer reminderMinute = Integer.valueOf(reminderTimeSplit[1]); + + Calendar calendar = Calendar.getInstance(); + calendar.set(Calendar.HOUR_OF_DAY, reminderHour); + calendar.set(Calendar.MINUTE, reminderMinute); + calendar.set(Calendar.SECOND, 0); + + if (Calendar.getInstance().getTimeInMillis() > calendar.getTimeInMillis()) { + calendar.add(Calendar.DATE, 1); // Tomorrow + } + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { - /* Send the daily notification updated intent just in case the receiver hasn't been called yet */ - Log.d("MeditationAssistant", "Sending initial daily notification updated intent"); - Intent intent = new Intent(); - intent.setAction(MeditationAssistant.ACTION_UPDATED); - sendBroadcast(intent); + cancelDailyReminder(context); + + reminderPendingIntent = PendingIntent.getBroadcast(context, dailyReminderNotificationID, new Intent(MeditationAssistant.ACTION_REMINDER), PendingIntent.FLAG_CANCEL_CURRENT); + + /* Don't use setAlarmClock here as it will always place an alarm icon in the status bar */ + setAlarm(false, calendar.getTimeInMillis(), reminderPendingIntent); + } else { + long lastJobScheduled = getPrefs().getLong("last_job_scheduled", 0); + if (System.currentTimeMillis() - lastJobScheduled < 250) { + Log.d("MeditationAssistant", "Rate limiting daily reminder job scheduling"); + return; + } + + JobInfo.Builder builder = buildDailyReminderJob(context, calendar); + int result = jobScheduler.schedule(builder.build()); + + getPrefs().edit().putLong("last_job_scheduled", System.currentTimeMillis()).apply(); + + String resultLabel = (result == JobScheduler.RESULT_SUCCESS) ? "successfully" : "unsuccessfully"; + Log.d("MeditationAssistant", "Scheduled daily reminder job " + resultLabel); } + + Log.d("MeditationAssistant", "Set daily reminder alarm for " + calendar); } + public void cancelDailyReminder(Context context) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { + if (reminderPendingIntent == null) { + return; + } + + try { + getAlarmManager().cancel(reminderPendingIntent); + } catch (Exception e) { + Log.e("MeditationAssistant", "AlarmManager update was not canceled. " + e.toString()); + } + try { + PendingIntent.getBroadcast(context, 0, new Intent(MeditationAssistant.ACTION_REMINDER), PendingIntent.FLAG_CANCEL_CURRENT).cancel(); + } catch (Exception e) { + Log.e("MeditationAssistant", "PendingIntent broadcast was not canceled. " + e.toString()); + } + try { + reminderPendingIntent.cancel(); + } catch (Exception e) { + Log.e("MeditationAssistant", "PendingIntent was not canceled. " + e.toString()); + } + } else { + jobScheduler.cancel(dailyReminderJobID); + Log.d("MeditationAssistant", "Canceled daily reminder job"); + } + } public String getPostDataString(HashMap params) throws UnsupportedEncodingException { StringBuilder result = new StringBuilder(); boolean first = true; @@ -1451,9 +1542,14 @@ public class MeditationAssistant extends Application { bellChannel.enableLights(false); bellChannel.enableVibration(false); + NotificationChannel reminderChannel = new NotificationChannel("reminder", getString(R.string.pref_daily_reminder), NotificationManager.IMPORTANCE_DEFAULT); + reminderChannel.enableLights(true); + reminderChannel.enableVibration(true); + NotificationManager notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); notificationManager.createNotificationChannel(sessionChannel); notificationManager.createNotificationChannel(bellChannel); + notificationManager.createNotificationChannel(reminderChannel); } public void showMindfulnessBellNotification() { diff --git a/MeditationAssistant/src/main/java/sh/ftp/rocketninelabs/meditationassistant/SettingsActivity.java b/MeditationAssistant/src/main/java/sh/ftp/rocketninelabs/meditationassistant/SettingsActivity.java index 4239abc..28be053 100644 --- a/MeditationAssistant/src/main/java/sh/ftp/rocketninelabs/meditationassistant/SettingsActivity.java +++ b/MeditationAssistant/src/main/java/sh/ftp/rocketninelabs/meditationassistant/SettingsActivity.java @@ -687,11 +687,9 @@ public class SettingsActivity extends PreferenceActivity { reminderPreferenceFragment = (ReminderPreferenceFragment) preferenceFragment; } - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { - bindPreferenceSummaryToValue(preferenceFragment == null ? findPreference("pref_daily_reminder_text") : preferenceFragment.findPreference("pref_daily_reminder_text")); - bindPreferenceSummaryToValue(preferenceFragment == null ? findPreference("pref_daily_reminder_time") : preferenceFragment.findPreference("pref_daily_reminder_time")); - bindPreferenceSummaryToValue(preferenceFragment == null ? findPreference("pref_daily_reminder") : preferenceFragment.findPreference("pref_daily_reminder")); - } + bindPreferenceSummaryToValue(preferenceFragment == null ? findPreference("pref_daily_reminder_text") : preferenceFragment.findPreference("pref_daily_reminder_text")); + bindPreferenceSummaryToValue(preferenceFragment == null ? findPreference("pref_daily_reminder_time") : preferenceFragment.findPreference("pref_daily_reminder_time")); + bindPreferenceSummaryToValue(preferenceFragment == null ? findPreference("pref_daily_reminder") : preferenceFragment.findPreference("pref_daily_reminder")); } if (pref_type.equals("all") || pref_type.equals("meditation")) { if (preferenceFragment != null) { @@ -826,14 +824,6 @@ public class SettingsActivity extends PreferenceActivity { PreferenceCategory fakeHeader = new PreferenceCategory(this); addPreferencesFromResource(R.xml.pref_session); - // Add 'Daily Reminder' preferences - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { - fakeHeader = new PreferenceCategory(this); - fakeHeader.setTitle(R.string.pref_daily_reminder); - getPreferenceScreen().addPreference(fakeHeader); - addPreferencesFromResource(R.xml.pref_reminder); - } - // Add 'Meditation' preferences fakeHeader = new PreferenceCategory(this); fakeHeader.setTitle(R.string.meditation); @@ -852,6 +842,12 @@ public class SettingsActivity extends PreferenceActivity { getPreferenceScreen().addPreference(fakeHeader); addPreferencesFromResource(R.xml.pref_medinet); + // Add 'Daily Reminder' preferences + fakeHeader = new PreferenceCategory(this); + fakeHeader.setTitle(R.string.pref_daily_reminder); + getPreferenceScreen().addPreference(fakeHeader); + addPreferencesFromResource(R.xml.pref_reminder); + // Add 'Miscellaneous' preferences fakeHeader = new PreferenceCategory(this); fakeHeader.setTitle(R.string.miscellaneous); @@ -874,10 +870,6 @@ public class SettingsActivity extends PreferenceActivity { public void onBuildHeaders(List
target) { if (!isSimplePreferences(this)) { loadHeadersFromResource(R.xml.pref_headers, target); - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { - loadHeadersFromResource(R.xml.pref_headers_pre26, target); - } - loadHeadersFromResource(R.xml.pref_headers_footer, target); } } diff --git a/MeditationAssistant/src/main/res/xml/pref_headers.xml b/MeditationAssistant/src/main/res/xml/pref_headers.xml index 28109f5..e0b429d 100644 --- a/MeditationAssistant/src/main/res/xml/pref_headers.xml +++ b/MeditationAssistant/src/main/res/xml/pref_headers.xml @@ -16,4 +16,12 @@ android:fragment="sh.ftp.rocketninelabs.meditationassistant.SettingsActivity$MediNETPreferenceFragment" android:title="@string/mediNET"/> +
+ +
+ diff --git a/MeditationAssistant/src/main/res/xml/pref_headers_footer.xml b/MeditationAssistant/src/main/res/xml/pref_headers_footer.xml deleted file mode 100644 index e9b395b..0000000 --- a/MeditationAssistant/src/main/res/xml/pref_headers_footer.xml +++ /dev/null @@ -1,7 +0,0 @@ - - -
- - diff --git a/MeditationAssistant/src/main/res/xml/pref_headers_pre26.xml b/MeditationAssistant/src/main/res/xml/pref_headers_pre26.xml deleted file mode 100644 index cface33..0000000 --- a/MeditationAssistant/src/main/res/xml/pref_headers_pre26.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - -
- - diff --git a/build.gradle b/build.gradle index 936bc89..93efa99 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:7.0.2' + classpath 'com.android.tools.build:gradle:7.2.0' } } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index dd8b57e..6efd978 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip