Created AlarmController class and moved relevant AlarmUtils code
This commit is contained in:
parent
08e12cd14f
commit
058d6c86b7
@ -7,7 +7,7 @@ import android.view.View;
|
||||
|
||||
import com.philliphsu.clock2.alarms.ScrollHandler;
|
||||
import com.philliphsu.clock2.model.DatabaseManager;
|
||||
import com.philliphsu.clock2.util.AlarmUtils;
|
||||
import com.philliphsu.clock2.util.AlarmController;
|
||||
|
||||
/**
|
||||
* Created by Phillip Hsu on 7/1/2016.
|
||||
@ -20,15 +20,19 @@ public final class AsyncItemChangeHandler {
|
||||
private final Context mContext;
|
||||
private final View mSnackbarAnchor;
|
||||
private final ScrollHandler mScrollHandler;
|
||||
private final AlarmController mAlarmController;
|
||||
|
||||
/**
|
||||
* @param snackbarAnchor an optional anchor for a Snackbar to anchor to
|
||||
* @param scrollHandler
|
||||
* @param context the Context from which we get the application context
|
||||
* @param snackbarAnchor
|
||||
*/
|
||||
public AsyncItemChangeHandler(Context context, View snackbarAnchor, ScrollHandler scrollHandler) {
|
||||
public AsyncItemChangeHandler(Context context, View snackbarAnchor,
|
||||
ScrollHandler scrollHandler,
|
||||
AlarmController alarmController) {
|
||||
mContext = context.getApplicationContext(); // to prevent memory leaks
|
||||
mSnackbarAnchor = snackbarAnchor;
|
||||
mScrollHandler = scrollHandler;
|
||||
mAlarmController = alarmController;
|
||||
}
|
||||
|
||||
public void asyncAddAlarm(final Alarm alarm) {
|
||||
@ -54,6 +58,7 @@ public final class AsyncItemChangeHandler {
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Integer integer) {
|
||||
mAlarmController.cancelAlarm(alarm, false);
|
||||
if (mSnackbarAnchor != null) {
|
||||
// TODO: Consider adding delay to allow the alarm item animation
|
||||
// to finish first before we show the snackbar. Inbox app does this.
|
||||
@ -87,17 +92,13 @@ public final class AsyncItemChangeHandler {
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Long result) {
|
||||
AlarmUtils.scheduleAlarm(mContext, mAlarm, true);
|
||||
// TODO: Consider adding delay to allow the alarm item animation
|
||||
// to finish first before we show the snackbar. Inbox app does this.
|
||||
mAlarmController.scheduleAlarm(mAlarm, true);
|
||||
if (mScrollHandler != null) {
|
||||
// Prepare to scroll to this alarm
|
||||
mScrollHandler.setScrollToStableId(result);
|
||||
}
|
||||
if (mSnackbarAnchor != null) {
|
||||
// TODO: Consider adding delay to allow the alarm item animation
|
||||
// to finish first before we show the snackbar. Inbox app does this.
|
||||
String message = AlarmUtils.getRingsInText(mContext, mAlarm.ringsIn());
|
||||
AlarmUtils.showSnackbar(mSnackbarAnchor, message);
|
||||
}
|
||||
}
|
||||
|
||||
final Long insertAlarm() {
|
||||
|
||||
@ -6,7 +6,7 @@ import android.content.Intent;
|
||||
|
||||
import com.philliphsu.clock2.model.AlarmDatabaseHelper.AlarmCursor;
|
||||
import com.philliphsu.clock2.model.DatabaseManager;
|
||||
import com.philliphsu.clock2.util.AlarmUtils;
|
||||
import com.philliphsu.clock2.util.AlarmController;
|
||||
|
||||
/**
|
||||
* An {@link IntentService} subclass for handling asynchronous task requests in
|
||||
@ -59,6 +59,7 @@ public class OnBootUpAlarmScheduler extends IntentService {
|
||||
@Override
|
||||
protected void onHandleIntent(Intent intent) {
|
||||
if (intent != null) {
|
||||
AlarmController controller = new AlarmController(this, null);
|
||||
// IntentService works in a background thread, so this won't hold us up.
|
||||
AlarmCursor cursor = DatabaseManager.getInstance(this).queryEnabledAlarms();
|
||||
while (cursor.moveToNext()) {
|
||||
@ -67,7 +68,7 @@ public class OnBootUpAlarmScheduler extends IntentService {
|
||||
throw new IllegalStateException(
|
||||
"queryEnabledAlarms() returned alarm(s) that aren't enabled");
|
||||
}
|
||||
AlarmUtils.scheduleAlarm(this, alarm, false);
|
||||
controller.scheduleAlarm(alarm, false);
|
||||
}
|
||||
cursor.close();
|
||||
|
||||
|
||||
@ -5,7 +5,7 @@ import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
||||
import com.philliphsu.clock2.model.DatabaseManager;
|
||||
import com.philliphsu.clock2.util.AlarmUtils;
|
||||
import com.philliphsu.clock2.util.AlarmController;
|
||||
|
||||
import static com.philliphsu.clock2.util.Preconditions.checkNotNull;
|
||||
|
||||
@ -47,12 +47,10 @@ public class PendingAlarmScheduler extends BroadcastReceiver {
|
||||
throw new IllegalStateException("Alarm must be enabled!");
|
||||
}
|
||||
alarm.ignoreUpcomingRingTime(false); // allow #ringsWithinHours() to behave normally
|
||||
// Because showToast = false, we don't do any UI work.
|
||||
// TODO: Since we're in a worker thread, verify that the
|
||||
// UI related code within will not cause us to crash.
|
||||
AlarmUtils.scheduleAlarm(context, alarm, false);
|
||||
// Update the db
|
||||
AlarmUtils.save(context, alarm);
|
||||
// No UI work is done
|
||||
AlarmController controller = new AlarmController(context, null);
|
||||
controller.scheduleAlarm(alarm, false);
|
||||
controller.save(alarm);
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
@ -10,7 +10,7 @@ import android.os.AsyncTask;
|
||||
import android.support.v4.app.NotificationCompat;
|
||||
|
||||
import com.philliphsu.clock2.model.DatabaseManager;
|
||||
import com.philliphsu.clock2.util.AlarmUtils;
|
||||
import com.philliphsu.clock2.util.AlarmController;
|
||||
|
||||
import static android.app.PendingIntent.FLAG_ONE_SHOT;
|
||||
import static com.philliphsu.clock2.util.DateFormatUtils.formatTime;
|
||||
@ -47,8 +47,7 @@ public class UpcomingAlarmReceiver extends BroadcastReceiver {
|
||||
@Override
|
||||
protected void onPostExecute(Alarm alarm) {
|
||||
if (ACTION_DISMISS_NOW.equals(intent.getAction())) {
|
||||
// This MUST be done on the UI thread.
|
||||
AlarmUtils.cancelAlarm(context, alarm, true);
|
||||
new AlarmController(context, null).cancelAlarm(alarm, false);
|
||||
} else {
|
||||
// Prepare notification
|
||||
// http://stackoverflow.com/a/15803726/5055032
|
||||
|
||||
@ -18,6 +18,7 @@ import com.philliphsu.clock2.DaysOfWeek;
|
||||
import com.philliphsu.clock2.OnListItemInteractionListener;
|
||||
import com.philliphsu.clock2.R;
|
||||
import com.philliphsu.clock2.model.AlarmsRepository;
|
||||
import com.philliphsu.clock2.util.AlarmController;
|
||||
import com.philliphsu.clock2.util.AlarmUtils;
|
||||
|
||||
import java.util.Date;
|
||||
@ -38,6 +39,8 @@ import static com.philliphsu.clock2.util.DateFormatUtils.formatTime;
|
||||
public class AlarmViewHolder extends BaseViewHolder<Alarm> implements AlarmCountdown.OnTickListener {
|
||||
private static final RelativeSizeSpan AMPM_SIZE_SPAN = new RelativeSizeSpan(0.5f);
|
||||
|
||||
private final AlarmController mAlarmController;
|
||||
|
||||
@Bind(R.id.time) TextView mTime;
|
||||
@Bind(R.id.on_off_switch) SwitchCompat mSwitch;
|
||||
@Bind(R.id.label) TextView mLabel;
|
||||
@ -45,8 +48,17 @@ public class AlarmViewHolder extends BaseViewHolder<Alarm> implements AlarmCount
|
||||
@Bind(R.id.recurring_days) TextView mDays;
|
||||
@Bind(R.id.dismiss) Button mDismissButton;
|
||||
|
||||
@Deprecated // TODO: Delete this, the only usage is from AlarmsAdapter (SortedList), which is not used anymore.
|
||||
public AlarmViewHolder(ViewGroup parent, OnListItemInteractionListener<Alarm> listener) {
|
||||
super(parent, R.layout.item_alarm, listener);
|
||||
mAlarmController = null;
|
||||
mCountdown.setOnTickListener(this);
|
||||
}
|
||||
|
||||
public AlarmViewHolder(ViewGroup parent, OnListItemInteractionListener<Alarm> listener,
|
||||
AlarmController alarmController) {
|
||||
super(parent, R.layout.item_alarm, listener);
|
||||
mAlarmController = alarmController;
|
||||
mCountdown.setOnTickListener(this);
|
||||
}
|
||||
|
||||
@ -76,7 +88,7 @@ public class AlarmViewHolder extends BaseViewHolder<Alarm> implements AlarmCount
|
||||
} else {
|
||||
// Dismisses the current upcoming alarm and handles scheduling the next alarm for us.
|
||||
// Since changes are saved to the database, this prompts a UI refresh.
|
||||
AlarmUtils.cancelAlarm(getContext(), alarm, true);
|
||||
mAlarmController.cancelAlarm(alarm, true);
|
||||
}
|
||||
// TOneverDO: AlarmUtils.cancelAlarm() otherwise it will be called twice
|
||||
/*
|
||||
@ -127,11 +139,10 @@ public class AlarmViewHolder extends BaseViewHolder<Alarm> implements AlarmCount
|
||||
alarm.setEnabled(checked);
|
||||
if (alarm.isEnabled()) {
|
||||
// TODO: On Moto X, upcoming notification doesn't post immediately
|
||||
AlarmUtils.scheduleAlarm(getContext(), alarm, true);
|
||||
AlarmUtils.sendShowSnackbarBroadcast(getContext(), AlarmUtils.getRingsInText(getContext(), alarm.ringsIn()));
|
||||
AlarmUtils.save(getContext(), alarm);
|
||||
mAlarmController.scheduleAlarm(alarm, true);
|
||||
mAlarmController.save(alarm);
|
||||
} else {
|
||||
AlarmUtils.cancelAlarm(getContext(), alarm, true);
|
||||
mAlarmController.cancelAlarm(alarm, true);
|
||||
// cancelAlarm() already calls save() for you.
|
||||
}
|
||||
mSwitch.setPressed(false); // clear the pressed focus, esp. if setPressed(true) was called manually
|
||||
|
||||
@ -8,6 +8,7 @@ import android.view.ViewGroup;
|
||||
import com.philliphsu.clock2.Alarm;
|
||||
import com.philliphsu.clock2.OnListItemInteractionListener;
|
||||
import com.philliphsu.clock2.model.AlarmDatabaseHelper.AlarmCursor;
|
||||
import com.philliphsu.clock2.util.AlarmController;
|
||||
|
||||
/**
|
||||
* Created by Phillip Hsu on 6/29/2016.
|
||||
@ -18,10 +19,13 @@ public class AlarmsCursorAdapter extends RecyclerView.Adapter<AlarmViewHolder> {
|
||||
private static final String TAG = "AlarmsCursorAdapter";
|
||||
|
||||
private final OnListItemInteractionListener<Alarm> mListener;
|
||||
private final AlarmController mAlarmController;
|
||||
private AlarmCursor mCursor;
|
||||
|
||||
public AlarmsCursorAdapter(OnListItemInteractionListener<Alarm> listener) {
|
||||
public AlarmsCursorAdapter(OnListItemInteractionListener<Alarm> listener,
|
||||
AlarmController alarmController) {
|
||||
mListener = listener;
|
||||
mAlarmController = alarmController;
|
||||
// Excerpt from docs of notifyDataSetChanged():
|
||||
// "RecyclerView will attempt to synthesize [artificially create?]
|
||||
// visible structural change events [when items are inserted, removed or
|
||||
@ -34,7 +38,7 @@ public class AlarmsCursorAdapter extends RecyclerView.Adapter<AlarmViewHolder> {
|
||||
|
||||
@Override
|
||||
public AlarmViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||
return new AlarmViewHolder(parent, mListener);
|
||||
return new AlarmViewHolder(parent, mListener, mAlarmController);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
package com.philliphsu.clock2.alarms;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.database.Cursor;
|
||||
@ -23,8 +22,8 @@ import com.philliphsu.clock2.OnListItemInteractionListener;
|
||||
import com.philliphsu.clock2.R;
|
||||
import com.philliphsu.clock2.editalarm.EditAlarmActivity;
|
||||
import com.philliphsu.clock2.model.AlarmsListCursorLoader;
|
||||
import com.philliphsu.clock2.util.AlarmUtils;
|
||||
import com.philliphsu.clock2.util.LocalBroadcastHelper;
|
||||
import com.philliphsu.clock2.util.AlarmController;
|
||||
import com.philliphsu.clock2.util.DelayedSnackbarHandler;
|
||||
|
||||
import butterknife.Bind;
|
||||
import butterknife.ButterKnife;
|
||||
@ -37,14 +36,10 @@ public class AlarmsFragment extends Fragment implements LoaderCallbacks<Cursor>,
|
||||
private static final int REQUEST_EDIT_ALARM = 0;
|
||||
// Public because MainActivity needs to use it.
|
||||
public static final int REQUEST_CREATE_ALARM = 1;
|
||||
/**
|
||||
* Local broadcast senders can tell us to show a snackbar with a message on their behalf.
|
||||
*/
|
||||
public static final String ACTION_SHOW_SNACKBAR_MSG = "com.philliphsu.clock2.alarms.action.SHOW_SNACKBAR_MSG";
|
||||
public static final String EXTRA_MSG = "com.philliphsu.clock2.alarms.extra.MSG";
|
||||
|
||||
private AlarmsCursorAdapter mAdapter;
|
||||
private AsyncItemChangeHandler mAsyncItemChangeHandler;
|
||||
private AlarmController mAlarmController;
|
||||
private Handler mHandler = new Handler();
|
||||
private View mSnackbarAnchor;
|
||||
private long mScrollToStableId = RecyclerView.NO_ID;
|
||||
@ -78,6 +73,9 @@ public class AlarmsFragment extends Fragment implements LoaderCallbacks<Cursor>,
|
||||
// Will succeed because the activity is created at this point.
|
||||
// See the Fragment lifecycle.
|
||||
mSnackbarAnchor = getActivity().findViewById(R.id.main_content);
|
||||
mAlarmController = new AlarmController(getActivity(), mSnackbarAnchor);
|
||||
mAsyncItemChangeHandler = new AsyncItemChangeHandler(getActivity(),
|
||||
mSnackbarAnchor, this, mAlarmController);
|
||||
|
||||
getLoaderManager().initLoader(0, null, this);
|
||||
}
|
||||
@ -87,33 +85,22 @@ public class AlarmsFragment extends Fragment implements LoaderCallbacks<Cursor>,
|
||||
Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.fragment_alarms, container, false);
|
||||
ButterKnife.bind(this, view);
|
||||
|
||||
// Set the adapter
|
||||
Context context = view.getContext();
|
||||
mList.setLayoutManager(new LinearLayoutManager(context));
|
||||
mAdapter = new AlarmsCursorAdapter(this);
|
||||
mAdapter = new AlarmsCursorAdapter(this, mAlarmController);
|
||||
mList.setAdapter(mAdapter);
|
||||
|
||||
mAsyncItemChangeHandler = new AsyncItemChangeHandler(
|
||||
getActivity(), mSnackbarAnchor, this);
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
LocalBroadcastHelper.registerReceiver(getActivity(),
|
||||
mShowSnackbarReceiver, ACTION_SHOW_SNACKBAR_MSG);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop() {
|
||||
// This will always be called when we leave this screen, either by exiting the app or
|
||||
// by navigating elsewhere. Since we unregister the receiver here, we will never receive
|
||||
// a "show alarm snoozed" broadcast, because the snooze action is always made elsewhere
|
||||
// in the app.
|
||||
super.onStop();
|
||||
Log.e(TAG, "onStop()");
|
||||
LocalBroadcastHelper.unregisterReceiver(getActivity(), mShowSnackbarReceiver);
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
// Show the pending Snackbar, if any, that was prepared for us
|
||||
// by another app component.
|
||||
DelayedSnackbarHandler.makeAndShow(mSnackbarAnchor);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -221,19 +208,6 @@ public class AlarmsFragment extends Fragment implements LoaderCallbacks<Cursor>,
|
||||
// TODO: Delete this method.
|
||||
}
|
||||
|
||||
private final BroadcastReceiver mShowSnackbarReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
// See Intent#putExtras(Bundle):
|
||||
// Putting a Bundle of extras into an intent will have its
|
||||
// contents added to the intent's collection of extras,
|
||||
// so we can individually retrieve the Bundle's extras
|
||||
// directly from the intent.
|
||||
String message = intent.getStringExtra(EXTRA_MSG);
|
||||
AlarmUtils.showSnackbar(mSnackbarAnchor, message);
|
||||
}
|
||||
};
|
||||
|
||||
private static abstract class BaseAsyncItemChangeRunnable {
|
||||
// TODO: Will holding onto this cause a memory leak?
|
||||
private final AsyncItemChangeHandler mAsyncItemChangeHandler;
|
||||
|
||||
@ -33,6 +33,7 @@ import com.philliphsu.clock2.SharedPreferencesHelper;
|
||||
import com.philliphsu.clock2.model.AlarmLoader;
|
||||
import com.philliphsu.clock2.model.DatabaseManager;
|
||||
import com.philliphsu.clock2.ringtone.RingtoneActivity;
|
||||
import com.philliphsu.clock2.util.AlarmController;
|
||||
import com.philliphsu.clock2.util.AlarmUtils;
|
||||
import com.philliphsu.clock2.util.LocalBroadcastHelper;
|
||||
|
||||
@ -520,7 +521,9 @@ public class EditAlarmActivity extends BaseActivity implements AlarmNumpad.KeyLi
|
||||
|
||||
@Override
|
||||
public void cancelAlarm(Alarm alarm, boolean showToast) {
|
||||
AlarmUtils.cancelAlarm(this, alarm, showToast);
|
||||
// TODO: Rewrite XML layout to use CoordinatorLayout and
|
||||
// pass in the snackbar anchor.
|
||||
new AlarmController(this, null).cancelAlarm(alarm, true);
|
||||
if (RingtoneActivity.isAlive()) {
|
||||
LocalBroadcastHelper.sendBroadcast(this, RingtoneActivity.ACTION_FINISH);
|
||||
}
|
||||
|
||||
@ -13,7 +13,7 @@ import android.widget.Button;
|
||||
import com.philliphsu.clock2.Alarm;
|
||||
import com.philliphsu.clock2.R;
|
||||
import com.philliphsu.clock2.model.AlarmLoader;
|
||||
import com.philliphsu.clock2.util.AlarmUtils;
|
||||
import com.philliphsu.clock2.util.AlarmController;
|
||||
import com.philliphsu.clock2.util.LocalBroadcastHelper;
|
||||
|
||||
/**
|
||||
@ -34,6 +34,7 @@ public class RingtoneActivity extends AppCompatActivity implements
|
||||
|
||||
private long mAlarmId;
|
||||
private Alarm mAlarm;
|
||||
private AlarmController mAlarmController;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
@ -59,6 +60,8 @@ public class RingtoneActivity extends AppCompatActivity implements
|
||||
.putExtra(EXTRA_ITEM_ID, mAlarmId);
|
||||
startService(intent);
|
||||
|
||||
mAlarmController = new AlarmController(this, null);
|
||||
|
||||
// TODO: Butterknife binding
|
||||
Button snooze = (Button) findViewById(R.id.btn_snooze);
|
||||
snooze.setOnClickListener(new View.OnClickListener() {
|
||||
@ -145,7 +148,7 @@ public class RingtoneActivity extends AppCompatActivity implements
|
||||
if (mAlarm != null) {
|
||||
// TODO: If the upcoming alarm notification isn't present, verify other notifications aren't affected.
|
||||
// This could be the case if we're starting a new instance of this activity after leaving the first launch.
|
||||
AlarmUtils.removeUpcomingAlarmNotification(this, mAlarm);
|
||||
mAlarmController.removeUpcomingAlarmNotification(mAlarm);
|
||||
}
|
||||
}
|
||||
|
||||
@ -160,7 +163,7 @@ public class RingtoneActivity extends AppCompatActivity implements
|
||||
|
||||
private void snooze() {
|
||||
if (mAlarm != null) {
|
||||
AlarmUtils.snoozeAlarm(this, mAlarm);
|
||||
mAlarmController.snoozeAlarm(mAlarm);
|
||||
}
|
||||
// Can't call dismiss() because we don't want to also call cancelAlarm()! Why? For example,
|
||||
// we don't want the alarm, if it has no recurrence, to be turned off right now.
|
||||
@ -170,7 +173,7 @@ public class RingtoneActivity extends AppCompatActivity implements
|
||||
private void dismiss() {
|
||||
if (mAlarm != null) {
|
||||
// TODO do we really need to cancel the intent and alarm?
|
||||
AlarmUtils.cancelAlarm(this, mAlarm, false);
|
||||
mAlarmController.cancelAlarm(mAlarm, false);
|
||||
}
|
||||
stopAndFinish();
|
||||
}
|
||||
|
||||
@ -22,6 +22,7 @@ import android.util.Log;
|
||||
import com.philliphsu.clock2.Alarm;
|
||||
import com.philliphsu.clock2.R;
|
||||
import com.philliphsu.clock2.model.DatabaseManager;
|
||||
import com.philliphsu.clock2.util.AlarmController;
|
||||
import com.philliphsu.clock2.util.AlarmUtils;
|
||||
import com.philliphsu.clock2.util.LocalBroadcastHelper;
|
||||
|
||||
@ -54,6 +55,7 @@ public class RingtoneService extends Service { // TODO: abstract this, make subc
|
||||
private Ringtone mRingtone;
|
||||
private Alarm mAlarm;
|
||||
private String mNormalRingTime;
|
||||
private AlarmController mAlarmController;
|
||||
private boolean mAutoSilenced = false;
|
||||
// TODO: Using Handler for this is ill-suited? Alarm ringing could outlast the
|
||||
// application's life. Use AlarmManager API instead.
|
||||
@ -63,7 +65,7 @@ public class RingtoneService extends Service { // TODO: abstract this, make subc
|
||||
public void run() {
|
||||
mAutoSilenced = true;
|
||||
// TODO do we really need to cancel the alarm and intent?
|
||||
AlarmUtils.cancelAlarm(RingtoneService.this, mAlarm, false);
|
||||
mAlarmController.cancelAlarm(mAlarm, false);
|
||||
finishActivity();
|
||||
stopSelf();
|
||||
}
|
||||
@ -72,7 +74,7 @@ public class RingtoneService extends Service { // TODO: abstract this, make subc
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
mAutoSilenced = true;
|
||||
// TODO: Do we need to call AlarmUtils.cancelAlarm()?
|
||||
// TODO: Do we need to call mAlarmController.cancelAlarm()?
|
||||
stopSelf();
|
||||
// Activity finishes itself
|
||||
}
|
||||
@ -101,9 +103,9 @@ public class RingtoneService extends Service { // TODO: abstract this, make subc
|
||||
}).start();
|
||||
} else {
|
||||
if (ACTION_SNOOZE.equals(intent.getAction())) {
|
||||
AlarmUtils.snoozeAlarm(this, mAlarm);
|
||||
mAlarmController.snoozeAlarm(mAlarm);
|
||||
} else if (ACTION_DISMISS.equals(intent.getAction())) {
|
||||
AlarmUtils.cancelAlarm(this, mAlarm, false); // TODO do we really need to cancel the intent and alarm?
|
||||
mAlarmController.cancelAlarm(mAlarm, false); // TODO do we really need to cancel the intent and alarm?
|
||||
} else {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
@ -119,6 +121,7 @@ public class RingtoneService extends Service { // TODO: abstract this, make subc
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
LocalBroadcastHelper.registerReceiver(this, mNotifyMissedReceiver, ACTION_NOTIFY_MISSED);
|
||||
mAlarmController = new AlarmController(this, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@ -0,0 +1,225 @@
|
||||
package com.philliphsu.clock2.util;
|
||||
|
||||
import android.app.AlarmManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.support.design.widget.Snackbar;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
|
||||
import com.philliphsu.clock2.Alarm;
|
||||
import com.philliphsu.clock2.PendingAlarmScheduler;
|
||||
import com.philliphsu.clock2.R;
|
||||
import com.philliphsu.clock2.UpcomingAlarmReceiver;
|
||||
import com.philliphsu.clock2.model.DatabaseManager;
|
||||
import com.philliphsu.clock2.ringtone.RingtoneActivity;
|
||||
import com.philliphsu.clock2.ringtone.RingtoneService;
|
||||
|
||||
import static android.app.PendingIntent.FLAG_CANCEL_CURRENT;
|
||||
import static android.app.PendingIntent.FLAG_NO_CREATE;
|
||||
import static android.app.PendingIntent.getActivity;
|
||||
import static com.philliphsu.clock2.util.DateFormatUtils.formatTime;
|
||||
import static java.util.concurrent.TimeUnit.HOURS;
|
||||
|
||||
/**
|
||||
* Created by Phillip Hsu on 7/10/2016.
|
||||
*
|
||||
* API to control alarm states and update the UI.
|
||||
* TODO: Move this out of the .utils package when done.
|
||||
* TODO: Rename to AlarmStateHandler? AlarmStateController?
|
||||
*/
|
||||
public final class AlarmController {
|
||||
private static final String TAG = "AlarmController";
|
||||
|
||||
private final Context mAppContext;
|
||||
private final View mSnackbarAnchor;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param context the Context from which the application context will be requested
|
||||
* @param snackbarAnchor an optional anchor for a Snackbar to anchor to
|
||||
*/
|
||||
public AlarmController(Context context, View snackbarAnchor) {
|
||||
mAppContext = context.getApplicationContext();
|
||||
mSnackbarAnchor = snackbarAnchor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedules the alarm with the {@link AlarmManager}.
|
||||
* If {@code alarm.}{@link Alarm#isEnabled() isEnabled()}
|
||||
* returns false, this does nothing and returns immediately.
|
||||
*/
|
||||
public void scheduleAlarm(Alarm alarm, boolean showSnackbar) {
|
||||
if (!alarm.isEnabled()) {
|
||||
Log.i(TAG, "Skipped scheduling an alarm because it was not enabled");
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Consider doing this in a new thread.
|
||||
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.
|
||||
long ringAt = alarm.isSnoozed() ? alarm.snoozingUntil() : alarm.ringsAt();
|
||||
int hoursToNotifyInAdvance = AlarmUtils.hoursBeforeUpcoming(mAppContext);
|
||||
long upcomingAt = ringAt - HOURS.toMillis(hoursToNotifyInAdvance);
|
||||
// If snoozed, upcoming note posted immediately.
|
||||
am.set(AlarmManager.RTC_WAKEUP, upcomingAt, notifyUpcomingAlarmIntent(alarm, false));
|
||||
am.setExact(AlarmManager.RTC_WAKEUP, ringAt, alarmIntent(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.
|
||||
showSnackbar(message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel the alarm. This does NOT check if you previously scheduled the alarm.
|
||||
*/
|
||||
public void cancelAlarm(Alarm alarm, boolean showSnackbar) {
|
||||
// TODO: Consider doing this in a new thread.
|
||||
Log.d(TAG, "Cancelling alarm " + alarm);
|
||||
AlarmManager am = (AlarmManager) mAppContext.getSystemService(Context.ALARM_SERVICE);
|
||||
|
||||
PendingIntent pi = alarmIntent(alarm, true);
|
||||
if (pi != null) {
|
||||
am.cancel(pi);
|
||||
pi.cancel();
|
||||
}
|
||||
|
||||
pi = notifyUpcomingAlarmIntent(alarm, true);
|
||||
if (pi != null) {
|
||||
am.cancel(pi);
|
||||
pi.cancel();
|
||||
}
|
||||
|
||||
// Does nothing if it's not posted.
|
||||
removeUpcomingAlarmNotification(alarm);
|
||||
|
||||
int hoursToNotifyInAdvance = AlarmUtils.hoursBeforeUpcoming(mAppContext);
|
||||
// TOneverDO: Place block after making value changes to the alarm.
|
||||
if (showSnackbar
|
||||
// TODO: Consider showing the snackbar for non-upcoming alarms too;
|
||||
// then, we can remove these checks.
|
||||
&& alarm.ringsWithinHours(hoursToNotifyInAdvance) || alarm.isSnoozed()) {
|
||||
long time = alarm.isSnoozed() ? alarm.snoozingUntil() : alarm.ringsAt();
|
||||
String msg = mAppContext.getString(R.string.upcoming_alarm_dismissed,
|
||||
formatTime(mAppContext, time));
|
||||
showSnackbar(msg);
|
||||
}
|
||||
|
||||
if (alarm.isSnoozed()) {
|
||||
alarm.stopSnoozing();
|
||||
}
|
||||
|
||||
if (!alarm.hasRecurrence()) {
|
||||
alarm.setEnabled(false);
|
||||
} else if (alarm.isEnabled()) {
|
||||
if (alarm.ringsWithinHours(hoursToNotifyInAdvance)) {
|
||||
// Still upcoming today, so wait until the normal ring time
|
||||
// passes before rescheduling the alarm.
|
||||
alarm.ignoreUpcomingRingTime(true); // Useful only for VH binding
|
||||
Intent intent = new Intent(mAppContext, PendingAlarmScheduler.class)
|
||||
.putExtra(PendingAlarmScheduler.EXTRA_ALARM_ID, alarm.id());
|
||||
pi = PendingIntent.getBroadcast(mAppContext, alarm.intId(),
|
||||
intent, PendingIntent.FLAG_ONE_SHOT);
|
||||
am.set(AlarmManager.RTC_WAKEUP, alarm.ringsAt(), pi);
|
||||
} else {
|
||||
scheduleAlarm(alarm, false);
|
||||
}
|
||||
}
|
||||
|
||||
save(alarm);
|
||||
|
||||
// If service is not running, nothing happens
|
||||
mAppContext.stopService(new Intent(mAppContext, RingtoneService.class));
|
||||
}
|
||||
|
||||
public void snoozeAlarm(Alarm alarm) {
|
||||
int minutesToSnooze = AlarmUtils.snoozeDuration(mAppContext);
|
||||
alarm.snooze(minutesToSnooze);
|
||||
scheduleAlarm(alarm, false);
|
||||
String message = mAppContext.getString(R.string.title_snoozing_until,
|
||||
formatTime(mAppContext, alarm.snoozingUntil()));
|
||||
// Since snoozing is always done by an app component away from
|
||||
// the list screen, the Snackbar will never be shown. In fact, this
|
||||
// controller has a null mSnackbarAnchor if we're using it for snoozing
|
||||
// an alarm. We solve this by preparing the message, and waiting until
|
||||
// the list screen is resumed so that it can display the Snackbar for us.
|
||||
DelayedSnackbarHandler.prepareMessage(message);
|
||||
save(alarm);
|
||||
}
|
||||
|
||||
public void removeUpcomingAlarmNotification(Alarm a) {
|
||||
Intent intent = new Intent(mAppContext, UpcomingAlarmReceiver.class)
|
||||
.setAction(UpcomingAlarmReceiver.ACTION_CANCEL_NOTIFICATION)
|
||||
.putExtra(UpcomingAlarmReceiver.EXTRA_ALARM_ID, a.id());
|
||||
mAppContext.sendBroadcast(intent);
|
||||
}
|
||||
|
||||
public void save(final Alarm alarm) {
|
||||
// TODO: Will using the Runnable like this cause a memory leak?
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
DatabaseManager.getInstance(mAppContext).updateAlarm(alarm.id(), alarm);
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
private PendingIntent alarmIntent(Alarm alarm, boolean retrievePrevious) {
|
||||
// TODO: Use appropriate subclass instead
|
||||
Intent intent = new Intent(mAppContext, RingtoneActivity.class)
|
||||
.putExtra(RingtoneActivity.EXTRA_ITEM_ID, alarm.id());
|
||||
int flag = retrievePrevious ? FLAG_NO_CREATE : FLAG_CANCEL_CURRENT;
|
||||
PendingIntent pi = getActivity(mAppContext, alarm.intId(), 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;
|
||||
}
|
||||
|
||||
private PendingIntent notifyUpcomingAlarmIntent(Alarm alarm, boolean retrievePrevious) {
|
||||
Intent intent = new Intent(mAppContext, UpcomingAlarmReceiver.class)
|
||||
.putExtra(UpcomingAlarmReceiver.EXTRA_ALARM_ID, alarm.id());
|
||||
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.intId(), 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;
|
||||
}
|
||||
|
||||
private void showSnackbar(String message) {
|
||||
// Is the window containing this anchor currently focused?
|
||||
if (mSnackbarAnchor != null && mSnackbarAnchor.hasWindowFocus()) {
|
||||
Snackbar.make(mSnackbarAnchor, message, Snackbar.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -4,19 +4,15 @@ import android.app.AlarmManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.annotation.StringRes;
|
||||
import android.support.design.widget.Snackbar;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.philliphsu.clock2.Alarm;
|
||||
import com.philliphsu.clock2.PendingAlarmScheduler;
|
||||
import com.philliphsu.clock2.R;
|
||||
import com.philliphsu.clock2.UpcomingAlarmReceiver;
|
||||
import com.philliphsu.clock2.alarms.AlarmsFragment;
|
||||
import com.philliphsu.clock2.model.DatabaseManager;
|
||||
import com.philliphsu.clock2.ringtone.RingtoneActivity;
|
||||
import com.philliphsu.clock2.ringtone.RingtoneService;
|
||||
@ -48,7 +44,7 @@ public final class AlarmUtils {
|
||||
* @deprecated {@code showToast} is no longer working. Callers must
|
||||
* handle popup confirmations on their own.
|
||||
*/
|
||||
// TODO: Delete showToast param
|
||||
// TODO: Consider moving usages to the background
|
||||
public static void scheduleAlarm(Context context, Alarm alarm, boolean showToast) {
|
||||
if (!alarm.isEnabled()) {
|
||||
Log.i(TAG, "Skipped scheduling an alarm because it was not enabled");
|
||||
@ -149,14 +145,6 @@ public final class AlarmUtils {
|
||||
public static void snoozeAlarm(Context c, Alarm a) {
|
||||
a.snooze(snoozeDuration(c));
|
||||
scheduleAlarm(c, a, true);
|
||||
// TODO: Based on the current lifecycle methods pair where we register/unregister the
|
||||
// receiver in AlarmsFragment, the snackbar won't be shown.
|
||||
// We have no reference to the snackbar anchor, so let AlarmsFragment
|
||||
// handle showing the snackbar for us. AlarmsFragment has no knowledge
|
||||
// of which alarm is snoozed (and actually doesn't need to know); we can build
|
||||
// the message for it. This is why we don't have a showAlarmSnoozedSnackbar(Alarm)
|
||||
// utility method.
|
||||
sendShowSnackbarBroadcast(c, getSnoozingUntilText(c, a.snoozingUntil()));
|
||||
save(c, a);
|
||||
}
|
||||
|
||||
@ -235,30 +223,4 @@ public final class AlarmUtils {
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
public static String getRingsInText(Context context, long ringsIn) {
|
||||
return context.getString(R.string.alarm_set_for,
|
||||
DurationUtils.toString(context, ringsIn, false /*abbreviate?*/));
|
||||
}
|
||||
|
||||
public static String getSnoozingUntilText(Context context, long snoozingUntil) {
|
||||
return context.getString(R.string.title_snoozing_until,
|
||||
formatTime(context, snoozingUntil));
|
||||
}
|
||||
|
||||
public static void sendShowSnackbarBroadcast(Context c, String message) {
|
||||
Bundle extra = new Bundle(1);
|
||||
extra.putString(AlarmsFragment.EXTRA_MSG, message);
|
||||
LocalBroadcastHelper.sendBroadcast(c, AlarmsFragment.ACTION_SHOW_SNACKBAR_MSG, extra);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show a snackbar confirmation about an event related to an alarm.
|
||||
* Used for showing an alarm has been snoozed.
|
||||
*/
|
||||
public static void showSnackbar(View snackbarAnchor, String message) {
|
||||
if (snackbarAnchor != null) {
|
||||
Snackbar.make(snackbarAnchor, message, Snackbar.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,58 @@
|
||||
package com.philliphsu.clock2.util;
|
||||
|
||||
import android.support.design.widget.Snackbar;
|
||||
import android.view.View;
|
||||
|
||||
/**
|
||||
* Created by Phillip Hsu on 7/10/2016.
|
||||
*
|
||||
* Handler to prepare a Snackbar to be shown only when requested to.
|
||||
* Useful when the Snackbar is created in an app component that
|
||||
* is not where it should be shown.
|
||||
*/
|
||||
public final class DelayedSnackbarHandler {
|
||||
// TODO: Consider wrapping this in a WeakReference, so that you
|
||||
// don't prevent this from being GCed if you never call #show().
|
||||
private static Snackbar snackbar;
|
||||
private static String message;
|
||||
|
||||
private DelayedSnackbarHandler() {}
|
||||
|
||||
/**
|
||||
* Saves a reference to the given Snackbar, so that you can
|
||||
* call {@link #show()} at a later time.
|
||||
*/
|
||||
public static void prepareSnackbar(Snackbar sb) {
|
||||
snackbar = sb;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the Snackbar previously prepared with
|
||||
* {@link #prepareSnackbar(Snackbar)}
|
||||
*/
|
||||
public static void show() {
|
||||
if (snackbar != null) {
|
||||
snackbar.show();
|
||||
snackbar = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves a static reference to the message, so that you can
|
||||
* call {@link #makeAndShow(View)} at a later time.
|
||||
*/
|
||||
public static void prepareMessage(String msg) {
|
||||
message = msg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes a Snackbar with the message previously prepared with
|
||||
* {@link #prepareMessage(String)} and shows it.
|
||||
*/
|
||||
public static void makeAndShow(View snackbarAnchor) {
|
||||
if (snackbarAnchor != null && message != null) {
|
||||
Snackbar.make(snackbarAnchor, message, Snackbar.LENGTH_LONG).show();
|
||||
message = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user