Fixed alarm not ringing because of RingtoneActivity being resumed on next launch and not created

This commit is contained in:
Phillip Hsu 2016-06-05 00:08:54 -07:00
parent 1ea774f21b
commit 53d5eed670
3 changed files with 80 additions and 5 deletions

View File

@ -24,8 +24,6 @@
android:label="@string/title_activity_ringtone" android:label="@string/title_activity_ringtone"
android:theme="@style/AppTheme.NoActionBar" android:theme="@style/AppTheme.NoActionBar"
android:excludeFromRecents="true" android:excludeFromRecents="true"
android:finishOnTaskLaunch="true"
android:launchMode="singleTask"
android:taskAffinity="com.philliphsu.clock2.RingtoneActivity"> android:taskAffinity="com.philliphsu.clock2.RingtoneActivity">
</activity> </activity>

View File

@ -1,7 +1,11 @@
package com.philliphsu.clock2.ringtone; package com.philliphsu.clock2.ringtone;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle; import android.os.Bundle;
import android.os.IBinder;
import android.support.v7.app.AppCompatActivity; import android.support.v7.app.AppCompatActivity;
import android.view.View; import android.view.View;
import android.widget.Button; import android.widget.Button;
@ -20,7 +24,8 @@ import static com.philliphsu.clock2.util.Preconditions.checkNotNull;
* TODO: Make this abstract and make appropriate subclasses for Alarms and Timers. * TODO: Make this abstract and make appropriate subclasses for Alarms and Timers.
* TODO: Implement dismiss and extend logic here. * TODO: Implement dismiss and extend logic here.
*/ */
public class RingtoneActivity extends AppCompatActivity { public class RingtoneActivity extends AppCompatActivity implements RingtoneService.RingtoneCallback {
private static final String TAG = "RingtoneActivity";
// Shared with RingtoneService // Shared with RingtoneService
public static final String EXTRA_ITEM_ID = "com.philliphsu.clock2.ringtone.extra.ITEM_ID"; public static final String EXTRA_ITEM_ID = "com.philliphsu.clock2.ringtone.extra.ITEM_ID";
@ -37,12 +42,15 @@ public class RingtoneActivity extends AppCompatActivity {
} }
mAlarm = checkNotNull(AlarmsRepository.getInstance(this).getItem(id)); mAlarm = checkNotNull(AlarmsRepository.getInstance(this).getItem(id));
// 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); AlarmUtils.removeUpcomingAlarmNotification(this, mAlarm);
// Play the ringtone // Play the ringtone
Intent intent = new Intent(this, RingtoneService.class) Intent intent = new Intent(this, RingtoneService.class)
.putExtra(EXTRA_ITEM_ID, mAlarm.id()); .putExtra(EXTRA_ITEM_ID, mAlarm.id());
startService(intent); startService(intent);
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
Button snooze = (Button) findViewById(R.id.btn_snooze); Button snooze = (Button) findViewById(R.id.btn_snooze);
snooze.setOnClickListener(new View.OnClickListener() { snooze.setOnClickListener(new View.OnClickListener() {
@ -84,6 +92,24 @@ public class RingtoneActivity extends AppCompatActivity {
} }
} }
@Override
public void onBackPressed() {
// Capture the back press and return. We want to limit the user's options for leaving
// this activity as much as possible.
}
@Override
protected void onDestroy() {
super.onDestroy();
unbindService(mConnection);
}
@Override
public void onAutoSilence() {
// Service should have stopped itself by this point
finish();
}
private void snooze() { private void snooze() {
mAlarm.snooze(1); // TODO: Read snooze duration from prefs mAlarm.snooze(1); // TODO: Read snooze duration from prefs
AlarmUtils.scheduleAlarm(this, mAlarm); AlarmUtils.scheduleAlarm(this, mAlarm);
@ -97,4 +123,19 @@ public class RingtoneActivity extends AppCompatActivity {
// TODO: Do we need to cancel the PendingIntent and the alarm in AlarmManager? // TODO: Do we need to cancel the PendingIntent and the alarm in AlarmManager?
finish(); finish();
} }
private RingtoneService mBoundService; // TODO: Don't need? Only used locally in ServiceConnection.
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mBoundService = ((RingtoneService.RingtoneBinder) service).getService();
mBoundService.setRingtoneCallback(RingtoneActivity.this);
}
@Override
public void onServiceDisconnected(ComponentName name) {
mBoundService = null;
}
};
} }

