diff --git a/app/src/main/java/com/philliphsu/clock2/MainActivity.java b/app/src/main/java/com/philliphsu/clock2/MainActivity.java index 5fe79ce..ef3343d 100644 --- a/app/src/main/java/com/philliphsu/clock2/MainActivity.java +++ b/app/src/main/java/com/philliphsu/clock2/MainActivity.java @@ -267,7 +267,7 @@ public class MainActivity extends BaseActivity { public Fragment getItem(int position) { switch (position) { case PAGE_ALARMS: - return AlarmsFragment.newInstance(1); + return new AlarmsFragment(); case PAGE_TIMERS: return new TimersFragment(); case PAGE_STOPWATCH: diff --git a/app/src/main/java/com/philliphsu/clock2/alarms/misc/AlarmController.java b/app/src/main/java/com/philliphsu/clock2/alarms/misc/AlarmController.java index c80b149..e940570 100644 --- a/app/src/main/java/com/philliphsu/clock2/alarms/misc/AlarmController.java +++ b/app/src/main/java/com/philliphsu/clock2/alarms/misc/AlarmController.java @@ -4,12 +4,15 @@ import android.app.AlarmManager; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; +import android.os.Build; import android.support.design.widget.Snackbar; import android.util.Log; import android.view.View; +import com.philliphsu.clock2.MainActivity; import com.philliphsu.clock2.R; import com.philliphsu.clock2.alarms.Alarm; +import com.philliphsu.clock2.alarms.ui.AlarmsFragment; import com.philliphsu.clock2.ringtone.AlarmActivity; import com.philliphsu.clock2.ringtone.playback.AlarmRingtoneService; import com.philliphsu.clock2.alarms.background.PendingAlarmScheduler; @@ -53,46 +56,56 @@ public final class AlarmController { * Schedules the alarm with the {@link AlarmManager}. * If {@code alarm.}{@link Alarm#isEnabled() isEnabled()} * returns false, this does nothing and returns immediately. + * + * If there is already an alarm for this Intent scheduled (with the equality of two + * intents being defined by filterEquals(Intent)), then it will be removed and replaced + * by this one. For most of our uses, the relevant criteria for equality will be the + * action, the data, and the class (component). Although not documented, the request code + * of a PendingIntent is also considered to determine equality of two intents. */ public void scheduleAlarm(Alarm alarm, boolean showSnackbar) { if (!alarm.isEnabled()) { - Log.i(TAG, "Skipped scheduling an alarm because it was not enabled"); return; } - // Does nothing if it's not posted. This is primarily here for when alarms // are updated, instead of newly created, so that we don't leave behind // stray upcoming alarm notifications. This occurs e.g. when a single-use // alarm is updated to recur on a weekday later than the current day. removeUpcomingAlarmNotification(alarm); - Log.d(TAG, "Scheduling alarm " + alarm); AlarmManager am = (AlarmManager) mAppContext.getSystemService(Context.ALARM_SERVICE); - // If there is already an alarm for this Intent scheduled (with the equality of two - // intents being defined by filterEquals(Intent)), then it will be removed and replaced - // by this one. For most of our uses, the relevant criteria for equality will be the - // action, the data, and the class (component). Although not documented, the request code - // of a PendingIntent is also considered to determine equality of two intents. - // WAKEUP alarm types wake the CPU up, but NOT the screen. If that is what you want, you need - // to handle that yourself by using a wakelock, etc.. - // We use a WAKEUP alarm to send the upcoming alarm notification so it goes off even if the - // device is asleep. Otherwise, it will not go off until the device is turned back on. final long ringAt = alarm.isSnoozed() ? alarm.snoozingUntil() : alarm.ringsAt(); - am.setExact(AlarmManager.RTC_WAKEUP, ringAt, alarmIntent(alarm, false)); + final PendingIntent alarmIntent = alarmIntent(alarm, false); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + Intent viewAlarm = new Intent(mAppContext, MainActivity.class); + viewAlarm.putExtra(AlarmsFragment.EXTRA_SCROLL_TO_ALARM_ID, alarm.getId()); + PendingIntent showIntent = PendingIntent.getActivity(mAppContext, + alarm.getIntId(), viewAlarm, FLAG_CANCEL_CURRENT); + AlarmManager.AlarmClockInfo info = new AlarmManager.AlarmClockInfo(ringAt, showIntent); + am.setAlarmClock(info, alarmIntent); + } else { + // WAKEUP alarm types wake the CPU up, but NOT the screen; + // you would handle that yourself by using a wakelock, etc.. + am.setExact(AlarmManager.RTC_WAKEUP, ringAt, alarmIntent); + // Show alarm in the status bar + Intent alarmChanged = new Intent("android.intent.action.ALARM_CHANGED"); + alarmChanged.putExtra("alarmSet", true/*enabled*/); + mAppContext.sendBroadcast(alarmChanged); + } final int hoursToNotifyInAdvance = AlarmPreferences.hoursBeforeUpcoming(mAppContext); if (hoursToNotifyInAdvance > 0 || alarm.isSnoozed()) { // If snoozed, upcoming note posted immediately. long upcomingAt = ringAt - HOURS.toMillis(hoursToNotifyInAdvance); + // We use a WAKEUP alarm to send the upcoming alarm notification so it goes off even if the + // device is asleep. Otherwise, it will not go off until the device is turned back on. am.set(AlarmManager.RTC_WAKEUP, upcomingAt, notifyUpcomingAlarmIntent(alarm, false)); } if (showSnackbar) { String message = mAppContext.getString(R.string.alarm_set_for, - DurationUtils.toString(mAppContext, alarm.ringsIn(), false /*abbreviate?*/)); - // TODO: Consider adding delay to allow the alarm item animation - // to finish first before we show the snackbar. Inbox app does this. + DurationUtils.toString(mAppContext, alarm.ringsIn(), false/*abbreviate*/)); showSnackbar(message); } } @@ -104,7 +117,6 @@ public final class AlarmController { * and is enabled. */ public void cancelAlarm(Alarm alarm, boolean showSnackbar, boolean rescheduleIfRecurring) { - // TODO: Consider doing this in a new thread. Log.d(TAG, "Cancelling alarm " + alarm); AlarmManager am = (AlarmManager) mAppContext.getSystemService(Context.ALARM_SERVICE); @@ -112,6 +124,12 @@ public final class AlarmController { if (pi != null) { am.cancel(pi); pi.cancel(); + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { + // Remove alarm in the status bar + Intent alarmChanged = new Intent("android.intent.action.ALARM_CHANGED"); + alarmChanged.putExtra("alarmSet", false/*enabled*/); + mAppContext.sendBroadcast(alarmChanged); + } } pi = notifyUpcomingAlarmIntent(alarm, true); @@ -124,6 +142,7 @@ public final class AlarmController { removeUpcomingAlarmNotification(alarm); final int hoursToNotifyInAdvance = AlarmPreferences.hoursBeforeUpcoming(mAppContext); + // ------------------------------------------------------------------------------------ // TOneverDO: Place block after making value changes to the alarm. if ((hoursToNotifyInAdvance > 0 && showSnackbar // TODO: Consider showing the snackbar for non-upcoming alarms too; @@ -134,6 +153,7 @@ public final class AlarmController { formatTime(mAppContext, time)); showSnackbar(msg); } + // ------------------------------------------------------------------------------------ if (alarm.isSnoozed()) { alarm.stopSnoozing(); @@ -195,40 +215,24 @@ public final class AlarmController { } private PendingIntent alarmIntent(Alarm alarm, boolean retrievePrevious) { - // TODO: Use appropriate subclass instead Intent intent = new Intent(mAppContext, AlarmActivity.class) .putExtra(AlarmActivity.EXTRA_RINGING_OBJECT, alarm); int flag = retrievePrevious ? FLAG_NO_CREATE : FLAG_CANCEL_CURRENT; - PendingIntent pi = getActivity(mAppContext, alarm.getIntId(), intent, flag); // Even when we try to retrieve a previous instance that actually did exist, - // null can be returned for some reason. -/* - if (retrievePrevious) { - checkNotNull(pi); - } -*/ - return pi; + // null can be returned for some reason. Thus, we don't checkNotNull(). + return getActivity(mAppContext, alarm.getIntId(), intent, flag); } private PendingIntent notifyUpcomingAlarmIntent(Alarm alarm, boolean retrievePrevious) { Intent intent = new Intent(mAppContext, UpcomingAlarmReceiver.class) .putExtra(UpcomingAlarmReceiver.EXTRA_ALARM, alarm); if (alarm.isSnoozed()) { - // TODO: Will this affect retrieving a previous instance? Say if the previous instance - // didn't have this action set initially, but at a later time we made a new instance - // with it set. intent.setAction(UpcomingAlarmReceiver.ACTION_SHOW_SNOOZING); } int flag = retrievePrevious ? FLAG_NO_CREATE : FLAG_CANCEL_CURRENT; - PendingIntent pi = PendingIntent.getBroadcast(mAppContext, alarm.getIntId(), intent, flag); // Even when we try to retrieve a previous instance that actually did exist, - // null can be returned for some reason. -/* - if (retrievePrevious) { - checkNotNull(pi); - } -*/ - return pi; + // null can be returned for some reason. Thus, we don't checkNotNull(). + return PendingIntent.getBroadcast(mAppContext, alarm.getIntId(), intent, flag); } private void showSnackbar(final String message) { diff --git a/app/src/main/java/com/philliphsu/clock2/alarms/ui/AlarmsFragment.java b/app/src/main/java/com/philliphsu/clock2/alarms/ui/AlarmsFragment.java index eab231b..baa7418 100644 --- a/app/src/main/java/com/philliphsu/clock2/alarms/ui/AlarmsFragment.java +++ b/app/src/main/java/com/philliphsu/clock2/alarms/ui/AlarmsFragment.java @@ -1,28 +1,22 @@ package com.philliphsu.clock2.alarms.ui; -import android.app.Activity; -import android.content.Intent; -import android.media.RingtoneManager; -import android.net.Uri; import android.os.Bundle; -import android.os.Handler; import android.support.annotation.Nullable; import android.support.v4.content.Loader; import android.support.v7.widget.RecyclerView; -import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import com.philliphsu.clock2.R; -import com.philliphsu.clock2.list.RecyclerViewFragment; -import com.philliphsu.clock2.dialogs.TimePickerDialogController; import com.philliphsu.clock2.alarms.Alarm; +import com.philliphsu.clock2.alarms.data.AlarmCursor; import com.philliphsu.clock2.alarms.data.AlarmsListCursorLoader; import com.philliphsu.clock2.alarms.data.AsyncAlarmsTableUpdateHandler; import com.philliphsu.clock2.alarms.misc.AlarmController; +import com.philliphsu.clock2.dialogs.TimePickerDialogController; +import com.philliphsu.clock2.list.RecyclerViewFragment; import com.philliphsu.clock2.timepickers.BaseTimePickerDialog; -import com.philliphsu.clock2.alarms.data.AlarmCursor; import com.philliphsu.clock2.util.DelayedSnackbarHandler; import static com.philliphsu.clock2.util.FragmentTagUtils.makeTag; @@ -30,54 +24,19 @@ import static com.philliphsu.clock2.util.FragmentTagUtils.makeTag; public class AlarmsFragment extends RecyclerViewFragment implements BaseTimePickerDialog.OnTimeSetListener { private static final String TAG = "AlarmsFragment"; - private static final String KEY_EXPANDED_POSITION = "expanded_position"; - - // TODO: Delete these constants. We no longer use EditAlarmActivity. -// @Deprecated -// private static final int REQUEST_EDIT_ALARM = 0; -// // Public because MainActivity needs to use it. -// // TODO: private because we handle fab clicks in the fragment now -// @Deprecated -// public static final int REQUEST_CREATE_ALARM = 1; - - // TODO: Delete this. We no longer use the system's ringtone picker. - public static final int REQUEST_PICK_RINGTONE = 1; public static final String EXTRA_SCROLL_TO_ALARM_ID = "com.philliphsu.clock2.alarms.extra.SCROLL_TO_ALARM_ID"; private AsyncAlarmsTableUpdateHandler mAsyncUpdateHandler; private AlarmController mAlarmController; - // TODO: Delete this. If I recall correctly, this was just used for delaying item animations. - private Handler mHandler = new Handler(); private View mSnackbarAnchor; private TimePickerDialogController mTimePickerDialogController; private int mExpandedPosition = RecyclerView.NO_POSITION; - /** - * Mandatory empty constructor for the fragment manager to instantiate the - * fragment (e.g. upon screen orientation changes). - */ - public AlarmsFragment() {} - - // TODO: Customize parameter initialization - @SuppressWarnings("unused") - public static AlarmsFragment newInstance(int columnCount) { - AlarmsFragment fragment = new AlarmsFragment(); - Bundle args = new Bundle(); - // TODO Put any arguments in bundle - fragment.setArguments(args); - return fragment; - } - @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - - if (getArguments() != null) { - // TODO Read arguments - } - if (savedInstanceState != null) { // Restore the value of the last expanded position here. // We cannot tell the adapter to expand this item until onLoadFinished() @@ -144,61 +103,8 @@ public class AlarmsFragment extends RecyclerViewFragment= 0) { scrollToPosition(position); - onScrolledToStableId(mScrollToStableId, position); + onScrolledToStableId(stableId, position); } } - // Reset - mScrollToStableId = RecyclerView.NO_ID; } }