I want the ability to set a reusable timer (e.g. 20 seconds) and then have it start counting down, but I could also minimize the app, do something else, and have the timer still notify me. The timer should also be start / stop / pause / reset-able.
I've seen AlarmManager but I read that it seems to be broken on several devices. Is there a more robust solution?
edit: trying a Service
The Manifest:
<?xml version="1.0" encoding="utf-8"?>
<manifest package="com.packagename.timertest"
xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<service android:name=".TimerService"
android:exported="false"/>
</application>
</manifest>
MainActivity for launching the fragment:
public class MainActivity extends AppCompatActivity {
private Button launchTimerPanelButton;
private FragmentManager fragmentManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
fragmentManager = getSupportFragmentManager();
launchTimerPanelButton = (Button) findViewById(R.id.launch_timer_button);
launchTimerPanelButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
TimerDialogFragment dialogFragment = TimerDialogFragment.newInstance(10);
dialogFragment.setStyle(DialogFragment.STYLE_NORMAL, R.style.CustomDialog);
dialogFragment.show(fragmentManager,"");
}
});
}
}
The XML for MainActivity:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.packagename.timertest.MainActivity">
<Button
android:id="@+id/launch_timer_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Launch Timer Panel"/>
</android.support.constraint.ConstraintLayout>
The DialogFragment:
public class TimerDialogFragment extends DialogFragment {
private static final String ARGUMENT_NUM_SECONDS = "state_num_seconds";
private static final String STATE_NUM_SECONDS = "state_num_seconds";
public static final String STATE_IS_BROADCAST_RECEIVER_REGISTERED = "state_is_broadcast_receiver_registered";
private int numSecondsInitial;
private int numSeconds;
private TextView secondsRemainingTextView;
private Button startButton;
private Button pauseButton;
private Button resetButton;
private Button closeButton;
private BroadcastReceiver restTimerReceiver;
private boolean isBroadcastReceiverRegistered;
public static TimerDialogFragment newInstance(int numSeconds) {
Bundle args = new Bundle();
TimerDialogFragment fragment = new TimerDialogFragment();
args.putInt(ARGUMENT_NUM_SECONDS, numSeconds);
fragment.setArguments(args);
return fragment;
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
numSecondsInitial = getArguments().getInt(ARGUMENT_NUM_SECONDS);
if (savedInstanceState == null) {
numSeconds = numSecondsInitial;
}
else {
numSeconds = savedInstanceState.getInt(STATE_NUM_SECONDS);
}
isBroadcastReceiverRegistered = false;
if (savedInstanceState != null) {
isBroadcastReceiverRegistered = savedInstanceState.getBoolean(STATE_IS_BROADCAST_RECEIVER_REGISTERED);
}
restTimerReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
//???????
}
};
registerBroadcastReceiver();
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View contentView = inflater.inflate(R.layout.dialogfragment_timer, container, false);
getDialog().setTitle("Timer");
secondsRemainingTextView = (TextView) contentView.findViewById(R.id.seconds_remaining_textview);
secondsRemainingTextView.setText(numSeconds + "");
startButton = (Button) contentView.findViewById(R.id.start_button);
pauseButton = (Button) contentView.findViewById(R.id.pause_button);
resetButton = (Button) contentView.findViewById(R.id.reset_button);
closeButton = (Button) contentView.findViewById(R.id.close_button);
startButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
}
});
pauseButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
}
});
resetButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
}
});
closeButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//end the timer first?
unregisterBroadcastReceiver();
dismiss();
}
});
return contentView;
}
@Override
public void onSaveInstanceState(Bundle outState) {
outState.putInt(STATE_NUM_SECONDS, numSeconds);
super.onSaveInstanceState(outState);
}
@Override
public void onResume() {
registerBroadcastReceiver();
super.onResume();
}
@Override
public void onPause() {
unregisterBroadcastReceiver();
super.onPause();
}
@Override
public void onDestroy() {
unregisterBroadcastReceiver();
super.onDestroy();
}
private void registerBroadcastReceiver() {
if (!isBroadcastReceiverRegistered) {
LocalBroadcastManager.getInstance(getActivity()).registerReceiver(restTimerReceiver, new IntentFilter(TimerService.TIMER_SERVICE));
isBroadcastReceiverRegistered = true;
}
}
private void unregisterBroadcastReceiver() {
if (isBroadcastReceiverRegistered) {
isBroadcastReceiverRegistered = false;
LocalBroadcastManager.getInstance(getActivity()).unregisterReceiver(restTimerReceiver);
}
}
}
its layout:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="10dp"
android:gravity="center">
<TextView
android:id="@+id/seconds_remaining_textview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:textAppearance="?android:attr/textAppearanceLarge"
android:gravity="center"
android:text="10"/>
<Button
android:id="@+id/start_button"
android:layout_marginTop="20dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:textAppearance="?android:attr/textAppearanceMedium"
android:gravity="center"
android:text="Start Timer"/>
<Button
android:id="@+id/pause_button"
android:layout_marginTop="20dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:textAppearance="?android:attr/textAppearanceMedium"
android:gravity="center"
android:text="Pause Timer"/>
<Button
android:id="@+id/reset_button"
android:layout_marginTop="20dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:textAppearance="?android:attr/textAppearanceMedium"
android:gravity="center"
android:text="Reset Timer"/>
<Button
android:id="@+id/close_button"
android:layout_marginTop="20dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:textAppearance="?android:attr/textAppearanceMedium"
android:gravity="center"
android:text="Close / End Timer"/>
</LinearLayout>
The service:
public class TimerService extends Service {
private String LOG_TAG = TimerService.class.getSimpleName();
public static final String TIMER_SERVICE = "timer_service";
@Override
public void onCreate() {
super.onCreate();
Log.i(LOG_TAG, "OnCreate");
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.i(LOG_TAG, "In onStartCommand");
new Thread(new Runnable() {
public void run() {
//something
}
}).start();
return START_REDELIVER_INTENT;
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
Log.i(LOG_TAG, "OnBind");
return null;
}
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
@Override
public void onTaskRemoved(Intent rootIntent) {
super.onTaskRemoved(rootIntent);
Log.i(LOG_TAG, "In onTaskRemoved");
}
@Override
public void onDestroy() {
super.onDestroy();
Log.i(LOG_TAG, "In onDestroy");
}
}