Background loading of single Alarm in relevant components, added queryEnabledAlarms() in database helper

This commit is contained in:
Phillip Hsu 2016-07-01 03:40:27 -07:00
parent 5f138f2756
commit b1657c221e
7 changed files with 91 additions and 30 deletions

View File

@ -0,0 +1,10 @@
package com.philliphsu.clock2;
import android.app.Fragment;
/**
* Created by Phillip Hsu on 6/30/2016.
*/
public abstract class BaseFragment extends Fragment {
public abstract void onFabClick();
}

View File

@ -59,13 +59,15 @@ public class OnBootUpAlarmScheduler extends IntentService {
@Override
protected void onHandleIntent(Intent intent) {
if (intent != null) {
// IntentService already works in a background thread, so we don't need to use a loader.
AlarmCursor cursor = DatabaseManager.getInstance(this).queryAlarms();
// IntentService works in a background thread, so this won't hold us up.
AlarmCursor cursor = DatabaseManager.getInstance(this).queryEnabledAlarms();
while (cursor.moveToNext()) {
Alarm alarm = cursor.getAlarm();
if (alarm.isEnabled()) {
AlarmUtils.scheduleAlarm(this, alarm, false);
if (!alarm.isEnabled()) {
throw new IllegalStateException(
"queryEnabledAlarms() returned alarm(s) that aren't enabled");
}
AlarmUtils.scheduleAlarm(this, alarm, false);
}
cursor.close();

View File

@ -159,6 +159,7 @@ public class AlarmsFragment extends Fragment implements LoaderCallbacks<Cursor>,
@Override
public void onClick(View v) {
DatabaseManager.getInstance(getActivity()).insertAlarm(item);
getLoaderManager().restartLoader(0, null, AlarmsFragment.this);
if (item.isEnabled()) {
AlarmUtils.scheduleAlarm(getActivity(), item, true);
}

View File

@ -32,6 +32,8 @@ public class AlarmDatabaseHelper extends SQLiteOpenHelper {
// and defines all the columns.
// TODO: Consider defining index constants for each column,
// and then removing all cursor getColumnIndex() calls.
// TODO: Consider making these public, so callers can customize their
// WHERE queries.
private static final String TABLE_ALARMS = "alarms";
private static final String COLUMN_ID = "_id";
private static final String COLUMN_HOUR = "hour";
@ -149,8 +151,16 @@ public class AlarmDatabaseHelper extends SQLiteOpenHelper {
public AlarmCursor queryAlarms() {
// Select all rows and columns
return queryAlarms(null);
}
public AlarmCursor queryEnabledAlarms() {
return queryAlarms(COLUMN_ENABLED + " = " + 1);
}
private AlarmCursor queryAlarms(String where) {
Cursor c = getReadableDatabase().query(TABLE_ALARMS,
null, null, null, null, null, SORT_ORDER);
null, where, null, null, null, SORT_ORDER);
return new AlarmCursor(c);
}

View File

@ -79,4 +79,8 @@ public class DatabaseManager {
public AlarmCursor queryAlarms() {
return mHelper.queryAlarms();
}
public AlarmCursor queryEnabledAlarms() {
return mHelper.queryEnabledAlarms();
}
}

View File

@ -5,28 +5,27 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.support.v4.content.Loader;
import android.support.v4.content.LocalBroadcastManager;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.view.WindowManager;
import android.widget.Button;
import com.philliphsu.clock2.Alarm;
import com.philliphsu.clock2.R;
import com.philliphsu.clock2.model.DatabaseManager;
import com.philliphsu.clock2.model.AlarmLoader;
import com.philliphsu.clock2.util.AlarmUtils;
import com.philliphsu.clock2.util.LocalBroadcastHelper;
import static com.philliphsu.clock2.util.Preconditions.checkNotNull;
/**
* An example full-screen activity that shows and hides the system UI (i.e.
* status bar and navigation/system bar) with user interaction.
*
* TODO: Make this abstract and make appropriate subclasses for Alarms and Timers.
*/
public class RingtoneActivity extends AppCompatActivity {
public class RingtoneActivity extends AppCompatActivity implements
android.support.v4.app.LoaderManager.LoaderCallbacks<Alarm> {
private static final String TAG = "RingtoneActivity";
// Shared with RingtoneService
@ -35,6 +34,7 @@ public class RingtoneActivity extends AppCompatActivity {
private static boolean sIsAlive = false;
private long mAlarmId;
private Alarm mAlarm;
@Override
@ -48,19 +48,17 @@ public class RingtoneActivity extends AppCompatActivity {
getWindow().addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
long id = getIntent().getLongExtra(EXTRA_ITEM_ID, -1);
if (id < 0) {
mAlarmId = getIntent().getLongExtra(EXTRA_ITEM_ID, -1);
if (mAlarmId < 0) {
throw new IllegalStateException("Cannot start RingtoneActivity without item's id");
}
mAlarm = checkNotNull(DatabaseManager.getInstance(this).getAlarm(id));
Log.d(TAG, "Ringing alarm " + mAlarm);
// 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);
// The reason we don't use a thread to load the alarm is because this is an
// Activity, which has complex lifecycle. LoaderManager is designed to help
// us through the vagaries of the lifecycle that could affect loading data.
getSupportLoaderManager().initLoader(0, null, this);
Intent intent = new Intent(this, RingtoneService.class)
.putExtra(EXTRA_ITEM_ID, id);
.putExtra(EXTRA_ITEM_ID, mAlarmId);
startService(intent);
// TODO: Butterknife binding
@ -139,19 +137,44 @@ public class RingtoneActivity extends AppCompatActivity {
sIsAlive = false;
}
@Override
public Loader<Alarm> onCreateLoader(int id, Bundle args) {
return new AlarmLoader(this, mAlarmId);
}
@Override
public void onLoadFinished(Loader<Alarm> loader, Alarm data) {
mAlarm = data;
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);
}
}
@Override
public void onLoaderReset(Loader<Alarm> loader) {
// Do nothing
}
public static boolean isAlive() {
return sIsAlive;
}
private void snooze() {
AlarmUtils.snoozeAlarm(this, mAlarm);
if (mAlarm != null) {
AlarmUtils.snoozeAlarm(this, 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.
stopAndFinish();
}
private void dismiss() {
AlarmUtils.cancelAlarm(this, mAlarm, false); // TODO do we really need to cancel the intent and alarm?
if (mAlarm != null) {
// TODO do we really need to cancel the intent and alarm?
AlarmUtils.cancelAlarm(this, mAlarm, false);
}
stopAndFinish();
}

View File

@ -64,7 +64,8 @@ public class RingtoneService extends Service { // TODO: abstract this, make subc
@Override
public void run() {
mAutoSilenced = true;
AlarmUtils.cancelAlarm(RingtoneService.this, mAlarm, false); // TODO do we really need to cancel the alarm and intent?
// TODO do we really need to cancel the alarm and intent?
AlarmUtils.cancelAlarm(RingtoneService.this, mAlarm, false);
finishActivity();
stopSelf();
}
@ -81,18 +82,29 @@ public class RingtoneService extends Service { // TODO: abstract this, make subc
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
long id = intent.getLongExtra(EXTRA_ITEM_ID, -1);
final long id = intent.getLongExtra(EXTRA_ITEM_ID, -1);
if (id < 0)
throw new IllegalStateException("No item id set");
Alarm alarm = checkNotNull(DatabaseManager.getInstance(this).getAlarm(id));
if (intent.getAction() == null) {
playRingtone(alarm);
// http://stackoverflow.com/q/8696146/5055032
// Start our own thread to load the alarm instead of using a loader,
// because Services do not have a built-in LoaderManager (because they have no need for one since
// their lifecycle is not complex like in Activities/Fragments) and our
// work is simple enough that getting loaders to work here is not
// worth the effort.
new Thread(new Runnable() {
@Override
public void run() {
mAlarm = checkNotNull(DatabaseManager
.getInstance(RingtoneService.this).getAlarm(id));
playRingtone();
}
}).start();
} else {
if (ACTION_SNOOZE.equals(intent.getAction())) {
AlarmUtils.snoozeAlarm(this, alarm);
AlarmUtils.snoozeAlarm(this, mAlarm);
} else if (ACTION_DISMISS.equals(intent.getAction())) {
AlarmUtils.cancelAlarm(this, alarm, false); // TODO do we really need to cancel the intent and alarm?
AlarmUtils.cancelAlarm(this, mAlarm, false); // TODO do we really need to cancel the intent and alarm?
} else {
throw new UnsupportedOperationException();
}
@ -143,9 +155,8 @@ public class RingtoneService extends Service { // TODO: abstract this, make subc
return null;
}
private void playRingtone(@NonNull Alarm alarm) {
private void playRingtone() {
if (mAudioManager == null && mRingtone == null) {
mAlarm = checkNotNull(alarm);
// TODO: The below call requires a notification, and there is no way to provide one suitable
// for both Alarms and Timers. Consider making this class abstract, and have subclasses
// implement an abstract method that calls startForeground(). You would then call that