diff --git a/README.md b/README.md index 6bdb9be..50ca090 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,14 @@ # Shake Alarm Clock An alarm clock app for Android which allows you to dismiss the ringing alarm by shaking the phone. +![GitHub All Releases](https://img.shields.io/github/downloads/WrichikBasu/ShakeAlarmClock/total) +![GitHub top language](https://img.shields.io/github/languages/top/WrichikBasu/ShakeAlarmClock) +![Snyk Vulnerabilities for GitHub Repo](https://img.shields.io/snyk/vulnerabilities/github/WrichikBasu/ShakeAlarmClock) +![GitHub](https://img.shields.io/github/license/WrichikBasu/ShakeAlarmClock) +[![GitHub release (latest by date)](https://img.shields.io/github/v/release/WrichikBasu/ShakeAlarmClock)](https://github.com/WrichikBasu/ShakeAlarmClock/releases/tag/v1.2) +![GitHub Release Date](https://img.shields.io/github/release-date/WrichikBasu/ShakeAlarmClock) +![GitHub last commit](https://img.shields.io/github/last-commit/WrichikBasu/ShakeAlarmClock) + Tired of pressing the power button/swiping the screen every time an alarm rings? Does your phone's UI have a tendency to freeze, thereby making it impossible to dismiss an alarm? No worries! You have come to the right place. With this app, you can simply shake your phone and dismiss the alarm. @@ -10,4 +18,4 @@ Tired of pressing the power button/swiping the screen every time an alarm rings? 1. Inbuilt dark theme, even in phones that do not support it. ## How to use? -Simply download the latest apk file, install it, give necessary permissions, and you are all set! +Simply download the latest apk file, install it and you are all set! diff --git a/app/build.gradle b/app/build.gradle index 43db4e5..f2b8e40 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -8,8 +8,8 @@ android { applicationId "in.basulabs.shakealarmclock" minSdkVersion 21 targetSdkVersion 30 - versionCode 3 - versionName "1.2" + versionCode 4 + versionName "1.2.1" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } @@ -23,8 +23,8 @@ android { buildTypes { release { - minifyEnabled false - shrinkResources false + minifyEnabled true + shrinkResources true proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 1166912..75e881e 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -79,7 +79,9 @@ - + @@ -92,10 +94,14 @@ - + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/in/basulabs/shakealarmclock/Activity_AlarmDetails.java b/app/src/main/java/in/basulabs/shakealarmclock/Activity_AlarmDetails.java index 764aec2..45e3add 100644 --- a/app/src/main/java/in/basulabs/shakealarmclock/Activity_AlarmDetails.java +++ b/app/src/main/java/in/basulabs/shakealarmclock/Activity_AlarmDetails.java @@ -5,15 +5,14 @@ import android.media.AudioManager; import android.media.RingtoneManager; import android.net.Uri; -import android.os.Build; import android.os.Bundle; +import android.util.Log; import android.view.MenuItem; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.AppCompatActivity; -import androidx.appcompat.app.AppCompatDelegate; import androidx.fragment.app.DialogFragment; import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentTransaction; @@ -25,54 +24,73 @@ import java.util.ArrayList; import java.util.Objects; +import static in.basulabs.shakealarmclock.ConstantsAndStatics.ACTION_EXISTING_ALARM; +import static in.basulabs.shakealarmclock.ConstantsAndStatics.ACTION_NEW_ALARM; +import static in.basulabs.shakealarmclock.ConstantsAndStatics.ACTION_NEW_ALARM_FROM_INTENT; +import static in.basulabs.shakealarmclock.ConstantsAndStatics.ALARM_TYPE_SOUND_ONLY; +import static in.basulabs.shakealarmclock.ConstantsAndStatics.BUNDLE_KEY_ALARM_DAY; +import static in.basulabs.shakealarmclock.ConstantsAndStatics.BUNDLE_KEY_ALARM_DETAILS; +import static in.basulabs.shakealarmclock.ConstantsAndStatics.BUNDLE_KEY_ALARM_HOUR; +import static in.basulabs.shakealarmclock.ConstantsAndStatics.BUNDLE_KEY_ALARM_MINUTE; +import static in.basulabs.shakealarmclock.ConstantsAndStatics.BUNDLE_KEY_ALARM_MONTH; +import static in.basulabs.shakealarmclock.ConstantsAndStatics.BUNDLE_KEY_ALARM_TONE_URI; +import static in.basulabs.shakealarmclock.ConstantsAndStatics.BUNDLE_KEY_ALARM_TYPE; +import static in.basulabs.shakealarmclock.ConstantsAndStatics.BUNDLE_KEY_ALARM_VOLUME; +import static in.basulabs.shakealarmclock.ConstantsAndStatics.BUNDLE_KEY_ALARM_YEAR; +import static in.basulabs.shakealarmclock.ConstantsAndStatics.BUNDLE_KEY_HAS_USER_CHOSEN_DATE; +import static in.basulabs.shakealarmclock.ConstantsAndStatics.BUNDLE_KEY_IS_REPEAT_ON; +import static in.basulabs.shakealarmclock.ConstantsAndStatics.BUNDLE_KEY_IS_SNOOZE_ON; +import static in.basulabs.shakealarmclock.ConstantsAndStatics.BUNDLE_KEY_OLD_ALARM_HOUR; +import static in.basulabs.shakealarmclock.ConstantsAndStatics.BUNDLE_KEY_OLD_ALARM_MINUTE; +import static in.basulabs.shakealarmclock.ConstantsAndStatics.BUNDLE_KEY_REPEAT_DAYS; +import static in.basulabs.shakealarmclock.ConstantsAndStatics.BUNDLE_KEY_SNOOZE_FREQUENCY; +import static in.basulabs.shakealarmclock.ConstantsAndStatics.BUNDLE_KEY_SNOOZE_TIME_IN_MINS; +import static in.basulabs.shakealarmclock.ConstantsAndStatics.SHARED_PREF_FILE_NAME; +import static in.basulabs.shakealarmclock.ConstantsAndStatics.SHARED_PREF_KEY_DEFAULT_ALARM_TONE_URI; +import static in.basulabs.shakealarmclock.ConstantsAndStatics.SHARED_PREF_KEY_DEFAULT_ALARM_VOLUME; +import static in.basulabs.shakealarmclock.ConstantsAndStatics.SHARED_PREF_KEY_DEFAULT_SNOOZE_FREQ; +import static in.basulabs.shakealarmclock.ConstantsAndStatics.SHARED_PREF_KEY_DEFAULT_SNOOZE_INTERVAL; +import static in.basulabs.shakealarmclock.ConstantsAndStatics.SHARED_PREF_KEY_DEFAULT_SNOOZE_IS_ON; + public class Activity_AlarmDetails extends AppCompatActivity implements Fragment_AlarmDetails_Main.FragmentGUIListener, AlertDialog_DiscardChanges.DialogListener { private FragmentManager fragmentManager; private ActionBar actionBar; + private ViewModel_AlarmDetails viewModel; + private SharedPreferences sharedPreferences; private static final String BACK_STACK_TAG = "activityAlarmDetails_fragment_stack"; - private static final int FRAGMENT_MAIN = 100, FRAGMENT_SNOOZE = 103, FRAGMENT_REPEAT = 110, - FRAGMENT_PICK_DATE = 203; + private static final int FRAGMENT_MAIN = 100; + private static final int FRAGMENT_SNOOZE = 103; + private static final int FRAGMENT_REPEAT = 110; + private static final int FRAGMENT_PICK_DATE = 203; + private static int whichFragment = 0; public static final int MODE_NEW_ALARM = 0, MODE_EXISTING_ALARM = 1; - private ViewModel_AlarmDetails viewModel; - - private SharedPreferences sharedPreferences; - //---------------------------------------------------------------------------------------------------- @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); + setContentView(R.layout.activity_addalarm); - setSupportActionBar(findViewById(R.id.toolbar2)); + setSupportActionBar(findViewById(R.id.toolbar2)); actionBar = getSupportActionBar(); assert actionBar != null; actionBar.setDisplayHomeAsUpEnabled(true); fragmentManager = getSupportFragmentManager(); - - sharedPreferences = getSharedPreferences(ConstantsAndStatics.SHARED_PREF_FILE_NAME, MODE_PRIVATE); - + sharedPreferences = getSharedPreferences(SHARED_PREF_FILE_NAME, MODE_PRIVATE); viewModel = new ViewModelProvider(this).get(ViewModel_AlarmDetails.class); - int defaultTheme; - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { - defaultTheme = ConstantsAndStatics.THEME_SYSTEM; - } else { - defaultTheme = ConstantsAndStatics.THEME_AUTO_TIME; - } - AppCompatDelegate.setDefaultNightMode( - ConstantsAndStatics.getTheme(sharedPreferences.getInt(ConstantsAndStatics.SHARED_PREF_KEY_THEME, defaultTheme))); - if (savedInstanceState == null) { - if (Objects.equals(getIntent().getAction(), ConstantsAndStatics.ACTION_NEW_ALARM)) { + if (Objects.equals(getIntent().getAction(), ACTION_NEW_ALARM)) { setVariablesInViewModel(); @@ -81,28 +99,66 @@ protected void onCreate(@Nullable Bundle savedInstanceState) { .addToBackStack(BACK_STACK_TAG) .commit(); - } else if (getIntent().getAction().equals(ConstantsAndStatics.ACTION_EXISTING_ALARM)) { + } else if (getIntent().getAction().equals(ACTION_EXISTING_ALARM)) { Bundle data = Objects.requireNonNull(getIntent().getExtras()) - .getBundle(ConstantsAndStatics.BUNDLE_KEY_ALARM_DETAILS); + .getBundle(BUNDLE_KEY_ALARM_DETAILS); assert data != null; - setVariablesInViewModel(data.getInt(ConstantsAndStatics.BUNDLE_KEY_ALARM_HOUR), - data.getInt(ConstantsAndStatics.BUNDLE_KEY_ALARM_MINUTE), - data.getInt(ConstantsAndStatics.BUNDLE_KEY_ALARM_DAY), - data.getInt(ConstantsAndStatics.BUNDLE_KEY_ALARM_MONTH), - data.getInt(ConstantsAndStatics.BUNDLE_KEY_ALARM_YEAR), - data.getBoolean(ConstantsAndStatics.BUNDLE_KEY_IS_SNOOZE_ON), - data.getBoolean(ConstantsAndStatics.BUNDLE_KEY_IS_REPEAT_ON), - data.getInt(ConstantsAndStatics.BUNDLE_KEY_SNOOZE_FREQUENCY), - data.getInt(ConstantsAndStatics.BUNDLE_KEY_SNOOZE_TIME_IN_MINS), - data.getInt(ConstantsAndStatics.BUNDLE_KEY_ALARM_TYPE), - data.getInt(ConstantsAndStatics.BUNDLE_KEY_ALARM_VOLUME), - data.getIntegerArrayList(ConstantsAndStatics.BUNDLE_KEY_REPEAT_DAYS), - Objects.requireNonNull( - data.getParcelable(ConstantsAndStatics.BUNDLE_KEY_ALARM_TONE_URI)), - data.getBoolean(ConstantsAndStatics.BUNDLE_KEY_HAS_USER_CHOSEN_DATE)); + setVariablesInViewModel(MODE_EXISTING_ALARM, + data.getInt(BUNDLE_KEY_ALARM_HOUR), + data.getInt(BUNDLE_KEY_ALARM_MINUTE), + data.getInt(BUNDLE_KEY_ALARM_DAY), + data.getInt(BUNDLE_KEY_ALARM_MONTH), + data.getInt(BUNDLE_KEY_ALARM_YEAR), + data.getBoolean(BUNDLE_KEY_IS_SNOOZE_ON), + data.getBoolean(BUNDLE_KEY_IS_REPEAT_ON), + data.getInt(BUNDLE_KEY_SNOOZE_FREQUENCY), + data.getInt(BUNDLE_KEY_SNOOZE_TIME_IN_MINS), + data.getInt(BUNDLE_KEY_ALARM_TYPE), + data.getInt(BUNDLE_KEY_ALARM_VOLUME), + data.getIntegerArrayList(BUNDLE_KEY_REPEAT_DAYS), + Objects.requireNonNull(data.getParcelable(BUNDLE_KEY_ALARM_TONE_URI)), + data.getBoolean(BUNDLE_KEY_HAS_USER_CHOSEN_DATE)); + + fragmentManager.beginTransaction() + .replace(R.id.addAlarmActFragHolder, new Fragment_AlarmDetails_Main()) + .addToBackStack(BACK_STACK_TAG) + .commit(); + + } else if (getIntent().getAction().equals(ACTION_NEW_ALARM_FROM_INTENT)) { + + Log.e(this.getClass().getSimpleName(), "Received intent."); + + Bundle data = getIntent().getExtras(); + + if (data == null) { + + Log.e(this.getClass().getSimpleName(), "No extras received."); + + setVariablesInViewModel(); + + } else { + + Log.e(this.getClass().getSimpleName(), "Extras received."); + + setVariablesInViewModel(MODE_NEW_ALARM, + data.getInt(BUNDLE_KEY_ALARM_HOUR), + data.getInt(BUNDLE_KEY_ALARM_MINUTE), + data.getInt(BUNDLE_KEY_ALARM_DAY), + data.getInt(BUNDLE_KEY_ALARM_MONTH), + data.getInt(BUNDLE_KEY_ALARM_YEAR), + sharedPreferences.getBoolean(SHARED_PREF_KEY_DEFAULT_SNOOZE_IS_ON, true), + data.getBoolean(BUNDLE_KEY_IS_REPEAT_ON), + sharedPreferences.getInt(SHARED_PREF_KEY_DEFAULT_SNOOZE_FREQ, 3), + sharedPreferences.getInt(SHARED_PREF_KEY_DEFAULT_SNOOZE_INTERVAL, 5), + data.getInt(BUNDLE_KEY_ALARM_TYPE), + data.getInt(BUNDLE_KEY_ALARM_VOLUME), + data.getIntegerArrayList(BUNDLE_KEY_REPEAT_DAYS), + Objects.requireNonNull(data.getParcelable(BUNDLE_KEY_ALARM_TONE_URI)), false); + + } fragmentManager.beginTransaction() .replace(R.id.addAlarmActFragHolder, new Fragment_AlarmDetails_Main()) @@ -116,40 +172,34 @@ protected void onCreate(@Nullable Bundle savedInstanceState) { setActionBarTitle(); - /*Log.e(this.getClass().getSimpleName(), - "onCreate Back stack length = " + fragmentManager.getBackStackEntryCount());*/ - } //---------------------------------------------------------------------------------------------------- /** - * Initialises all the variables in {@link #viewModel} to default values. Use this if the Fragment is to - * be created for a new alarm. + * Initialises all the variables in {@link #viewModel} to default values. */ private void setVariablesInViewModel() { viewModel.setMode(MODE_NEW_ALARM); viewModel.setAlarmDateTime(LocalDateTime.now().plusHours(1)); - viewModel.setIsSnoozeOn(sharedPreferences.getBoolean(ConstantsAndStatics.SHARED_PREF_KEY_DEFAULT_SNOOZE_IS_ON, - true)); + viewModel.setIsSnoozeOn(sharedPreferences.getBoolean(SHARED_PREF_KEY_DEFAULT_SNOOZE_IS_ON, true)); viewModel.setIsRepeatOn(false); viewModel.setAlarmToneUri(Uri.parse(sharedPreferences - .getString(ConstantsAndStatics.SHARED_PREF_KEY_DEFAULT_ALARM_TONE_URI, + .getString(SHARED_PREF_KEY_DEFAULT_ALARM_TONE_URI, RingtoneManager.getActualDefaultRingtoneUri(this, RingtoneManager.TYPE_ALARM) .toString()))); - viewModel.setAlarmType(ConstantsAndStatics.ALARM_TYPE_SOUND_ONLY); + viewModel.setAlarmType(ALARM_TYPE_SOUND_ONLY); AudioManager audioManager = (AudioManager) getSystemService(AUDIO_SERVICE); - viewModel.setAlarmVolume(sharedPreferences.getInt(ConstantsAndStatics.SHARED_PREF_KEY_DEFAULT_ALARM_VOLUME, + viewModel.setAlarmVolume(sharedPreferences.getInt(SHARED_PREF_KEY_DEFAULT_ALARM_VOLUME, audioManager.getStreamMaxVolume(AudioManager.STREAM_ALARM) - 2)); - viewModel.setSnoozeFreq(sharedPreferences.getInt(ConstantsAndStatics.SHARED_PREF_KEY_DEFAULT_SNOOZE_FREQ, 3)); - viewModel.setSnoozeIntervalInMins( - sharedPreferences.getInt(ConstantsAndStatics.SHARED_PREF_KEY_DEFAULT_SNOOZE_INTERVAL, 5)); + viewModel.setSnoozeFreq(sharedPreferences.getInt(SHARED_PREF_KEY_DEFAULT_SNOOZE_FREQ, 3)); + viewModel.setSnoozeIntervalInMins(sharedPreferences.getInt(SHARED_PREF_KEY_DEFAULT_SNOOZE_INTERVAL, 5)); viewModel.setRepeatDays(new ArrayList<>()); @@ -170,32 +220,13 @@ private void setVariablesInViewModel() { //---------------------------------------------------------------------------------------------------- - /** - * Initialises all the variables in {@link #viewModel} to the supplied parameters. Use this if an existing - * alarm is to be displayed. - * - * @param alarmHour The alarm hour. - * @param alarmMinute The alarm minutes. - * @param dayOfMonth The day of the month. - * @param month - * @param year - * @param isSnoozeOn - * @param isRepeatOn - * @param snoozeFreq - * @param snoozeIntervalInMins - * @param alarmType - * @param alarmVolume - * @param repeatDays - * @param alarmToneUri - * @param hasUserChosenDate - */ - private void setVariablesInViewModel(int alarmHour, int alarmMinute, int dayOfMonth, int month, + private void setVariablesInViewModel(int mode, int alarmHour, int alarmMinute, int dayOfMonth, int month, int year, boolean isSnoozeOn, boolean isRepeatOn, int snoozeFreq, int snoozeIntervalInMins, int alarmType, int alarmVolume, @Nullable ArrayList repeatDays, @NonNull Uri alarmToneUri, boolean hasUserChosenDate) { - viewModel.setMode(MODE_EXISTING_ALARM); + viewModel.setMode(mode); viewModel.setAlarmDateTime(LocalDateTime.of(year, month, dayOfMonth, alarmHour, alarmMinute)); @@ -212,10 +243,8 @@ private void setVariablesInViewModel(int alarmHour, int alarmMinute, int dayOfMo viewModel.setSnoozeFreq(snoozeFreq); viewModel.setSnoozeIntervalInMins(snoozeIntervalInMins); } else { - viewModel.setSnoozeFreq( - sharedPreferences.getInt(ConstantsAndStatics.SHARED_PREF_KEY_DEFAULT_SNOOZE_FREQ, 3)); - viewModel.setSnoozeIntervalInMins( - sharedPreferences.getInt(ConstantsAndStatics.SHARED_PREF_KEY_DEFAULT_SNOOZE_INTERVAL, 5)); + viewModel.setSnoozeFreq(sharedPreferences.getInt(SHARED_PREF_KEY_DEFAULT_SNOOZE_FREQ, 3)); + viewModel.setSnoozeIntervalInMins(sharedPreferences.getInt(SHARED_PREF_KEY_DEFAULT_SNOOZE_INTERVAL, 5)); } if (isRepeatOn && repeatDays != null) { @@ -238,16 +267,18 @@ private void setVariablesInViewModel(int alarmHour, int alarmMinute, int dayOfMo viewModel.setHasUserChosenDate(hasUserChosenDate); - viewModel.setOldAlarmHour(alarmHour); - viewModel.setOldAlarmMinute(alarmMinute); + if (mode == MODE_EXISTING_ALARM) { + viewModel.setOldAlarmHour(alarmHour); + viewModel.setOldAlarmMinute(alarmMinute); + } } //---------------------------------------------------------------------------------------------------- /** - * Sets the ActionBar title as per the created fragment. Uses {@link #whichFragment} to determine the - * current fragment. + * Sets the ActionBar title as per the created fragment. Uses {@link #whichFragment} to determine the current + * fragment. */ private void setActionBarTitle() { switch (whichFragment) { @@ -273,9 +304,6 @@ private void setActionBarTitle() { @Override public void onBackPressed() { - //Log.e(this.getClass().getSimpleName(), "Back pressed."); - /*Log.e(this.getClass().getSimpleName(), - "onBackPressed Back stack length = " + fragmentManager.getBackStackEntryCount());*/ if (fragmentManager.getBackStackEntryCount() > 1) { fragmentManager.popBackStackImmediate(); @@ -283,9 +311,6 @@ public void onBackPressed() { setActionBarTitle(); } else { onCancelButtonClick(); - /*setResult(RESULT_CANCELED); - this.finish();*/ - //NavUtils.navigateUpFromSameTask(this); } } @@ -306,33 +331,33 @@ public boolean onOptionsItemSelected(@NonNull MenuItem item) { public void onSaveButtonClick() { Bundle data = new Bundle(); - data.putInt(ConstantsAndStatics.BUNDLE_KEY_ALARM_HOUR, viewModel.getAlarmDateTime().getHour()); - data.putInt(ConstantsAndStatics.BUNDLE_KEY_ALARM_MINUTE, viewModel.getAlarmDateTime().getMinute()); - data.putInt(ConstantsAndStatics.BUNDLE_KEY_ALARM_DAY, viewModel.getAlarmDateTime().getDayOfMonth()); - data.putInt(ConstantsAndStatics.BUNDLE_KEY_ALARM_MONTH, viewModel.getAlarmDateTime().getMonthValue()); - data.putInt(ConstantsAndStatics.BUNDLE_KEY_ALARM_YEAR, viewModel.getAlarmDateTime().getYear()); - data.putInt(ConstantsAndStatics.BUNDLE_KEY_ALARM_TYPE, viewModel.getAlarmType()); - data.putBoolean(ConstantsAndStatics.BUNDLE_KEY_IS_SNOOZE_ON, viewModel.getIsSnoozeOn()); - data.putBoolean(ConstantsAndStatics.BUNDLE_KEY_IS_REPEAT_ON, viewModel.getIsRepeatOn()); - data.putInt(ConstantsAndStatics.BUNDLE_KEY_ALARM_VOLUME, viewModel.getAlarmVolume()); - data.putInt(ConstantsAndStatics.BUNDLE_KEY_SNOOZE_TIME_IN_MINS, viewModel.getSnoozeIntervalInMins()); - data.putInt(ConstantsAndStatics.BUNDLE_KEY_SNOOZE_FREQUENCY, viewModel.getSnoozeFreq()); - data.putIntegerArrayList(ConstantsAndStatics.BUNDLE_KEY_REPEAT_DAYS, viewModel.getRepeatDays()); - data.putParcelable(ConstantsAndStatics.BUNDLE_KEY_ALARM_TONE_URI, viewModel.getAlarmToneUri()); + data.putInt(BUNDLE_KEY_ALARM_HOUR, viewModel.getAlarmDateTime().getHour()); + data.putInt(BUNDLE_KEY_ALARM_MINUTE, viewModel.getAlarmDateTime().getMinute()); + data.putInt(BUNDLE_KEY_ALARM_DAY, viewModel.getAlarmDateTime().getDayOfMonth()); + data.putInt(BUNDLE_KEY_ALARM_MONTH, viewModel.getAlarmDateTime().getMonthValue()); + data.putInt(BUNDLE_KEY_ALARM_YEAR, viewModel.getAlarmDateTime().getYear()); + data.putInt(BUNDLE_KEY_ALARM_TYPE, viewModel.getAlarmType()); + data.putBoolean(BUNDLE_KEY_IS_SNOOZE_ON, viewModel.getIsSnoozeOn()); + data.putBoolean(BUNDLE_KEY_IS_REPEAT_ON, viewModel.getIsRepeatOn()); + data.putInt(BUNDLE_KEY_ALARM_VOLUME, viewModel.getAlarmVolume()); + data.putInt(BUNDLE_KEY_SNOOZE_TIME_IN_MINS, viewModel.getSnoozeIntervalInMins()); + data.putInt(BUNDLE_KEY_SNOOZE_FREQUENCY, viewModel.getSnoozeFreq()); + data.putIntegerArrayList(BUNDLE_KEY_REPEAT_DAYS, viewModel.getRepeatDays()); + data.putParcelable(BUNDLE_KEY_ALARM_TONE_URI, viewModel.getAlarmToneUri()); if (viewModel.getIsRepeatOn()) { - data.putBoolean(ConstantsAndStatics.BUNDLE_KEY_HAS_USER_CHOSEN_DATE, false); + data.putBoolean(BUNDLE_KEY_HAS_USER_CHOSEN_DATE, false); } else { - data.putBoolean(ConstantsAndStatics.BUNDLE_KEY_HAS_USER_CHOSEN_DATE, viewModel.getHasUserChosenDate()); + data.putBoolean(BUNDLE_KEY_HAS_USER_CHOSEN_DATE, viewModel.getHasUserChosenDate()); } if (viewModel.getMode() == MODE_EXISTING_ALARM) { - data.putInt(ConstantsAndStatics.BUNDLE_KEY_OLD_ALARM_HOUR, viewModel.getOldAlarmHour()); - data.putInt(ConstantsAndStatics.BUNDLE_KEY_OLD_ALARM_MINUTE, viewModel.getOldAlarmMinute()); + data.putInt(BUNDLE_KEY_OLD_ALARM_HOUR, viewModel.getOldAlarmHour()); + data.putInt(BUNDLE_KEY_OLD_ALARM_MINUTE, viewModel.getOldAlarmMinute()); } Intent intent = new Intent(); - intent.putExtra(ConstantsAndStatics.BUNDLE_KEY_ALARM_DETAILS, data); + intent.putExtra(BUNDLE_KEY_ALARM_DETAILS, data); setResult(RESULT_OK, intent); this.finish(); } diff --git a/app/src/main/java/in/basulabs/shakealarmclock/Activity_AlarmsList.java b/app/src/main/java/in/basulabs/shakealarmclock/Activity_AlarmsList.java index f5f52f7..f6bd03b 100644 --- a/app/src/main/java/in/basulabs/shakealarmclock/Activity_AlarmsList.java +++ b/app/src/main/java/in/basulabs/shakealarmclock/Activity_AlarmsList.java @@ -1,6 +1,5 @@ package in.basulabs.shakealarmclock; -import android.Manifest; import android.annotation.SuppressLint; import android.app.AlarmManager; import android.app.PendingIntent; @@ -14,57 +13,56 @@ import android.os.Handler; import android.os.Looper; import android.os.Message; +import android.util.Log; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.ViewStub; import android.widget.Button; -import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.annotation.RequiresApi; import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatDelegate; -import androidx.core.app.ActivityCompat; -import androidx.core.content.ContextCompat; -import androidx.fragment.app.DialogFragment; import androidx.lifecycle.ViewModelProvider; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; -import java.time.DayOfWeek; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; import java.time.ZoneId; import java.time.ZonedDateTime; -import java.time.temporal.TemporalAdjusters; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Objects; -public class Activity_AlarmsList extends AppCompatActivity implements AlarmAdapter.AdapterInterface, - AlertDialog_PermissionReason.DialogListener { +public class Activity_AlarmsList extends AppCompatActivity implements AlarmAdapter.AdapterInterface { private AlarmAdapter alarmAdapter; private RecyclerView alarmsRecyclerView; + private AlarmDatabase alarmDatabase; + private ViewModel_AlarmsList viewModel; + private ViewStub viewStub; - private static final int NEW_ALARM_REQUEST_CODE = 2564, EXISTING_ALARM_REQUEST_CODE = 3198, - MY_PERMISSIONS_REQUEST = 857; - - private static final int MODE_ADD_NEW_ALARM = 103, MODE_ACTIVATE_EXISTING_ALARM = 604; - - private static final int MODE_DELETE_ALARM = 504, MODE_DEACTIVATE_ONLY = 509; + /** + * Request code: Used when {@link Activity_AlarmDetails} is created for adding a new alarm. + */ + private static final int NEW_ALARM_REQUEST_CODE = 2564; - private AlarmDatabase alarmDatabase; + /** + * Request code: Used when {@link Activity_AlarmDetails} is created for displaying the details of an existing + * alarm. + */ + private static final int EXISTING_ALARM_REQUEST_CODE = 3198; - private ViewModel_AlarmsList viewModel; + private static final int MODE_ADD_NEW_ALARM = 103; + private static final int MODE_ACTIVATE_EXISTING_ALARM = 604; - private boolean showPermissionDialog = false; + private static final int MODE_DELETE_ALARM = 504; + private static final int MODE_DEACTIVATE_ONLY = 509; - private ViewStub viewStub; @SuppressLint("StaticFieldLeak") private static Activity_AlarmsList myInstance; @@ -77,6 +75,8 @@ protected void onCreate(Bundle savedInstanceState) { setContentView(R.layout.activity_alarmslist); setSupportActionBar(findViewById(R.id.toolbar)); + Log.e(this.getClass().getSimpleName(), "Inside onCreate()"); + alarmDatabase = AlarmDatabase.getInstance(this); viewModel = new ViewModelProvider(this).get(ViewModel_AlarmsList.class); @@ -87,14 +87,16 @@ protected void onCreate(Bundle savedInstanceState) { int defaultTheme = Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q ? ConstantsAndStatics.THEME_SYSTEM : ConstantsAndStatics.THEME_AUTO_TIME; - AppCompatDelegate.setDefaultNightMode(ConstantsAndStatics - .getTheme(sharedPreferences.getInt(ConstantsAndStatics.SHARED_PREF_KEY_THEME, defaultTheme))); + if (savedInstanceState == null) { + AppCompatDelegate.setDefaultNightMode(ConstantsAndStatics + .getTheme(sharedPreferences.getInt(ConstantsAndStatics.SHARED_PREF_KEY_THEME, defaultTheme))); + } myInstance = this; Button addAlarmButton = findViewById(R.id.addAlarmButton); addAlarmButton.setOnClickListener(view -> { - Intent intent = new Intent(getApplicationContext(), Activity_AlarmDetails.class); + Intent intent = new Intent(this, Activity_AlarmDetails.class); intent.setAction(ConstantsAndStatics.ACTION_NEW_ALARM); startActivityForResult(intent, NEW_ALARM_REQUEST_CODE); }); @@ -104,20 +106,30 @@ protected void onCreate(Bundle savedInstanceState) { viewStub = findViewById(R.id.viewStub); - SharedPreferences.Editor prefEditor = - getSharedPreferences(ConstantsAndStatics.SHARED_PREF_FILE_NAME, MODE_PRIVATE) - .edit() - .remove(ConstantsAndStatics.SHARED_PREF_KEY_WAS_APP_RECENTLY_ACTIVE) - .putBoolean(ConstantsAndStatics.SHARED_PREF_KEY_WAS_APP_RECENTLY_ACTIVE, true); - prefEditor.commit(); - initAdapter(); manageViewStub(viewModel.getAlarmsCount(alarmDatabase)); viewModel.getLiveAlarmsCount().observe(this, this::manageViewStub); - checkAndRequestPermission(); + if (getIntent().getAction() != null) { + + if (getIntent().getAction().equals(ConstantsAndStatics.ACTION_NEW_ALARM_FROM_INTENT)) { + + Log.e(this.getClass().getSimpleName(), "Received intent."); + + Intent intent = new Intent(this, Activity_AlarmDetails.class); + intent.setAction(ConstantsAndStatics.ACTION_NEW_ALARM_FROM_INTENT); + + if (getIntent().getExtras() != null) { + Log.e(this.getClass().getSimpleName(), "Extras received too!"); + intent.putExtras(getIntent().getExtras()); + } + + startActivityForResult(intent, NEW_ALARM_REQUEST_CODE); + + } + } } //-------------------------------------------------------------------------------------------------- @@ -176,65 +188,6 @@ public static void onDateChanged() { //-------------------------------------------------------------------------------------------------- - @Override - protected void onResume() { - super.onResume(); - if (showPermissionDialog) { - showPermissionDialog = false; - showPermissionDialog(); - } - } - - //-------------------------------------------------------------------------------------------------- - - private void showPermissionDialog() { - if (! viewModel.getHasPermissionsDialogBeenShownBefore()) { - viewModel.setHasPermissionsDialogBeenShownBefore(true); - DialogFragment dialogPermissionReason = new AlertDialog_PermissionReason( - getResources().getString(R.string.permissionReasonExp_normal)); - dialogPermissionReason.setCancelable(false); - dialogPermissionReason.show(getSupportFragmentManager(), ""); - } - } - - //-------------------------------------------------------------------------------------------------- - - @SuppressWarnings("StatementWithEmptyBody") - private void checkAndRequestPermission() { - if (! viewModel.getHasPermissionBeenRequested()) { - viewModel.setHasPermissionBeenRequested(true); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) - == PackageManager.PERMISSION_GRANTED) { - } else if (shouldShowRequestPermissionRationale(Manifest.permission.READ_EXTERNAL_STORAGE)) { - showPermissionDialog(); - } else { - ActivityCompat.requestPermissions(this, - new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, - MY_PERMISSIONS_REQUEST); - } - } - } - } - - //-------------------------------------------------------------------------------------------------- - - @RequiresApi(api = Build.VERSION_CODES.M) - @Override - public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, - @NonNull int[] grantResults) { - if (requestCode == MY_PERMISSIONS_REQUEST) { - if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { - Toast.makeText(this, "Permission granted", Toast.LENGTH_SHORT).show(); - initAdapter(); - } else { - showPermissionDialog = true; - } - } - } - - //-------------------------------------------------------------------------------------------------- - /** * Initialises {@link #alarmAdapter}, and sets the adapter in {@link #alarmsRecyclerView}. */ @@ -252,56 +205,17 @@ private void initAdapter() { * @param alarmEntity The {@link AlarmEntity} object representing the alarm. * @param repeatDays The days in which the alarm is to repeat. Can be {@code null} is repeat is OFF. */ - private void addOrActivateAlarm(int mode, AlarmEntity alarmEntity, - @Nullable ArrayList repeatDays) { + private void addOrActivateAlarm(int mode, AlarmEntity alarmEntity, @Nullable ArrayList repeatDays) { ConstantsAndStatics.cancelScheduledPeriodicWork(this); AlarmManager alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE); - LocalDateTime alarmDateTime; - LocalDate alarmDate = LocalDate.of(alarmEntity.alarmYear, alarmEntity.alarmMonth, - alarmEntity.alarmDay); - LocalTime alarmTime = LocalTime.of(alarmEntity.alarmHour, alarmEntity.alarmMinutes); - - if (alarmEntity.isRepeatOn && repeatDays != null && repeatDays.size() > 0) { - - Collections.sort(repeatDays); - - alarmDateTime = LocalDateTime.of(LocalDate.now(), alarmTime); - int dayOfWeek = alarmDateTime.getDayOfWeek().getValue(); - - for (int i = 0; i < repeatDays.size(); i++) { - if (repeatDays.get(i) == dayOfWeek) { - if (alarmTime.isAfter(LocalTime.now())) { - // Alarm possible today, nothing more to do, break out of loop. - break; - } - } else if (repeatDays.get(i) > dayOfWeek) { - ///////////////////////////////////////////////////////////////////////// - // There is a day available in the same week for the alarm to ring; - // select that day and break from loop. - //////////////////////////////////////////////////////////////////////// - alarmDateTime = - alarmDateTime.with(TemporalAdjusters.next(DayOfWeek.of(repeatDays.get(i)))); - break; - } - if (i == repeatDays.size() - 1) { - // No day possible in this week. Select the first available date from next week. - alarmDateTime = alarmDateTime - .with(TemporalAdjusters.next(DayOfWeek.of(repeatDays.get(0)))); - } - } - - } else { + if (repeatDays != null) { Collections.sort(repeatDays); } - alarmDateTime = LocalDateTime.of(alarmDate, alarmTime); - - if (! alarmDateTime.isAfter(LocalDateTime.now())) { - alarmDateTime = alarmDateTime.plusDays(1); - } - - } + LocalDateTime alarmDateTime = ConstantsAndStatics.getAlarmDateTime(LocalDate.of(alarmEntity.alarmYear, + alarmEntity.alarmMonth, alarmEntity.alarmDay), LocalTime.of(alarmEntity.alarmHour, + alarmEntity.alarmMinutes), alarmEntity.isRepeatOn, repeatDays); /////////////////////////////////////////////////////////////////////////////////////////// // IMPORTANT: @@ -336,8 +250,8 @@ private void addOrActivateAlarm(int mode, AlarmEntity alarmEntity, data.putInt(ConstantsAndStatics.BUNDLE_KEY_ALARM_ID, alarmID); intent.putExtra(ConstantsAndStatics.BUNDLE_KEY_ALARM_DETAILS, data); - PendingIntent pendingIntent = PendingIntent - .getBroadcast(Activity_AlarmsList.this, alarmID, intent, 0); + PendingIntent pendingIntent = PendingIntent.getBroadcast(Activity_AlarmsList.this, alarmID, intent, + PendingIntent.FLAG_CANCEL_CURRENT); ZonedDateTime zonedDateTime = ZonedDateTime.of(alarmDateTime.withSecond(0), ZoneId.systemDefault()); @@ -403,8 +317,8 @@ private void toggleAlarmState(int hour, int mins, final int newAlarmState) { if (newAlarmState == 0) { deleteOrDeactivateAlarm(MODE_DEACTIVATE_ONLY, hour, mins); } else { - addOrActivateAlarm(MODE_ACTIVATE_EXISTING_ALARM, viewModel.getAlarmEntity(alarmDatabase, hour, - mins), viewModel.getRepeatDays(alarmDatabase, hour, mins)); + addOrActivateAlarm(MODE_ACTIVATE_EXISTING_ALARM, viewModel.getAlarmEntity(alarmDatabase, hour, mins), + viewModel.getRepeatDays(alarmDatabase, hour, mins)); } } @@ -543,26 +457,6 @@ public void handleMessage(@NonNull Message msg) { //-------------------------------------------------------------------------------------------------- - @Override - public void onDialogPositiveClick(DialogFragment dialogFragment) { - if (dialogFragment.getClass().equals(AlertDialog_PermissionReason.class)) { - ActivityCompat.requestPermissions(this, - new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, MY_PERMISSIONS_REQUEST); - } - } - - //-------------------------------------------------------------------------------------------------- - - @Override - public void onDialogNegativeClick(DialogFragment dialogFragment) { - if (dialogFragment.getClass().equals(AlertDialog_PermissionReason.class)) { - Toast.makeText(this, "Alarm may not ring without permission to read storage.", - Toast.LENGTH_LONG).show(); - } - } - - //-------------------------------------------------------------------------------------------------- - private boolean isInstalledOnInternalStorage() { ApplicationInfo io = getApplicationInfo(); return ! io.sourceDir.startsWith("/mnt/") && diff --git a/app/src/main/java/in/basulabs/shakealarmclock/Activity_IntentManager.java b/app/src/main/java/in/basulabs/shakealarmclock/Activity_IntentManager.java new file mode 100644 index 0000000..0303faf --- /dev/null +++ b/app/src/main/java/in/basulabs/shakealarmclock/Activity_IntentManager.java @@ -0,0 +1,333 @@ +package in.basulabs.shakealarmclock; + +import android.app.AlarmManager; +import android.app.PendingIntent; +import android.app.VoiceInteractor; +import android.content.Intent; +import android.content.SharedPreferences; +import android.database.Cursor; +import android.media.AudioManager; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.provider.AlarmClock; +import android.util.Log; + +import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatActivity; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicInteger; + +import static in.basulabs.shakealarmclock.ConstantsAndStatics.ACTION_NEW_ALARM_FROM_INTENT; +import static in.basulabs.shakealarmclock.ConstantsAndStatics.ALARM_TYPE_SOUND_AND_VIBRATE; +import static in.basulabs.shakealarmclock.ConstantsAndStatics.ALARM_TYPE_SOUND_ONLY; +import static in.basulabs.shakealarmclock.ConstantsAndStatics.ALARM_TYPE_VIBRATE_ONLY; +import static in.basulabs.shakealarmclock.ConstantsAndStatics.BUNDLE_KEY_ALARM_DAY; +import static in.basulabs.shakealarmclock.ConstantsAndStatics.BUNDLE_KEY_ALARM_DETAILS; +import static in.basulabs.shakealarmclock.ConstantsAndStatics.BUNDLE_KEY_ALARM_HOUR; +import static in.basulabs.shakealarmclock.ConstantsAndStatics.BUNDLE_KEY_ALARM_ID; +import static in.basulabs.shakealarmclock.ConstantsAndStatics.BUNDLE_KEY_ALARM_MINUTE; +import static in.basulabs.shakealarmclock.ConstantsAndStatics.BUNDLE_KEY_ALARM_MONTH; +import static in.basulabs.shakealarmclock.ConstantsAndStatics.BUNDLE_KEY_ALARM_TONE_URI; +import static in.basulabs.shakealarmclock.ConstantsAndStatics.BUNDLE_KEY_ALARM_TYPE; +import static in.basulabs.shakealarmclock.ConstantsAndStatics.BUNDLE_KEY_ALARM_VOLUME; +import static in.basulabs.shakealarmclock.ConstantsAndStatics.BUNDLE_KEY_ALARM_YEAR; +import static in.basulabs.shakealarmclock.ConstantsAndStatics.BUNDLE_KEY_IS_REPEAT_ON; +import static in.basulabs.shakealarmclock.ConstantsAndStatics.BUNDLE_KEY_REPEAT_DAYS; +import static in.basulabs.shakealarmclock.ConstantsAndStatics.SHARED_PREF_FILE_NAME; +import static in.basulabs.shakealarmclock.ConstantsAndStatics.SHARED_PREF_KEY_DEFAULT_ALARM_TONE_URI; +import static in.basulabs.shakealarmclock.ConstantsAndStatics.SHARED_PREF_KEY_DEFAULT_ALARM_VOLUME; +import static in.basulabs.shakealarmclock.ConstantsAndStatics.SHARED_PREF_KEY_DEFAULT_SNOOZE_FREQ; +import static in.basulabs.shakealarmclock.ConstantsAndStatics.SHARED_PREF_KEY_DEFAULT_SNOOZE_INTERVAL; +import static in.basulabs.shakealarmclock.ConstantsAndStatics.SHARED_PREF_KEY_DEFAULT_SNOOZE_IS_ON; + +/** + * An activity to manage activity actions for this app. + *

