/*
*
* Copyright (c) Microsoft. All rights reserved.
* Licensed under the MIT license.
*
* Project Oxford: http://ProjectOxford.ai
*
* Project Oxford Mimicker Alarm Github:
* https://github.com/Microsoft/ProjectOxford-Apps-MimickerAlarm
*
* Copyright (c) Microsoft Corporation
* All rights reserved.
*
* MIT License:
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
*/
package com.microsoft.mimickeralarm.scheduling;
import android.app.Notification;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.support.v7.app.NotificationCompat;
import android.support.v7.preference.PreferenceManager;
import android.util.Log;
import com.microsoft.mimickeralarm.R;
import com.microsoft.mimickeralarm.appcore.AlarmMainActivity;
import com.microsoft.mimickeralarm.model.Alarm;
import com.microsoft.mimickeralarm.model.AlarmList;
import com.microsoft.mimickeralarm.ringing.AlarmRingingActivity;
import com.microsoft.mimickeralarm.ringing.AlarmRingingService;
import com.microsoft.mimickeralarm.utilities.DateTimeUtilities;
import java.util.Calendar;
import java.util.List;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.UUID;
/**
* This class handles the state of the Mimicker alarm notification. It communicates with the
* AlarmRingingService to display the alarm hosted by a foreground service. The notification will
* display the following:
* <p/>
* The next alarm that is scheduled. If the next alarm is a snoozed alarm, the notification will
* correctly reflect that case. If the user taps the notification, the Mimicker app should
* launch at the correct alarm setting page.
* <p/>
* If an alarm is ringing, the notification will display that an alarm is ringing. If the user
* taps on the notification, it should launch the alarm ringing screen if it is not already
* visible.
* <p/>
* This class is a singleton which is called at boot and from various key points with the
* application lifetime.
*/
public class AlarmNotificationManager {
public final static int NOTIFICATION_ID = 60653426;
public static final String NOTIFICATION_NEXT_ALARM = "next_alarm";
public static final String NOTIFICATION_ALARM_RUNNING = "alarm_running";
private static final String TAG = "AlarmNotificationMgr";
private static AlarmNotificationManager sManager;
private Context mContext;
private UUID mCurrentAlarmId;
private long mCurrentAlarmTime;
private boolean mNotificationsActive;
private boolean mWakeLockEnable;
private AlarmNotificationManager(Context context) {
mContext = context;
resetState();
}
public static AlarmNotificationManager get(Context context) {
if (sManager == null) {
sManager = new AlarmNotificationManager(context);
Log.d(TAG, "Initialized!");
}
return sManager;
}
public static Notification createNextAlarmNotification(Context context, UUID alarmId, long alarmTime) {
Bitmap icon = BitmapFactory.decodeResource(context.getResources(), R.mipmap.ic_launcher_no_bg);
NotificationCompat.Builder builder = new NotificationCompat.Builder(context);
builder.setSmallIcon(R.drawable.alarm_clock_notification)
.setLargeIcon(icon)
.setContentTitle(context.getString(R.string.notification_next_alarm_content_title))
.setSubText(AlarmList.get(context).getAlarm(alarmId).getTitle())
.setContentText(DateTimeUtilities.getDayAndTimeAlarmDisplayString(context, alarmTime))
.setPriority(Notification.PRIORITY_LOW)
.setVisibility(Notification.VISIBILITY_PRIVATE);
Intent startIntent = new Intent(context, AlarmMainActivity.class);
startIntent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
startIntent.putExtra(AlarmRingingService.ALARM_ID, alarmId);
PendingIntent contentIntent = PendingIntent.getActivity(context,
(int) Math.abs(alarmId.getLeastSignificantBits()), startIntent, 0);
builder.setContentIntent(contentIntent);
return builder.build();
}
public static Notification createAlarmRunningNotification(Context context, UUID alarmId) {
NotificationCompat.Builder builder = new NotificationCompat.Builder(context);
builder.setSmallIcon(R.drawable.alarm_clock_notification);
Bitmap icon = BitmapFactory.decodeResource(context.getResources(), R.mipmap.ic_launcher_no_bg);
builder.setLargeIcon(icon);
builder.setContentTitle(context.getString(R.string.notification_alarm_ringing_content_title));
String title = AlarmList.get(context).getAlarm(alarmId).getTitle();
builder.setContentText(title);
Intent ringingIntent = new Intent(context, AlarmRingingActivity.class);
ringingIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
ringingIntent.putExtra(AlarmRingingService.ALARM_ID, alarmId);
PendingIntent contentIntent = PendingIntent.getActivity(context,
(int) Math.abs(alarmId.getLeastSignificantBits()), ringingIntent, 0);
builder.setContentIntent(contentIntent);
return builder.build();
}
public void handleNextAlarmNotificationStatus() {
// Check if notifications are enabled
if (!shouldEnableNotifications()) return;
// Find the alarm that will fire next
List<Alarm> alarms = AlarmList.get(mContext).getAlarms();
Calendar now = Calendar.getInstance();
SortedMap<Long, UUID> alarmValues = new TreeMap<>();
for (Alarm alarm : alarms) {
if (alarm.isEnabled()) {
alarmValues.put(AlarmScheduler.getAlarmTimeIncludeSnoozed(now, alarm), alarm.getId());
}
}
// Decide whether we need to enable, update or remove the notification, or do nothing
if (!alarmValues.isEmpty()) {
Long alarmTime = alarmValues.firstKey();
UUID alarmId = alarmValues.get(alarmTime);
boolean wakelockEnable = shouldEnableWakeLock();
if (!doesCurrentStateMatchAlarmDetails(alarmId, alarmTime, wakelockEnable)) {
updateStateWithAlarmDetails(alarmId, alarmTime, wakelockEnable);
AlarmRingingService.startForegroundService(mContext,
mCurrentAlarmId,
mCurrentAlarmTime,
NOTIFICATION_NEXT_ALARM);
AlarmRingingService.toggleWakeLock(mContext, wakelockEnable);
}
} else {
disableNotifications();
}
}
public void handleAlarmRunningNotificationStatus(UUID alarmId) {
// Check if notifications are enabled
if (!shouldEnableNotifications()) return;
updateStateWithAlarmDetails(alarmId, 0, false);
AlarmRingingService.startForegroundService(mContext,
alarmId,
0,
NOTIFICATION_ALARM_RUNNING);
}
public void disableNotifications() {
// We only attempt to disable the notification if it is already active
if (mNotificationsActive) {
AlarmRingingService.stopForegroundService(mContext);
resetState();
}
}
public void toggleWakeLock(boolean wakelockEnable) {
if (mNotificationsActive) {
mWakeLockEnable = wakelockEnable;
AlarmRingingService.toggleWakeLock(mContext, mWakeLockEnable);
}
}
private void updateStateWithAlarmDetails(UUID alarmId, long alarmTime, boolean wakelockEnable) {
mNotificationsActive = true;
mCurrentAlarmId = alarmId;
mCurrentAlarmTime = alarmTime;
mWakeLockEnable = wakelockEnable;
}
private boolean doesCurrentStateMatchAlarmDetails(UUID alarmId, long alarmTime, boolean wakelockEnable) {
return (mCurrentAlarmTime == alarmTime &&
mCurrentAlarmId.equals(alarmId) &&
mWakeLockEnable == wakelockEnable);
}
private void resetState() {
mNotificationsActive = false;
mCurrentAlarmId = new UUID(0, 0);
mCurrentAlarmTime = 0;
mWakeLockEnable = false;
}
private boolean shouldEnableNotifications() {
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(mContext);
return preferences.getBoolean(mContext.getString(R.string.pref_enable_notifications_key), false);
}
private boolean shouldEnableWakeLock() {
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(mContext);
return preferences.getBoolean(mContext.getString(R.string.pref_enable_reliability_key), false);
}
}