Background loading of single Alarm in relevant components, added queryEnabledAlarms() in database helper
This commit is contained in:
parent
5f138f2756
commit
b1657c221e
10
app/src/main/java/com/philliphsu/clock2/BaseFragment.java
Normal file
10
app/src/main/java/com/philliphsu/clock2/BaseFragment.java
Normal 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();
|
||||
}
|
||||
@ -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();
|
||||
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
|
||||
@ -79,4 +79,8 @@ public class DatabaseManager {
|
||||
public AlarmCursor queryAlarms() {
|
||||
return mHelper.queryAlarms();
|
||||
}
|
||||
|
||||
public AlarmCursor queryEnabledAlarms() {
|
||||
return mHelper.queryEnabledAlarms();
|
||||
}
|
||||
}
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
Reference in New Issue
Block a user