RecyclerViewFragment and other abstract classes in use for alarms and timers
This commit is contained in:
parent
b43ff03662
commit
066ac67325
@ -6,6 +6,7 @@ import android.support.annotation.NonNull;
|
||||
|
||||
import com.google.auto.value.AutoValue;
|
||||
import com.philliphsu.clock2.model.JsonSerializable;
|
||||
import com.philliphsu.clock2.model.ObjectWithId;
|
||||
|
||||
import org.json.JSONObject;
|
||||
|
||||
@ -21,11 +22,10 @@ import static com.philliphsu.clock2.DaysOfWeek.SUNDAY;
|
||||
* Created by Phillip Hsu on 5/26/2016.
|
||||
*/
|
||||
@AutoValue
|
||||
public abstract class Alarm implements JsonSerializable, Parcelable {
|
||||
public abstract class Alarm extends ObjectWithId implements JsonSerializable, Parcelable {
|
||||
private static final int MAX_MINUTES_CAN_SNOOZE = 30;
|
||||
|
||||
// =================== MUTABLE =======================
|
||||
private long id;
|
||||
private long snoozingUntilMillis;
|
||||
private boolean enabled;
|
||||
private final boolean[] recurringDays = new boolean[NUM_DAYS];
|
||||
@ -204,18 +204,15 @@ public abstract class Alarm implements JsonSerializable, Parcelable {
|
||||
}
|
||||
|
||||
public int intId() {
|
||||
return (int) id;
|
||||
}
|
||||
|
||||
public void setId(long id) {
|
||||
this.id = id;
|
||||
return (int) getId();
|
||||
}
|
||||
|
||||
// TODO: Remove method signature from JsonSerializable interface.
|
||||
// TODO: Remove final modifier.
|
||||
// TODO: Rename to getId() so usages refer to ObjectWithId#getId(), then delete this method.
|
||||
@Override
|
||||
public final long id() {
|
||||
return id;
|
||||
return getId();
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
@ -246,7 +243,7 @@ public abstract class Alarm implements JsonSerializable, Parcelable {
|
||||
// because when we recreate the object, we can't initialize
|
||||
// those mutable fields until after we call build(). Values
|
||||
// in the parcel are read in the order they were written.
|
||||
dest.writeLong(id);
|
||||
dest.writeLong(getId());
|
||||
dest.writeLong(snoozingUntilMillis);
|
||||
dest.writeInt(enabled ? 1 : 0);
|
||||
dest.writeBooleanArray(recurringDays);
|
||||
@ -261,7 +258,7 @@ public abstract class Alarm implements JsonSerializable, Parcelable {
|
||||
.ringtone(in.readString())
|
||||
.vibrates(in.readInt() != 0)
|
||||
.build();
|
||||
alarm.id = in.readLong();
|
||||
alarm.setId(in.readLong());
|
||||
alarm.snoozingUntilMillis = in.readLong();
|
||||
alarm.enabled = in.readInt() != 0;
|
||||
in.readBooleanArray(alarm.recurringDays);
|
||||
|
||||
@ -1,13 +1,10 @@
|
||||
package com.philliphsu.clock2.alarms;
|
||||
|
||||
import android.database.Cursor;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.util.Log;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import com.philliphsu.clock2.Alarm;
|
||||
import com.philliphsu.clock2.BaseCursorAdapter;
|
||||
import com.philliphsu.clock2.OnListItemInteractionListener;
|
||||
import com.philliphsu.clock2.model.AlarmDatabaseHelper.AlarmCursor;
|
||||
import com.philliphsu.clock2.util.AlarmController;
|
||||
|
||||
/**
|
||||
@ -15,63 +12,19 @@ import com.philliphsu.clock2.util.AlarmController;
|
||||
*
|
||||
* TODO: Extend from BaseCursorAdapter
|
||||
*/
|
||||
public class AlarmsCursorAdapter extends RecyclerView.Adapter<AlarmViewHolder> {
|
||||
public class AlarmsCursorAdapter extends BaseCursorAdapter<Alarm, AlarmViewHolder, com.philliphsu.clock2.model.AlarmCursor> {
|
||||
private static final String TAG = "AlarmsCursorAdapter";
|
||||
|
||||
private final OnListItemInteractionListener<Alarm> mListener;
|
||||
private final AlarmController mAlarmController;
|
||||
private AlarmCursor mCursor;
|
||||
|
||||
public AlarmsCursorAdapter(OnListItemInteractionListener<Alarm> listener,
|
||||
AlarmController alarmController) {
|
||||
mListener = listener;
|
||||
super(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
|
||||
// moved] for adapters that report that they have stable IDs when
|
||||
// [notifyDataSetChanged()] is used. This can help for the purposes of
|
||||
// animation and visual object persistence [?] but individual item views
|
||||
// will still need to be rebound and relaid out."
|
||||
setHasStableIds(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AlarmViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||
return new AlarmViewHolder(parent, mListener, mAlarmController);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(AlarmViewHolder holder, int position) {
|
||||
if (!mCursor.moveToPosition(position)) {
|
||||
Log.e(TAG, "Failed to bind alarm " + position);
|
||||
return;
|
||||
}
|
||||
holder.onBind(mCursor.getAlarm());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return mCursor == null ? 0 : mCursor.getCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId(int position) {
|
||||
if (mCursor == null || !mCursor.moveToPosition(position)) {
|
||||
return super.getItemId(position); // -1
|
||||
}
|
||||
return mCursor.getId();
|
||||
}
|
||||
|
||||
// TODO: Cursor param should be the appropriate subclass?
|
||||
public void swapCursor(Cursor cursor) {
|
||||
if (mCursor == cursor) {
|
||||
return;
|
||||
}
|
||||
if (mCursor != null) {
|
||||
mCursor.close();
|
||||
}
|
||||
mCursor = (AlarmCursor) cursor;
|
||||
notifyDataSetChanged();
|
||||
protected AlarmViewHolder onCreateViewHolder(ViewGroup parent, OnListItemInteractionListener<Alarm> listener) {
|
||||
return new AlarmViewHolder(parent, listener, mAlarmController);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,26 +1,20 @@
|
||||
package com.philliphsu.clock2.alarms;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.database.Cursor;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.app.LoaderManager.LoaderCallbacks;
|
||||
import android.support.v4.content.Loader;
|
||||
import android.support.v7.widget.LinearLayoutManager;
|
||||
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.Alarm;
|
||||
import com.philliphsu.clock2.AsyncItemChangeHandler;
|
||||
import com.philliphsu.clock2.OnListItemInteractionListener;
|
||||
import com.philliphsu.clock2.R;
|
||||
import com.philliphsu.clock2.RecyclerViewFragment;
|
||||
import com.philliphsu.clock2.editalarm.EditAlarmActivity;
|
||||
import com.philliphsu.clock2.model.AlarmCursor;
|
||||
import com.philliphsu.clock2.model.AlarmsListCursorLoader;
|
||||
import com.philliphsu.clock2.util.AlarmController;
|
||||
import com.philliphsu.clock2.util.DelayedSnackbarHandler;
|
||||
@ -28,10 +22,11 @@ import com.philliphsu.clock2.util.DelayedSnackbarHandler;
|
||||
import butterknife.Bind;
|
||||
import butterknife.ButterKnife;
|
||||
|
||||
// TODO: Use native fragments since we're targeting API >=19?
|
||||
// TODO: Use native LoaderCallbacks.
|
||||
public class AlarmsFragment extends Fragment implements LoaderCallbacks<Cursor>,
|
||||
OnListItemInteractionListener<Alarm>, ScrollHandler {
|
||||
public class AlarmsFragment extends RecyclerViewFragment<
|
||||
Alarm,
|
||||
AlarmViewHolder,
|
||||
AlarmCursor,
|
||||
AlarmsCursorAdapter> implements ScrollHandler {
|
||||
private static final String TAG = "AlarmsFragment";
|
||||
private static final int REQUEST_EDIT_ALARM = 0;
|
||||
// Public because MainActivity needs to use it.
|
||||
@ -76,23 +71,6 @@ public class AlarmsFragment extends Fragment implements LoaderCallbacks<Cursor>,
|
||||
mAlarmController = new AlarmController(getActivity(), mSnackbarAnchor);
|
||||
mAsyncItemChangeHandler = new AsyncItemChangeHandler(getActivity(),
|
||||
mSnackbarAnchor, this, mAlarmController);
|
||||
|
||||
getLoaderManager().initLoader(0, null, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
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, mAlarmController);
|
||||
mList.setAdapter(mAdapter);
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -110,20 +88,29 @@ public class AlarmsFragment extends Fragment implements LoaderCallbacks<Cursor>,
|
||||
}
|
||||
|
||||
@Override
|
||||
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
|
||||
public Loader<AlarmCursor> onCreateLoader(int id, Bundle args) {
|
||||
return new AlarmsListCursorLoader(getActivity());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
|
||||
mAdapter.swapCursor(data);
|
||||
public void onLoadFinished(Loader<AlarmCursor> loader, AlarmCursor data) {
|
||||
super.onLoadFinished(loader, data);
|
||||
// Scroll to the last modified alarm
|
||||
performScrollToStableId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoaderReset(Loader<Cursor> loader) {
|
||||
mAdapter.swapCursor(null);
|
||||
public void onFabClick() {
|
||||
Intent intent = new Intent(getActivity(), EditAlarmActivity.class);
|
||||
startActivityForResult(intent, REQUEST_CREATE_ALARM);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AlarmsCursorAdapter getAdapter() {
|
||||
if (mAdapter == null) {
|
||||
mAdapter = new AlarmsCursorAdapter(this, mAlarmController);
|
||||
}
|
||||
return mAdapter;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@ -0,0 +1,58 @@
|
||||
package com.philliphsu.clock2.model;
|
||||
|
||||
import android.database.Cursor;
|
||||
|
||||
import com.philliphsu.clock2.Alarm;
|
||||
|
||||
import static com.philliphsu.clock2.DaysOfWeek.FRIDAY;
|
||||
import static com.philliphsu.clock2.DaysOfWeek.MONDAY;
|
||||
import static com.philliphsu.clock2.DaysOfWeek.SATURDAY;
|
||||
import static com.philliphsu.clock2.DaysOfWeek.SUNDAY;
|
||||
import static com.philliphsu.clock2.DaysOfWeek.THURSDAY;
|
||||
import static com.philliphsu.clock2.DaysOfWeek.TUESDAY;
|
||||
import static com.philliphsu.clock2.DaysOfWeek.WEDNESDAY;
|
||||
|
||||
/**
|
||||
* Created by Phillip Hsu on 7/30/2016.
|
||||
*/
|
||||
// An alternative method to creating an Alarm from a cursor is to
|
||||
// make an Alarm constructor that takes an Cursor param. However,
|
||||
// this method has the advantage of keeping the contents of
|
||||
// the Alarm class as pure Java, which can facilitate unit testing
|
||||
// because it has no dependence on Cursor, which is part of the Android SDK.
|
||||
public class AlarmCursor extends BaseItemCursor<Alarm> {
|
||||
private static final String TAG = "AlarmCursor";
|
||||
|
||||
public AlarmCursor(Cursor c) {
|
||||
super(c);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return an Alarm instance configured for the current row,
|
||||
* or null if the current row is invalid
|
||||
*/
|
||||
@Override
|
||||
public Alarm getItem() {
|
||||
if (isBeforeFirst() || isAfterLast())
|
||||
return null;
|
||||
Alarm alarm = Alarm.builder()
|
||||
.hour(getInt(getColumnIndexOrThrow(AlarmsTable.COLUMN_HOUR)))
|
||||
.minutes(getInt(getColumnIndexOrThrow(AlarmsTable.COLUMN_MINUTES)))
|
||||
.vibrates(isTrue(AlarmsTable.COLUMN_VIBRATES))
|
||||
.ringtone(getString(getColumnIndexOrThrow(AlarmsTable.COLUMN_RINGTONE)))
|
||||
.label(getString(getColumnIndexOrThrow(AlarmsTable.COLUMN_LABEL)))
|
||||
.build();
|
||||
alarm.setId(getLong(getColumnIndexOrThrow(AlarmsTable.COLUMN_ID)));
|
||||
alarm.setEnabled(isTrue(AlarmsTable.COLUMN_ENABLED));
|
||||
alarm.setSnoozing(getLong(getColumnIndexOrThrow(AlarmsTable.COLUMN_SNOOZING_UNTIL_MILLIS)));
|
||||
alarm.setRecurring(SUNDAY, isTrue(AlarmsTable.COLUMN_SUNDAY));
|
||||
alarm.setRecurring(MONDAY, isTrue(AlarmsTable.COLUMN_MONDAY));
|
||||
alarm.setRecurring(TUESDAY, isTrue(AlarmsTable.COLUMN_TUESDAY));
|
||||
alarm.setRecurring(WEDNESDAY, isTrue(AlarmsTable.COLUMN_WEDNESDAY));
|
||||
alarm.setRecurring(THURSDAY, isTrue(AlarmsTable.COLUMN_THURSDAY));
|
||||
alarm.setRecurring(FRIDAY, isTrue(AlarmsTable.COLUMN_FRIDAY));
|
||||
alarm.setRecurring(SATURDAY, isTrue(AlarmsTable.COLUMN_SATURDAY));
|
||||
alarm.ignoreUpcomingRingTime(isTrue(AlarmsTable.COLUMN_IGNORE_UPCOMING_RING_TIME));
|
||||
return alarm;
|
||||
}
|
||||
}
|
||||
@ -24,6 +24,7 @@ import static com.philliphsu.clock2.DaysOfWeek.WEDNESDAY;
|
||||
*
|
||||
* TODO: We can generalize this class to all data models, not just Alarms.
|
||||
*/
|
||||
@Deprecated
|
||||
public class AlarmDatabaseHelper extends SQLiteOpenHelper {
|
||||
private static final String TAG = "AlarmDatabaseHelper";
|
||||
private static final String DB_NAME = "alarms.db";
|
||||
|
||||
@ -1,19 +1,27 @@
|
||||
package com.philliphsu.clock2.model;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
|
||||
import com.philliphsu.clock2.Alarm;
|
||||
|
||||
/**
|
||||
* Created by Phillip Hsu on 6/28/2016.
|
||||
*/
|
||||
public class AlarmsListCursorLoader extends SQLiteCursorLoader {
|
||||
public class AlarmsListCursorLoader extends NewSQLiteCursorLoader<Alarm, AlarmCursor> {
|
||||
public static final String ACTION_CHANGE_CONTENT
|
||||
= "com.philliphsu.clock2.model.AlarmsListCursorLoader.action.CHANGE_CONTENT";
|
||||
|
||||
public AlarmsListCursorLoader(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Cursor loadCursor() {
|
||||
return DatabaseManager.getInstance(getContext()).queryAlarms();
|
||||
protected AlarmCursor loadCursor() {
|
||||
return new AlarmsTableManager(getContext()).queryItems();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getOnContentChangeAction() {
|
||||
return ACTION_CHANGE_CONTENT;
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,94 @@
|
||||
package com.philliphsu.clock2.model;
|
||||
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
|
||||
/**
|
||||
* Created by Phillip Hsu on 7/30/2016.
|
||||
*/
|
||||
public final class AlarmsTable {
|
||||
private AlarmsTable() {}
|
||||
|
||||
// TODO: Consider defining index constants for each column,
|
||||
// and then removing all cursor getColumnIndex() calls.
|
||||
public static final String TABLE_ALARMS = "alarms";
|
||||
|
||||
// TODO: Consider implementing BaseColumns instead to get _id column.
|
||||
public static final String COLUMN_ID = "_id";
|
||||
public static final String COLUMN_HOUR = "hour";
|
||||
public static final String COLUMN_MINUTES = "minutes";
|
||||
public static final String COLUMN_LABEL = "label";
|
||||
public static final String COLUMN_RINGTONE = "ringtone";
|
||||
public static final String COLUMN_VIBRATES = "vibrates";
|
||||
public static final String COLUMN_ENABLED = "enabled";
|
||||
|
||||
// TODO: Delete this column, becuase new sort order does not consider it
|
||||
@Deprecated
|
||||
public static final String COLUMN_RING_TIME_MILLIS = "ring_time_millis";
|
||||
|
||||
public static final String COLUMN_SNOOZING_UNTIL_MILLIS = "snoozing_until_millis";
|
||||
public static final String COLUMN_SUNDAY = "sunday";
|
||||
public static final String COLUMN_MONDAY = "monday";
|
||||
public static final String COLUMN_TUESDAY = "tuesday";
|
||||
public static final String COLUMN_WEDNESDAY = "wednesday";
|
||||
public static final String COLUMN_THURSDAY = "thursday";
|
||||
public static final String COLUMN_FRIDAY = "friday";
|
||||
public static final String COLUMN_SATURDAY = "saturday";
|
||||
public static final String COLUMN_IGNORE_UPCOMING_RING_TIME = "ignore_upcoming_ring_time";
|
||||
|
||||
// First sort by ring time in ascending order (smaller values first),
|
||||
// then break ties by sorting by id in ascending order.
|
||||
@Deprecated
|
||||
private static final String SORT_ORDER =
|
||||
COLUMN_RING_TIME_MILLIS + " ASC, " + COLUMN_ID + " ASC";
|
||||
|
||||
public static final String NEW_SORT_ORDER = COLUMN_HOUR + " ASC, "
|
||||
+ COLUMN_MINUTES + " ASC, "
|
||||
// TOneverDO: Sort COLUMN_ENABLED or else alarms could be reordered
|
||||
// if you toggle them on/off, which looks confusing.
|
||||
// TODO: Figure out how to get the order to be:
|
||||
// No recurring days ->
|
||||
// Recurring earlier in user's weekday order ->
|
||||
// Recurring everyday
|
||||
// As written now, this is incorrect! For one, it assumes
|
||||
// the standard week order (starting on Sunday).
|
||||
// DESC gives us (Sunday -> Saturday -> No recurring days),
|
||||
// ASC gives us the reverse (No recurring days -> Saturday -> Sunday).
|
||||
// TODO: If assuming standard week order, try ASC for all days but
|
||||
// write COLUMN_SATURDAY first, then COLUMN_FRIDAY, ... , COLUMN_SUNDAY.
|
||||
// Check if that gives us (No recurring days -> Sunday -> Saturday).
|
||||
// + COLUMN_SUNDAY + " DESC, "
|
||||
// + COLUMN_MONDAY + " DESC, "
|
||||
// + COLUMN_TUESDAY + " DESC, "
|
||||
// + COLUMN_WEDNESDAY + " DESC, "
|
||||
// + COLUMN_THURSDAY + " DESC, "
|
||||
// + COLUMN_FRIDAY + " DESC, "
|
||||
// + COLUMN_SATURDAY + " DESC, "
|
||||
// All else equal, newer alarms first
|
||||
+ COLUMN_ID + " DESC"; // TODO: If duplicate alarm times disallowed, delete this
|
||||
|
||||
public static void onCreate(SQLiteDatabase db) {
|
||||
db.execSQL("CREATE TABLE " + TABLE_ALARMS + " ("
|
||||
+ COLUMN_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
|
||||
+ COLUMN_HOUR + " INTEGER NOT NULL, "
|
||||
+ COLUMN_MINUTES + " INTEGER NOT NULL, "
|
||||
+ COLUMN_LABEL + " TEXT, "
|
||||
+ COLUMN_RINGTONE + " TEXT NOT NULL, "
|
||||
+ COLUMN_VIBRATES + " INTEGER NOT NULL, "
|
||||
+ COLUMN_ENABLED + " INTEGER NOT NULL, "
|
||||
+ COLUMN_RING_TIME_MILLIS + " INTEGER NOT NULL, "
|
||||
+ COLUMN_SNOOZING_UNTIL_MILLIS + " INTEGER, "
|
||||
+ COLUMN_SUNDAY + " INTEGER NOT NULL DEFAULT 0, "
|
||||
+ COLUMN_MONDAY + " INTEGER NOT NULL DEFAULT 0, "
|
||||
+ COLUMN_TUESDAY + " INTEGER NOT NULL DEFAULT 0, "
|
||||
+ COLUMN_WEDNESDAY + " INTEGER NOT NULL DEFAULT 0, "
|
||||
+ COLUMN_THURSDAY + " INTEGER NOT NULL DEFAULT 0, "
|
||||
+ COLUMN_FRIDAY + " INTEGER NOT NULL DEFAULT 0, "
|
||||
+ COLUMN_SATURDAY + " INTEGER NOT NULL DEFAULT 0, "
|
||||
+ COLUMN_IGNORE_UPCOMING_RING_TIME + " INTEGER NOT NULL);");
|
||||
}
|
||||
|
||||
public static void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
||||
db.execSQL("DROP TABLE IF EXISTS " + TABLE_ALARMS);
|
||||
onCreate(db);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,85 @@
|
||||
package com.philliphsu.clock2.model;
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
|
||||
import com.philliphsu.clock2.Alarm;
|
||||
|
||||
import static com.philliphsu.clock2.DaysOfWeek.FRIDAY;
|
||||
import static com.philliphsu.clock2.DaysOfWeek.MONDAY;
|
||||
import static com.philliphsu.clock2.DaysOfWeek.SATURDAY;
|
||||
import static com.philliphsu.clock2.DaysOfWeek.SUNDAY;
|
||||
import static com.philliphsu.clock2.DaysOfWeek.THURSDAY;
|
||||
import static com.philliphsu.clock2.DaysOfWeek.TUESDAY;
|
||||
import static com.philliphsu.clock2.DaysOfWeek.WEDNESDAY;
|
||||
|
||||
/**
|
||||
* Created by Phillip Hsu on 7/30/2016.
|
||||
*/
|
||||
public class AlarmsTableManager extends DatabaseTableManager<Alarm> {
|
||||
|
||||
public AlarmsTableManager(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getQuerySortOrder() {
|
||||
return AlarmsTable.NEW_SORT_ORDER;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AlarmCursor queryItem(long id) {
|
||||
return wrapInAlarmCursor(super.queryItem(id));
|
||||
}
|
||||
|
||||
@Override
|
||||
public AlarmCursor queryItems() {
|
||||
return wrapInAlarmCursor(super.queryItems());
|
||||
}
|
||||
|
||||
public AlarmCursor queryEnabledAlarms() {
|
||||
return queryItems(AlarmsTable.COLUMN_ENABLED + " = 1", null);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AlarmCursor queryItems(String where, String limit) {
|
||||
return wrapInAlarmCursor(super.queryItems(where, limit));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getTableName() {
|
||||
return AlarmsTable.TABLE_ALARMS;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ContentValues toContentValues(Alarm alarm) {
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(AlarmsTable.COLUMN_HOUR, alarm.hour());
|
||||
values.put(AlarmsTable.COLUMN_MINUTES, alarm.minutes());
|
||||
values.put(AlarmsTable.COLUMN_LABEL, alarm.label());
|
||||
values.put(AlarmsTable.COLUMN_RINGTONE, alarm.ringtone());
|
||||
values.put(AlarmsTable.COLUMN_VIBRATES, alarm.vibrates());
|
||||
values.put(AlarmsTable.COLUMN_ENABLED, alarm.isEnabled());
|
||||
values.put(AlarmsTable.COLUMN_RING_TIME_MILLIS, alarm.ringsAt());
|
||||
values.put(AlarmsTable.COLUMN_SNOOZING_UNTIL_MILLIS, alarm.snoozingUntil());
|
||||
values.put(AlarmsTable.COLUMN_SUNDAY, alarm.isRecurring(SUNDAY));
|
||||
values.put(AlarmsTable.COLUMN_MONDAY, alarm.isRecurring(MONDAY));
|
||||
values.put(AlarmsTable.COLUMN_TUESDAY, alarm.isRecurring(TUESDAY));
|
||||
values.put(AlarmsTable.COLUMN_WEDNESDAY, alarm.isRecurring(WEDNESDAY));
|
||||
values.put(AlarmsTable.COLUMN_THURSDAY, alarm.isRecurring(THURSDAY));
|
||||
values.put(AlarmsTable.COLUMN_FRIDAY, alarm.isRecurring(FRIDAY));
|
||||
values.put(AlarmsTable.COLUMN_SATURDAY, alarm.isRecurring(SATURDAY));
|
||||
values.put(AlarmsTable.COLUMN_IGNORE_UPCOMING_RING_TIME, alarm.isIgnoringUpcomingRingTime());
|
||||
return values;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getOnContentChangeAction() {
|
||||
return AlarmsListCursorLoader.ACTION_CHANGE_CONTENT;
|
||||
}
|
||||
|
||||
private AlarmCursor wrapInAlarmCursor(Cursor c) {
|
||||
return new AlarmCursor(c);
|
||||
}
|
||||
}
|
||||
@ -11,6 +11,7 @@ import com.philliphsu.clock2.util.LocalBroadcastHelper;
|
||||
/**
|
||||
* Created by Phillip Hsu on 7/29/2016.
|
||||
*/
|
||||
@Deprecated
|
||||
public abstract class BaseDatabaseHelper<T extends ObjectWithId> extends SQLiteOpenHelper {
|
||||
|
||||
public static final String COLUMN_ID = "_id";
|
||||
|
||||
@ -25,7 +25,7 @@ public abstract class BaseItemCursor<T extends ObjectWithId> extends CursorWrapp
|
||||
Log.e(TAG, "Failed to retrieve id, cursor out of range");
|
||||
return -1;
|
||||
}
|
||||
return getLong(getColumnIndexOrThrow(BaseDatabaseHelper.COLUMN_ID));
|
||||
return getLong(getColumnIndexOrThrow("_id")); // TODO: Refer to a constant instead of a hardcoded value
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -0,0 +1,33 @@
|
||||
package com.philliphsu.clock2.model;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
|
||||
/**
|
||||
* Created by Phillip Hsu on 7/30/2016.
|
||||
*/
|
||||
public class ClockAppDatabaseHelper extends SQLiteOpenHelper {
|
||||
private static final String TAG = "ClockAppDatabaseHelper";
|
||||
private static final String DB_NAME = "clock_app.db";
|
||||
private static final int VERSION_1 = 1;
|
||||
|
||||
/**
|
||||
* @param context the Context with which the application context will be retrieved
|
||||
*/
|
||||
public ClockAppDatabaseHelper(Context context) {
|
||||
super(context.getApplicationContext(), DB_NAME, null, VERSION_1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(SQLiteDatabase db) {
|
||||
AlarmsTable.onCreate(db);
|
||||
TimersTable.onCreate(db);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
||||
AlarmsTable.onUpgrade(db, oldVersion, newVersion);
|
||||
TimersTable.onUpgrade(db, oldVersion, newVersion);
|
||||
}
|
||||
}
|
||||
@ -10,6 +10,7 @@ import java.util.ArrayList;
|
||||
/**
|
||||
* Created by Phillip Hsu on 6/25/2016.
|
||||
*/
|
||||
@Deprecated
|
||||
public class DatabaseManager {
|
||||
|
||||
private static DatabaseManager sDatabaseManager;
|
||||
|
||||
@ -0,0 +1,112 @@
|
||||
package com.philliphsu.clock2.model;
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
|
||||
import com.philliphsu.clock2.util.LocalBroadcastHelper;
|
||||
|
||||
/**
|
||||
* Created by Phillip Hsu on 7/30/2016.
|
||||
*/
|
||||
public abstract class DatabaseTableManager<T extends ObjectWithId> {
|
||||
// TODO: Consider implementing BaseColumns for your table schemas.
|
||||
// This column should be present in all table schemas, and the value is simple enough
|
||||
// we can reproduce it here instead of relying on our subclasses to retrieve it from
|
||||
// their designated table schema.
|
||||
private static final String COLUMN_ID = "_id";
|
||||
|
||||
private final SQLiteOpenHelper mDbHelper;
|
||||
private final Context mAppContext;
|
||||
|
||||
public DatabaseTableManager(Context context) {
|
||||
// Internally uses the app context
|
||||
mDbHelper = new ClockAppDatabaseHelper(context);
|
||||
mAppContext = context.getApplicationContext();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the table managed by this helper
|
||||
*/
|
||||
protected abstract String getTableName();
|
||||
|
||||
/**
|
||||
* @return the ContentValues representing the item's properties.
|
||||
* You do not need to put a mapping for {@link #COLUMN_ID}, since
|
||||
* the database manages ids for us (if you created your table
|
||||
* with {@link #COLUMN_ID} as an {@code INTEGER PRIMARY KEY}).
|
||||
*/
|
||||
protected abstract ContentValues toContentValues(T item);
|
||||
|
||||
/**
|
||||
* @return the Intent action that will be used to send broadcasts
|
||||
* to our designated {@link NewSQLiteCursorLoader} whenever an
|
||||
* underlying change to our data is detected. The Loader should
|
||||
* receive the broadcast and reload its data.
|
||||
*/
|
||||
protected abstract String getOnContentChangeAction();
|
||||
|
||||
/**
|
||||
* @return optional String specifying the sort order
|
||||
* to use when querying the database. The default
|
||||
* implementation returns null, which may return
|
||||
* queries unordered.
|
||||
*/
|
||||
protected String getQuerySortOrder() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public long insertItem(T item) {
|
||||
long id = mDbHelper.getWritableDatabase().insert(
|
||||
getTableName(), null, toContentValues(item));
|
||||
item.setId(id);
|
||||
notifyContentChanged();
|
||||
return id;
|
||||
}
|
||||
|
||||
public int updateItem(long id, T newItem) {
|
||||
newItem.setId(id);
|
||||
SQLiteDatabase db = mDbHelper.getWritableDatabase();
|
||||
int rowsUpdated = db.update(getTableName(),
|
||||
toContentValues(newItem),
|
||||
COLUMN_ID + " = " + id,
|
||||
null);
|
||||
notifyContentChanged();
|
||||
return rowsUpdated;
|
||||
}
|
||||
|
||||
public int deleteItem(T item) {
|
||||
SQLiteDatabase db = mDbHelper.getWritableDatabase();
|
||||
int rowsDeleted = db.delete(getTableName(),
|
||||
COLUMN_ID + " = " + item.getId(),
|
||||
null);
|
||||
notifyContentChanged();
|
||||
return rowsDeleted;
|
||||
}
|
||||
|
||||
public Cursor queryItem(long id) {
|
||||
return queryItems(COLUMN_ID + " = " + id, "1");
|
||||
}
|
||||
|
||||
public Cursor queryItems() {
|
||||
// Select all rows and columns
|
||||
return queryItems(null, null);
|
||||
}
|
||||
|
||||
protected Cursor queryItems(String where, String limit) {
|
||||
return mDbHelper.getReadableDatabase().query(getTableName(),
|
||||
null, // All columns
|
||||
where, // Selection, i.e. where COLUMN_* = [value we're looking for]
|
||||
null, // selection args, none b/c id already specified in selection
|
||||
null, // group by
|
||||
null, // having
|
||||
getQuerySortOrder(), // order/sort by
|
||||
limit); // limit
|
||||
}
|
||||
|
||||
private void notifyContentChanged() {
|
||||
LocalBroadcastHelper.sendBroadcast(mAppContext, getOnContentChangeAction());
|
||||
}
|
||||
}
|
||||
@ -18,9 +18,8 @@ public abstract class NewSQLiteCursorLoader<
|
||||
T extends ObjectWithId,
|
||||
C extends BaseItemCursor<T>>
|
||||
extends AsyncTaskLoader<C> {
|
||||
private static final String TAG = "SQLiteCursorLoader";
|
||||
|
||||
public static final String ACTION_CHANGE_CONTENT = "com.philliphsu.clock2.model.action.CHANGE_CONTENT";
|
||||
private static final String TAG = "SQLiteCursorLoader";
|
||||
|
||||
private C mCursor;
|
||||
private OnContentChangeReceiver mOnContentChangeReceiver;
|
||||
@ -31,6 +30,13 @@ public abstract class NewSQLiteCursorLoader<
|
||||
|
||||
protected abstract C loadCursor();
|
||||
|
||||
/**
|
||||
* @return the Intent action that will be registered to this Loader
|
||||
* for receiving broadcasts about underlying data changes to our
|
||||
* designated database table
|
||||
*/
|
||||
protected abstract String getOnContentChangeAction();
|
||||
|
||||
/* Runs on a worker thread */
|
||||
@Override
|
||||
public C loadInBackground() {
|
||||
@ -81,7 +87,7 @@ public abstract class NewSQLiteCursorLoader<
|
||||
if (mOnContentChangeReceiver == null) {
|
||||
mOnContentChangeReceiver = new OnContentChangeReceiver();
|
||||
LocalBroadcastHelper.registerReceiver(getContext(),
|
||||
mOnContentChangeReceiver, ACTION_CHANGE_CONTENT);
|
||||
mOnContentChangeReceiver, getOnContentChangeAction());
|
||||
}
|
||||
|
||||
if (takeContentChanged() || mCursor == null) {
|
||||
|
||||
@ -0,0 +1,30 @@
|
||||
package com.philliphsu.clock2.model;
|
||||
|
||||
import android.database.Cursor;
|
||||
|
||||
import com.philliphsu.clock2.Timer;
|
||||
|
||||
/**
|
||||
* Created by Phillip Hsu on 7/30/2016.
|
||||
*/
|
||||
public class TimerCursor extends BaseItemCursor<Timer> {
|
||||
|
||||
public TimerCursor(Cursor cursor) {
|
||||
super(cursor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Timer getItem() {
|
||||
if (isBeforeFirst() || isAfterLast())
|
||||
return null;
|
||||
int hour = getInt(getColumnIndexOrThrow(TimersTable.COLUMN_HOUR));
|
||||
int minute = getInt(getColumnIndexOrThrow(TimersTable.COLUMN_MINUTE));
|
||||
int second = getInt(getColumnIndexOrThrow(TimersTable.COLUMN_SECOND));
|
||||
String label = getString(getColumnIndexOrThrow(TimersTable.COLUMN_LABEL));
|
||||
// String group = getString(getColumnIndexOrThrow(COLUMN_GROUP));
|
||||
Timer t = Timer.create(hour, minute, second, label, /*group*/"");
|
||||
t.setEndTime(getInt(getColumnIndexOrThrow(TimersTable.COLUMN_END_TIME)));
|
||||
t.setPauseTime(getInt(getColumnIndexOrThrow(TimersTable.COLUMN_PAUSE_TIME)));
|
||||
return t;
|
||||
}
|
||||
}
|
||||
@ -10,6 +10,7 @@ import com.philliphsu.clock2.Timer;
|
||||
/**
|
||||
* Created by Phillip Hsu on 7/29/2016.
|
||||
*/
|
||||
@Deprecated
|
||||
public class TimerDatabaseHelper extends BaseDatabaseHelper<Timer> {
|
||||
private static final String TAG = "TimerDatabaseHelper";
|
||||
private static final String DB_NAME = "timers.db";
|
||||
@ -108,25 +109,4 @@ public class TimerDatabaseHelper extends BaseDatabaseHelper<Timer> {
|
||||
return new TimerCursor(c);
|
||||
}
|
||||
|
||||
public static class TimerCursor extends BaseItemCursor<Timer> {
|
||||
|
||||
public TimerCursor(Cursor cursor) {
|
||||
super(cursor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Timer getItem() {
|
||||
if (isBeforeFirst() || isAfterLast())
|
||||
return null;
|
||||
int hour = getInt(getColumnIndexOrThrow(COLUMN_HOUR));
|
||||
int minute = getInt(getColumnIndexOrThrow(COLUMN_MINUTE));
|
||||
int second = getInt(getColumnIndexOrThrow(COLUMN_SECOND));
|
||||
String label = getString(getColumnIndexOrThrow(COLUMN_LABEL));
|
||||
// String group = getString(getColumnIndexOrThrow(COLUMN_GROUP));
|
||||
Timer t = Timer.create(hour, minute, second, label, /*group*/"");
|
||||
t.setEndTime(getInt(getColumnIndexOrThrow(COLUMN_END_TIME)));
|
||||
t.setPauseTime(getInt(getColumnIndexOrThrow(COLUMN_PAUSE_TIME)));
|
||||
return t;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -7,14 +7,21 @@ import com.philliphsu.clock2.Timer;
|
||||
/**
|
||||
* Created by Phillip Hsu on 7/29/2016.
|
||||
*/
|
||||
public class TimersListCursorLoader extends NewSQLiteCursorLoader<Timer, TimerDatabaseHelper.TimerCursor> {
|
||||
public class TimersListCursorLoader extends NewSQLiteCursorLoader<Timer, TimerCursor> {
|
||||
public static final String ACTION_CHANGE_CONTENT
|
||||
= "com.philliphsu.clock2.model.TimersListCursorLoader.action.CHANGE_CONTENT";
|
||||
|
||||
public TimersListCursorLoader(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TimerDatabaseHelper.TimerCursor loadCursor() {
|
||||
return new TimerDatabaseHelper(getContext()).queryItems();
|
||||
protected TimerCursor loadCursor() {
|
||||
return new TimersTableManager(getContext()).queryItems();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getOnContentChangeAction() {
|
||||
return ACTION_CHANGE_CONTENT;
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,54 @@
|
||||
package com.philliphsu.clock2.model;
|
||||
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
|
||||
/**
|
||||
* Created by Phillip Hsu on 7/30/2016.
|
||||
*/
|
||||
public final class TimersTable {
|
||||
private TimersTable() {}
|
||||
|
||||
// TODO: Consider defining index constants for each column,
|
||||
// and then removing all cursor getColumnIndex() calls.
|
||||
public static final String TABLE_TIMERS = "timers";
|
||||
|
||||
// TODO: Consider implementing BaseColumns instead to get _id column.
|
||||
public static final String COLUMN_ID = "_id";
|
||||
public static final String COLUMN_HOUR = "hour";
|
||||
public static final String COLUMN_MINUTE = "minute";
|
||||
public static final String COLUMN_SECOND = "second";
|
||||
public static final String COLUMN_LABEL = "label";
|
||||
|
||||
// http://stackoverflow.com/q/24183958/5055032
|
||||
// https://www.sqlite.org/lang_keywords.html
|
||||
// GROUP is a reserved keyword, so your CREATE TABLE statement
|
||||
// will not compile if you include this!
|
||||
// public static final String COLUMN_GROUP = "group";
|
||||
|
||||
public static final String COLUMN_END_TIME = "end_time";
|
||||
public static final String COLUMN_PAUSE_TIME = "pause_time";
|
||||
|
||||
public static final String SORT_ORDER =
|
||||
COLUMN_HOUR + " ASC, "
|
||||
+ COLUMN_MINUTE + " ASC, "
|
||||
+ COLUMN_SECOND + " ASC, "
|
||||
// All else equal, newer timers first
|
||||
+ COLUMN_ID + " DESC";
|
||||
|
||||
public static void onCreate(SQLiteDatabase db) {
|
||||
db.execSQL("CREATE TABLE " + TABLE_TIMERS + " ("
|
||||
+ COLUMN_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
|
||||
+ COLUMN_HOUR + " INTEGER NOT NULL, "
|
||||
+ COLUMN_MINUTE + " INTEGER NOT NULL, "
|
||||
+ COLUMN_SECOND + " INTEGER NOT NULL, "
|
||||
+ COLUMN_LABEL + " TEXT NOT NULL, "
|
||||
// + COLUMN_GROUP + " TEXT NOT NULL, "
|
||||
+ COLUMN_END_TIME + " INTEGER NOT NULL, "
|
||||
+ COLUMN_PAUSE_TIME + " INTEGER NOT NULL);");
|
||||
}
|
||||
|
||||
public static void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
||||
db.execSQL("DROP TABLE IF EXISTS " + TABLE_TIMERS);
|
||||
onCreate(db);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,64 @@
|
||||
package com.philliphsu.clock2.model;
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
|
||||
import com.philliphsu.clock2.Timer;
|
||||
|
||||
/**
|
||||
* Created by Phillip Hsu on 7/30/2016.
|
||||
*/
|
||||
public class TimersTableManager extends DatabaseTableManager<Timer> {
|
||||
|
||||
public TimersTableManager(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getQuerySortOrder() {
|
||||
return TimersTable.SORT_ORDER;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TimerCursor queryItem(long id) {
|
||||
return wrapInTimerCursor(queryItem(id));
|
||||
}
|
||||
|
||||
@Override
|
||||
public TimerCursor queryItems() {
|
||||
return wrapInTimerCursor(super.queryItems());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TimerCursor queryItems(String where, String limit) {
|
||||
return wrapInTimerCursor(super.queryItems(where, limit));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getTableName() {
|
||||
return TimersTable.TABLE_TIMERS;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ContentValues toContentValues(Timer timer) {
|
||||
ContentValues cv = new ContentValues();
|
||||
cv.put(TimersTable.COLUMN_HOUR, timer.hour());
|
||||
cv.put(TimersTable.COLUMN_MINUTE, timer.minute());
|
||||
cv.put(TimersTable.COLUMN_SECOND, timer.second());
|
||||
cv.put(TimersTable.COLUMN_LABEL, timer.label());
|
||||
// cv.put(TimersTable.COLUMN_GROUP, timer.group());
|
||||
cv.put(TimersTable.COLUMN_END_TIME, timer.endTime());
|
||||
cv.put(TimersTable.COLUMN_PAUSE_TIME, timer.pauseTime());
|
||||
return cv;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getOnContentChangeAction() {
|
||||
return TimersListCursorLoader.ACTION_CHANGE_CONTENT;
|
||||
}
|
||||
|
||||
private TimerCursor wrapInTimerCursor(Cursor c) {
|
||||
return new TimerCursor(c);
|
||||
}
|
||||
}
|
||||
@ -5,12 +5,12 @@ import android.view.ViewGroup;
|
||||
import com.philliphsu.clock2.BaseCursorAdapter;
|
||||
import com.philliphsu.clock2.OnListItemInteractionListener;
|
||||
import com.philliphsu.clock2.Timer;
|
||||
import com.philliphsu.clock2.model.TimerDatabaseHelper;
|
||||
import com.philliphsu.clock2.model.TimerCursor;
|
||||
|
||||
/**
|
||||
* Created by Phillip Hsu on 7/29/2016.
|
||||
*/
|
||||
public class TimersCursorAdapter extends BaseCursorAdapter<Timer, TimerViewHolder, TimerDatabaseHelper.TimerCursor> {
|
||||
public class TimersCursorAdapter extends BaseCursorAdapter<Timer, TimerViewHolder, TimerCursor> {
|
||||
|
||||
public TimersCursorAdapter(OnListItemInteractionListener<Timer> listener) {
|
||||
super(listener);
|
||||
|
||||
@ -8,13 +8,14 @@ import android.support.v4.content.Loader;
|
||||
import com.philliphsu.clock2.RecyclerViewFragment;
|
||||
import com.philliphsu.clock2.Timer;
|
||||
import com.philliphsu.clock2.edittimer.EditTimerActivity;
|
||||
import com.philliphsu.clock2.model.TimerCursor;
|
||||
import com.philliphsu.clock2.model.TimerDatabaseHelper;
|
||||
import com.philliphsu.clock2.model.TimersListCursorLoader;
|
||||
|
||||
public class TimersFragment extends RecyclerViewFragment<
|
||||
Timer,
|
||||
TimerViewHolder,
|
||||
TimerDatabaseHelper.TimerCursor,
|
||||
TimerCursor,
|
||||
TimersCursorAdapter> {
|
||||
public static final int REQUEST_CREATE_TIMER = 0;
|
||||
|
||||
@ -38,7 +39,7 @@ public class TimersFragment extends RecyclerViewFragment<
|
||||
}
|
||||
|
||||
@Override
|
||||
public Loader<TimerDatabaseHelper.TimerCursor> onCreateLoader(int id, Bundle args) {
|
||||
public Loader<TimerCursor> onCreateLoader(int id, Bundle args) {
|
||||
return new TimersListCursorLoader(getActivity());
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user