View File

@ -9,6 +9,7 @@ import android.media.AudioManager;
import android.media.Ringtone; import android.media.Ringtone;
import android.media.RingtoneManager; import android.media.RingtoneManager;
import android.net.Uri; import android.net.Uri;
import android.os.Binder;
import android.os.Handler; import android.os.Handler;
import android.os.IBinder; import android.os.IBinder;
import android.support.v4.app.NotificationCompat; import android.support.v4.app.NotificationCompat;
@ -28,6 +29,17 @@ import static com.philliphsu.clock2.util.Preconditions.checkNotNull;
* of the RingtoneService will be tied to that of its RingtoneActivity because users are not likely to * of the RingtoneService will be tied to that of its RingtoneActivity because users are not likely to
* navigate away from the Activity without making an action. But if they do accidentally navigate away, * navigate away from the Activity without making an action. But if they do accidentally navigate away,
* they have plenty of time to make the desired action via the notification. * they have plenty of time to make the desired action via the notification.
*
* This is both a started and bound service. See https://developer.android.com/guide/components/bound-services.html#Lifecycle
* "... the service runs until the service stops itself with stopSelf() or another component
* calls stopService(), regardless of whether it is bound to any clients."
* The regardless phrase didn't work for me. I had to unbind in RingtoneActivity first before calling
* stopSelf() on this service for the ringtone to stop playing.
* TODO: Consider making this purely a bound service, so you don't have to bind/unbind AND start/stop
* manually. Instead of implementing onStartCommand() and calling startService(), you would write a public
* method that the activity calls to start playing the ringtone. When the activity calls its onDestroy(), it unbinds
* itself from this service, and the system will know to destroy this service instead of you manually
* calling stopSelf() or stopService().
*/ */
public class RingtoneService extends Service { // TODO: abstract this, make subclasses public class RingtoneService extends Service { // TODO: abstract this, make subclasses
private static final String TAG = "RingtoneService"; private static final String TAG = "RingtoneService";
@ -37,6 +49,7 @@ public class RingtoneService extends Service { // TODO: abstract this, make subc
private Alarm mAlarm; private Alarm mAlarm;
private String mNormalRingTime; private String mNormalRingTime;
private boolean mAutoSilenced = false; private boolean mAutoSilenced = false;
private RingtoneCallback mRingtoneCallback;
// TODO: Using Handler for this is ill-suited? Alarm ringing could outlast the // TODO: Using Handler for this is ill-suited? Alarm ringing could outlast the
// application's life. Use AlarmManager API instead. // application's life. Use AlarmManager API instead.
private final Handler mSilenceHandler = new Handler(); private final Handler mSilenceHandler = new Handler();
@ -44,9 +57,17 @@ public class RingtoneService extends Service { // TODO: abstract this, make subc
@Override @Override
public void run() { public void run() {
mAutoSilenced = true; mAutoSilenced = true;
if (mRingtoneCallback != null) {
// Finish the activity, which fires onDestroy() and then unbinds itself from this service.
// All clients must be unbound before stopSelf() (and stopService()?) will succeed.
// See https://developer.android.com/guide/components/bound-services.html#Lifecycle
// Figure 1 regarding the lifecycle of started and bound services.
mRingtoneCallback.onAutoSilence();
}
stopSelf(); stopSelf();
} }
}; };
private final IBinder mBinder = new RingtoneBinder();
// TODO: Apply the setting for "Silence after" here by using an AlarmManager to // TODO: Apply the setting for "Silence after" here by using an AlarmManager to
// schedule an alarm in the future to stop this service, and also update the foreground // schedule an alarm in the future to stop this service, and also update the foreground
@ -121,7 +142,22 @@ public class RingtoneService extends Service { // TODO: abstract this, make subc
@Override @Override
public IBinder onBind(Intent intent) { public IBinder onBind(Intent intent) {
return null; // Binding to this service is not supported return mBinder;
}
public void setRingtoneCallback(RingtoneCallback callback) {
mRingtoneCallback = callback;
}
// Needed so clients can get the Service instance and e.g. call setRingtoneCallback().
public class RingtoneBinder extends Binder {
RingtoneService getService() {
return RingtoneService.this;
}
}
public interface RingtoneCallback {
void onAutoSilence();
} }
private void scheduleAutoSilence() { private void scheduleAutoSilence() {