+ * This activity has no display and is transparent. It handles incoming intents and finishes itself in {@link + * #onCreate(Bundle)}. + *

+ * The following activity actions are handled: + *

    + *
  • {@link android.provider.AlarmClock#ACTION_SET_ALARM}
  • + *
  • {@link android.provider.AlarmClock#ACTION_DISMISS_ALARM}
  • + *
  • {@link android.provider.AlarmClock#ACTION_SNOOZE_ALARM}
  • + *
+ *

+ */ +public class Activity_IntentManager extends AppCompatActivity { + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + Log.e(this.getClass().getSimpleName(), Objects.requireNonNull(getIntent().getAction())); + + Intent intent = getIntent(); + + switch (Objects.requireNonNull(intent.getAction())) { + + case AlarmClock.ACTION_SET_ALARM: + + Log.e(this.getClass().getSimpleName(), "received action SET_ALARM."); + + if (! intent.hasExtra(AlarmClock.EXTRA_HOUR) || ! intent.hasExtra(AlarmClock.EXTRA_MINUTES)) { + /////////////////////////////////////////////////////////////////////// + // These two extras are necessary for an alarm to be set. Without + // these, the user will be redirected to Activity_AlarmsList. + /////////////////////////////////////////////////////////////////////// + Log.e(this.getClass().getSimpleName(), "Intent does not have any extras."); + + Intent intent1 = new Intent(this, Activity_AlarmsList.class); + intent1.setAction(ACTION_NEW_ALARM_FROM_INTENT) + .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); + startActivity(intent1); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + if (isVoiceInteraction()) { + + Log.e(this.getClass().getSimpleName(), "Voice interaction."); + + Bundle status = new Bundle(); + VoiceInteractor.Prompt prompt = new VoiceInteractor.Prompt( + new String[]{"You can do that in the app."}, + "You can do that in the app."); + + VoiceInteractor.Request request = new VoiceInteractor.CompleteVoiceRequest(prompt, status); + getVoiceInteractor().submitRequest(request); + } + } + } else { + setAlarm(); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + if (isVoiceInteraction()) { + + Log.e(this.getClass().getSimpleName(), "Voice interaction."); + + Bundle status = new Bundle(); + VoiceInteractor.Prompt prompt = new VoiceInteractor.Prompt( + new String[]{"Your alarm has been set by Shake Alarm Clock."}, + "Your alarm has been set by Shake Alarm Clock."); + + VoiceInteractor.Request request = new VoiceInteractor.CompleteVoiceRequest(prompt, status); + getVoiceInteractor().submitRequest(request); + } + } + } + + break; + + case AlarmClock.ACTION_DISMISS_ALARM: + + if (Service_RingAlarm.isThisServiceRunning || Service_SnoozeAlarm.isThisServiceRunning) { + Intent intent1 = new Intent(); + intent1.setAction(ConstantsAndStatics.ACTION_CANCEL_ALARM); + sendBroadcast(intent1); + } else { + Intent intent1 = new Intent(this, Activity_AlarmsList.class); + intent1.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); + startActivity(intent1); + } + + break; + + case AlarmClock.ACTION_SNOOZE_ALARM: + + Intent intent1 = new Intent(); + intent1.setAction(ConstantsAndStatics.ACTION_SNOOZE_ALARM); + sendBroadcast(intent1); + break; + } + + finish(); + + } + + //------------------------------------------------------------------------------------------------------------- + + private void setAlarm() { + + SharedPreferences sharedPreferences = getSharedPreferences(SHARED_PREF_FILE_NAME, MODE_PRIVATE); + + /////////////////////////////////////////////// + // Retrieve all data passed with intent: + ////////////////////////////////////////////// + + Intent intent = getIntent(); + + LocalTime alarmTime = LocalTime.of(Objects.requireNonNull(intent.getExtras()).getInt(AlarmClock.EXTRA_HOUR), + intent.getExtras().getInt(AlarmClock.EXTRA_MINUTES)); + + ArrayList repeatDays; + if (intent.hasExtra(AlarmClock.EXTRA_DAYS)) { + + repeatDays = intent.getIntegerArrayListExtra(AlarmClock.EXTRA_DAYS); + assert repeatDays != null; + + // The EXTRA_DAYS follow java.util.Calendar (Sunday is 1 and Saturday is 7). In this app, we follow + // java.time.DayOfWeek enum (Monday is 1 and Sunday is 7). We change repeatDays accordingly. + ArrayList temp = new ArrayList<>(); + for (int i : repeatDays) { + if (i == 1) { + temp.add(7); + } else { + temp.add(i - 1); + } + } + repeatDays = temp; + + Collections.sort(repeatDays); + } else { + repeatDays = null; + } + + boolean isRepeatOn = repeatDays != null; + + Uri alarmToneUri; + if (intent.hasExtra(AlarmClock.EXTRA_RINGTONE)) { + + if (Objects.equals(intent.getExtras().getString(AlarmClock.EXTRA_RINGTONE), AlarmClock.VALUE_RINGTONE_SILENT)) { + alarmToneUri = null; + } else { + + alarmToneUri = Uri.parse(intent.getExtras().getString(AlarmClock.EXTRA_RINGTONE)); + + if (! doesFileExist(alarmToneUri)) { + // Uri invalid or file doesn't exist; fall back to default tone + alarmToneUri = Uri.parse(sharedPreferences.getString(SHARED_PREF_KEY_DEFAULT_ALARM_TONE_URI, + "content://settings/system/alarm_alert")); + } + + } + } else { + alarmToneUri = Uri.parse(sharedPreferences.getString(SHARED_PREF_KEY_DEFAULT_ALARM_TONE_URI, + "content://settings/system/alarm_alert")); + } + + int volume; + if (alarmToneUri != null) { + AudioManager audioManager = (AudioManager) getSystemService(AUDIO_SERVICE); + volume = sharedPreferences.getInt(SHARED_PREF_KEY_DEFAULT_ALARM_VOLUME, + audioManager.getStreamMaxVolume(AudioManager.STREAM_ALARM) - 1); + } else { + volume = 0; + } + + int alarmType; + if (intent.hasExtra(AlarmClock.EXTRA_VIBRATE)) { + if (intent.getExtras().getBoolean(AlarmClock.EXTRA_VIBRATE)) { + alarmType = volume > 0 ? ALARM_TYPE_SOUND_AND_VIBRATE : ALARM_TYPE_VIBRATE_ONLY; + } else { + alarmType = ALARM_TYPE_SOUND_ONLY; + } + } else { + alarmType = volume > 0 ? ALARM_TYPE_SOUND_AND_VIBRATE : ALARM_TYPE_VIBRATE_ONLY; + } + + //////////////////////////////// + // Now set the alarm: + //////////////////////////////// + AlarmDatabase alarmDatabase = AlarmDatabase.getInstance(this); + + LocalDateTime alarmDateTime = ConstantsAndStatics.getAlarmDateTime(LocalDate.now(), alarmTime, + isRepeatOn, repeatDays); + + if (intent.getExtras().getBoolean(AlarmClock.EXTRA_SKIP_UI, false)) { + // We have been asked to skip the UI. Alarm will be set by this activity itself. + + AlarmEntity alarmEntity = new AlarmEntity(alarmTime.getHour(), alarmTime.getMinute(), true, + sharedPreferences.getBoolean(SHARED_PREF_KEY_DEFAULT_SNOOZE_IS_ON, true), + sharedPreferences.getInt(SHARED_PREF_KEY_DEFAULT_SNOOZE_INTERVAL, 5), + sharedPreferences.getInt(SHARED_PREF_KEY_DEFAULT_SNOOZE_FREQ, 3), + volume, isRepeatOn, alarmType, alarmDateTime.getDayOfMonth(), alarmDateTime.getMonthValue(), + alarmDateTime.getYear(), alarmToneUri, false); + + AtomicInteger alarmID = new AtomicInteger(); + + Thread thread = new Thread(() -> { + alarmDatabase.alarmDAO().addAlarm(alarmEntity); + alarmID.set(alarmDatabase.alarmDAO().getAlarmId(alarmEntity.alarmHour, alarmEntity.alarmMinutes)); + }); + + thread.start(); + try { + thread.join(); + } catch (InterruptedException ignored) { + } + + AlarmManager alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE); + + Intent intent1 = new Intent(this, AlarmBroadcastReceiver.class); + intent1.setAction(ConstantsAndStatics.ACTION_DELIVER_ALARM) + .setFlags(Intent.FLAG_RECEIVER_FOREGROUND); + + alarmEntity.alarmID = alarmID.get(); + Bundle data = alarmEntity.getAlarmDetailsInABundle(); + data.putIntegerArrayList(BUNDLE_KEY_REPEAT_DAYS, repeatDays); + data.remove(BUNDLE_KEY_ALARM_ID); + data.putInt(BUNDLE_KEY_ALARM_ID, alarmID.get()); + intent1.putExtra(BUNDLE_KEY_ALARM_DETAILS, data); + + PendingIntent pendingIntent = PendingIntent.getBroadcast(this, alarmID.get(), + intent1, PendingIntent.FLAG_CANCEL_CURRENT); + + ZonedDateTime zonedDateTime = ZonedDateTime.of(alarmDateTime.withSecond(0), ZoneId.systemDefault()); + + alarmManager.setAlarmClock(new AlarmManager.AlarmClockInfo(zonedDateTime.toEpochSecond() * 1000, + pendingIntent), pendingIntent); + + ConstantsAndStatics.schedulePeriodicWork(this); + + } else { + // We have been asked not to skip the UI. We have some data, and with that data we will start + // Activity_AlarmsList, which will, in turn, start Activity_AlarmDetails, and thereafter the UI will + // be shown. Any missing data will be filled in by Activity_AlarmDetails. + + Intent intent1 = new Intent(this, Activity_AlarmsList.class); + intent1.setAction(ACTION_NEW_ALARM_FROM_INTENT) + .putExtra(BUNDLE_KEY_ALARM_HOUR, alarmDateTime.getHour()) + .putExtra(BUNDLE_KEY_ALARM_MINUTE, alarmDateTime.getMinute()) + .putExtra(BUNDLE_KEY_ALARM_DAY, alarmDateTime.getDayOfMonth()) + .putExtra(BUNDLE_KEY_ALARM_MONTH, alarmDateTime.getMonthValue()) + .putExtra(BUNDLE_KEY_ALARM_YEAR, alarmDateTime.getYear()) + .putExtra(BUNDLE_KEY_ALARM_VOLUME, volume) + .putExtra(BUNDLE_KEY_ALARM_TONE_URI, alarmToneUri) + .putExtra(BUNDLE_KEY_ALARM_TYPE, alarmType) + .putExtra(BUNDLE_KEY_IS_REPEAT_ON, isRepeatOn) + .putExtra(BUNDLE_KEY_REPEAT_DAYS, repeatDays) + .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); + startActivity(intent1); + + } + + } + + //-------------------------------------------------------------------------------------------------------------- + + /** + * Finds whether a file exists or not. + * + * @param uri The Uri of the file. + * + * @return {@code true} if the file exists, otherwise {@code false}. + */ + private boolean doesFileExist(Uri uri) { + try (Cursor cursor = getContentResolver().query(uri, null, null, null, null)) { + return cursor != null; + } + } + + //--------------------------------------------------------------------------------------------------------------- +} diff --git a/app/src/main/java/in/basulabs/shakealarmclock/AlarmDAO.java b/app/src/main/java/in/basulabs/shakealarmclock/AlarmDAO.java index ac221ed..2623446 100644 --- a/app/src/main/java/in/basulabs/shakealarmclock/AlarmDAO.java +++ b/app/src/main/java/in/basulabs/shakealarmclock/AlarmDAO.java @@ -106,7 +106,7 @@ public interface AlarmDAO { * * @return The alarms that are currently active, i.e., in ON state. */ - @Query("SELECT * FROM alarm_entity WHERE isAlarmOn = 1") + @Query("SELECT * FROM alarm_entity WHERE isAlarmOn = 1 ORDER BY alarmHour, alarmMinutes") List getActiveAlarms(); /** diff --git a/app/src/main/java/in/basulabs/shakealarmclock/ConstantsAndStatics.java b/app/src/main/java/in/basulabs/shakealarmclock/ConstantsAndStatics.java index 4ee7326..e27e788 100644 --- a/app/src/main/java/in/basulabs/shakealarmclock/ConstantsAndStatics.java +++ b/app/src/main/java/in/basulabs/shakealarmclock/ConstantsAndStatics.java @@ -4,13 +4,20 @@ import android.content.Intent; import android.util.Log; +import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatDelegate; import androidx.work.Configuration; import androidx.work.ExistingPeriodicWorkPolicy; import androidx.work.PeriodicWorkRequest; import androidx.work.WorkManager; +import java.time.DayOfWeek; +import java.time.LocalDate; +import java.time.LocalDateTime; import java.time.LocalTime; +import java.time.temporal.TemporalAdjusters; +import java.util.ArrayList; +import java.util.Collections; import java.util.concurrent.TimeUnit; /** @@ -147,9 +154,12 @@ final class ConstantsAndStatics { */ static final String ACTION_EXISTING_ALARM = "in.basulabs.shakealarmclock.ACTION_EXISTING_ALARM"; + static final String ACTION_NEW_ALARM_FROM_INTENT = + "in.basulabs.shakealarmclock.ACTION_NEW_ALARM_FROM_INTENT"; + /** - * Indicates whether {@link Activity_RingtonePicker} should play the ringtone when the user clicks on a - * {@link android.widget.RadioButton}. Default: {@code true}. + * Indicates whether {@link Activity_RingtonePicker} should play the ringtone when the user clicks on a {@link + * android.widget.RadioButton}. Default: {@code true}. */ static final String EXTRA_PLAY_RINGTONE = "in.basulabs.shakealarmclock.EXTRA_PLAY_RINGTONE"; @@ -288,6 +298,8 @@ final class ConstantsAndStatics { */ static final String WORK_NAME_ACTIVATE_ALARMS = "in.basulabs.WORK_ACTIVATE_ALARMS"; + //--------------------------------------------------------------------------------------------------------- + /** * Creates a {@link PeriodicWorkRequest} and enqueues a unique work using {@link * WorkManager#enqueueUniquePeriodicWork(String, ExistingPeriodicWorkPolicy, PeriodicWorkRequest)}. @@ -307,6 +319,8 @@ static void schedulePeriodicWork(Context context) { ExistingPeriodicWorkPolicy.REPLACE, periodicWorkRequest); } + //--------------------------------------------------------------------------------------------------------- + /** * Cancels a scheduled work using {@link WorkManager#cancelUniqueWork(String)}. * @@ -321,6 +335,8 @@ static void cancelScheduledPeriodicWork(Context context) { WorkManager.getInstance(context).cancelUniqueWork(WORK_NAME_ACTIVATE_ALARMS); } + //--------------------------------------------------------------------------------------------------------- + /** * Get the theme that can be applied using {@link AppCompatDelegate#setDefaultNightMode(int)}. * @@ -339,7 +355,7 @@ static int getTheme(int theme) { case THEME_DARK: return AppCompatDelegate.MODE_NIGHT_YES; default: - if (LocalTime.now().isAfter(LocalTime.of(22, 0)) + if (LocalTime.now().isAfter(LocalTime.of(21, 59)) || LocalTime.now().isBefore(LocalTime.of(6, 0))) { return AppCompatDelegate.MODE_NIGHT_YES; } else { @@ -348,6 +364,8 @@ static int getTheme(int theme) { } } + //--------------------------------------------------------------------------------------------------------- + static void killServices(Context context, int alarmID) { if (Service_RingAlarm.isThisServiceRunning && Service_RingAlarm.alarmID == alarmID) { Intent intent = new Intent(context, Service_RingAlarm.class); @@ -359,4 +377,61 @@ static void killServices(Context context, int alarmID) { } } + //--------------------------------------------------------------------------------------------------------- + + /** + * Get the date and time when the alarm should ring. + * + * @param alarmDate The alarm date as chosen by the user. + * @param alarmTime The alarm time as chosen by the user. + * @param isRepeatOn Whether repeat is on or off. + * @param repeatDays The days when the alarm should be repeated. Should follow {@link DayOfWeek} enum. + * + * @return A {@link LocalDateTime} object representing when the alarm should ring. This should be transformed into a + * {@link java.time.ZonedDateTime} object and then passed to {@link android.app.AlarmManager}. + */ + static LocalDateTime getAlarmDateTime(LocalDate alarmDate, LocalTime alarmTime, boolean isRepeatOn, + @Nullable ArrayList repeatDays) { + + LocalDateTime alarmDateTime; + + if (isRepeatOn && repeatDays != null && repeatDays.size() > 0) { + + Collections.sort(repeatDays); + + alarmDateTime = LocalDateTime.of(LocalDate.now(), alarmTime); + int dayOfWeek = alarmDateTime.getDayOfWeek().getValue(); + + for (int i = 0; i < repeatDays.size(); i++) { + if (repeatDays.get(i) == dayOfWeek) { + if (alarmTime.isAfter(LocalTime.now())) { + // Alarm possible today, nothing more to do, break out of loop. + break; + } + } else if (repeatDays.get(i) > dayOfWeek) { + ///////////////////////////////////////////////////////////////////////// + // There is a day available in the same week for the alarm to ring; + // select that day and break from loop. + //////////////////////////////////////////////////////////////////////// + alarmDateTime = alarmDateTime.with(TemporalAdjusters.next(DayOfWeek.of(repeatDays.get(i)))); + break; + } + if (i == repeatDays.size() - 1) { + // No day possible in this week. Select the first available date from next week. + alarmDateTime = alarmDateTime.with(TemporalAdjusters.next(DayOfWeek.of(repeatDays.get(0)))); + } + } + + } else { + + alarmDateTime = LocalDateTime.of(alarmDate, alarmTime); + + if (! alarmDateTime.isAfter(LocalDateTime.now())) { + alarmDateTime = alarmDateTime.plusDays(1); + } + } + + return alarmDateTime; + } + } diff --git a/app/src/main/java/in/basulabs/shakealarmclock/Service_RingAlarm.java b/app/src/main/java/in/basulabs/shakealarmclock/Service_RingAlarm.java index dd0d307..c04dc62 100644 --- a/app/src/main/java/in/basulabs/shakealarmclock/Service_RingAlarm.java +++ b/app/src/main/java/in/basulabs/shakealarmclock/Service_RingAlarm.java @@ -250,7 +250,6 @@ private Notification buildRingNotification() { */ private void ringAlarm() { - notificationManager.notify(NOTIFICATION_ID, buildRingNotification()); initialiseShakeSensor(); @@ -330,6 +329,7 @@ private void snoozeAlarm() { intent.putExtra(BUNDLE_KEY_NO_OF_TIMES_SNOOZED, numberOfTimesTheAlarmHasBeenSnoozed); ContextCompat.startForegroundService(this, intent); + stopForeground(true); stopSelf(); } else { dismissAlarm(); @@ -367,8 +367,7 @@ private void dismissAlarm() { LocalTime alarmTime = LocalTime.of(alarmDetails.getInt(ConstantsAndStatics.BUNDLE_KEY_ALARM_HOUR), alarmDetails.getInt(ConstantsAndStatics.BUNDLE_KEY_ALARM_MINUTE)); - ArrayList repeatDays = alarmDetails - .getIntegerArrayList(ConstantsAndStatics.BUNDLE_KEY_REPEAT_DAYS); + ArrayList repeatDays = alarmDetails.getIntegerArrayList(ConstantsAndStatics.BUNDLE_KEY_REPEAT_DAYS); assert repeatDays != null; Collections.sort(repeatDays); @@ -397,6 +396,7 @@ private void dismissAlarm() { } ConstantsAndStatics.schedulePeriodicWork(this); + stopForeground(true); stopSelf(); } @@ -438,12 +438,12 @@ private void setAlarm(LocalDateTime alarmDateTime) { intent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND); intent.putExtra(ConstantsAndStatics.BUNDLE_KEY_ALARM_DETAILS, alarmDetails); - PendingIntent pendingIntent = PendingIntent.getBroadcast(this, alarmID, intent, 0); + PendingIntent pendingIntent = PendingIntent.getBroadcast(this, alarmID, intent, PendingIntent.FLAG_CANCEL_CURRENT); ZonedDateTime zonedDateTime = ZonedDateTime.of(alarmDateTime.withSecond(0), ZoneId.systemDefault()); - alarmManager.setAlarmClock( - new AlarmManager.AlarmClockInfo(zonedDateTime.toEpochSecond() * 1000, pendingIntent), pendingIntent); + alarmManager.setAlarmClock(new AlarmManager.AlarmClockInfo(zonedDateTime.toEpochSecond() * 1000, + pendingIntent), pendingIntent); } //--------------------------------------------------------------------------------------------------- diff --git a/app/src/main/java/in/basulabs/shakealarmclock/Service_SnoozeAlarm.java b/app/src/main/java/in/basulabs/shakealarmclock/Service_SnoozeAlarm.java index 4a53746..4d0c2b2 100644 --- a/app/src/main/java/in/basulabs/shakealarmclock/Service_SnoozeAlarm.java +++ b/app/src/main/java/in/basulabs/shakealarmclock/Service_SnoozeAlarm.java @@ -235,6 +235,7 @@ private void dismissAlarm() { pendingIntent2), pendingIntent2); } ConstantsAndStatics.schedulePeriodicWork(this); + stopForeground(true); stopSelf(); } diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 1ca95f6..ed19651 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -29,7 +29,19 @@ @color/defaultLabelColor - + \ No newline at end of file