Merge remote-tracking branch 'goog/ub-deskclock-gatling'

Bug: 35405410
Test: lunch aosp_bullhead-userdebug && make -j24
Change-Id: I8e51683a7710bc88141dbcd9bbfce5693454ec80
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 954c445..9da916f 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  Copyright (C) 2015 The Android Open Source Project
+  Copyright (C) 2016 The Android Open Source Project
 
   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
@@ -17,10 +17,7 @@
 
 <manifest
     xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:tools="http://schemas.android.com/tools"
-    package="com.android.deskclock"
-    android:versionCode="452"
-    android:versionName="4.5.2">
+    package="com.android.deskclock">
 
     <original-package android:name="com.android.alarmclock" />
     <original-package android:name="com.android.deskclock" />
@@ -53,11 +50,9 @@
         android:requiredForAllUsers="true"
         android:supportsRtl="true">
 
-        <provider
-            android:name=".provider.ClockProvider"
-            android:authorities="com.android.deskclock"
-            android:directBootAware="true"
-            android:exported="false" />
+        <!-- ============================================================== -->
+        <!-- Main app components.                                           -->
+        <!-- ============================================================== -->
 
         <activity
             android:name=".DeskClock"
@@ -67,47 +62,73 @@
             android:windowSoftInputMode="adjustPan">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
+
                 <category android:name="android.intent.category.DEFAULT" />
                 <category android:name="android.intent.category.LAUNCHER" />
             </intent-filter>
         </activity>
 
-        <activity-alias
-            android:name=".DockClock"
-            android:enabled="false"
-            android:label="@string/app_label"
-            android:launchMode="singleTask"
-            android:targetActivity="DeskClock"
-            android:theme="@style/DeskClockTheme">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.DESK_DOCK" />
-            </intent-filter>
-        </activity-alias>
-
-        <activity
-            android:name=".settings.SettingsActivity"
-            android:excludeFromRecents="true"
-            android:label="@string/settings"
-            android:taskAffinity=""
-            android:theme="@style/SettingsTheme">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-            </intent-filter>
-        </activity>
-
         <activity
             android:name=".worldclock.CitySelectionActivity"
             android:excludeFromRecents="true"
             android:label="@string/cities_activity_title"
+            android:parentActivityName=".DeskClock"
             android:taskAffinity=""
-            android:theme="@style/CitiesTheme">
+            android:theme="@style/CitiesTheme" />
+
+        <activity
+            android:name=".settings.SettingsActivity"
+            android:excludeFromRecents="true"
+            android:label="@string/settings"
+            android:parentActivityName=".DeskClock"
+            android:taskAffinity=""
+            android:theme="@style/SettingsTheme" />
+
+        <activity
+            android:name=".HandleShortcuts"
+            android:excludeFromRecents="true"
+            android:launchMode="singleInstance"
+            android:taskAffinity=""
+            android:theme="@android:style/Theme.NoDisplay" />
+
+        <!-- ============================================================== -->
+        <!-- AlarmClock API components.                                     -->
+        <!-- ============================================================== -->
+
+        <activity
+            android:name=".HandleApiCalls"
+            android:excludeFromRecents="true"
+            android:launchMode="singleInstance"
+            android:taskAffinity=""
+            android:theme="@android:style/Theme.NoDisplay">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
+                <action android:name="android.intent.action.DISMISS_ALARM" />
+                <action android:name="android.intent.action.SHOW_ALARMS" />
+                <action android:name="android.intent.action.SHOW_TIMERS" />
+                <action android:name="android.intent.action.SNOOZE_ALARM" />
+
+                <category android:name="android.intent.category.DEFAULT" />
+                <category android:name="android.intent.category.VOICE" />
             </intent-filter>
         </activity>
 
+        <activity-alias
+            android:name=".HandleSetAlarmApiCalls"
+            android:permission="com.android.alarm.permission.SET_ALARM"
+            android:targetActivity=".HandleApiCalls">
+            <intent-filter>
+                <action android:name="android.intent.action.SET_ALARM" />
+                <action android:name="android.intent.action.SET_TIMER" />
+
+                <category android:name="android.intent.category.DEFAULT" />
+                <category android:name="android.intent.category.VOICE" />
+            </intent-filter>
+        </activity-alias>
+
+        <!-- ============================================================== -->
+        <!-- Alarm components.                                              -->
+        <!-- ============================================================== -->
+
         <activity
             android:name=".alarms.AlarmActivity"
             android:directBootAware="true"
@@ -119,147 +140,112 @@
             android:windowSoftInputMode="stateAlwaysHidden" />
 
         <activity
+            android:name=".AlarmSelectionActivity"
+            android:label="@string/dismiss_alarm"
+            android:theme="@android:style/Theme.Holo.Light.Dialog.NoActionBar" />
+
+        <provider
+            android:name=".provider.ClockProvider"
+            android:authorities="com.android.deskclock"
+            android:directBootAware="true"
+            android:exported="false" />
+
+        <receiver
+            android:name=".AlarmInitReceiver"
+            android:directBootAware="true">
+            <intent-filter>
+                <action android:name="android.intent.action.BOOT_COMPLETED" />
+                <action android:name="android.intent.action.LOCALE_CHANGED" />
+                <action android:name="android.intent.action.LOCKED_BOOT_COMPLETED" />
+                <action android:name="android.intent.action.MY_PACKAGE_REPLACED" />
+                <action android:name="android.intent.action.TIME_SET" />
+                <action android:name="android.intent.action.TIMEZONE_CHANGED" />
+            </intent-filter>
+        </receiver>
+
+        <receiver
+            android:name=".alarms.AlarmStateManager"
+            android:directBootAware="true" />
+
+        <service
+            android:name=".alarms.AlarmService"
+            android:directBootAware="true" />
+
+        <!-- ============================================================== -->
+        <!-- Timer components.                                              -->
+        <!-- ============================================================== -->
+
+        <activity
+            android:name=".timer.ExpiredTimersActivity"
+            android:configChanges="screenSize|keyboardHidden|keyboard|navigation"
+            android:directBootAware="true"
+            android:excludeFromRecents="true"
+            android:launchMode="singleInstance"
+            android:resizeableActivity="false"
+            android:showOnLockScreen="true"
+            android:taskAffinity=""
+            android:theme="@style/ExpiredTimersActivityTheme" />
+
+        <!-- Legacy broadcast receiver that honors old scheduled timers across app upgrade. -->
+        <receiver
+            android:name="com.android.deskclock.timer.TimerReceiver"
+            android:exported="false">
+            <intent-filter>
+                <action android:name="times_up" />
+            </intent-filter>
+        </receiver>
+
+        <service
+            android:name=".timer.TimerService"
+            android:description="@string/timer_service_desc"
+            android:directBootAware="true" />
+
+        <!-- ============================================================== -->
+        <!-- Stopwatch components.                                          -->
+        <!-- ============================================================== -->
+
+        <service
+            android:name=".stopwatch.StopwatchService"
+            android:description="@string/stopwatch_service_desc"
+            android:directBootAware="true" />
+
+
+        <!-- ============================================================== -->
+        <!-- Screen saver components.                                       -->
+        <!-- ============================================================== -->
+
+        <activity
             android:name=".ScreensaverActivity"
             android:excludeFromRecents="true"
             android:resizeableActivity="false"
             android:taskAffinity=""
             android:theme="@style/ScreensaverActivityTheme" />
 
-        <receiver
-            android:name=".alarms.AlarmStateManager"
-            android:directBootAware="true"
-            android:exported="false" />
+        <activity
+            android:name=".settings.ScreensaverSettingsActivity"
+            android:excludeFromRecents="true"
+            android:label="@string/screensaver_settings"
+            android:taskAffinity=""
+            android:theme="@style/SettingsTheme" />
 
         <service
-            android:name=".alarms.AlarmService"
-            android:directBootAware="true"
-            android:exported="false" />
+            android:name=".Screensaver"
+            android:label="@string/app_label"
+            android:permission="android.permission.BIND_DREAM_SERVICE">
+            <intent-filter>
+                <action android:name="android.service.dreams.DreamService" />
+                <action android:name="android.app.action.NEXT_ALARM_CLOCK_CHANGED" />
 
-        <activity
-            android:name=".HandleApiCalls"
-            android:excludeFromRecents="true"
-            android:launchMode="singleTask"
-            android:permission="com.android.alarm.permission.SET_ALARM"
-            android:taskAffinity=""
-            android:theme="@android:style/Theme.NoDisplay">
-            <intent-filter>
-                <action android:name="android.intent.action.SET_ALARM" />
                 <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.VOICE" />
             </intent-filter>
-            <intent-filter>
-                <action android:name="android.intent.action.DISMISS_ALARM" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.VOICE" />
-            </intent-filter>
-            <intent-filter>
-                <action android:name="android.intent.action.SNOOZE_ALARM" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.VOICE" />
-            </intent-filter>
-            <intent-filter>
-                <action android:name="android.intent.action.SHOW_ALARMS" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.VOICE" />
-            </intent-filter>
-            <intent-filter>
-                <action android:name="android.intent.action.SET_TIMER" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.VOICE" />
-            </intent-filter>
-        </activity>
+            <meta-data
+                android:name="android.service.dream"
+                android:resource="@xml/screensaver_info" />
+        </service>
 
-        <activity-alias
-            android:name="HandleSetAlarm"
-            android:exported="true"
-            android:targetActivity=".HandleApiCalls" />
-
-        <activity
-            android:name=".HandleDeskClockApiCalls"
-            android:excludeFromRecents="true"
-            android:launchMode="singleTask"
-            android:permission="com.android.alarm.permission.SET_ALARM"
-            android:taskAffinity=""
-            android:theme="@android:style/Theme.NoDisplay">
-            <intent-filter>
-                <action android:name="com.android.deskclock.action.SHOW_CLOCK" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.VOICE" />
-            </intent-filter>
-            <intent-filter>
-                <action android:name="com.android.deskclock.action.ADD_CLOCK" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.VOICE" />
-            </intent-filter>
-            <intent-filter>
-                <action android:name="com.android.deskclock.action.DELETE_CLOCK" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.VOICE" />
-            </intent-filter>
-            <intent-filter>
-                <action android:name="com.android.deskclock.action.START_TIMER" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.VOICE" />
-            </intent-filter>
-            <intent-filter>
-                <action android:name="com.android.deskclock.action.RESET_TIMER" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.VOICE" />
-            </intent-filter>
-            <intent-filter>
-                <action android:name="com.android.deskclock.action.PAUSE_TIMER" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.VOICE" />
-            </intent-filter>
-            <intent-filter>
-                <action android:name="com.android.deskclock.action.SHOW_TIMERS" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.VOICE" />
-            </intent-filter>
-            <intent-filter>
-                <action android:name="com.android.deskclock.action.DELETE_TIMER" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.VOICE" />
-            </intent-filter>
-            <intent-filter>
-                <action android:name="com.android.deskclock.action.SHOW_STOPWATCH" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.VOICE" />
-            </intent-filter>
-            <intent-filter>
-                <action android:name="com.android.deskclock.action.START_STOPWATCH" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.VOICE" />
-            </intent-filter>
-            <intent-filter>
-                <action android:name="com.android.deskclock.action.PAUSE_STOPWATCH" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.VOICE" />
-            </intent-filter>
-            <intent-filter>
-                <action android:name="com.android.deskclock.action.LAP_STOPWATCH" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.VOICE" />
-            </intent-filter>
-            <intent-filter>
-                <action android:name="com.android.deskclock.action.RESET_STOPWATCH" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.VOICE" />
-            </intent-filter>
-        </activity>
-
-        <receiver
-            android:name=".AlarmInitReceiver"
-            android:directBootAware="true">
-            <intent-filter>
-                <action android:name="android.intent.action.LOCKED_BOOT_COMPLETED" />
-                <action android:name="android.intent.action.BOOT_COMPLETED" />
-                <action android:name="android.intent.action.TIME_SET" />
-                <action android:name="android.intent.action.TIMEZONE_CHANGED" />
-                <action android:name="android.intent.action.LOCALE_CHANGED" />
-                <action android:name="android.intent.action.MY_PACKAGE_REPLACED" />
-            </intent-filter>
-        </receiver>
+        <!-- ============================================================== -->
+        <!-- App widget components.                                         -->
+        <!-- ============================================================== -->
 
         <receiver
             android:name="com.android.alarmclock.AnalogAppWidgetProvider"
@@ -280,12 +266,13 @@
             android:name="com.android.alarmclock.DigitalAppWidgetProvider"
             android:label="@string/digital_gadget">
             <intent-filter>
-                <action android:name="android.intent.action.SCREEN_ON" />
-                <action android:name="android.intent.action.DATE_CHANGED" />
-                <action android:name="android.intent.action.LOCALE_CHANGED" />
-                <action android:name="android.intent.action.TIMEZONE_CHANGED" />
                 <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
                 <action android:name="android.app.action.NEXT_ALARM_CLOCK_CHANGED" />
+                <action android:name="android.intent.action.DATE_CHANGED" />
+                <action android:name="android.intent.action.LOCALE_CHANGED" />
+                <action android:name="android.intent.action.SCREEN_ON" />
+                <action android:name="android.intent.action.TIME_SET" />
+                <action android:name="android.intent.action.TIMEZONE_CHANGED" />
                 <action android:name="com.android.deskclock.ALARM_CHANGED" />
                 <action android:name="com.android.deskclock.ON_DAY_CHANGE" />
                 <action android:name="com.android.deskclock.WORLD_CITIES_CHANGED" />
@@ -297,75 +284,7 @@
 
         <service
             android:name="com.android.alarmclock.DigitalAppWidgetCityService"
-            android:exported="false"
             android:permission="android.permission.BIND_REMOTEVIEWS" />
 
-        <!-- Screen saver implementation -->
-        <service
-            android:name=".Screensaver"
-            android:exported="true"
-            android:label="@string/app_label"
-            android:permission="android.permission.BIND_DREAM_SERVICE">
-            <intent-filter>
-                <action android:name="android.service.dreams.DreamService" />
-                <action android:name="android.app.action.NEXT_ALARM_CLOCK_CHANGED" />
-                <category android:name="android.intent.category.DEFAULT" />
-            </intent-filter>
-            <meta-data
-                android:name="android.service.dream"
-                android:resource="@xml/screensaver_info" />
-        </service>
-
-        <!-- Settings activity for screensaver -->
-        <activity
-            android:name=".settings.ScreensaverSettingsActivity"
-            android:excludeFromRecents="true"
-            android:exported="true"
-            android:label="@string/screensaver_settings"
-            android:taskAffinity=""
-            android:theme="@style/SettingsTheme">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-            </intent-filter>
-        </activity>
-
-        <activity
-            android:name=".AlarmSelectionActivity"
-            android:label="@string/dismiss_alarm"
-            android:theme="@android:style/Theme.Holo.Light.Dialog.NoActionBar" />
-
-        <!-- This activity displays only the timers that have expired with only a reset button
-         present. This makes the activity appropriate for display above the lock screen so that
-         users have the limited ability to silence expired timers but nothing else. -->
-        <activity
-            android:name=".timer.ExpiredTimersActivity"
-            android:configChanges="screenSize|keyboardHidden|keyboard|navigation"
-            android:excludeFromRecents="true"
-            android:launchMode="singleInstance"
-            android:resizeableActivity="false"
-            android:showOnLockScreen="true"
-            android:taskAffinity=""
-            android:theme="@style/ExpiredTimersActivityTheme" />
-
-        <!-- Legacy broadcast receiver that honors old scheduled timers across app upgrade. -->
-        <receiver android:name="com.android.deskclock.timer.TimerReceiver"
-                  android:exported="false">
-            <intent-filter>
-                <action android:name="times_up" />
-            </intent-filter>
-        </receiver>
-
-        <service
-            android:name=".timer.TimerService"
-            android:description="@string/timer_service_desc"
-            android:exported="false"
-            tools:ignore="ManifestResource" />
-
-        <service
-            android:name=".stopwatch.StopwatchService"
-            android:description="@string/stopwatch_service_desc"
-            android:exported="false"
-            tools:ignore="ManifestResource" />
-
     </application>
 </manifest>
diff --git a/res/drawable-hdpi/alarm_background_expanded.9.png b/res/drawable-hdpi/alarm_background_expanded.9.png
index d8abf86..f32e39d 100644
--- a/res/drawable-hdpi/alarm_background_expanded.9.png
+++ b/res/drawable-hdpi/alarm_background_expanded.9.png
Binary files differ
diff --git a/res/drawable-hdpi/appwidget_clock_dial.png b/res/drawable-hdpi/appwidget_clock_dial.png
index 244730b..f1af971 100644
--- a/res/drawable-hdpi/appwidget_clock_dial.png
+++ b/res/drawable-hdpi/appwidget_clock_dial.png
Binary files differ
diff --git a/res/drawable-hdpi/appwidget_clock_hour.png b/res/drawable-hdpi/appwidget_clock_hour.png
index 5cfa1a5..ca6455b 100644
--- a/res/drawable-hdpi/appwidget_clock_hour.png
+++ b/res/drawable-hdpi/appwidget_clock_hour.png
Binary files differ
diff --git a/res/drawable-hdpi/appwidget_clock_minute.png b/res/drawable-hdpi/appwidget_clock_minute.png
index 65ad547..f7cb190 100644
--- a/res/drawable-hdpi/appwidget_clock_minute.png
+++ b/res/drawable-hdpi/appwidget_clock_minute.png
Binary files differ
diff --git a/res/drawable-hdpi/bg_day_selected.png b/res/drawable-hdpi/bg_day_selected.png
index 06c329d..5f0579c 100644
--- a/res/drawable-hdpi/bg_day_selected.png
+++ b/res/drawable-hdpi/bg_day_selected.png
Binary files differ
diff --git a/res/drawable-hdpi/bg_gray_circle.png b/res/drawable-hdpi/bg_gray_circle.png
index a232341..5ee0d68 100644
--- a/res/drawable-hdpi/bg_gray_circle.png
+++ b/res/drawable-hdpi/bg_gray_circle.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_alarm_off_white_80dp.png b/res/drawable-hdpi/ic_alarm_off_white_80dp.png
deleted file mode 100644
index 287d834..0000000
--- a/res/drawable-hdpi/ic_alarm_off_white_80dp.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/ic_lap_white_24dp.png b/res/drawable-hdpi/ic_lap_white_24dp.png
index fa1a443..e2b6a4d 100644
--- a/res/drawable-hdpi/ic_lap_white_24dp.png
+++ b/res/drawable-hdpi/ic_lap_white_24dp.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_reset_white_24dp.png b/res/drawable-hdpi/ic_reset_white_24dp.png
index 6776737..18b4075 100644
--- a/res/drawable-hdpi/ic_reset_white_24dp.png
+++ b/res/drawable-hdpi/ic_reset_white_24dp.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_snooze_white_24dp.png b/res/drawable-hdpi/ic_snooze_white_24dp.png
index a9cc73b..e1c06b6 100644
--- a/res/drawable-hdpi/ic_snooze_white_24dp.png
+++ b/res/drawable-hdpi/ic_snooze_white_24dp.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_snooze_white_80dp.png b/res/drawable-hdpi/ic_snooze_white_80dp.png
deleted file mode 100644
index e761103..0000000
--- a/res/drawable-hdpi/ic_snooze_white_80dp.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/ic_swipe_circle_bottom.png b/res/drawable-hdpi/ic_swipe_circle_bottom.png
index 9feacdf..0339525 100644
--- a/res/drawable-hdpi/ic_swipe_circle_bottom.png
+++ b/res/drawable-hdpi/ic_swipe_circle_bottom.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_swipe_circle_dark.png b/res/drawable-hdpi/ic_swipe_circle_dark.png
index 4a74bb6..db79e37 100644
--- a/res/drawable-hdpi/ic_swipe_circle_dark.png
+++ b/res/drawable-hdpi/ic_swipe_circle_dark.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_swipe_circle_light.png b/res/drawable-hdpi/ic_swipe_circle_light.png
index 44add09..d4e6f06 100644
--- a/res/drawable-hdpi/ic_swipe_circle_light.png
+++ b/res/drawable-hdpi/ic_swipe_circle_light.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_swipe_circle_top.png b/res/drawable-hdpi/ic_swipe_circle_top.png
index 12a3323..751bcb4 100644
--- a/res/drawable-hdpi/ic_swipe_circle_top.png
+++ b/res/drawable-hdpi/ic_swipe_circle_top.png
Binary files differ
diff --git a/res/drawable-hdpi/stat_notify_alarm.png b/res/drawable-hdpi/stat_notify_alarm.png
index fb60e5c..ee55115 100644
--- a/res/drawable-hdpi/stat_notify_alarm.png
+++ b/res/drawable-hdpi/stat_notify_alarm.png
Binary files differ
diff --git a/res/drawable-hdpi/stat_notify_stopwatch.png b/res/drawable-hdpi/stat_notify_stopwatch.png
index 7d931d3..ef53893 100644
--- a/res/drawable-hdpi/stat_notify_stopwatch.png
+++ b/res/drawable-hdpi/stat_notify_stopwatch.png
Binary files differ
diff --git a/res/drawable-hdpi/stat_notify_timer.png b/res/drawable-hdpi/stat_notify_timer.png
index a42884a..133d12e 100644
--- a/res/drawable-hdpi/stat_notify_timer.png
+++ b/res/drawable-hdpi/stat_notify_timer.png
Binary files differ
diff --git a/res/drawable-mdpi/alarm_background_expanded.9.png b/res/drawable-mdpi/alarm_background_expanded.9.png
index 4539033..645471f 100644
--- a/res/drawable-mdpi/alarm_background_expanded.9.png
+++ b/res/drawable-mdpi/alarm_background_expanded.9.png
Binary files differ
diff --git a/res/drawable-mdpi/appwidget_clock_dial.png b/res/drawable-mdpi/appwidget_clock_dial.png
index a00d36c..8637171 100644
--- a/res/drawable-mdpi/appwidget_clock_dial.png
+++ b/res/drawable-mdpi/appwidget_clock_dial.png
Binary files differ
diff --git a/res/drawable-mdpi/appwidget_clock_hour.png b/res/drawable-mdpi/appwidget_clock_hour.png
index cd64fae..ef50bd7 100644
--- a/res/drawable-mdpi/appwidget_clock_hour.png
+++ b/res/drawable-mdpi/appwidget_clock_hour.png
Binary files differ
diff --git a/res/drawable-mdpi/appwidget_clock_minute.png b/res/drawable-mdpi/appwidget_clock_minute.png
index 864c81e..4f38398 100644
--- a/res/drawable-mdpi/appwidget_clock_minute.png
+++ b/res/drawable-mdpi/appwidget_clock_minute.png
Binary files differ
diff --git a/res/drawable-mdpi/bg_day_selected.png b/res/drawable-mdpi/bg_day_selected.png
index 3d0682f..f031f18 100644
--- a/res/drawable-mdpi/bg_day_selected.png
+++ b/res/drawable-mdpi/bg_day_selected.png
Binary files differ
diff --git a/res/drawable-mdpi/bg_gray_circle.png b/res/drawable-mdpi/bg_gray_circle.png
index a8db63f..68328ff 100644
--- a/res/drawable-mdpi/bg_gray_circle.png
+++ b/res/drawable-mdpi/bg_gray_circle.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_alarm_off_white_80dp.png b/res/drawable-mdpi/ic_alarm_off_white_80dp.png
deleted file mode 100644
index fa08809..0000000
--- a/res/drawable-mdpi/ic_alarm_off_white_80dp.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/ic_lap_white_24dp.png b/res/drawable-mdpi/ic_lap_white_24dp.png
index 4dec663..6a347e7 100644
--- a/res/drawable-mdpi/ic_lap_white_24dp.png
+++ b/res/drawable-mdpi/ic_lap_white_24dp.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_reset_white_24dp.png b/res/drawable-mdpi/ic_reset_white_24dp.png
index b349029..9826622 100644
--- a/res/drawable-mdpi/ic_reset_white_24dp.png
+++ b/res/drawable-mdpi/ic_reset_white_24dp.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_snooze_white_24dp.png b/res/drawable-mdpi/ic_snooze_white_24dp.png
index 62b5b8f..2497228 100644
--- a/res/drawable-mdpi/ic_snooze_white_24dp.png
+++ b/res/drawable-mdpi/ic_snooze_white_24dp.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_snooze_white_80dp.png b/res/drawable-mdpi/ic_snooze_white_80dp.png
deleted file mode 100644
index 42f9c6a..0000000
--- a/res/drawable-mdpi/ic_snooze_white_80dp.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/ic_swipe_circle_bottom.png b/res/drawable-mdpi/ic_swipe_circle_bottom.png
index 478e765..ae16030 100644
--- a/res/drawable-mdpi/ic_swipe_circle_bottom.png
+++ b/res/drawable-mdpi/ic_swipe_circle_bottom.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_swipe_circle_dark.png b/res/drawable-mdpi/ic_swipe_circle_dark.png
index c48521e..24ca589 100644
--- a/res/drawable-mdpi/ic_swipe_circle_dark.png
+++ b/res/drawable-mdpi/ic_swipe_circle_dark.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_swipe_circle_light.png b/res/drawable-mdpi/ic_swipe_circle_light.png
index 26319b6..e9c1def 100644
--- a/res/drawable-mdpi/ic_swipe_circle_light.png
+++ b/res/drawable-mdpi/ic_swipe_circle_light.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_swipe_circle_top.png b/res/drawable-mdpi/ic_swipe_circle_top.png
index af4f934..bb8d32b 100644
--- a/res/drawable-mdpi/ic_swipe_circle_top.png
+++ b/res/drawable-mdpi/ic_swipe_circle_top.png
Binary files differ
diff --git a/res/drawable-mdpi/stat_notify_alarm.png b/res/drawable-mdpi/stat_notify_alarm.png
index a57ca16..aa0ba15 100644
--- a/res/drawable-mdpi/stat_notify_alarm.png
+++ b/res/drawable-mdpi/stat_notify_alarm.png
Binary files differ
diff --git a/res/drawable-mdpi/stat_notify_stopwatch.png b/res/drawable-mdpi/stat_notify_stopwatch.png
index 1d1a8a3..e82d339 100644
--- a/res/drawable-mdpi/stat_notify_stopwatch.png
+++ b/res/drawable-mdpi/stat_notify_stopwatch.png
Binary files differ
diff --git a/res/drawable-mdpi/stat_notify_timer.png b/res/drawable-mdpi/stat_notify_timer.png
index d7d64cb..896130e 100644
--- a/res/drawable-mdpi/stat_notify_timer.png
+++ b/res/drawable-mdpi/stat_notify_timer.png
Binary files differ
diff --git a/res/drawable-nodpi/appwidget_analog_clock_preview.png b/res/drawable-nodpi/appwidget_analog_clock_preview.png
index 349c92f..2c7d4c9 100644
--- a/res/drawable-nodpi/appwidget_analog_clock_preview.png
+++ b/res/drawable-nodpi/appwidget_analog_clock_preview.png
Binary files differ
diff --git a/res/drawable-nodpi/appwidget_digital_clock_preview.png b/res/drawable-nodpi/appwidget_digital_clock_preview.png
index e0922a1..25a4954 100644
--- a/res/drawable-nodpi/appwidget_digital_clock_preview.png
+++ b/res/drawable-nodpi/appwidget_digital_clock_preview.png
Binary files differ
diff --git a/res/drawable-sw600dp-hdpi/bg_gray_circle.png b/res/drawable-sw600dp-hdpi/bg_gray_circle.png
index 7a67540..2be09ea 100644
--- a/res/drawable-sw600dp-hdpi/bg_gray_circle.png
+++ b/res/drawable-sw600dp-hdpi/bg_gray_circle.png
Binary files differ
diff --git a/res/drawable-sw600dp-hdpi/ic_swipe_circle_bottom.png b/res/drawable-sw600dp-hdpi/ic_swipe_circle_bottom.png
index 9fa2d79..00f5356 100644
--- a/res/drawable-sw600dp-hdpi/ic_swipe_circle_bottom.png
+++ b/res/drawable-sw600dp-hdpi/ic_swipe_circle_bottom.png
Binary files differ
diff --git a/res/drawable-sw600dp-hdpi/ic_swipe_circle_dark.png b/res/drawable-sw600dp-hdpi/ic_swipe_circle_dark.png
index 6a2cba6..498a658 100644
--- a/res/drawable-sw600dp-hdpi/ic_swipe_circle_dark.png
+++ b/res/drawable-sw600dp-hdpi/ic_swipe_circle_dark.png
Binary files differ
diff --git a/res/drawable-sw600dp-hdpi/ic_swipe_circle_light.png b/res/drawable-sw600dp-hdpi/ic_swipe_circle_light.png
index deb37c4..3f73433 100644
--- a/res/drawable-sw600dp-hdpi/ic_swipe_circle_light.png
+++ b/res/drawable-sw600dp-hdpi/ic_swipe_circle_light.png
Binary files differ
diff --git a/res/drawable-sw600dp-hdpi/ic_swipe_circle_top.png b/res/drawable-sw600dp-hdpi/ic_swipe_circle_top.png
index c61ceaf..222e84e 100644
--- a/res/drawable-sw600dp-hdpi/ic_swipe_circle_top.png
+++ b/res/drawable-sw600dp-hdpi/ic_swipe_circle_top.png
Binary files differ
diff --git a/res/drawable-sw600dp-mdpi/bg_gray_circle.png b/res/drawable-sw600dp-mdpi/bg_gray_circle.png
index 8caf124..4145e3d 100644
--- a/res/drawable-sw600dp-mdpi/bg_gray_circle.png
+++ b/res/drawable-sw600dp-mdpi/bg_gray_circle.png
Binary files differ
diff --git a/res/drawable-sw600dp-mdpi/ic_swipe_circle_bottom.png b/res/drawable-sw600dp-mdpi/ic_swipe_circle_bottom.png
index 04e7162..236ec1d 100644
--- a/res/drawable-sw600dp-mdpi/ic_swipe_circle_bottom.png
+++ b/res/drawable-sw600dp-mdpi/ic_swipe_circle_bottom.png
Binary files differ
diff --git a/res/drawable-sw600dp-mdpi/ic_swipe_circle_dark.png b/res/drawable-sw600dp-mdpi/ic_swipe_circle_dark.png
index ffe7378..ee766a9 100644
--- a/res/drawable-sw600dp-mdpi/ic_swipe_circle_dark.png
+++ b/res/drawable-sw600dp-mdpi/ic_swipe_circle_dark.png
Binary files differ
diff --git a/res/drawable-sw600dp-mdpi/ic_swipe_circle_light.png b/res/drawable-sw600dp-mdpi/ic_swipe_circle_light.png
index e987389..0708ded 100644
--- a/res/drawable-sw600dp-mdpi/ic_swipe_circle_light.png
+++ b/res/drawable-sw600dp-mdpi/ic_swipe_circle_light.png
Binary files differ
diff --git a/res/drawable-sw600dp-mdpi/ic_swipe_circle_top.png b/res/drawable-sw600dp-mdpi/ic_swipe_circle_top.png
index a006ed0..a9b1e44 100644
--- a/res/drawable-sw600dp-mdpi/ic_swipe_circle_top.png
+++ b/res/drawable-sw600dp-mdpi/ic_swipe_circle_top.png
Binary files differ
diff --git a/res/drawable-sw600dp-xhdpi/bg_gray_circle.png b/res/drawable-sw600dp-xhdpi/bg_gray_circle.png
index 6fd50eb..6fe8be4 100644
--- a/res/drawable-sw600dp-xhdpi/bg_gray_circle.png
+++ b/res/drawable-sw600dp-xhdpi/bg_gray_circle.png
Binary files differ
diff --git a/res/drawable-sw600dp-xhdpi/ic_swipe_circle_bottom.png b/res/drawable-sw600dp-xhdpi/ic_swipe_circle_bottom.png
index ce0790b..5a92622 100644
--- a/res/drawable-sw600dp-xhdpi/ic_swipe_circle_bottom.png
+++ b/res/drawable-sw600dp-xhdpi/ic_swipe_circle_bottom.png
Binary files differ
diff --git a/res/drawable-sw600dp-xhdpi/ic_swipe_circle_dark.png b/res/drawable-sw600dp-xhdpi/ic_swipe_circle_dark.png
index ff6719e..b05a147 100644
--- a/res/drawable-sw600dp-xhdpi/ic_swipe_circle_dark.png
+++ b/res/drawable-sw600dp-xhdpi/ic_swipe_circle_dark.png
Binary files differ
diff --git a/res/drawable-sw600dp-xhdpi/ic_swipe_circle_light.png b/res/drawable-sw600dp-xhdpi/ic_swipe_circle_light.png
index e9226d6..ca8ad07 100644
--- a/res/drawable-sw600dp-xhdpi/ic_swipe_circle_light.png
+++ b/res/drawable-sw600dp-xhdpi/ic_swipe_circle_light.png
Binary files differ
diff --git a/res/drawable-sw600dp-xhdpi/ic_swipe_circle_top.png b/res/drawable-sw600dp-xhdpi/ic_swipe_circle_top.png
index 01d5b22..16d2584 100644
--- a/res/drawable-sw600dp-xhdpi/ic_swipe_circle_top.png
+++ b/res/drawable-sw600dp-xhdpi/ic_swipe_circle_top.png
Binary files differ
diff --git a/res/drawable-sw600dp-xxhdpi/bg_gray_circle.png b/res/drawable-sw600dp-xxhdpi/bg_gray_circle.png
index 151997b..5c222ba 100644
--- a/res/drawable-sw600dp-xxhdpi/bg_gray_circle.png
+++ b/res/drawable-sw600dp-xxhdpi/bg_gray_circle.png
Binary files differ
diff --git a/res/drawable-sw600dp-xxhdpi/ic_swipe_circle_bottom.png b/res/drawable-sw600dp-xxhdpi/ic_swipe_circle_bottom.png
index 5f9b8de..eac049a 100644
--- a/res/drawable-sw600dp-xxhdpi/ic_swipe_circle_bottom.png
+++ b/res/drawable-sw600dp-xxhdpi/ic_swipe_circle_bottom.png
Binary files differ
diff --git a/res/drawable-sw600dp-xxhdpi/ic_swipe_circle_dark.png b/res/drawable-sw600dp-xxhdpi/ic_swipe_circle_dark.png
index c4203e6..eced912 100644
--- a/res/drawable-sw600dp-xxhdpi/ic_swipe_circle_dark.png
+++ b/res/drawable-sw600dp-xxhdpi/ic_swipe_circle_dark.png
Binary files differ
diff --git a/res/drawable-sw600dp-xxhdpi/ic_swipe_circle_light.png b/res/drawable-sw600dp-xxhdpi/ic_swipe_circle_light.png
index bc48518..e4c3e0c 100644
--- a/res/drawable-sw600dp-xxhdpi/ic_swipe_circle_light.png
+++ b/res/drawable-sw600dp-xxhdpi/ic_swipe_circle_light.png
Binary files differ
diff --git a/res/drawable-sw600dp-xxhdpi/ic_swipe_circle_top.png b/res/drawable-sw600dp-xxhdpi/ic_swipe_circle_top.png
index e859e7d..044ea05 100644
--- a/res/drawable-sw600dp-xxhdpi/ic_swipe_circle_top.png
+++ b/res/drawable-sw600dp-xxhdpi/ic_swipe_circle_top.png
Binary files differ
diff --git a/res/drawable-sw600dp-xxxhdpi/bg_gray_circle.png b/res/drawable-sw600dp-xxxhdpi/bg_gray_circle.png
index 78d7474..06e9dab 100644
--- a/res/drawable-sw600dp-xxxhdpi/bg_gray_circle.png
+++ b/res/drawable-sw600dp-xxxhdpi/bg_gray_circle.png
Binary files differ
diff --git a/res/drawable-sw600dp-xxxhdpi/ic_swipe_circle_bottom.png b/res/drawable-sw600dp-xxxhdpi/ic_swipe_circle_bottom.png
index 50b8e27..6a3ef5c 100644
--- a/res/drawable-sw600dp-xxxhdpi/ic_swipe_circle_bottom.png
+++ b/res/drawable-sw600dp-xxxhdpi/ic_swipe_circle_bottom.png
Binary files differ
diff --git a/res/drawable-sw600dp-xxxhdpi/ic_swipe_circle_dark.png b/res/drawable-sw600dp-xxxhdpi/ic_swipe_circle_dark.png
index 3ef8bef..472997b 100644
--- a/res/drawable-sw600dp-xxxhdpi/ic_swipe_circle_dark.png
+++ b/res/drawable-sw600dp-xxxhdpi/ic_swipe_circle_dark.png
Binary files differ
diff --git a/res/drawable-sw600dp-xxxhdpi/ic_swipe_circle_light.png b/res/drawable-sw600dp-xxxhdpi/ic_swipe_circle_light.png
index e92afbf..b158013 100644
--- a/res/drawable-sw600dp-xxxhdpi/ic_swipe_circle_light.png
+++ b/res/drawable-sw600dp-xxxhdpi/ic_swipe_circle_light.png
Binary files differ
diff --git a/res/drawable-sw600dp-xxxhdpi/ic_swipe_circle_top.png b/res/drawable-sw600dp-xxxhdpi/ic_swipe_circle_top.png
index 919a53d..f53dffb 100644
--- a/res/drawable-sw600dp-xxxhdpi/ic_swipe_circle_top.png
+++ b/res/drawable-sw600dp-xxxhdpi/ic_swipe_circle_top.png
Binary files differ
diff --git a/res/drawable-xhdpi/alarm_background_expanded.9.png b/res/drawable-xhdpi/alarm_background_expanded.9.png
index bb0d814..9b6551e 100644
--- a/res/drawable-xhdpi/alarm_background_expanded.9.png
+++ b/res/drawable-xhdpi/alarm_background_expanded.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/appwidget_clock_dial.png b/res/drawable-xhdpi/appwidget_clock_dial.png
index 5cbe218..23b0c32 100644
--- a/res/drawable-xhdpi/appwidget_clock_dial.png
+++ b/res/drawable-xhdpi/appwidget_clock_dial.png
Binary files differ
diff --git a/res/drawable-xhdpi/appwidget_clock_hour.png b/res/drawable-xhdpi/appwidget_clock_hour.png
index b010b9b..b75896e 100644
--- a/res/drawable-xhdpi/appwidget_clock_hour.png
+++ b/res/drawable-xhdpi/appwidget_clock_hour.png
Binary files differ
diff --git a/res/drawable-xhdpi/appwidget_clock_minute.png b/res/drawable-xhdpi/appwidget_clock_minute.png
index d1c4046..c03fde5 100644
--- a/res/drawable-xhdpi/appwidget_clock_minute.png
+++ b/res/drawable-xhdpi/appwidget_clock_minute.png
Binary files differ
diff --git a/res/drawable-xhdpi/bg_day_selected.png b/res/drawable-xhdpi/bg_day_selected.png
index b1c4411..9df40ee 100644
--- a/res/drawable-xhdpi/bg_day_selected.png
+++ b/res/drawable-xhdpi/bg_day_selected.png
Binary files differ
diff --git a/res/drawable-xhdpi/bg_gray_circle.png b/res/drawable-xhdpi/bg_gray_circle.png
index d74c4ff..20aaa41 100644
--- a/res/drawable-xhdpi/bg_gray_circle.png
+++ b/res/drawable-xhdpi/bg_gray_circle.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_alarm_off_white_80dp.png b/res/drawable-xhdpi/ic_alarm_off_white_80dp.png
deleted file mode 100644
index 8d0dbef..0000000
--- a/res/drawable-xhdpi/ic_alarm_off_white_80dp.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/ic_lap_white_24dp.png b/res/drawable-xhdpi/ic_lap_white_24dp.png
index 6d487e1..f0e6de5 100644
--- a/res/drawable-xhdpi/ic_lap_white_24dp.png
+++ b/res/drawable-xhdpi/ic_lap_white_24dp.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_reset_white_24dp.png b/res/drawable-xhdpi/ic_reset_white_24dp.png
index d25318e..75683fa 100644
--- a/res/drawable-xhdpi/ic_reset_white_24dp.png
+++ b/res/drawable-xhdpi/ic_reset_white_24dp.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_snooze_white_24dp.png b/res/drawable-xhdpi/ic_snooze_white_24dp.png
index 1931630..deb3513 100644
--- a/res/drawable-xhdpi/ic_snooze_white_24dp.png
+++ b/res/drawable-xhdpi/ic_snooze_white_24dp.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_snooze_white_80dp.png b/res/drawable-xhdpi/ic_snooze_white_80dp.png
deleted file mode 100644
index 4d26f63..0000000
--- a/res/drawable-xhdpi/ic_snooze_white_80dp.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/ic_swipe_circle_bottom.png b/res/drawable-xhdpi/ic_swipe_circle_bottom.png
index aefc0dd..776a20c 100644
--- a/res/drawable-xhdpi/ic_swipe_circle_bottom.png
+++ b/res/drawable-xhdpi/ic_swipe_circle_bottom.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_swipe_circle_dark.png b/res/drawable-xhdpi/ic_swipe_circle_dark.png
index b460f22..733977d 100644
--- a/res/drawable-xhdpi/ic_swipe_circle_dark.png
+++ b/res/drawable-xhdpi/ic_swipe_circle_dark.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_swipe_circle_light.png b/res/drawable-xhdpi/ic_swipe_circle_light.png
index 6602ffd..b14900c 100644
--- a/res/drawable-xhdpi/ic_swipe_circle_light.png
+++ b/res/drawable-xhdpi/ic_swipe_circle_light.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_swipe_circle_top.png b/res/drawable-xhdpi/ic_swipe_circle_top.png
index ac4a843..fc7ce14 100644
--- a/res/drawable-xhdpi/ic_swipe_circle_top.png
+++ b/res/drawable-xhdpi/ic_swipe_circle_top.png
Binary files differ
diff --git a/res/drawable-xhdpi/stat_notify_alarm.png b/res/drawable-xhdpi/stat_notify_alarm.png
index 04d2e6a..c70a512 100644
--- a/res/drawable-xhdpi/stat_notify_alarm.png
+++ b/res/drawable-xhdpi/stat_notify_alarm.png
Binary files differ
diff --git a/res/drawable-xhdpi/stat_notify_stopwatch.png b/res/drawable-xhdpi/stat_notify_stopwatch.png
index cd1bf42..85b8e49 100644
--- a/res/drawable-xhdpi/stat_notify_stopwatch.png
+++ b/res/drawable-xhdpi/stat_notify_stopwatch.png
Binary files differ
diff --git a/res/drawable-xhdpi/stat_notify_timer.png b/res/drawable-xhdpi/stat_notify_timer.png
index 0709a92..428649f 100644
--- a/res/drawable-xhdpi/stat_notify_timer.png
+++ b/res/drawable-xhdpi/stat_notify_timer.png
Binary files differ
diff --git a/res/drawable-xxhdpi/alarm_background_expanded.9.png b/res/drawable-xxhdpi/alarm_background_expanded.9.png
index 83c91f8..dae60b4 100644
--- a/res/drawable-xxhdpi/alarm_background_expanded.9.png
+++ b/res/drawable-xxhdpi/alarm_background_expanded.9.png
Binary files differ
diff --git a/res/drawable-xxhdpi/appwidget_clock_dial.png b/res/drawable-xxhdpi/appwidget_clock_dial.png
index 24a2262..69a95ba 100644
--- a/res/drawable-xxhdpi/appwidget_clock_dial.png
+++ b/res/drawable-xxhdpi/appwidget_clock_dial.png
Binary files differ
diff --git a/res/drawable-xxhdpi/appwidget_clock_hour.png b/res/drawable-xxhdpi/appwidget_clock_hour.png
index 7774ba5..f87ed5c 100644
--- a/res/drawable-xxhdpi/appwidget_clock_hour.png
+++ b/res/drawable-xxhdpi/appwidget_clock_hour.png
Binary files differ
diff --git a/res/drawable-xxhdpi/appwidget_clock_minute.png b/res/drawable-xxhdpi/appwidget_clock_minute.png
index 2e8be54..6a2b7e6 100644
--- a/res/drawable-xxhdpi/appwidget_clock_minute.png
+++ b/res/drawable-xxhdpi/appwidget_clock_minute.png
Binary files differ
diff --git a/res/drawable-xxhdpi/bg_day_selected.png b/res/drawable-xxhdpi/bg_day_selected.png
index 0899cee..2653570 100644
--- a/res/drawable-xxhdpi/bg_day_selected.png
+++ b/res/drawable-xxhdpi/bg_day_selected.png
Binary files differ
diff --git a/res/drawable-xxhdpi/bg_gray_circle.png b/res/drawable-xxhdpi/bg_gray_circle.png
index eca75c9..a4528fb 100644
--- a/res/drawable-xxhdpi/bg_gray_circle.png
+++ b/res/drawable-xxhdpi/bg_gray_circle.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_alarm_off_white_80dp.png b/res/drawable-xxhdpi/ic_alarm_off_white_80dp.png
deleted file mode 100644
index 2183270..0000000
--- a/res/drawable-xxhdpi/ic_alarm_off_white_80dp.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_lap_white_24dp.png b/res/drawable-xxhdpi/ic_lap_white_24dp.png
index cf821c4..048c76f 100644
--- a/res/drawable-xxhdpi/ic_lap_white_24dp.png
+++ b/res/drawable-xxhdpi/ic_lap_white_24dp.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_reset_white_24dp.png b/res/drawable-xxhdpi/ic_reset_white_24dp.png
index 368dcd7..b20fcda 100644
--- a/res/drawable-xxhdpi/ic_reset_white_24dp.png
+++ b/res/drawable-xxhdpi/ic_reset_white_24dp.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_snooze_white_24dp.png b/res/drawable-xxhdpi/ic_snooze_white_24dp.png
index 95be7c4..eed45c3 100644
--- a/res/drawable-xxhdpi/ic_snooze_white_24dp.png
+++ b/res/drawable-xxhdpi/ic_snooze_white_24dp.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_snooze_white_80dp.png b/res/drawable-xxhdpi/ic_snooze_white_80dp.png
deleted file mode 100644
index 1ddd5ed..0000000
--- a/res/drawable-xxhdpi/ic_snooze_white_80dp.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_swipe_circle_bottom.png b/res/drawable-xxhdpi/ic_swipe_circle_bottom.png
index ebe37f4..e638e0b 100644
--- a/res/drawable-xxhdpi/ic_swipe_circle_bottom.png
+++ b/res/drawable-xxhdpi/ic_swipe_circle_bottom.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_swipe_circle_dark.png b/res/drawable-xxhdpi/ic_swipe_circle_dark.png
index b319546..4066de0 100644
--- a/res/drawable-xxhdpi/ic_swipe_circle_dark.png
+++ b/res/drawable-xxhdpi/ic_swipe_circle_dark.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_swipe_circle_light.png b/res/drawable-xxhdpi/ic_swipe_circle_light.png
index 148add7..d779711 100644
--- a/res/drawable-xxhdpi/ic_swipe_circle_light.png
+++ b/res/drawable-xxhdpi/ic_swipe_circle_light.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_swipe_circle_top.png b/res/drawable-xxhdpi/ic_swipe_circle_top.png
index 724e0c1..8b00d9f 100644
--- a/res/drawable-xxhdpi/ic_swipe_circle_top.png
+++ b/res/drawable-xxhdpi/ic_swipe_circle_top.png
Binary files differ
diff --git a/res/drawable-xxhdpi/stat_notify_alarm.png b/res/drawable-xxhdpi/stat_notify_alarm.png
old mode 100755
new mode 100644
index cc23351..0ec8936
--- a/res/drawable-xxhdpi/stat_notify_alarm.png
+++ b/res/drawable-xxhdpi/stat_notify_alarm.png
Binary files differ
diff --git a/res/drawable-xxhdpi/stat_notify_stopwatch.png b/res/drawable-xxhdpi/stat_notify_stopwatch.png
index d3ab85e..f516fd1 100644
--- a/res/drawable-xxhdpi/stat_notify_stopwatch.png
+++ b/res/drawable-xxhdpi/stat_notify_stopwatch.png
Binary files differ
diff --git a/res/drawable-xxhdpi/stat_notify_timer.png b/res/drawable-xxhdpi/stat_notify_timer.png
index 98c0202..2d58733 100644
--- a/res/drawable-xxhdpi/stat_notify_timer.png
+++ b/res/drawable-xxhdpi/stat_notify_timer.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/alarm_background_expanded.9.png b/res/drawable-xxxhdpi/alarm_background_expanded.9.png
index c1fb7ff..ec1ce50 100644
--- a/res/drawable-xxxhdpi/alarm_background_expanded.9.png
+++ b/res/drawable-xxxhdpi/alarm_background_expanded.9.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/appwidget_clock_dial.png b/res/drawable-xxxhdpi/appwidget_clock_dial.png
index e481bf5..8b19f63 100644
--- a/res/drawable-xxxhdpi/appwidget_clock_dial.png
+++ b/res/drawable-xxxhdpi/appwidget_clock_dial.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/appwidget_clock_hour.png b/res/drawable-xxxhdpi/appwidget_clock_hour.png
index 7492e04..74e30fc 100644
--- a/res/drawable-xxxhdpi/appwidget_clock_hour.png
+++ b/res/drawable-xxxhdpi/appwidget_clock_hour.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/appwidget_clock_minute.png b/res/drawable-xxxhdpi/appwidget_clock_minute.png
index cec5ff7..784f63f 100644
--- a/res/drawable-xxxhdpi/appwidget_clock_minute.png
+++ b/res/drawable-xxxhdpi/appwidget_clock_minute.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/bg_day_selected.png b/res/drawable-xxxhdpi/bg_day_selected.png
index 4ae1fc0..e8b7cfe 100644
--- a/res/drawable-xxxhdpi/bg_day_selected.png
+++ b/res/drawable-xxxhdpi/bg_day_selected.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/bg_gray_circle.png b/res/drawable-xxxhdpi/bg_gray_circle.png
index c0792a3..7d26b41 100644
--- a/res/drawable-xxxhdpi/bg_gray_circle.png
+++ b/res/drawable-xxxhdpi/bg_gray_circle.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_lap_white_24dp.png b/res/drawable-xxxhdpi/ic_lap_white_24dp.png
index cb71614..8b6e323 100644
--- a/res/drawable-xxxhdpi/ic_lap_white_24dp.png
+++ b/res/drawable-xxxhdpi/ic_lap_white_24dp.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_reset_white_24dp.png b/res/drawable-xxxhdpi/ic_reset_white_24dp.png
index 26131b1..b3ff489 100644
--- a/res/drawable-xxxhdpi/ic_reset_white_24dp.png
+++ b/res/drawable-xxxhdpi/ic_reset_white_24dp.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_snooze_white_24dp.png b/res/drawable-xxxhdpi/ic_snooze_white_24dp.png
index 8662eef..a7b4ff1 100644
--- a/res/drawable-xxxhdpi/ic_snooze_white_24dp.png
+++ b/res/drawable-xxxhdpi/ic_snooze_white_24dp.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_swipe_circle_bottom.png b/res/drawable-xxxhdpi/ic_swipe_circle_bottom.png
index 93c81a8..4afc0b7 100644
--- a/res/drawable-xxxhdpi/ic_swipe_circle_bottom.png
+++ b/res/drawable-xxxhdpi/ic_swipe_circle_bottom.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_swipe_circle_dark.png b/res/drawable-xxxhdpi/ic_swipe_circle_dark.png
index 6e339b8..9dda49c 100644
--- a/res/drawable-xxxhdpi/ic_swipe_circle_dark.png
+++ b/res/drawable-xxxhdpi/ic_swipe_circle_dark.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_swipe_circle_light.png b/res/drawable-xxxhdpi/ic_swipe_circle_light.png
index ae48b7f..04984ca 100644
--- a/res/drawable-xxxhdpi/ic_swipe_circle_light.png
+++ b/res/drawable-xxxhdpi/ic_swipe_circle_light.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_swipe_circle_top.png b/res/drawable-xxxhdpi/ic_swipe_circle_top.png
index 82168a3..66b7761 100644
--- a/res/drawable-xxxhdpi/ic_swipe_circle_top.png
+++ b/res/drawable-xxxhdpi/ic_swipe_circle_top.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/stat_notify_alarm.png b/res/drawable-xxxhdpi/stat_notify_alarm.png
index a435523..133fb91 100644
--- a/res/drawable-xxxhdpi/stat_notify_alarm.png
+++ b/res/drawable-xxxhdpi/stat_notify_alarm.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/stat_notify_stopwatch.png b/res/drawable-xxxhdpi/stat_notify_stopwatch.png
index 276e3bd..7602137 100644
--- a/res/drawable-xxxhdpi/stat_notify_stopwatch.png
+++ b/res/drawable-xxxhdpi/stat_notify_stopwatch.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/stat_notify_timer.png b/res/drawable-xxxhdpi/stat_notify_timer.png
index 7b37922..d4d7a76 100644
--- a/res/drawable-xxxhdpi/stat_notify_timer.png
+++ b/res/drawable-xxxhdpi/stat_notify_timer.png
Binary files differ
diff --git a/res/drawable/ic_dismiss.xml b/res/drawable/ic_dismiss.xml
new file mode 100644
index 0000000..ba91772
--- /dev/null
+++ b/res/drawable/ic_dismiss.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="80dp"
+    android:height="80dp"
+    android:viewportWidth="80.0"
+    android:viewportHeight="80.0">
+    <path
+        android:fillColor="#FFFFFFFF"
+        android:pathData="M40,28.3c7.3,0 13.2,5.9 13.2,13.2c0,1.6 -0.3,3.1 -0.8,4.5l2.9,2.9c1.1,-2.2 1.7,-4.7 1.7,-7.4c0,-9.3 -7.6,-16.9 -16.9,-16.9c-2.7,0 -5.1,0.6 -7.4,1.7l2.9,2.9C36.9,28.6 38.4,28.3 40,28.3zM58.8,27.8l-8.7,-7.3l-2.4,2.9l8.7,7.3L58.8,27.8zM22.9,21.3l-2.4,2.4l2.5,2.5l-2.1,1.7l2.7,2.7l2.1,-1.8l1.5,1.5c-2.6,3 -4.1,6.8 -4.1,11.1c0,9.3 7.6,16.9 16.9,16.9c4.2,0 8.1,-1.6 11.1,-4.1l4.1,4.1l2.4,-2.4L24.7,23.2L22.9,21.3zM48.4,51.6c-2.3,1.9 -5.2,3 -8.4,3c-7.3,0 -13.2,-5.9 -13.2,-13.2c0,-3.2 1.1,-6.1 3,-8.4C29.9,33.1 48.4,51.6 48.4,51.6zM32.5,23.2l-2.7,-2.7l-1.6,1.3l2.7,2.7C30.9,24.5 32.5,23.2 32.5,23.2z"/>
+</vector>
\ No newline at end of file
diff --git a/res/drawable/ic_fab_alarm.xml b/res/drawable/ic_fab_alarm.xml
index db7cec6..b61cf75 100644
--- a/res/drawable/ic_fab_alarm.xml
+++ b/res/drawable/ic_fab_alarm.xml
@@ -15,17 +15,14 @@
 -->
 
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="@dimen/fab_alarm_size"
-        android:height="@dimen/fab_alarm_size"
-        android:viewportHeight="224.0"
-        android:viewportWidth="224.0">
+    android:width="@dimen/fab_alarm_size"
+    android:height="@dimen/fab_alarm_size"
+    android:viewportHeight="56.0"
+    android:viewportWidth="56.0">
     <path
         android:fillColor="#FFFFFFFF"
-        android:pathData="M-224.3,48h128v128h-128V48z" />
+        android:pathData="M28,20c-5.2,0 -9.3,4.2 -9.3,9.3c0,5.2 4.2,9.3 9.3,9.3c5.2,0 9.3,-4.2 9.3,-9.3C37.3,24.2 33.2,20 28,20zM33,34.5l-6.3,-3.8v-8h2v7l5.3,3.2L33,34.5z" />
     <path
         android:fillColor="#FFFFFFFF"
-        android:pathData="M112,80.4c-20.6,0 -37.3,16.7 -37.3,37.3c0,20.6 16.7,37.3 37.3,37.3s37.3,-16.7 37.3,-37.3C149.3,97.1 132.6,80.4 112,80.4zM132,138.2L106.7,123V91h8v28l21.3,12.6L132,138.2z" />
-    <path
-        android:fillColor="#FFFFFFFF"
-        android:pathData="M112,0C50.1,0 0,50.1 0,112s50.1,112 112,112s112,-50.1 112,-112S173.9,0 112,0zM83.2,58.3l6.8,8.2L65.5,87l-6.9,-8.2L83.2,58.3zM112,165.7c-26.6,0 -48,-21.5 -48,-48s21.5,-48 48,-48s48,21.5 48,48S138.5,165.7 112,165.7zM158.5,87l-24.5,-20.6l6.9,-8.2l24.5,20.6L158.5,87z" />
+        android:pathData="M28,0C12.5,0 0,12.5 0,28c0,15.5 12.5,28 28,28c15.5,0 28,-12.5 28,-28C56,12.5 43.5,0 28,0zM20.8,14.5l1.7,2l-6.1,5.1l-1.7,-2L20.8,14.5zM28,41.3c-6.6,0 -12,-5.4 -12,-12s5.4,-12 12,-12c6.6,0 12,5.4 12,12S34.6,41.3 28,41.3zM39.6,21.7l-6.1,-5.1l1.7,-2l6.1,5.1L39.6,21.7z" />
 </vector>
\ No newline at end of file
diff --git a/res/drawable/ic_language.xml b/res/drawable/ic_language.xml
deleted file mode 100644
index d1b1e0c..0000000
--- a/res/drawable/ic_language.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2016 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="24dp"
-        android:height="24dp"
-        android:viewportHeight="24.0"
-        android:viewportWidth="24.0">
-    <path android:fillColor="#FFFFFFFF"
-          android:pathData="M11.99,2C6.47,2 2,6.48 2,12s4.47,10 9.99,10C17.52,22 22,17.52 22,12S17.52,2 11.99,2zM18.92,8h-2.95c-0.32,-1.25 -0.78,-2.45 -1.38,-3.56 1.84,0.63 3.37,1.91 4.33,3.56zM12,4.04c0.83,1.2 1.48,2.53 1.91,3.96h-3.82c0.43,-1.43 1.08,-2.76 1.91,-3.96zM4.26,14C4.1,13.36 4,12.69 4,12s0.1,-1.36 0.26,-2h3.38c-0.08,0.66 -0.14,1.32 -0.14,2 0,0.68 0.06,1.34 0.14,2L4.26,14zM5.08,16h2.95c0.32,1.25 0.78,2.45 1.38,3.56 -1.84,-0.63 -3.37,-1.9 -4.33,-3.56zM8.03,8L5.08,8c0.96,-1.66 2.49,-2.93 4.33,-3.56C8.81,5.55 8.35,6.75 8.03,8zM12,19.96c-0.83,-1.2 -1.48,-2.53 -1.91,-3.96h3.82c-0.43,1.43 -1.08,2.76 -1.91,3.96zM14.34,14L9.66,14c-0.09,-0.66 -0.16,-1.32 -0.16,-2 0,-0.68 0.07,-1.35 0.16,-2h4.68c0.09,0.65 0.16,1.32 0.16,2 0,0.68 -0.07,1.34 -0.16,2zM14.59,19.56c0.6,-1.11 1.06,-2.31 1.38,-3.56h2.95c-0.96,1.65 -2.49,2.93 -4.33,3.56zM16.36,14c0.08,-0.66 0.14,-1.32 0.14,-2 0,-0.68 -0.06,-1.34 -0.14,-2h3.38c0.16,0.64 0.26,1.31 0.26,2s-0.1,1.36 -0.26,2h-3.38z" />
-</vector>
\ No newline at end of file
diff --git a/res/drawable/ic_public.xml b/res/drawable/ic_public.xml
new file mode 100644
index 0000000..ce36b36
--- /dev/null
+++ b/res/drawable/ic_public.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:fillColor="#FFFFFFFF"
+        android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM11,19.93c-3.95,-0.49 -7,-3.85 -7,-7.93 0,-0.62 0.08,-1.21 0.21,-1.79L9,15v1c0,1.1 0.9,2 2,2v1.93zM17.9,17.39c-0.26,-0.81 -1,-1.39 -1.9,-1.39h-1v-3c0,-0.55 -0.45,-1 -1,-1L8,12v-2h2c0.55,0 1,-0.45 1,-1L11,7h2c1.1,0 2,-0.9 2,-2v-0.41c2.93,1.19 5,4.06 5,7.41 0,2.08 -0.8,3.97 -2.1,5.39z"/>
+</vector>
\ No newline at end of file
diff --git a/res/drawable/ic_ringtone_silent.xml b/res/drawable/ic_ringtone_silent.xml
index 8976623..4e6d0ab 100644
--- a/res/drawable/ic_ringtone_silent.xml
+++ b/res/drawable/ic_ringtone_silent.xml
@@ -15,11 +15,11 @@
 -->
 
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="24dp"
-        android:height="24dp"
-        android:viewportWidth="24.0"
-        android:viewportHeight="24.0">
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportHeight="24.0"
+    android:viewportWidth="24.0">
     <path
         android:fillColor="#FFFFFFFF"
-        android:pathData="M12,22c1.1,0 2,-0.9 2,-2h-4c0,1.1 0.9,2 2,2zM18,16v-5c0,-3.07 -1.63,-5.64 -4.5,-6.32L13.5,4c0,-0.83 -0.67,-1.5 -1.5,-1.5s-1.5,0.67 -1.5,1.5v0.68C7.64,5.36 6,7.92 6,11v5l-2,2v1h16v-1l-2,-2zM16,17L8,17v-6c0,-2.48 1.51,-4.5 4,-4.5s4,2.02 4,4.5v6z"/>
+        android:pathData="M20,18.69L7.84,6.14 5.27,3.49 4,4.76l2.8,2.8v0.01c-0.52,0.99 -0.8,2.16 -0.8,3.42v5l-2,2v1h13.73l2,2L21,19.72l-1,-1.03zM12,22c1.11,0 2,-0.89 2,-2h-4c0,1.11 0.89,2 2,2zM18,14.68L18,11c0,-3.08 -1.64,-5.64 -4.5,-6.32L13.5,4c0,-0.83 -0.67,-1.5 -1.5,-1.5s-1.5,0.67 -1.5,1.5v0.68c-0.15,0.03 -0.29,0.08 -0.42,0.12 -0.1,0.03 -0.2,0.07 -0.3,0.11h-0.01c-0.01,0 -0.01,0 -0.02,0.01 -0.23,0.09 -0.46,0.2 -0.68,0.31 0,0 -0.01,0 -0.01,0.01L18,14.68z" />
 </vector>
\ No newline at end of file
diff --git a/res/drawable/ic_snooze.xml b/res/drawable/ic_snooze.xml
new file mode 100644
index 0000000..44b4a8e
--- /dev/null
+++ b/res/drawable/ic_snooze.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="80dp"
+    android:height="80dp"
+    android:viewportWidth="80.0"
+    android:viewportHeight="80.0">
+    <path
+        android:fillColor="#FFFFFFFF"
+        android:pathData="M19,25.1l11.5,0l0,3.8l-5.7,7.6l5.7,0l0,3.9l-11.5,0l0,-3.9l5.8,-7.6l-5.8,0z"/>
+    <path
+        android:fillColor="#FFFFFFFF"
+        android:pathData="M34.3,32.7l11.4,0l0,3.8l-5.7,7.7l5.7,0l0,3.8l-11.4,0l0,-3.8l5.7,-7.7l-5.7,0z"/>
+    <path
+        android:fillColor="#FFFFFFFF"
+        android:pathData="M49.5,40.4l11.5,0l0,3.8l-5.8,7.6l5.8,0l0,3.8l-11.5,0l0,-3.8l5.7,-7.6l-5.7,0z"/>
+</vector>
\ No newline at end of file
diff --git a/res/drawable/shortcut_new_alarm.xml b/res/drawable/shortcut_new_alarm.xml
new file mode 100644
index 0000000..b364bf5
--- /dev/null
+++ b/res/drawable/shortcut_new_alarm.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2016 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportHeight="48.0"
+    android:viewportWidth="48.0">
+    <path
+        android:fillColor="#F5F5F5"
+        android:pathData="M24,24m-22,0a22,22 0,1 1,44 0a22,22 0,1 1,-44 0" />
+    <path
+        android:fillColor="@color/default_background"
+        android:pathData="M34,17.7l-4.6,-3.9l-1.3,1.5l4.6,3.9L34,17.7zM19.9,15.4l-1.3,-1.5L14,17.7l1.3,1.5L19.9,15.4zM24.5,20H23v6l4.8,2.9l0.8,-1.2l-4,-2.4V20zM24,16c-5,0 -9,4 -9,9s4,9 9,9c5,0 9,-4 9,-9S29,16 24,16zM24,32c-3.9,0 -7,-3.1 -7,-7s3.1,-7 7,-7s7,3.1 7,7S27.9,32 24,32z" />
+</vector>
diff --git a/res/drawable/shortcut_new_timer.xml b/res/drawable/shortcut_new_timer.xml
new file mode 100644
index 0000000..3babea6
--- /dev/null
+++ b/res/drawable/shortcut_new_timer.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2016 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportHeight="48.0"
+    android:viewportWidth="48.0">
+    <path
+        android:fillColor="#F5F5F5"
+        android:pathData="M24,24m-22,0a22,22 0,1 1,44 0a22,22 0,1 1,-44 0" />
+    <path
+        android:fillColor="@color/default_background"
+        android:pathData="M25.4,24l5.9,-6.3c0.2,-0.3 0.3,-0.7 0.1,-1.1c-0.1,-0.4 -0.5,-0.6 -0.9,-0.6h-13c-0.4,0 -0.8,0.2 -0.9,0.6c-0.2,0.4 -0.1,0.8 0.2,1.1l5.8,6.3l-5.9,6.3c-0.2,0.3 -0.3,0.7 -0.1,1.1c0.1,0.4 0.5,0.6 0.9,0.6h13c0.4,0 0.8,-0.2 0.9,-0.6c0.2,-0.4 0.1,-0.8 -0.2,-1.1L25.4,24zM19.8,18h8.4L24,22.5L19.8,18z" />
+</vector>
diff --git a/res/drawable/shortcut_screensaver.xml b/res/drawable/shortcut_screensaver.xml
new file mode 100644
index 0000000..095b5a1
--- /dev/null
+++ b/res/drawable/shortcut_screensaver.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2016 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportHeight="48.0"
+    android:viewportWidth="48.0">
+    <path
+        android:fillColor="#F5F5F5"
+        android:pathData="M24,24m-22,0a22,22 0,1 1,44 0a22,22 0,1 1,-44 0" />
+    <path
+        android:fillColor="@color/default_background"
+        android:pathData="M22.1,20c0,-1.8 0.6,-3.5 1.5,-5c-4.8,0.2 -8.7,4.2 -8.7,9c0,5 4.1,9 9,9c3.1,0 5.8,-1.6 7.5,-4C26.2,29 22.1,25 22.1,20z" />
+</vector>
diff --git a/res/drawable/shortcut_stopwatch.xml b/res/drawable/shortcut_stopwatch.xml
new file mode 100644
index 0000000..e9a93c2
--- /dev/null
+++ b/res/drawable/shortcut_stopwatch.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2016 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportHeight="48.0"
+    android:viewportWidth="48.0">
+    <path
+        android:fillColor="#F5F5F5"
+        android:pathData="M24,24m-22,0a22,22 0,1 1,44 0a22,22 0,1 1,-44 0" />
+    <path
+        android:fillColor="@color/default_background"
+        android:pathData="M27,13h-6v2h6V13zM23,26h2v-6h-2V26zM31,19.4l1.4,-1.4c-0.4,-0.5 -0.9,-1 -1.4,-1.4L29.6,18c-1.5,-1.2 -3.5,-2 -5.6,-2c-5,0 -9,4 -9,9s4,9 9,9s9,-4 9,-9C33,22.9 32.3,20.9 31,19.4zM24,32c-3.9,0 -7,-3.1 -7,-7s3.1,-7 7,-7s7,3.1 7,7S27.9,32 24,32z" />
+</vector>
diff --git a/res/layout-land/clock_fragment.xml b/res/layout-land/clock_fragment.xml
index 5e52e0e..91eff5f 100644
--- a/res/layout-land/clock_fragment.xml
+++ b/res/layout-land/clock_fragment.xml
@@ -25,32 +25,41 @@
         android:layout_height="match_parent"
         android:layout_weight="@integer/gutter_width_percent" />
 
-    <!-- Clock: 60% of total width. -->
+    <!-- Clock: 62% of total width (4% given to right gutter). -->
     <LinearLayout
         android:layout_width="0dp"
         android:layout_height="match_parent"
-        android:layout_weight="60"
+        android:layout_weight="62"
         android:gravity="center"
         android:paddingBottom="@dimen/fab_height">
 
         <include
             android:id="@+id/main_clock_left_pane"
             layout="@layout/main_clock_frame"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content" />
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_weight="29" />
+
+        <!-- Right gutter. -->
+        <Space
+            android:layout_width="0dp"
+            android:layout_height="match_parent"
+            android:layout_weight="2" />
+
 
     </LinearLayout>
 
-    <!-- World Clock List: 36% of total width. Right gutter is applied in world_clock_item. -->
-    <ListView
+    <!-- World Clock List: 33% of total width. Right gutter is applied in world_clock_item. -->
+    <android.support.v7.widget.RecyclerView
         android:id="@+id/cities"
         android:layout_width="0dp"
         android:layout_height="wrap_content"
         android:layout_gravity="center"
-        android:layout_weight="36"
+        android:layout_weight="33"
         android:clickable="false"
         android:clipToPadding="false"
         android:paddingBottom="@dimen/fab_height"
-        android:scrollbarStyle="outsideOverlay" />
+        android:scrollbarStyle="outsideOverlay"
+        android:scrollbars="vertical" />
 
 </LinearLayout>
\ No newline at end of file
diff --git a/res/layout-land/main_clock_frame.xml b/res/layout-land/main_clock_frame.xml
new file mode 100644
index 0000000..a6b208a
--- /dev/null
+++ b/res/layout-land/main_clock_frame.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="utf-8"?><!-- Copyright (C) 2016 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:gravity="center_horizontal"
+    android:orientation="vertical">
+
+    <FrameLayout
+        android:layout_width="wrap_content"
+        android:layout_height="0dp"
+        android:layout_weight="1">
+
+        <com.android.deskclock.AnalogClock
+            android:id="@+id/analog_clock"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginBottom="@dimen/bottom_text_spacing_analog"
+            android:layout_marginEnd="@dimen/analog_clock_margin"
+            android:layout_marginLeft="@dimen/analog_clock_margin"
+            android:layout_marginRight="@dimen/analog_clock_margin"
+            android:layout_marginStart="@dimen/analog_clock_margin"
+            android:layout_marginTop="@dimen/circle_margin_top" />
+
+        <TextClock
+            android:id="@+id/digital_clock"
+            style="@style/big_thin"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:ellipsize="none"
+            android:singleLine="true"
+            android:textColor="@color/clock_white"
+            android:textSize="@dimen/main_clock_font_size" />
+
+    </FrameLayout>
+
+    <include layout="@layout/date_and_next_alarm_time" />
+
+    <View
+        android:id="@+id/hairline"
+        android:layout_width="match_parent"
+        android:layout_height="@dimen/hairline_height"
+        android:layout_marginTop="24dp"
+        android:background="@color/hairline"
+        android:visibility="gone" />
+
+</LinearLayout>
diff --git a/res/layout-land/world_clock_item.xml b/res/layout-land/world_clock_item.xml
index 99932ad..15683f9 100644
--- a/res/layout-land/world_clock_item.xml
+++ b/res/layout-land/world_clock_item.xml
@@ -16,15 +16,12 @@
 <LinearLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:orientation="horizontal">
+    android:layout_height="wrap_content">
 
     <LinearLayout
         android:layout_width="0dp"
-        android:layout_height="match_parent"
-        android:layout_gravity="center_horizontal"
-        android:layout_weight="8"
-        android:gravity="center_horizontal"
+        android:layout_height="wrap_content"
+        android:layout_weight="29"
         android:orientation="vertical">
 
         <FrameLayout
@@ -55,6 +52,7 @@
         <LinearLayout
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
+            android:layout_gravity="center"
             android:gravity="center">
 
             <FrameLayout
@@ -89,6 +87,6 @@
     <Space
         android:layout_width="0dp"
         android:layout_height="match_parent"
-        android:layout_weight="1" />
+        android:layout_weight="@integer/gutter_width_percent" />
 
 </LinearLayout>
diff --git a/res/layout-v21/stopwatch_notif_expanded.xml b/res/layout-v21/stopwatch_notif_expanded.xml
index 6b4a6b9..7ef36b4 100644
--- a/res/layout-v21/stopwatch_notif_expanded.xml
+++ b/res/layout-v21/stopwatch_notif_expanded.xml
@@ -24,7 +24,6 @@
     android:showDividers="middle">
 
     <FrameLayout
-        xmlns:android="http://schemas.android.com/apk/res/android"
         android:id="@+id/swn_expanded_hitspace"
         android:layout_width="match_parent"
         android:layout_height="64dp"
diff --git a/res/layout/alarm_activity.xml b/res/layout/alarm_activity.xml
index b2cb714..b2d7e6e 100644
--- a/res/layout/alarm_activity.xml
+++ b/res/layout/alarm_activity.xml
@@ -32,7 +32,7 @@
 
         <TextView
             android:id="@+id/title"
-            android:layout_width="0dip"
+            android:layout_width="0dp"
             android:layout_height="wrap_content"
             android:layout_marginTop="?attr/actionBarSize"
             android:gravity="center"
@@ -46,7 +46,7 @@
 
         <TextClock
             android:id="@+id/digital_clock"
-            android:layout_width="0dip"
+            android:layout_width="0dp"
             android:layout_height="wrap_content"
             android:gravity="center"
             android:includeFontPadding="false"
@@ -60,8 +60,8 @@
 
         <com.android.deskclock.widget.CircleView
             android:id="@+id/pulse"
-            android:layout_width="0dip"
-            android:layout_height="0dip"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
             android:gravity="center"
             android:layerType="hardware"
             app:layout_row="2"
@@ -77,7 +77,7 @@
             android:layout_height="wrap_content"
             android:background="@drawable/bg_circle_accent"
             android:contentDescription="@string/alarm_alert_snooze_text"
-            android:src="@drawable/ic_snooze_white_80dp"
+            app:srcCompat="@drawable/ic_snooze"
             app:layout_row="2"
             app:layout_column="0"
             app:layout_columnWeight="1"
@@ -89,7 +89,7 @@
             android:layout_height="wrap_content"
             android:background="@drawable/bg_circle_white"
             android:contentDescription="@string/alarm_alert_dismiss_text"
-            android:src="@drawable/ic_alarm_off_white_80dp"
+            app:srcCompat="@drawable/ic_dismiss"
             app:layout_row="2"
             app:layout_column="2"
             app:layout_columnWeight="1"
@@ -154,4 +154,4 @@
 
     </LinearLayout>
 
-</FrameLayout>
+</FrameLayout>
\ No newline at end of file
diff --git a/res/layout/alarm_volume_preference.xml b/res/layout/alarm_volume_preference.xml
index 5343826..af21dcf 100644
--- a/res/layout/alarm_volume_preference.xml
+++ b/res/layout/alarm_volume_preference.xml
@@ -1,19 +1,18 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2015 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License
-  -->
+<!-- Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
 
 <LinearLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
@@ -36,25 +35,25 @@
         android:layout_height="wrap_content"
         android:ellipsize="marquee"
         android:singleLine="true"
-        android:textAppearance="@style/Preference_TextAppearanceMaterialSubhead"/>
+        android:textAppearance="@style/Preference_TextAppearanceMaterialSubhead" />
 
     <LinearLayout
         android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:paddingTop="16dp">
+        android:layout_height="48dp">
 
         <ImageView
             android:id="@+id/alarm_icon"
             android:layout_width="wrap_content"
-            android:layout_height="wrap_content"/>
+            android:layout_height="match_parent"
+            android:importantForAccessibility="no" />
 
         <SeekBar
             android:id="@+id/alarm_volume_slider"
             android:layout_width="0dp"
-            android:layout_height="wrap_content"
+            android:layout_height="match_parent"
             android:layout_gravity="center_vertical"
-            android:layout_weight="1"/>
+            android:layout_weight="1" />
 
     </LinearLayout>
 
-</LinearLayout>
+</LinearLayout>
\ No newline at end of file
diff --git a/res/layout/cities_activity.xml b/res/layout/cities_activity.xml
index c5687ae..07c5247 100644
--- a/res/layout/cities_activity.xml
+++ b/res/layout/cities_activity.xml
@@ -23,7 +23,6 @@
     <include layout="@layout/drop_shadow" />
 
     <ListView
-        xmlns:android="http://schemas.android.com/apk/res/android"
         android:id="@+id/cities_list"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
diff --git a/res/layout/clock_fragment.xml b/res/layout/clock_fragment.xml
index 0c57da5..97b7641 100644
--- a/res/layout/clock_fragment.xml
+++ b/res/layout/clock_fragment.xml
@@ -14,20 +14,15 @@
      limitations under the License.
 -->
 
-<LinearLayout
+<!-- Guttered content. The gutters are applied in world_clock_item and main_clock_frame. -->
+<android.support.v7.widget.RecyclerView
     xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent">
-
-    <!-- Guttered content. Gutters are applied in world_clock_item and main_clock_frame. -->
-    <ListView
-        android:id="@+id/cities"
-        android:layout_width="0dp"
-        android:layout_height="wrap_content"
-        android:layout_weight="1"
-        android:clickable="false"
-        android:clipToPadding="false"
-        android:paddingBottom="@dimen/fab_height"
-        android:scrollbarStyle="outsideOverlay" />
-
-</LinearLayout>
\ No newline at end of file
+    android:id="@+id/cities"
+    android:layout_width="0dp"
+    android:layout_height="match_parent"
+    android:layout_weight="1"
+    android:clickable="false"
+    android:clipToPadding="false"
+    android:paddingBottom="@dimen/fab_height"
+    android:scrollbarStyle="outsideOverlay"
+    android:scrollbars="vertical" />
\ No newline at end of file
diff --git a/res/layout/desk_clock.xml b/res/layout/desk_clock.xml
index f00861d..e038f55 100644
--- a/res/layout/desk_clock.xml
+++ b/res/layout/desk_clock.xml
@@ -55,13 +55,13 @@
         android:layout_height="match_parent"
         app:layout_behavior="@string/appbar_scrolling_view_behavior">
 
-        <include layout="@layout/drop_shadow" />
-
         <com.android.deskclock.widget.RtlViewPager
             android:id="@+id/desk_clock_pager"
             android:layout_width="match_parent"
             android:layout_height="match_parent" />
 
+        <include layout="@layout/drop_shadow" />
+
     </FrameLayout>
 
     <LinearLayout
@@ -70,7 +70,7 @@
         android:layout_gravity="bottom"
         android:baselineAligned="false"
         android:orientation="horizontal"
-        app:layout_behavior="com.android.deskclock.widget.toast.LinearLayoutWithSnackbarBehavior">
+        app:layout_behavior="com.android.deskclock.widget.toast.SnackbarSlidingBehavior">
 
         <FrameLayout
             android:layout_width="0dp"
@@ -122,4 +122,4 @@
 
         </FrameLayout>
     </LinearLayout>
-</android.support.design.widget.CoordinatorLayout>
\ No newline at end of file
+</android.support.design.widget.CoordinatorLayout>
diff --git a/res/layout/snooze_length_picker.xml b/res/layout/snooze_length_picker.xml
index a843681..355ca76 100644
--- a/res/layout/snooze_length_picker.xml
+++ b/res/layout/snooze_length_picker.xml
@@ -25,8 +25,7 @@
         android:id="@+id/minutes_picker"
         android:layout_width="wrap_content"
         android:layout_height="match_parent"
-        android:focusable="true"
-        android:focusableInTouchMode="true" />
+        android:focusable="true"/>
 
     <TextView
         android:id="@+id/title"
diff --git a/res/layout/stopwatch_notif_expanded.xml b/res/layout/stopwatch_notif_expanded.xml
index ec3e951..b673172 100644
--- a/res/layout/stopwatch_notif_expanded.xml
+++ b/res/layout/stopwatch_notif_expanded.xml
@@ -24,7 +24,6 @@
     android:showDividers="middle">
 
     <FrameLayout
-        xmlns:android="http://schemas.android.com/apk/res/android"
         android:id="@+id/swn_expanded_hitspace"
         android:layout_width="match_parent"
         android:layout_height="64dp"
diff --git a/res/layout/world_clock_item.xml b/res/layout/world_clock_item.xml
index 3d91447..d34eb77 100644
--- a/res/layout/world_clock_item.xml
+++ b/res/layout/world_clock_item.xml
@@ -13,9 +13,11 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
-    android:layout_height="match_parent"
+    android:layout_height="wrap_content"
     android:orientation="horizontal">
 
     <!-- Left gutter. -->
diff --git a/res/mipmap-hdpi/ic_launcher_alarmclock.png b/res/mipmap-hdpi/ic_launcher_alarmclock.png
index 57133cd..c47e34d 100644
--- a/res/mipmap-hdpi/ic_launcher_alarmclock.png
+++ b/res/mipmap-hdpi/ic_launcher_alarmclock.png
Binary files differ
diff --git a/res/mipmap-mdpi/ic_launcher_alarmclock.png b/res/mipmap-mdpi/ic_launcher_alarmclock.png
index 5856a0e..a2ff58b 100644
--- a/res/mipmap-mdpi/ic_launcher_alarmclock.png
+++ b/res/mipmap-mdpi/ic_launcher_alarmclock.png
Binary files differ
diff --git a/res/mipmap-xhdpi/ic_launcher_alarmclock.png b/res/mipmap-xhdpi/ic_launcher_alarmclock.png
index 93099e0..a9ffb99 100644
--- a/res/mipmap-xhdpi/ic_launcher_alarmclock.png
+++ b/res/mipmap-xhdpi/ic_launcher_alarmclock.png
Binary files differ
diff --git a/res/mipmap-xxhdpi/ic_launcher_alarmclock.png b/res/mipmap-xxhdpi/ic_launcher_alarmclock.png
index f8114f4..cab6599 100644
--- a/res/mipmap-xxhdpi/ic_launcher_alarmclock.png
+++ b/res/mipmap-xxhdpi/ic_launcher_alarmclock.png
Binary files differ
diff --git a/res/mipmap-xxxhdpi/ic_launcher_alarmclock.png b/res/mipmap-xxxhdpi/ic_launcher_alarmclock.png
index cc62321..fa3fc05 100644
--- a/res/mipmap-xxxhdpi/ic_launcher_alarmclock.png
+++ b/res/mipmap-xxxhdpi/ic_launcher_alarmclock.png
Binary files differ
diff --git a/res/values-land/dimens.xml b/res/values-land/dimens.xml
index 88fb475..ff14e29 100644
--- a/res/values-land/dimens.xml
+++ b/res/values-land/dimens.xml
@@ -17,9 +17,6 @@
 <!-- These resources are around just to allow their values to be customized
      for different hardware and product builds. -->
 <resources>
-    <dimen name="ampm_text_size">32dip</dimen>
-    <dimen name="date_text_size">15sp</dimen>
-    <dimen name="next_alarm_text_size">15sp</dimen>
     <dimen name="time_margin_top">24dip</dimen>
 
     <dimen name="dialpad_font_size">24sp</dimen>
@@ -36,4 +33,4 @@
 
     <dimen name="medium_font_size">48sp</dimen>
 
-</resources>
\ No newline at end of file
+</resources>
diff --git a/res/values-sw320dp-port/dimens.xml b/res/values-sw320dp-port/dimens.xml
new file mode 100644
index 0000000..ebbdc0f
--- /dev/null
+++ b/res/values-sw320dp-port/dimens.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<resources>
+    <dimen name="no_alarms_size">120dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/res/values-sw360dp/dimens.xml b/res/values-sw360dp/dimens.xml
index 2764fbc..86141ed 100644
--- a/res/values-sw360dp/dimens.xml
+++ b/res/values-sw360dp/dimens.xml
@@ -18,4 +18,5 @@
     <dimen name="big_font_size">70sp</dimen>
     <dimen name="main_clock_font_size">80sp</dimen>
     <dimen name="timer_setup_font_size">56sp</dimen>
+    <dimen name="no_alarms_size">120dp</dimen>
 </resources>
\ No newline at end of file
diff --git a/res/values-sw600dp-land/dimens.xml b/res/values-sw600dp-land/dimens.xml
index 29ed85e..88bfc05 100644
--- a/res/values-sw600dp-land/dimens.xml
+++ b/res/values-sw600dp-land/dimens.xml
@@ -17,14 +17,10 @@
 <!-- These resources are around just to allow their values to be customized
      for different hardware and product builds. -->
 <resources>
-    <dimen name="ampm_text_size">90dip</dimen>
-    <dimen name="date_text_size">15sp</dimen>
-    <dimen name="next_alarm_text_size">15sp</dimen>
     <dimen name="time_margin_top">32dip</dimen>
 
     <dimen name="dialpad_font_size">42sp</dimen>
     <dimen name="timer_setup_font_size">78sp</dimen>
-    <dimen name="timer_setup_button_size">36sp</dimen>
     <dimen name="timer_setup_delete_margin">19sp</dimen>
 
     <!-- Size of margin for circles. -->
@@ -33,4 +29,4 @@
 
     <!-- The maximum size of the font for the time in widgets. -->
     <dimen name="widget_max_clock_font_size">100dp</dimen>
-</resources>
\ No newline at end of file
+</resources>
diff --git a/res/values-sw600dp/dimens.xml b/res/values-sw600dp/dimens.xml
index b3379fa..6a7ecb9 100644
--- a/res/values-sw600dp/dimens.xml
+++ b/res/values-sw600dp/dimens.xml
@@ -18,9 +18,6 @@
      for different hardware and product builds. -->
 <resources>
     <dimen name="label_text_size">18sp</dimen>
-    <dimen name="ampm_text_size">54dip</dimen>
-    <dimen name="date_text_size">15sp</dimen>
-    <dimen name="next_alarm_text_size">15sp</dimen>
     <dimen name="time_margin_top">48dip</dimen>
     <dimen name="screensaver_margin">20dip</dimen>
     <dimen name="alarm_label_padding">64dip</dimen>
@@ -34,7 +31,6 @@
     <dimen name="medium_font_size">96sp</dimen>
     <dimen name="label_font_size">18sp</dimen>
     <dimen name="alarm_label_size">18sp</dimen>
-    <dimen name="body_font_size">20sp</dimen>
     <dimen name="day_button_font_size">20sp</dimen>
     <dimen name="alarm_info_font_size">48sp</dimen>
     <dimen name="no_alarm_font_size">20sp</dimen>
@@ -44,18 +40,13 @@
     <dimen name="fab_alarm_size">80dp</dimen>
 
     <dimen name="body_font_padding">8dp</dimen>
-    <dimen name="medium_font_padding">24dp</dimen>
 
-    <dimen name="small_space">32dp</dimen>
     <dimen name="medium_space_top">46dp</dimen>
-    <dimen name="medium_space_bottom">18dp</dimen>
 
     <dimen name="label_margin_big">8dp</dimen>
-    <dimen name="label_margin_small">4dp</dimen>
 
     <dimen name="dialpad_font_size">56sp</dimen>
     <dimen name="timer_setup_font_size">86sp</dimen>
-    <dimen name="timer_setup_button_size">36sp</dimen>
     <dimen name="timer_setup_delete_margin">21sp</dimen>
 
     <!-- Specified in sp to match the top margin of the time on the adjacent world clock tab. -->
@@ -65,9 +56,6 @@
     <dimen name="circle_margin_top">48dp</dimen>
     <dimen name="analog_clock_margin">96dp</dimen>
 
-     <!-- Width of the clock, for use with alarm buttons. -->
-    <dimen name="alarm_alert_display_width">550dip</dimen>
-
     <!-- Size of analog clock in world clock. -->
     <dimen name="world_clock_analog_size">200dp</dimen>
 
@@ -84,4 +72,4 @@
     <dimen name="city_widget_name_font_size">20dp</dimen>
     <!-- The maximum size of the font for the time in widgets. -->
     <dimen name="widget_max_clock_font_size">125dp</dimen>
-</resources>
\ No newline at end of file
+</resources>
diff --git a/res/values-sw720dp-land/dimens.xml b/res/values-sw720dp-land/dimens.xml
index bf22cf2..5d70c21 100644
--- a/res/values-sw720dp-land/dimens.xml
+++ b/res/values-sw720dp-land/dimens.xml
@@ -19,7 +19,6 @@
 <resources>
 
     <dimen name="timer_setup_font_size">78sp</dimen>
-    <dimen name="timer_setup_button_size">36sp</dimen>
     <dimen name="timer_setup_delete_margin">19sp</dimen>
 
-</resources>
\ No newline at end of file
+</resources>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index fbe2785..94e813f 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -20,7 +20,6 @@
     <dimen name="label_text_size">14sp</dimen>
     <dimen name="label_edittext_padding">21dp</dimen>
     <dimen name="time_margin_top">32dip</dimen>
-    <dimen name="timer_padding">16dp</dimen>
     <dimen name="screensaver_margin">16dip</dimen>
     <dimen name="alarm_label_padding">8dip</dimen>
     <dimen name="bottom_text_spacing_digital">-8dp</dimen>
@@ -66,16 +65,13 @@
     <dimen name="alarm_icon_padding">6dp</dimen>
 
     <dimen name="backspace_icon_size">24dp</dimen>
-    <dimen name="no_alarms_size">120dp</dimen>
+    <dimen name="no_alarms_size">90dp</dimen>
     <dimen name="fab_alarm_size">56dp</dimen>
 
-    <dimen name="medium_font_padding">12dp</dimen>
-
     <dimen name="medium_space_top">22dp</dimen>
     <dimen name="style_label_space">4dip</dimen>
 
     <dimen name="label_margin_big">4dp</dimen>
-    <dimen name="label_margin_small">2dp</dimen>
 
     <dimen name="dialpad_font_size">36sp</dimen>
     <!--padding should be (in dip) ~ 60% dialpad_font_size -->
@@ -172,4 +168,4 @@
     <!-- When fab buttons decrease in size this padding is used to scale the icon appropriately. -->
     <dimen name="fab_button_padding">0dp</dimen>
 
-</resources>
\ No newline at end of file
+</resources>
diff --git a/res/values/donottranslate_events.xml b/res/values/donottranslate_events.xml
index e2d96b7..d48e362 100644
--- a/res/values/donottranslate_events.xml
+++ b/res/values/donottranslate_events.xml
@@ -19,6 +19,7 @@
     <string name="category_clock">Clock</string>
     <string name="category_timer">Timer</string>
     <string name="category_stopwatch">Stopwatch</string>
+    <string name="category_screensaver">Screensaver</string>
     <string name="category_analog_widget">Analog Widget</string>
     <string name="category_digital_widget">Digital Widget</string>
 
@@ -27,7 +28,6 @@
     <string name="action_hide">Hide</string>
     <string name="action_snooze">Snooze</string>
     <string name="action_create">Create</string>
-    <string name="action_add">Add</string>
     <string name="action_delete">Delete</string>
     <string name="action_update">Update</string>
     <string name="action_start">Start</string>
@@ -43,7 +43,6 @@
     <string name="label_deskclock">DeskClock</string>
     <string name="label_notification">Notification</string>
     <string name="label_intent">Intent</string>
-    <string name="label_widget">Widget</string>
     <string name="label_hardware_button">HardwareButton</string>
-    <string name="label_reboot">Reboot</string>
+    <string name="label_shortcut">Shortcut</string>
 </resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index dccf10a..58fe640 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -53,9 +53,6 @@
     <!-- Button labels on the alarm dialog: Dismiss -->
     <string name="alarm_alert_dismiss_text">Dismiss</string>
 
-    <!-- Button labels on the pre-dismiss alarm notifications: Dismiss now -->
-    <string name="alarm_alert_dismiss_now_text">Dismiss now</string>
-
     <!-- Alarm Alert screen: this message is shown after an alarm rung
          unattended for a number of minutes.  It tells the user that
          the alarm has been silenced.-->
@@ -347,8 +344,8 @@
     <string name="menu_item_settings">Settings</string>
     <!-- Menu item on most screens to get to the help information -->
     <string name="menu_item_help">Help</string>
-    <!-- Menu item on clock screen to enter night mode. -->
-    <string name="menu_item_night_mode">Night mode</string>
+    <!-- Menu item on clock screen to enter screen saver. -->
+    <string name="menu_item_night_mode">Screen saver</string>
     <!-- Menu item on Cities screen to sort by GMT offset -->
     <string name="menu_item_sort_by_gmt_offset">Sort by time</string>
     <!-- Menu item on Cities screen to sort by alphabetical order -->
@@ -448,8 +445,17 @@
         remaining. Arg is the number of timers. [CHAR LIMIT=25]
     -->
     <string name="timer_multi_times_up"><xliff:g id="NUM_TIMERS" example="2">%d</xliff:g> timers expired</string>
+    <!--
+        Notification content shown when multiple timers have been missed. Arg is the number of
+        timers. [CHAR LIMIT=25]
+    -->
+    <string name="timer_multi_missed"><xliff:g id="number" example="2">%d</xliff:g> timers missed</string>
     <!-- Label associated with a notification for a Timer -->
     <string name="timer_notification_label">Timer</string>
+    <!-- Label associated with a notification for a missed Timer. [CHAR LIMIT=30] -->
+    <string name="missed_timer_notification_label">Missed timer</string>
+    <!-- Label associated with a notification for a missed named Timer. [CHAR LIMIT=NONE] -->
+    <string name="missed_named_timer_notification_label">Missed timer: <xliff:g id="name" example="Pick up kids">%s</xliff:g></string>
     <!-- Describes the purpose of the notification button to pause the timer. [CHAR LIMIT=15] -->
     <string name="timer_pause">Pause</string>
     <!-- Describes the purpose of the notification button to reset all running timers. [CHAR LIMIT=31] -->
@@ -701,6 +707,37 @@
         <item>"Asia/Jakarta"</item>
     </string-array>
 
+    <!-- Short label for a shortcut to create a new alarm. The maximum length is ~10 characters
+         (longer translations may be truncated). -->
+    <string name="shortcut_new_alarm_short">New alarm</string>
+    <!-- Long label for a shortcut to create a new alarm. The maximum length is ~25 characters
+         (longer translations may be truncated). -->
+    <string name="shortcut_new_alarm_long">Create new alarm</string>
+    <!-- Short label for a shortcut to create a new timer. The maximum length is ~10 characters
+         (longer translations may be truncated). -->
+    <string name="shortcut_new_timer_short">New timer</string>
+    <!-- Long label for a shortcut to create a new timer. The maximum length is ~25 characters
+    (longer translations may be truncated). -->
+    <string name="shortcut_new_timer_long">Create new timer</string>
+    <!-- Short label for a shortcut to start the stopwatch. The maximum length is ~10 characters
+         (longer translations may be truncated). -->
+    <string name="shortcut_start_stopwatch_short">Start</string>
+    <!-- Long label for a shortcut to start the stopwatch. The maximum length is ~25 characters
+         (longer translations may be truncated). -->
+    <string name="shortcut_start_stopwatch_long">Start stopwatch</string>
+    <!-- Short label for a shortcut to pause the stopwatch. The maximum length is ~10 characters
+         (longer translations may be truncated). -->
+    <string name="shortcut_pause_stopwatch_short">Pause</string>
+    <!-- Long label for a shortcut to pause the stopwatch. The maximum length is ~25 characters
+         (longer translations may be truncated). -->
+    <string name="shortcut_pause_stopwatch_long">Pause stopwatch</string>
+    <!-- Short label for a shortcut to start the screensaver. The maximum length is ~10 characters
+         (longer translations may be truncated). -->
+    <string name="shortcut_start_screensaver_short">Screen saver</string>
+    <!-- Long label for a shortcut to start the screensaver. The maximum length is ~25 characters
+         (longer translations may be truncated). -->
+    <string name="shortcut_start_screensaver_long">Start screen saver</string>
+
     <!-- Header in the preferences settings for the section pertaining to alarms -->
     <string name="alarm_settings">Alarms</string>
     <!-- Describes the service that processes actions originating from timer notifications. -->
@@ -744,9 +781,9 @@
     <!-- screensaver settings strings -->
     <!-- Title for the screen saver settings activity. -->
     <string name="screensaver_settings">Screen saver settings</string>
-    <!-- Title for check box to pick intensity of display diminuation during dream mode -->
+    <!-- Title for check box to pick intensity of display diminuation during screen saver -->
     <string name="night_mode_title">Night mode</string>
-    <!-- Describes intensity of display diminuation during dream mode -->
+    <!-- Describes intensity of display diminuation during screen saver -->
     <string name="night_mode_summary">Very dim display (for dark rooms)</string>
 
     <!-- Description of the down caret in the alarm alert screen to expand the alarm content to edit perspective. [CHAR LIMIT=NONE] -->
@@ -774,6 +811,13 @@
     -->
     <string name="invalid_time">Invalid time <xliff:g id="invalid_hour" example="25">%1$d</xliff:g>:<xliff:g id="invalid_minutes" example="63">%2$d</xliff:g> <xliff:g id="invalid_ampm" example="PM">%3$s</xliff:g></string>
 
+    <!-- String that indicates no alarm can be located with the given deeplink. This happens when a
+    user sends a voice command to mutate an existing alarm but the deeplink no longer refers to a
+    valid alarm.
+    [CHAR LIMIT=NONE]
+    -->
+    <string name="cannot_locate_alarm">Unable to locate alarm.</string>
+
     <!-- String that represents that no alarm has been specified for a requested hour:minutes.
     This happens when a user sends a voice command 'dismiss my alarm at 3:00pm' but they have no
     alarms specified for that time. %s represents the time of the alarm.
@@ -788,6 +832,29 @@
     -->
     <string name="no_scheduled_alarms">No scheduled alarms</string>
 
+    <!-- String that indicates the alarm is still scheduled and cannot yet be snoozed.
+    [CHAR LIMIT=NONE]
+    -->
+    <string name="cannot_snooze_scheduled_alarm">The alarm has not yet fired.</string>
+
+    <!-- String that indicates the alarm is missed and cannot be snoozed or dismissed. This
+    happens when a user sends a voice command 'dismiss alarm' or 'snooze alarm' but the alarm is
+    already in the missed state.
+    [CHAR LIMIT=NONE]
+    -->
+    <string name="alarm_is_already_missed">The alarm is already missed.</string>
+
+    <!-- String that indicates the alarm is dismissed or predismissed and cannot be snoozed or
+    dismissed again.
+    [CHAR LIMIT=NONE]
+    -->
+    <string name="alarm_is_already_dismissed">The alarm is already dismissed.</string>
+
+    <!-- String that indicates the alarm is snoozed cannot be snoozed again.
+    [CHAR LIMIT=NONE]
+    -->
+    <string name="alarm_is_already_snoozed">The alarm is already snoozed.</string>
+
     <!-- String that represents that the user specified that they want to select an alarm to
     dismiss by specifying a 'label' but they didn't specify any labels.
     [CHAR LIMIT=NONE]
@@ -801,36 +868,6 @@
     -->
     <string name="no_alarms_with_label">No alarms contain the label</string>
 
-    <!-- String that represents that the user has sent a voice command 'stop the stopwatch' or
-    'lap the stopwatch' when the stopwatch wasn't running so the command wasn't executed.
-    [CHAR LIMIT=NONE]
-    -->
-    <string name="stopwatch_isnt_running">Stopwatch isn\'t running</string>
-
-    <!-- String that represents that the user has successfully sent a voice command pausing
-     the stopwatch.
-    [CHAR LIMIT=NONE]
-    -->
-    <string name="stopwatch_paused">Stopwatch paused</string>
-
-    <!-- String that represents that the user has successfully sent a voice command resetting
-    the stopwatch.
-    [CHAR LIMIT=NONE]
-    -->
-    <string name="stopwatch_reset">Stopwatch reset</string>
-
-    <!-- String that represents that the user has successfully sent a voice command lapping
-    the stopwatch.
-    [CHAR LIMIT=NONE]
-    -->
-    <string name="stopwatch_lapped">Stopwatch lapped</string>
-
-    <!-- String that represents that the user has successfully sent a voice command starting
-    the stopwatch.
-    [CHAR LIMIT=NONE]
-    -->
-    <string name="stopwatch_started">Stopwatch started</string>
-
     <!-- String that represents that the user has sent a voice command 'dismiss my alarm at 3pm' when
     there was no alarm scheduled for that time (they might have had an alarm for 3pm on the list
     but it was disabled).
@@ -850,85 +887,23 @@
     -->
     <string name="alarm_is_set">Alarm is set for <xliff:g id="alarm_time" example="14:20">%s</xliff:g></string>
 
-    <!-- String that represents that the user attempted to control a timer with a voice action when
-    no timers are defined.
-    [CHAR LIMIT=NONE]
-    -->
-    <string name="no_timers_exist">No timers exist</string>
-
-    <!-- String that represents that the user attempted to control a timer with a voice action when
-    more than one timer is defined.
-    [CHAR LIMIT=NONE]
-    -->
-    <string name="too_many_timers_exist">More than one timer exists</string>
-
-    <!-- String that represents that the user attempted to control a timer with a voice action but
-    the timer could not be located.
-    [CHAR LIMIT=NONE]
-    -->
-    <string name="timer_does_not_exist">The timer has been removed.</string>
-
     <!-- String that represents that the user has successfully created a timer through a voice action.
     [CHAR LIMIT=NONE]
     -->
     <string name="timer_created">Timer created</string>
 
-    <!-- String that represents that the user has successfully reset a timer through a voice action.
-    [CHAR LIMIT=NONE]
-    -->
-    <string name="timer_was_reset">Timer reset</string>
-
     <!-- String that represents that the user has successfully reset a timer through a voice action
     that was marked as deleteAfterUse and was thus deleted instead.
     [CHAR LIMIT=NONE]
     -->
     <string name="timer_deleted">Timer deleted</string>
 
-    <!-- String that represents that the user has successfully started a timer through a voice action.
-    [CHAR LIMIT=NONE]
-    -->
-    <string name="timer_started">Timer started</string>
-
     <!-- String that represents that the user attempted to start a timer through a voice action
     but specified invalid length.
     [CHAR LIMIT=NONE]
     -->
     <string name="invalid_timer_length">Invalid timer length</string>
 
-    <!-- String that represents that the user attempted to add or delete a world clock through
-    a voice action
-    but they didn't specify a city so no world clock was selected.
-    [CHAR LIMIT=NONE]
-    -->
-    <string name="no_city_selected">No city selected</string>
-
-    <!-- String that represents that the user attempted to add or delete a world clock through
-    a voice action
-    but the city they specified wasn't listed in the database.
-    [CHAR LIMIT=NONE]
-    -->
-    <string name="the_city_you_specified_is_not_available">The city you specified is not available</string>
-
-    <!-- String that represents that the user attempted to add a world clock through a voice action
-    but the city they specified is already added to the list.
-    [CHAR LIMIT=NONE]
-    -->
-    <string name="the_city_already_added">That city has already been added</string>
-
-    <!-- String that represents that the user successfully added a world clock through a
-    voice action
-    %s represents the name of the city they added.
-    [CHAR LIMIT=NONE]
-    -->
-    <string name="city_added"><xliff:g id="city_name" example="Paris">%s</xliff:g> added</string>
-
-    <!-- String that represents that the user successfully deleted a world clock through
-    a voice action
-    %s represents the name of the city they deleted.
-    [CHAR LIMIT=NONE]
-    -->
-    <string name="city_deleted"><xliff:g id="city_name" example="Paris">%s</xliff:g> deleted</string>
-
     <!-- String that represents that the user attempted to dismiss an alarm that is more than
     24 hours away
     %s represents the time of the alarm (e.g. 15:39)
diff --git a/src/com/android/alarmclock/DigitalAppWidgetProvider.java b/src/com/android/alarmclock/DigitalAppWidgetProvider.java
index ea07dc4..1ae7a00 100644
--- a/src/com/android/alarmclock/DigitalAppWidgetProvider.java
+++ b/src/com/android/alarmclock/DigitalAppWidgetProvider.java
@@ -65,6 +65,7 @@
 import static android.content.Intent.ACTION_LOCALE_CHANGED;
 import static android.content.Intent.ACTION_SCREEN_ON;
 import static android.content.Intent.ACTION_TIMEZONE_CHANGED;
+import static android.content.Intent.ACTION_TIME_CHANGED;
 import static android.util.TypedValue.COMPLEX_UNIT_PX;
 import static android.view.View.GONE;
 import static android.view.View.MeasureSpec.UNSPECIFIED;
@@ -125,7 +126,7 @@
 
     @Override
     public void onReceive(@NonNull Context context, @NonNull Intent intent) {
-        LOGGER.i("Digital Widget processing action %s", intent.getAction());
+        LOGGER.i("onReceive: " + intent);
         super.onReceive(context, intent);
 
         final AppWidgetManager wm = AppWidgetManager.getInstance(context);
@@ -136,15 +137,17 @@
         final ComponentName provider = new ComponentName(context, getClass());
         final int[] widgetIds = wm.getAppWidgetIds(provider);
 
-        switch (intent.getAction()) {
-            case ACTION_SCREEN_ON:
+        final String action = intent.getAction();
+        switch (action) {
+            case ACTION_NEXT_ALARM_CLOCK_CHANGED:
             case ACTION_DATE_CHANGED:
+            case ACTION_LOCALE_CHANGED:
+            case ACTION_SCREEN_ON:
+            case ACTION_TIME_CHANGED:
+            case ACTION_TIMEZONE_CHANGED:
             case ACTION_ALARM_CHANGED:
             case ACTION_ON_DAY_CHANGE:
-            case ACTION_LOCALE_CHANGED:
-            case ACTION_TIMEZONE_CHANGED:
             case ACTION_WORLD_CITIES_CHANGED:
-            case ACTION_NEXT_ALARM_CLOCK_CHANGED:
                 for (int widgetId : widgetIds) {
                     relayoutWidget(context, wm, widgetId, wm.getAppWidgetOptions(widgetId));
                 }
@@ -343,16 +346,21 @@
      * Add the day-change callback if it is needed (selected cities exist).
      */
     private void updateDayChangeCallback(Context context) {
-        final List<City> selectedCities = DataModel.getDataModel().getSelectedCities();
-        if (selectedCities.isEmpty()) {
+        final DataModel dm = DataModel.getDataModel();
+        final List<City> selectedCities = dm.getSelectedCities();
+        final boolean showHomeClock = dm.getShowHomeClock();
+        if (selectedCities.isEmpty() && !showHomeClock) {
             // Remove the existing day-change callback.
             removeDayChangeCallback(context);
             return;
         }
 
         // Look up the time at which the next day change occurs across all timezones.
-        final Set<TimeZone> zones = new ArraySet<>(selectedCities.size() + 1);
+        final Set<TimeZone> zones = new ArraySet<>(selectedCities.size() + 2);
         zones.add(TimeZone.getDefault());
+        if (showHomeClock) {
+            zones.add(dm.getHomeCity().getTimeZone());
+        }
         for (City city : selectedCities) {
             zones.add(city.getTimeZone());
         }
@@ -530,4 +538,4 @@
             builder.append(String.format(Locale.ENGLISH, format, args));
         }
     }
-}
\ No newline at end of file
+}
diff --git a/src/com/android/deskclock/AlarmClockFragment.java b/src/com/android/deskclock/AlarmClockFragment.java
index c3999db..7c8f93f 100644
--- a/src/com/android/deskclock/AlarmClockFragment.java
+++ b/src/com/android/deskclock/AlarmClockFragment.java
@@ -174,6 +174,15 @@
     }
 
     @Override
+    public void onStart() {
+        super.onStart();
+
+        if (!isTabSelected()) {
+            TimePickerCompat.removeTimeEditDialog(getFragmentManager());
+        }
+    }
+
+    @Override
     public void onResume() {
         super.onResume();
 
diff --git a/src/com/android/deskclock/AlarmInitReceiver.java b/src/com/android/deskclock/AlarmInitReceiver.java
index a5dc04e..3848ad1 100644
--- a/src/com/android/deskclock/AlarmInitReceiver.java
+++ b/src/com/android/deskclock/AlarmInitReceiver.java
@@ -23,8 +23,8 @@
 import android.os.PowerManager.WakeLock;
 
 import com.android.deskclock.alarms.AlarmStateManager;
+import com.android.deskclock.controller.Controller;
 import com.android.deskclock.data.DataModel;
-import com.android.deskclock.events.Events;
 
 public class AlarmInitReceiver extends BroadcastReceiver {
 
@@ -60,18 +60,29 @@
         // We need to increment the global id out of the async task to prevent race conditions
         AlarmStateManager.updateGlobalIntentId(context);
 
-        // Clear stopwatch data and reset timers because they rely on elapsed real-time values
-        // which are meaningless after a device reboot.
+        // Updates stopwatch and timer data after a device reboot so they are as accurate as
+        // possible.
         if (ACTION_BOOT_COMPLETED.equals(action)) {
-            DataModel.getDataModel().resetStopwatch();
-            Events.sendStopwatchEvent(R.string.action_reset, R.string.label_reboot);
-            DataModel.getDataModel().resetTimers(R.string.label_reboot);
+            DataModel.getDataModel().updateAfterReboot();
+            // Stopwatch and timer data need to be updated on time change so the reboot
+            // functionality works as expected.
+        } else if (Intent.ACTION_TIME_CHANGED.equals(action)) {
+            DataModel.getDataModel().updateAfterTimeSet();
+        }
+
+        // Update shortcuts so they exist for the user.
+        if (Intent.ACTION_BOOT_COMPLETED.equals(action)
+                || Intent.ACTION_LOCALE_CHANGED.equals(action)) {
+            Controller.getController().updateShortcuts();
         }
 
         // Notifications are canceled by the system on application upgrade. This broadcast signals
         // that the new app is free to rebuild the notifications using the existing data.
+        // Additionally on new app installs, make sure to enable shortcuts immediately as opposed
+        // to waiting for system reboot.
         if (Intent.ACTION_MY_PACKAGE_REPLACED.equals(action)) {
             DataModel.getDataModel().updateAllNotifications();
+            Controller.getController().updateShortcuts();
         }
 
         AsyncHandler.post(new Runnable() {
diff --git a/src/com/android/deskclock/AlarmSelectionActivity.java b/src/com/android/deskclock/AlarmSelectionActivity.java
index f046996..4bd983f 100644
--- a/src/com/android/deskclock/AlarmSelectionActivity.java
+++ b/src/com/android/deskclock/AlarmSelectionActivity.java
@@ -17,7 +17,6 @@
 
 import android.app.Activity;
 import android.app.ListActivity;
-import android.content.Context;
 import android.content.Intent;
 import android.os.AsyncTask;
 import android.os.Bundle;
@@ -32,13 +31,23 @@
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Locale;
 
 public class AlarmSelectionActivity extends ListActivity {
 
+    /** Used by default when an invalid action provided. */
+    private static final int ACTION_INVALID = -1;
+
+    /** Action used to signify alarm should be dismissed on selection. */
+    public static final int ACTION_DISMISS = 0;
+
+    public static final String EXTRA_ACTION = "com.android.deskclock.EXTRA_ACTION";
     public static final String EXTRA_ALARMS = "com.android.deskclock.EXTRA_ALARMS";
 
     private final List<AlarmSelection> mSelections = new ArrayList<>();
 
+    private int mAction;
+
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         // this activity is shown if:
@@ -62,6 +71,7 @@
 
         final Intent intent = getIntent();
         final Parcelable[] alarmsFromIntent = intent.getParcelableArrayExtra(EXTRA_ALARMS);
+        mAction = intent.getIntExtra(EXTRA_ACTION, ACTION_INVALID);
 
         // reading alarms from intent
         // PickSelection is started only if there are more than 1 relevant alarm
@@ -70,7 +80,7 @@
             final Alarm alarm = (Alarm) parcelable;
 
             // filling mSelections that go into the UI picker list
-            final String label = String.format("%d %02d", alarm.hour, alarm.minutes);
+            final String label = String.format(Locale.US, "%d %02d", alarm.hour, alarm.minutes);
             mSelections.add(new AlarmSelection(label, alarm));
         }
 
@@ -84,26 +94,32 @@
         final AlarmSelection selection = mSelections.get((int) id);
         final Alarm alarm = selection.getAlarm();
         if (alarm != null) {
-            new ProcessAlarmActionAsync(this, alarm, this).execute();
+            new ProcessAlarmActionAsync(alarm, this, mAction).execute();
         }
         finish();
     }
 
     private static class ProcessAlarmActionAsync extends AsyncTask<Void, Void, Void> {
 
-        private final Context mContext;
         private final Alarm mAlarm;
         private final Activity mActivity;
+        private final int mAction;
 
-        public ProcessAlarmActionAsync(Context context, Alarm alarm, Activity activity) {
-            mContext = context;
+        public ProcessAlarmActionAsync(Alarm alarm, Activity activity, int action) {
             mAlarm = alarm;
             mActivity = activity;
+            mAction = action;
         }
 
         @Override
         protected Void doInBackground(Void... parameters) {
-            HandleApiCalls.dismissAlarm(mAlarm, mContext, mActivity);
+            switch (mAction) {
+                case ACTION_DISMISS:
+                    HandleApiCalls.dismissAlarm(mAlarm, mActivity);
+                    break;
+                case ACTION_INVALID:
+                    LogUtils.i("Invalid action");
+            }
             return null;
         }
     }
diff --git a/src/com/android/deskclock/AlarmUtils.java b/src/com/android/deskclock/AlarmUtils.java
index 5f91a00..4082925 100644
--- a/src/com/android/deskclock/AlarmUtils.java
+++ b/src/com/android/deskclock/AlarmUtils.java
@@ -42,6 +42,12 @@
         return (String) DateFormat.format(pattern, time);
     }
 
+    public static String getFormattedTime(Context context, long timeInMillis) {
+        final Calendar c = Calendar.getInstance();
+        c.setTimeInMillis(timeInMillis);
+        return getFormattedTime(context, c);
+    }
+
     public static String getAlarmText(Context context, AlarmInstance instance,
             boolean includeLabel) {
         String alarmTimeStr = getFormattedTime(context, instance.getAlarmTime());
diff --git a/src/com/android/deskclock/AnalogClock.java b/src/com/android/deskclock/AnalogClock.java
index b0159ad..bb8bd65 100644
--- a/src/com/android/deskclock/AnalogClock.java
+++ b/src/com/android/deskclock/AnalogClock.java
@@ -20,12 +20,11 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.graphics.Canvas;
-import android.graphics.drawable.Drawable;
-import android.support.annotation.DrawableRes;
+import android.support.v7.widget.AppCompatImageView;
 import android.text.format.DateFormat;
 import android.util.AttributeSet;
-import android.view.View;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
 
 import java.text.SimpleDateFormat;
 import java.util.Calendar;
@@ -36,7 +35,7 @@
 /**
  * This widget display an analog clock with two hands for hours and minutes.
  */
-public class AnalogClock extends View {
+public class AnalogClock extends FrameLayout {
 
     private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
         @Override
@@ -62,10 +61,9 @@
         }
     };
 
-    private final Drawable mDial;
-    private final Drawable mHourHand;
-    private final Drawable mMinuteHand;
-    private final Drawable mSecondHand;
+    private final ImageView mHourHand;
+    private final ImageView mMinuteHand;
+    private final ImageView mSecondHand;
 
     private Calendar mTime;
     private String mDescFormat;
@@ -86,10 +84,19 @@
         mTime = Calendar.getInstance();
         mDescFormat = ((SimpleDateFormat) DateFormat.getTimeFormat(context)).toLocalizedPattern();
 
-        mDial = initDrawable(context, R.drawable.clock_analog_dial);
-        mHourHand = initDrawable(context, R.drawable.clock_analog_hour);
-        mMinuteHand = initDrawable(context, R.drawable.clock_analog_minute);
-        mSecondHand = initDrawable(context, R.drawable.clock_analog_second);
+        final ImageView dial = new AppCompatImageView(context);
+        dial.setImageResource(R.drawable.clock_analog_dial);
+        addView(dial);
+
+        mHourHand = new AppCompatImageView(context);
+        mHourHand.setImageResource(R.drawable.clock_analog_hour);
+        addView(mHourHand);
+        mMinuteHand = new AppCompatImageView(context);
+        mMinuteHand.setImageResource(R.drawable.clock_analog_minute);
+        addView(mMinuteHand);
+        mSecondHand = new AppCompatImageView(context);
+        mSecondHand.setImageResource(R.drawable.clock_analog_second);
+        addView(mSecondHand);
     }
 
     @Override
@@ -121,77 +128,16 @@
         removeCallbacks(mClockTick);
     }
 
-    @Override
-    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        final int minWidth = Math.max(mDial.getIntrinsicWidth(), getSuggestedMinimumWidth());
-        final int minHeight = Math.max(mDial.getIntrinsicHeight(), getSuggestedMinimumHeight());
-        setMeasuredDimension(getDefaultSize(minWidth, widthMeasureSpec),
-                getDefaultSize(minHeight, heightMeasureSpec));
-    }
-
-    @Override
-    protected void onDraw(Canvas canvas) {
-        super.onDraw(canvas);
-
-        final int w = getWidth();
-        final int h = getHeight();
-
-        final int saveCount = canvas.save();
-
-        // Center the canvas at the mid-point.
-        canvas.translate(w / 2, h / 2);
-
-        // Scale down the clock if necessary.
-        final float scale = Math.min((float) w / mDial.getIntrinsicWidth(),
-                (float) h / mDial.getIntrinsicHeight());
-        if (scale < 1f) {
-            canvas.scale(scale, scale, 0f, 0f);
-        }
-
-        mDial.draw(canvas);
-
-        final float hourAngle = mTime.get(Calendar.HOUR) * 30f;
-        canvas.rotate(hourAngle, 0f, 0f);
-        mHourHand.draw(canvas);
-
-        final float minuteAngle = mTime.get(Calendar.MINUTE) * 6f;
-        canvas.rotate(minuteAngle - hourAngle, 0f, 0f);
-        mMinuteHand.draw(canvas);
-
-        if (mEnableSeconds) {
-            final float secondAngle = mTime.get(Calendar.SECOND) * 6f;
-            canvas.rotate(secondAngle - minuteAngle, 0f, 0f);
-            mSecondHand.draw(canvas);
-        }
-
-        canvas.restoreToCount(saveCount);
-    }
-
-    @Override
-    protected boolean verifyDrawable(Drawable who) {
-        return mDial == who
-                || mHourHand == who
-                || mMinuteHand == who
-                || mSecondHand == who
-                || super.verifyDrawable(who);
-    }
-
-    private Drawable initDrawable(Context context, @DrawableRes int id) {
-        final Drawable d = Utils.getVectorDrawable(context, id);
-
-        // Center the drawable using its bounds.
-        final int midX = d.getIntrinsicWidth() / 2;
-        final int midY = d.getIntrinsicHeight() / 2;
-        d.setBounds(-midX, -midY, midX, midY);
-
-        // Register callback to support non-bitmap drawables.
-        d.setCallback(this);
-
-        return d;
-    }
-
     private void onTimeChanged() {
         mTime.setTimeInMillis(System.currentTimeMillis());
+        final float hourAngle = mTime.get(Calendar.HOUR) * 30f;
+        mHourHand.setRotation(hourAngle);
+        final float minuteAngle = mTime.get(Calendar.MINUTE) * 6f;
+        mMinuteHand.setRotation(minuteAngle);
+        if (mEnableSeconds) {
+            final float secondAngle = mTime.get(Calendar.SECOND) * 6f;
+            mSecondHand.setRotation(secondAngle);
+        }
         setContentDescription(DateFormat.format(mDescFormat, mTime));
         invalidate();
     }
@@ -205,7 +151,10 @@
     public void enableSeconds(boolean enable) {
         mEnableSeconds = enable;
         if (mEnableSeconds) {
+            mSecondHand.setVisibility(VISIBLE);
             mClockTick.run();
+        } else {
+            mSecondHand.setVisibility(GONE);
         }
     }
 }
diff --git a/src/com/android/deskclock/ClockFragment.java b/src/com/android/deskclock/ClockFragment.java
index 075e1c3..84cde0b 100644
--- a/src/com/android/deskclock/ClockFragment.java
+++ b/src/com/android/deskclock/ClockFragment.java
@@ -29,22 +29,22 @@
 import android.os.Handler;
 import android.provider.Settings;
 import android.support.annotation.NonNull;
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.view.GestureDetector;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.View;
-import android.view.View.OnTouchListener;
-import android.view.ViewConfiguration;
 import android.view.ViewGroup;
-import android.widget.AbsListView;
-import android.widget.BaseAdapter;
 import android.widget.ImageButton;
 import android.widget.ImageView;
-import android.widget.ListView;
 import android.widget.TextClock;
 import android.widget.TextView;
 
 import com.android.deskclock.data.City;
+import com.android.deskclock.data.CityListener;
 import com.android.deskclock.data.DataModel;
+import com.android.deskclock.events.Events;
 import com.android.deskclock.uidata.UiDataModel;
 import com.android.deskclock.worldclock.CitySelectionActivity;
 
@@ -74,17 +74,16 @@
     // Detects changes to the next scheduled alarm pre-L.
     private ContentObserver mAlarmObserver;
 
-    private Handler mHandler;
-
     private TextClock mDigitalClock;
     private View mAnalogClock, mClockFrame;
-    private View mHairline;
     private SelectedCitiesAdapter mCityAdapter;
-    private ListView mCityList;
+    private RecyclerView mCityList;
     private String mDateFormat;
     private String mDateFormatForAccessibility;
 
-    /** The public no-arg constructor required by all fragments. */
+    /**
+     * The public no-arg constructor required by all fragments.
+     */
     public ClockFragment() {
         super(CLOCKS);
     }
@@ -93,8 +92,7 @@
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
 
-        mHandler = new Handler();
-        mAlarmObserver = Utils.isPreL() ? new AlarmObserverPreL(mHandler) : null;
+        mAlarmObserver = Utils.isPreL() ? new AlarmObserverPreL() : null;
         mAlarmChangeReceiver = Utils.isLOrLater() ? new AlarmChangedBroadcastReceiver() : null;
     }
 
@@ -102,33 +100,41 @@
     public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle icicle) {
         super.onCreateView(inflater, container, icicle);
 
-        final OnTouchListener startScreenSaverListener = new StartScreenSaverListener();
         final View fragmentView = inflater.inflate(R.layout.clock_fragment, container, false);
 
-        mCityAdapter = new SelectedCitiesAdapter(getActivity());
+        mDateFormat = getString(R.string.abbrev_wday_month_day_no_year);
+        mDateFormatForAccessibility = getString(R.string.full_wday_month_day_no_year);
 
-        mCityList = (ListView) fragmentView.findViewById(R.id.cities);
-        mCityList.setDivider(null);
+        mCityAdapter = new SelectedCitiesAdapter(getActivity(), mDateFormat,
+                mDateFormatForAccessibility);
+
+        mCityList = (RecyclerView) fragmentView.findViewById(R.id.cities);
+        mCityList.setLayoutManager(new LinearLayoutManager(getActivity()));
         mCityList.setAdapter(mCityAdapter);
-        mCityList.setOnTouchListener(startScreenSaverListener);
-        mCityList.setOnScrollListener(new VerticalScrollPositionUpdater());
+        DataModel.getDataModel().addCityListener(mCityAdapter);
 
-        fragmentView.setOnTouchListener(startScreenSaverListener);
+        final ScrollPositionWatcher scrollPositionWatcher = new ScrollPositionWatcher();
+        mCityList.addOnScrollListener(scrollPositionWatcher);
+
+        final Context context = container.getContext();
+        mCityList.setOnTouchListener(new CityListOnLongClickListener(context));
+        fragmentView.setOnLongClickListener(new StartScreenSaverListener());
 
         // On tablet landscape, the clock frame will be a distinct view. Otherwise, it'll be added
         // on as a header to the main listview.
         mClockFrame = fragmentView.findViewById(R.id.main_clock_left_pane);
-        if (mClockFrame == null) {
-            mClockFrame = inflater.inflate(R.layout.main_clock_frame, mCityList, false);
-            mCityList.addHeaderView(mClockFrame, null, false);
-            mHairline = mClockFrame.findViewById(R.id.hairline);
-        } else {
-            final View hairline = mClockFrame.findViewById(R.id.hairline);
-            hairline.setVisibility(GONE);
+        if (mClockFrame != null) {
+            mDigitalClock = (TextClock) mClockFrame.findViewById(R.id.digital_clock);
+            mAnalogClock = mClockFrame.findViewById(R.id.analog_clock);
+            Utils.refreshAlarm(context, mClockFrame);
+            Utils.setTimeFormat(mDigitalClock);
+            Utils.updateDate(mDateFormat, mDateFormatForAccessibility, mClockFrame);
+            Utils.setClockStyle(mDigitalClock, mAnalogClock);
         }
 
-        mDigitalClock = (TextClock) mClockFrame.findViewById(R.id.digital_clock);
-        mAnalogClock = mClockFrame.findViewById(R.id.analog_clock);
+        // Schedule a runnable to update the date every quarter hour.
+        UiDataModel.getUiDataModel().addQuarterHourCallback(mQuarterHourUpdater, 100);
+
         return fragmentView;
     }
 
@@ -136,7 +142,9 @@
     public void onActivityCreated(Bundle savedInstanceState) {
         super.onActivityCreated(savedInstanceState);
 
-        Utils.setTimeFormat(mDigitalClock);
+        if (mDigitalClock != null) {
+            Utils.setTimeFormat(mDigitalClock);
+        }
     }
 
     @Override
@@ -155,20 +163,16 @@
         }
 
         // Resume can be invoked after changing the clock style.
-        Utils.setClockStyle(mDigitalClock, mAnalogClock);
+        if (mDigitalClock != null && mAnalogClock != null) {
+            Utils.setClockStyle(mDigitalClock, mAnalogClock);
+        }
 
         final View view = getView();
         if (view != null && view.findViewById(R.id.main_clock_left_pane) != null) {
             // Center the main clock frame by hiding the world clocks when none are selected.
-            mCityList.setVisibility(mCityAdapter.getCount() == 0 ? GONE : VISIBLE);
+            mCityList.setVisibility(mCityAdapter.getItemCount() == 0 ? GONE : VISIBLE);
         }
 
-        // In portrait, the hairline is shown only when the adapter contains cities.
-        if (mHairline != null) {
-            mHairline.setVisibility(mCityAdapter.getCount() == 0 ? GONE : VISIBLE);
-        }
-
-        refreshDates();
         refreshAlarm();
 
         // Alarm observer is null on L or later.
@@ -177,15 +181,11 @@
             final Uri uri = Settings.System.getUriFor(Settings.System.NEXT_ALARM_FORMATTED);
             activity.getContentResolver().registerContentObserver(uri, false, mAlarmObserver);
         }
-
-        // Schedule a runnable to update the date every quarter hour.
-        UiDataModel.getUiDataModel().addQuarterHourCallback(mQuarterHourUpdater, 100);
     }
 
     @Override
     public void onPause() {
         super.onPause();
-        UiDataModel.getUiDataModel().removePeriodicCallback(mQuarterHourUpdater);
 
         final Activity activity = getActivity();
         if (mAlarmChangeReceiver != null) {
@@ -197,6 +197,13 @@
     }
 
     @Override
+    public void onDestroyView() {
+        super.onDestroyView();
+        UiDataModel.getUiDataModel().removePeriodicCallback(mQuarterHourUpdater);
+        DataModel.getDataModel().removeCityListener(mCityAdapter);
+    }
+
+    @Override
     public void onFabClick(@NonNull ImageView fab) {
         startActivity(new Intent(getActivity(), CitySelectionActivity.class));
     }
@@ -204,7 +211,7 @@
     @Override
     public void onUpdateFab(@NonNull ImageView fab) {
         fab.setVisibility(VISIBLE);
-        fab.setImageResource(R.drawable.ic_language);
+        fab.setImageResource(R.drawable.ic_public);
         fab.setContentDescription(fab.getResources().getString(R.string.button_cities));
     }
 
@@ -215,78 +222,55 @@
     }
 
     /**
-     * Refresh the displayed dates in response to a change that may have changed them.
-     */
-    private void refreshDates() {
-        // Refresh the date in the main clock.
-        Utils.updateDate(mDateFormat, mDateFormatForAccessibility, mClockFrame);
-
-        // Refresh the day-of-week in each world clock.
-        mCityAdapter.notifyDataSetChanged();
-    }
-
-    /**
      * Refresh the next alarm time.
      */
     private void refreshAlarm() {
-        Utils.refreshAlarm(getActivity(), mClockFrame);
+        if (mClockFrame != null) {
+            Utils.refreshAlarm(getActivity(), mClockFrame);
+        } else {
+            mCityAdapter.refreshAlarm();
+        }
     }
 
     /**
-     * Long pressing over the main clock or any world clock item starts the screen saver.
+     * Long pressing over the main clock starts the screen saver.
      */
-    private final class StartScreenSaverListener implements OnTouchListener, Runnable {
+    private final class StartScreenSaverListener implements View.OnLongClickListener {
 
-        private float mTouchSlop = -1;
-        private int mLongPressTimeout = -1;
-        private float mLastTouchX, mLastTouchY;
+        @Override
+        public boolean onLongClick(View view) {
+            startActivity(new Intent(getActivity(), ScreensaverActivity.class)
+                    .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+                    .putExtra(Events.EXTRA_EVENT_LABEL, R.string.label_deskclock));
+            return true;
+        }
+    }
+
+    /**
+     * Long pressing over the city list starts the screen saver.
+     */
+    private final class CityListOnLongClickListener extends GestureDetector.SimpleOnGestureListener
+            implements View.OnTouchListener {
+
+        private final GestureDetector mGestureDetector;
+
+        public CityListOnLongClickListener(Context context) {
+            mGestureDetector = new GestureDetector(context, this);
+        }
+
+        @Override
+        public void onLongPress(MotionEvent e) {
+            getView().performLongClick();
+        }
+
+        @Override
+        public boolean onDown(MotionEvent e) {
+            return true;
+        }
 
         @Override
         public boolean onTouch(View v, MotionEvent event) {
-            if (mTouchSlop == -1) {
-                mTouchSlop = ViewConfiguration.get(getActivity()).getScaledTouchSlop();
-                mLongPressTimeout = ViewConfiguration.getLongPressTimeout();
-            }
-
-            switch (event.getAction()) {
-                case (MotionEvent.ACTION_DOWN):
-                    // Create and post a runnable to start the screen saver in the future.
-                    mHandler.postDelayed(this, mLongPressTimeout);
-                    mLastTouchX = event.getX();
-                    mLastTouchY = event.getY();
-                    return true;
-
-                case (MotionEvent.ACTION_MOVE):
-                    final float xDiff = Math.abs(event.getX() - mLastTouchX);
-                    final float yDiff = Math.abs(event.getY() - mLastTouchY);
-                    if (xDiff >= mTouchSlop || yDiff >= mTouchSlop) {
-                        mHandler.removeCallbacks(this);
-                    }
-                    break;
-                default:
-                    mHandler.removeCallbacks(this);
-            }
-            return false;
-        }
-
-        @Override
-        public void run() {
-            startActivity(new Intent(getActivity(), ScreensaverActivity.class)
-                    .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
-        }
-    }
-
-    /**
-     * Updates the vertical scroll state of this tab in the {@link UiDataModel} as it changes.
-     */
-    private final class VerticalScrollPositionUpdater implements AbsListView.OnScrollListener {
-        @Override
-        public void onScrollStateChanged(AbsListView view, int scrollState) {}
-
-        @Override
-        public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
-                int totalItemCount) {
-            setTabScrolledToTop(Utils.isScrolledToTop(view));
+            return mGestureDetector.onTouchEvent(event);
         }
     }
 
@@ -298,7 +282,7 @@
     private final class QuarterHourRunnable implements Runnable {
         @Override
         public void run() {
-            refreshDates();
+            mCityAdapter.notifyDataSetChanged();
         }
     }
 
@@ -308,13 +292,13 @@
      * {@link AlarmManager#ACTION_NEXT_ALARM_CLOCK_CHANGED}.
      */
     private final class AlarmObserverPreL extends ContentObserver {
-        public AlarmObserverPreL(Handler handler) {
-            super(handler);
+        public AlarmObserverPreL() {
+            super(new Handler());
         }
 
         @Override
         public void onChange(boolean selfChange) {
-            Utils.refreshAlarm(getActivity(), mClockFrame);
+            refreshAlarm();
         }
     }
 
@@ -329,105 +313,105 @@
     }
 
     /**
+     * Updates the vertical scroll state of this tab in the {@link UiDataModel} as the user scrolls
+     * the recyclerview or when the size/position of elements within the recyclerview changes.
+     */
+    private final class ScrollPositionWatcher extends RecyclerView.OnScrollListener
+            implements View.OnLayoutChangeListener {
+        @Override
+        public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
+            setTabScrolledToTop(Utils.isScrolledToTop(mCityList));
+        }
+
+        @Override
+        public void onLayoutChange(View v, int left, int top, int right, int bottom,
+                int oldLeft, int oldTop, int oldRight, int oldBottom) {
+            setTabScrolledToTop(Utils.isScrolledToTop(mCityList));
+        }
+    }
+
+    /**
      * This adapter lists all of the selected world clocks. Optionally, it also includes a clock at
      * the top for the home timezone if "Automatic home clock" is turned on in settings and the
      * current time at home does not match the current time in the timezone of the current location.
+     * If the phone is in portrait mode it will also include the main clock at the top.
      */
-    private static final class SelectedCitiesAdapter extends BaseAdapter {
+    private static final class SelectedCitiesAdapter extends RecyclerView.Adapter
+            implements CityListener {
+
+        private final static int MAIN_CLOCK = R.layout.main_clock_frame;
+        private final static int WORLD_CLOCK = R.layout.world_clock_item;
 
         private final LayoutInflater mInflater;
         private final Context mContext;
-        private final boolean mIsLandscape;
+        private final boolean mIsPortrait;
+        private final boolean mShowHomeClock;
+        private final String mDateFormat;
+        private final String mDateFormatForAccessibility;
 
-        public SelectedCitiesAdapter(Context context) {
+        public SelectedCitiesAdapter(Context context, String dateFormat,
+                String dateFormatForAccessibility) {
             mContext = context;
+            mDateFormat = dateFormat;
+            mDateFormatForAccessibility = dateFormatForAccessibility;
             mInflater = LayoutInflater.from(context);
-            mIsLandscape = Utils.isLandscape(context);
+            mIsPortrait = Utils.isPortrait(context);
+            mShowHomeClock = DataModel.getDataModel().getShowHomeClock();
         }
 
         @Override
-        public int getCount() {
-            final int homeClockCount = getShowHomeClock() ? 1 : 0;
+        public int getItemViewType(int position) {
+            if (position == 0 && mIsPortrait) {
+                return MAIN_CLOCK;
+            }
+            return WORLD_CLOCK;
+        }
+
+        @Override
+        public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+            final View view = mInflater.inflate(viewType, parent, false);
+            switch (viewType) {
+                case WORLD_CLOCK:
+                    return new CityViewHolder(view);
+                case MAIN_CLOCK:
+                    return new MainClockViewHolder(view);
+                default:
+                    throw new IllegalArgumentException("View type not recognized");
+            }
+        }
+
+        @Override
+        public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
+            final int viewType = getItemViewType(position);
+            switch (viewType) {
+                case WORLD_CLOCK:
+                    // Retrieve the city to bind.
+                    final City city;
+                    // If showing home clock, put it at the top
+                    if (mShowHomeClock && position == (mIsPortrait ? 1 : 0)) {
+                        city = getHomeCity();
+                    } else {
+                        final int positionAdjuster = (mIsPortrait ? 1 : 0)
+                                + (mShowHomeClock ? 1 : 0);
+                        city = getCities().get(position - positionAdjuster);
+                    }
+                    ((CityViewHolder) holder).bind(mContext, city, position, mIsPortrait);
+                    break;
+                case MAIN_CLOCK:
+                    ((MainClockViewHolder) holder).bind(mContext, mDateFormat,
+                            mDateFormatForAccessibility, getItemCount() > 1);
+                    break;
+                default:
+                    throw new IllegalArgumentException("Unexpected view type: " + viewType);
+            }
+        }
+
+        @Override
+        public int getItemCount() {
+            final int mainClockCount = mIsPortrait ? 1 : 0;
+            final int homeClockCount = mShowHomeClock ? 1 : 0;
             final int worldClockCount = getCities().size();
-            return homeClockCount + worldClockCount;
-        }
-
-        @Override
-        public Object getItem(int position) {
-            if (getShowHomeClock()) {
-                return position == 0 ? getHomeCity() : getCities().get(position - 1);
-            }
-
-            return getCities().get(position);
-        }
-
-        @Override
-        public long getItemId(int position) {
-            return position;
-        }
-
-        @Override
-        public View getView(int position, View view, ViewGroup parent) {
-            // Retrieve the city to bind.
-            final City city = (City) getItem(position);
-
-            // Inflate a new view for the city, if necessary.
-            if (view == null) {
-                view = mInflater.inflate(R.layout.world_clock_item, parent, false);
-            }
-
-            // Configure the digital clock or analog clock depending on the user preference.
-            final TextClock digitalClock = (TextClock) view.findViewById(R.id.digital_clock);
-            final AnalogClock analogClock = (AnalogClock) view.findViewById(R.id.analog_clock);
-            if (DataModel.getDataModel().getClockStyle() == DataModel.ClockStyle.ANALOG) {
-                digitalClock.setVisibility(GONE);
-                analogClock.setVisibility(VISIBLE);
-                analogClock.setTimeZone(city.getTimeZone().getID());
-                analogClock.enableSeconds(false);
-            } else {
-                analogClock.setVisibility(GONE);
-                digitalClock.setVisibility(VISIBLE);
-                digitalClock.setTimeZone(city.getTimeZone().getID());
-                digitalClock.setFormat12Hour(Utils.get12ModeFormat(0.22f /* amPmRatio */));
-                digitalClock.setFormat24Hour(Utils.get24ModeFormat());
-            }
-
-            // Supply top and bottom padding dynamically.
-            final Resources res = mContext.getResources();
-            final int padding = res.getDimensionPixelSize(R.dimen.medium_space_top);
-            final int top = position == 0 && mIsLandscape ? 0 : padding;
-            final int left = view.getPaddingLeft();
-            final int right = view.getPaddingRight();
-            final int bottom = view.getPaddingBottom();
-            view.setPadding(left, top, right, bottom);
-
-            // Bind the city name.
-            final TextView name = (TextView) view.findViewById(R.id.city_name);
-            name.setText(city.getName());
-
-            // Compute if the city week day matches the weekday of the current timezone.
-            final Calendar localCal = Calendar.getInstance(TimeZone.getDefault());
-            final Calendar cityCal = Calendar.getInstance(city.getTimeZone());
-            final boolean displayDayOfWeek = localCal.get(DAY_OF_WEEK) != cityCal.get(DAY_OF_WEEK);
-
-            // Bind the week day display.
-            final TextView dayOfWeek = (TextView) view.findViewById(R.id.city_day);
-            dayOfWeek.setVisibility(displayDayOfWeek ? VISIBLE : GONE);
-            if (displayDayOfWeek) {
-                final Locale locale = Locale.getDefault();
-                final String weekday = cityCal.getDisplayName(DAY_OF_WEEK, Calendar.SHORT, locale);
-                dayOfWeek.setText(mContext.getString(R.string.world_day_of_week_label, weekday));
-            }
-
-            return view;
-        }
-
-        /**
-         * @return {@code false} to prevent the cities from responding to touch
-         */
-        @Override
-        public boolean isEnabled(int position) {
-            return false;
+            return mainClockCount + homeClockCount + worldClockCount;
         }
 
         private City getHomeCity() {
@@ -438,8 +422,99 @@
             return DataModel.getDataModel().getSelectedCities();
         }
 
-        private boolean getShowHomeClock() {
-            return DataModel.getDataModel().getShowHomeClock();
+        public void refreshAlarm() {
+            if (mIsPortrait && getItemCount() > 0) {
+                notifyItemChanged(0);
+            }
+        }
+
+        @Override
+        public void citiesChanged(List<City> oldCities, List<City> newCities) {
+            notifyDataSetChanged();
+        }
+
+        private static final class CityViewHolder extends RecyclerView.ViewHolder {
+
+            private final TextView mName;
+            private final TextView mCityDay;
+            private final TextClock mDigitalClock;
+            private final AnalogClock mAnalogClock;
+
+            private CityViewHolder(View itemView) {
+                super(itemView);
+
+                mName = (TextView) itemView.findViewById(R.id.city_name);
+                mCityDay = (TextView) itemView.findViewById(R.id.city_day);
+                mDigitalClock = (TextClock) itemView.findViewById(R.id.digital_clock);
+                mAnalogClock = (AnalogClock) itemView.findViewById(R.id.analog_clock);
+            }
+
+            private void bind(Context context, City city, int position, boolean isPortrait) {
+                // Configure the digital clock or analog clock depending on the user preference.
+                if (DataModel.getDataModel().getClockStyle() == DataModel.ClockStyle.ANALOG) {
+                    mDigitalClock.setVisibility(GONE);
+                    mAnalogClock.setVisibility(VISIBLE);
+                    mAnalogClock.setTimeZone(city.getTimeZone().getID());
+                    mAnalogClock.enableSeconds(false);
+                } else {
+                    mAnalogClock.setVisibility(GONE);
+                    mDigitalClock.setVisibility(VISIBLE);
+                    mDigitalClock.setTimeZone(city.getTimeZone().getID());
+                    mDigitalClock.setFormat12Hour(Utils.get12ModeFormat(0.22f /* amPmRatio */));
+                    mDigitalClock.setFormat24Hour(Utils.get24ModeFormat());
+                }
+
+                // Supply top and bottom padding dynamically.
+                final Resources res = context.getResources();
+                final int padding = res.getDimensionPixelSize(R.dimen.medium_space_top);
+                final int top = position == 0 && !isPortrait ? 0 : padding;
+                final int left = itemView.getPaddingLeft();
+                final int right = itemView.getPaddingRight();
+                final int bottom = itemView.getPaddingBottom();
+                itemView.setPadding(left, top, right, bottom);
+
+                // Bind the city name.
+                mName.setText(city.getName());
+
+                // Compute if the city week day matches the weekday of the current timezone.
+                final Calendar localCal = Calendar.getInstance(TimeZone.getDefault());
+                final Calendar cityCal = Calendar.getInstance(city.getTimeZone());
+                final boolean displayDayOfWeek =
+                        localCal.get(DAY_OF_WEEK) != cityCal.get(DAY_OF_WEEK);
+
+                // Bind the week day display.
+                mCityDay.setVisibility(displayDayOfWeek ? VISIBLE : GONE);
+                if (displayDayOfWeek) {
+                    final Locale locale = Locale.getDefault();
+                    final String weekday = cityCal.getDisplayName(DAY_OF_WEEK, Calendar.SHORT,
+                            locale);
+                    mCityDay.setText(context.getString(R.string.world_day_of_week_label, weekday));
+                }
+            }
+        }
+
+        private static final class MainClockViewHolder extends RecyclerView.ViewHolder {
+
+            private final View mHairline;
+            private final TextClock mDigitalClock;
+            private final AnalogClock mAnalogClock;
+
+            private MainClockViewHolder(View itemView) {
+                super(itemView);
+
+                mHairline = itemView.findViewById(R.id.hairline);
+                mDigitalClock = (TextClock) itemView.findViewById(R.id.digital_clock);
+                mAnalogClock = (AnalogClock) itemView.findViewById(R.id.analog_clock);
+            }
+
+            private void bind(Context context, String dateFormat,
+                    String dateFormatForAccessibility, boolean showHairline) {
+                Utils.refreshAlarm(context, itemView);
+                Utils.setTimeFormat(mDigitalClock);
+                Utils.updateDate(dateFormat, dateFormatForAccessibility, itemView);
+                Utils.setClockStyle(mDigitalClock, mAnalogClock);
+                mHairline.setVisibility(showHairline ? VISIBLE : GONE);
+            }
         }
     }
-}
\ No newline at end of file
+}
diff --git a/src/com/android/deskclock/DeskClock.java b/src/com/android/deskclock/DeskClock.java
index e7414b7..02a65ea 100644
--- a/src/com/android/deskclock/DeskClock.java
+++ b/src/com/android/deskclock/DeskClock.java
@@ -61,7 +61,6 @@
 import com.android.deskclock.actionbarmenu.NightModeMenuItemController;
 import com.android.deskclock.actionbarmenu.OptionsMenuManager;
 import com.android.deskclock.actionbarmenu.SettingsMenuItemController;
-import com.android.deskclock.alarms.AlarmStateManager;
 import com.android.deskclock.data.DataModel;
 import com.android.deskclock.events.Events;
 import com.android.deskclock.provider.Alarm;
@@ -296,9 +295,6 @@
         // Honor changes to the selected tab from outside entities.
         UiDataModel.getUiDataModel().addTabListener(mTabChangeWatcher);
 
-        // Update the next alarm time on app startup because the user might have altered the data.
-        AlarmStateManager.updateNextAlarm(this);
-
         if (savedInstanceState == null) {
             // Set the background color to initially match the theme value so that we can
             // smoothly transition to the dynamic color.
@@ -370,8 +366,10 @@
 
     @Override
     public void onPause() {
-        mDropShadowController.stop();
-        mDropShadowController = null;
+        if (mDropShadowController != null) {
+            mDropShadowController.stop();
+            mDropShadowController = null;
+        }
 
         super.onPause();
     }
@@ -529,7 +527,12 @@
     }
 
     private boolean isSystemAlarmRingtoneSilent() {
-        return RingtoneManager.getActualDefaultRingtoneUri(this, TYPE_ALARM) == null;
+        try {
+            return RingtoneManager.getActualDefaultRingtoneUri(this, TYPE_ALARM) == null;
+        } catch (Exception e) {
+            // Since this is purely informational, avoid crashing the app.
+            return false;
+        }
     }
 
     private void showSilentRingtoneSnackbar() {
@@ -548,7 +551,12 @@
     }
 
     private boolean isAlarmStreamMuted() {
-        return mAudioManager.getStreamVolume(STREAM_ALARM) <= 0;
+        try {
+            return mAudioManager.getStreamVolume(STREAM_ALARM) <= 0;
+        } catch (Exception e) {
+            // Since this is purely informational, avoid crashing the app.
+            return false;
+        }
     }
 
     private void showAlarmVolumeMutedSnackbar() {
@@ -572,7 +580,13 @@
         if (!Utils.isMOrLater()) {
             return false;
         }
-        return mNotificationManager.getCurrentInterruptionFilter() == INTERRUPTION_FILTER_NONE;
+
+        try {
+            return mNotificationManager.getCurrentInterruptionFilter() == INTERRUPTION_FILTER_NONE;
+        } catch (Exception e) {
+            // Since this is purely informational, avoid crashing the app.
+            return false;
+        }
     }
 
     private void showDoNotDisturbIsBlockingAlarmsSnackbar() {
@@ -805,7 +819,7 @@
         private final FragmentManager mFragmentManager;
         private final Context mContext;
 
-        public TabFragmentAdapter(AppCompatActivity activity) {
+        TabFragmentAdapter(AppCompatActivity activity) {
             super(activity.getFragmentManager());
             mContext = activity;
             mFragmentManager = activity.getFragmentManager();
diff --git a/src/com/android/deskclock/DeskClockApplication.java b/src/com/android/deskclock/DeskClockApplication.java
index 208e59d..5929032 100644
--- a/src/com/android/deskclock/DeskClockApplication.java
+++ b/src/com/android/deskclock/DeskClockApplication.java
@@ -19,6 +19,7 @@
 import android.app.Application;
 import android.content.Context;
 
+import com.android.deskclock.controller.Controller;
 import com.android.deskclock.data.DataModel;
 import com.android.deskclock.events.Events;
 import com.android.deskclock.events.LogEventTracker;
@@ -33,6 +34,7 @@
         final Context applicationContext = getApplicationContext();
         DataModel.getDataModel().setContext(applicationContext);
         UiDataModel.getUiDataModel().setContext(applicationContext);
+        Controller.getController().setContext(applicationContext);
         Events.addEventTracker(new LogEventTracker(applicationContext));
     }
 }
\ No newline at end of file
diff --git a/src/com/android/deskclock/HandleApiCalls.java b/src/com/android/deskclock/HandleApiCalls.java
index e6e600e..2256a37 100644
--- a/src/com/android/deskclock/HandleApiCalls.java
+++ b/src/com/android/deskclock/HandleApiCalls.java
@@ -20,11 +20,9 @@
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
-import android.media.RingtoneManager;
 import android.net.Uri;
 import android.os.AsyncTask;
 import android.os.Bundle;
-import android.os.Looper;
 import android.os.Parcelable;
 import android.provider.AlarmClock;
 import android.text.TextUtils;
@@ -38,14 +36,21 @@
 import com.android.deskclock.provider.AlarmInstance;
 import com.android.deskclock.provider.DaysOfWeek;
 import com.android.deskclock.timer.TimerFragment;
+import com.android.deskclock.timer.TimerService;
 import com.android.deskclock.uidata.UiDataModel;
 
 import java.util.ArrayList;
 import java.util.Calendar;
+import java.util.Date;
 import java.util.Iterator;
 import java.util.List;
 
 import static android.text.format.DateUtils.SECOND_IN_MILLIS;
+import static com.android.deskclock.AlarmSelectionActivity.ACTION_DISMISS;
+import static com.android.deskclock.AlarmSelectionActivity.EXTRA_ACTION;
+import static com.android.deskclock.AlarmSelectionActivity.EXTRA_ALARMS;
+import static com.android.deskclock.provider.AlarmInstance.FIRED_STATE;
+import static com.android.deskclock.provider.AlarmInstance.SNOOZE_STATE;
 import static com.android.deskclock.uidata.UiDataModel.Tab.ALARMS;
 import static com.android.deskclock.uidata.UiDataModel.Tab.TIMERS;
 
@@ -56,39 +61,54 @@
  */
 public class HandleApiCalls extends Activity {
 
+    private static final LogUtils.Logger LOGGER = new LogUtils.Logger("HandleApiCalls");
+
+    static final String ACTION_SHOW_TIMERS = "android.intent.action.SHOW_TIMERS";
+
     private Context mAppContext;
 
     @Override
     protected void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+
+        mAppContext = getApplicationContext();
+
         try {
-            super.onCreate(icicle);
-            mAppContext = getApplicationContext();
             final Intent intent = getIntent();
             final String action = intent == null ? null : intent.getAction();
             if (action == null) {
                 return;
             }
+            LOGGER.i("onCreate: " + intent);
+
             switch (action) {
                 case AlarmClock.ACTION_SET_ALARM:
                     handleSetAlarm(intent);
                     break;
                 case AlarmClock.ACTION_SHOW_ALARMS:
-                    handleShowAlarms();
+                    handleShowAlarms(intent);
                     break;
                 case AlarmClock.ACTION_SET_TIMER:
                     handleSetTimer(intent);
                     break;
+                case ACTION_SHOW_TIMERS:
+                    handleShowTimers(intent);
+                    break;
                 case AlarmClock.ACTION_DISMISS_ALARM:
                     handleDismissAlarm(intent);
                     break;
                 case AlarmClock.ACTION_SNOOZE_ALARM:
-                    handleSnoozeAlarm();
+                    handleSnoozeAlarm(intent);
+                    break;
             }
+        } catch (Exception e) {
+            LOGGER.wtf(e);
         } finally {
             finish();
         }
     }
 
+
     private void handleDismissAlarm(Intent intent) {
         // Change to the alarms tab.
         UiDataModel.getUiDataModel().setSelectedTab(ALARMS);
@@ -99,38 +119,49 @@
         new DismissAlarmAsync(mAppContext, intent, this).execute();
     }
 
-    public static void dismissAlarm(Alarm alarm, Context context, Activity activity) {
-        // only allow on background thread
-        if (Looper.myLooper() == Looper.getMainLooper()) {
-            throw new IllegalStateException("dismissAlarm must be called on a " +
-                    "background thread");
-        }
-
-        final AlarmInstance alarmInstance = AlarmInstance.getNextUpcomingInstanceByAlarmId(
+    public static void dismissAlarm(Alarm alarm, Activity activity) {
+        final Context context = activity.getApplicationContext();
+        final AlarmInstance instance = AlarmInstance.getNextUpcomingInstanceByAlarmId(
                 context.getContentResolver(), alarm.id);
-        if (alarmInstance == null) {
+        if (instance == null) {
             final String reason = context.getString(R.string.no_alarm_scheduled_for_this_time);
             Voice.notifyFailure(activity, reason);
-            LogUtils.i(reason);
+            LOGGER.i("No alarm instance to dismiss");
             return;
         }
 
-        final String time = DateFormat.getTimeFormat(context).format(
-                alarmInstance.getAlarmTime().getTime());
-        if (Utils.isAlarmWithin24Hours(alarmInstance)) {
-            AlarmStateManager.setPreDismissState(context, alarmInstance);
-            final String reason = context.getString(R.string.alarm_is_dismissed, time);
-            LogUtils.i(reason);
-            Voice.notifySuccess(activity, reason);
-            Events.sendAlarmEvent(R.string.action_dismiss, R.string.label_intent);
+        dismissAlarmInstance(instance, activity);
+    }
+
+    public static void dismissAlarmInstance(AlarmInstance instance, Activity activity) {
+        Utils.enforceNotMainLooper();
+
+        final Context context = activity.getApplicationContext();
+        final Date alarmTime = instance.getAlarmTime().getTime();
+        final String time = DateFormat.getTimeFormat(context).format(alarmTime);
+
+        if (instance.mAlarmState == FIRED_STATE || instance.mAlarmState == SNOOZE_STATE) {
+            // Always dismiss alarms that are fired or snoozed.
+            AlarmStateManager.deleteInstanceAndUpdateParent(context, instance);
+        } else if (Utils.isAlarmWithin24Hours(instance)) {
+            // Upcoming alarms are always predismissed.
+            AlarmStateManager.setPreDismissState(context, instance);
         } else {
+            // Otherwise the alarm cannot be dismissed at this time.
             final String reason = context.getString(
                     R.string.alarm_cant_be_dismissed_still_more_than_24_hours_away, time);
             Voice.notifyFailure(activity, reason);
-            LogUtils.i(reason);
+            LOGGER.i("Can't dismiss alarm more than 24 hours in advance");
         }
+
+        // Log the successful dismissal.
+        final String reason = context.getString(R.string.alarm_is_dismissed, time);
+        Voice.notifySuccess(activity, reason);
+        LOGGER.i("Alarm dismissed: " + instance);
+        Events.sendAlarmEvent(R.string.action_dismiss, R.string.label_intent);
     }
 
+
     private static class DismissAlarmAsync extends AsyncTask<Void, Void, Void> {
 
         private final Context mContext;
@@ -145,20 +176,20 @@
 
         @Override
         protected Void doInBackground(Void... parameters) {
+            final ContentResolver cr = mContext.getContentResolver();
             final List<Alarm> alarms = getEnabledAlarms(mContext);
             if (alarms.isEmpty()) {
                 final String reason = mContext.getString(R.string.no_scheduled_alarms);
-                LogUtils.i(reason);
                 Voice.notifyFailure(mActivity, reason);
+                LOGGER.i("No scheduled alarms");
                 return null;
             }
 
             // remove Alarms in MISSED, DISMISSED, and PREDISMISSED states
             for (Iterator<Alarm> i = alarms.iterator(); i.hasNext();) {
-                final AlarmInstance alarmInstance = AlarmInstance.getNextUpcomingInstanceByAlarmId(
-                        mContext.getContentResolver(), i.next().id);
-                if (alarmInstance == null ||
-                        alarmInstance.mAlarmState > AlarmInstance.FIRED_STATE) {
+                final AlarmInstance instance = AlarmInstance.getNextUpcomingInstanceByAlarmId(
+                        cr, i.next().id);
+                if (instance == null || instance.mAlarmState > FIRED_STATE) {
                     i.remove();
                 }
             }
@@ -169,8 +200,8 @@
                 final Intent pickSelectionIntent = new Intent(mContext,
                         AlarmSelectionActivity.class)
                         .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
-                        .putExtra(AlarmSelectionActivity.EXTRA_ALARMS,
-                                alarms.toArray(new Parcelable[alarms.size()]));
+                        .putExtra(EXTRA_ACTION, ACTION_DISMISS)
+                        .putExtra(EXTRA_ALARMS, alarms.toArray(new Parcelable[alarms.size()]));
                 mContext.startActivity(pickSelectionIntent);
                 Voice.notifySuccess(mActivity, mContext.getString(R.string.pick_alarm_to_dismiss));
                 return null;
@@ -187,7 +218,8 @@
             if (!AlarmClock.ALARM_SEARCH_MODE_ALL.equals(searchMode) && matchingAlarms.size() > 1) {
               final Intent pickSelectionIntent = new Intent(mContext, AlarmSelectionActivity.class)
                         .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
-                        .putExtra(AlarmSelectionActivity.EXTRA_ALARMS,
+                        .putExtra(EXTRA_ACTION, ACTION_DISMISS)
+                        .putExtra(EXTRA_ALARMS,
                                 matchingAlarms.toArray(new Parcelable[matchingAlarms.size()]));
                 mContext.startActivity(pickSelectionIntent);
                 Voice.notifySuccess(mActivity, mContext.getString(R.string.pick_alarm_to_dismiss));
@@ -196,8 +228,8 @@
 
             // Apply the action to the matching alarms
             for (Alarm alarm : matchingAlarms) {
-                dismissAlarm(alarm, mContext, mActivity);
-                LogUtils.i("Alarm %s is dismissed", alarm);
+                dismissAlarm(alarm, mActivity);
+                LOGGER.i("Alarm dismissed: " + alarm);
             }
             return null;
         }
@@ -209,28 +241,31 @@
         }
     }
 
-    private void handleSnoozeAlarm() {
-        new SnoozeAlarmAsync(mAppContext, this).execute();
+    private void handleSnoozeAlarm(Intent intent) {
+        new SnoozeAlarmAsync(intent, this).execute();
     }
 
     private static class SnoozeAlarmAsync extends AsyncTask<Void, Void, Void> {
 
         private final Context mContext;
+        private final Intent mIntent;
         private final Activity mActivity;
 
-        public SnoozeAlarmAsync(Context context, Activity activity) {
-            mContext = context;
+        public SnoozeAlarmAsync(Intent intent, Activity activity) {
+            mContext = activity.getApplicationContext();
+            mIntent = intent;
             mActivity = activity;
         }
 
         @Override
         protected Void doInBackground(Void... parameters) {
+            final ContentResolver cr = mContext.getContentResolver();
             final List<AlarmInstance> alarmInstances = AlarmInstance.getInstancesByState(
-                    mContext.getContentResolver(), AlarmInstance.FIRED_STATE);
+                    cr, FIRED_STATE);
             if (alarmInstances.isEmpty()) {
                 final String reason = mContext.getString(R.string.no_firing_alarms);
-                LogUtils.i(reason);
                 Voice.notifyFailure(mActivity, reason);
+                LOGGER.i("No firing alarms");
                 return null;
             }
 
@@ -242,18 +277,15 @@
     }
 
     static void snoozeAlarm(AlarmInstance alarmInstance, Context context, Activity activity) {
-        // only allow on background thread
-        if (Looper.myLooper() == Looper.getMainLooper()) {
-            throw new IllegalStateException("snoozeAlarm must be called on a " +
-                    "background thread");
-        }
+        Utils.enforceNotMainLooper();
+
         final String time = DateFormat.getTimeFormat(context).format(
                 alarmInstance.getAlarmTime().getTime());
         final String reason = context.getString(R.string.alarm_is_snoozed, time);
-        LogUtils.i(reason);
-        Voice.notifySuccess(activity, reason);
         AlarmStateManager.setSnoozeState(context, alarmInstance, true);
-        LogUtils.i("Snooze %d:%d", alarmInstance.mHour, alarmInstance.mMinute);
+
+        Voice.notifySuccess(activity, reason);
+        LOGGER.i("Alarm snoozed: " + alarmInstance);
         Events.sendAlarmEvent(R.string.action_snooze, R.string.label_intent);
     }
 
@@ -262,17 +294,32 @@
      * @param intent Intent passed to the app
      */
     private void handleSetAlarm(Intent intent) {
-        // If not provided or invalid, show UI
-        final int hour = intent.getIntExtra(AlarmClock.EXTRA_HOUR, -1);
-
-        // If not provided, use zero. If it is provided, make sure it's valid, otherwise, show UI
-        final int minutes;
-        if (intent.hasExtra(AlarmClock.EXTRA_MINUTES)) {
-            minutes = intent.getIntExtra(AlarmClock.EXTRA_MINUTES, -1);
-        } else {
-            minutes = 0;
+        // Validate the hour, if one was given.
+        int hour = -1;
+        if (intent.hasExtra(AlarmClock.EXTRA_HOUR)) {
+            hour = intent.getIntExtra(AlarmClock.EXTRA_HOUR, hour);
+            if (hour < 0 || hour > 23) {
+                final int mins = intent.getIntExtra(AlarmClock.EXTRA_MINUTES, 0);
+                Voice.notifyFailure(this, getString(R.string.invalid_time, hour, mins, " "));
+                LOGGER.i("Illegal hour: " + hour);
+                return;
+            }
         }
-        if (hour < 0 || hour > 23 || minutes < 0 || minutes > 59) {
+
+        // Validate the minute, if one was given.
+        final int minutes = intent.getIntExtra(AlarmClock.EXTRA_MINUTES, 0);
+        if (minutes < 0 || minutes > 59) {
+            Voice.notifyFailure(this, getString(R.string.invalid_time, hour, minutes, " "));
+            LOGGER.i("Illegal minute: " + minutes);
+            return;
+        }
+
+        final boolean skipUi = intent.getBooleanExtra(AlarmClock.EXTRA_SKIP_UI, false);
+        final ContentResolver cr = getContentResolver();
+
+        // If time information was not provided an existing alarm cannot be located and a new one
+        // cannot be created so show the UI for creating the alarm from scratch per spec.
+        if (hour == -1) {
             // Change to the alarms tab.
             UiDataModel.getUiDataModel().setSelectedTab(ALARMS);
 
@@ -284,73 +331,74 @@
             // Open DeskClock which is now positioned on the alarms tab.
             startActivity(createAlarm);
             Voice.notifyFailure(this, getString(R.string.invalid_time, hour, minutes, " "));
-            LogUtils.i("HandleApiCalls no/invalid time; opening UI");
+            LOGGER.i("Missing alarm time; opening UI");
             return;
         }
 
-        Events.sendAlarmEvent(R.string.action_create, R.string.label_intent);
-        final boolean skipUi = intent.getBooleanExtra(AlarmClock.EXTRA_SKIP_UI, false);
-
         final StringBuilder selection = new StringBuilder();
-        final List<String> args = new ArrayList<>();
-        setSelectionFromIntent(intent, hour, minutes, selection, args);
+        final List<String> argsList = new ArrayList<>();
+        setSelectionFromIntent(intent, hour, minutes, selection, argsList);
 
-        // Update existing alarm matching the selection criteria; see setSelectionFromIntent.
-        final ContentResolver cr = getContentResolver();
-        final List<Alarm> alarms = Alarm.getAlarms(cr,
-                selection.toString(),
-                args.toArray(new String[args.size()]));
+        // Try to locate an existing alarm using the intent data.
+        final String[] args = argsList.toArray(new String[argsList.size()]);
+        final List<Alarm> alarms = Alarm.getAlarms(cr, selection.toString(), args);
+
+        final Alarm alarm;
         if (!alarms.isEmpty()) {
-            final Alarm alarm = alarms.get(0);
+            // Enable the first matching alarm.
+            alarm = alarms.get(0);
             alarm.enabled = true;
             Alarm.updateAlarm(cr, alarm);
 
-            // Delete all old instances and create a new one with updated values
+            // Delete all old instances.
             AlarmStateManager.deleteAllInstances(this, alarm.id);
-            setupInstance(alarm.createInstanceAfter(Calendar.getInstance()), skipUi);
-            LogUtils.i("HandleApiCalls deleted old, created new alarm: %s", alarm);
-            return;
+
+            Events.sendAlarmEvent(R.string.action_update, R.string.label_intent);
+            LOGGER.i("Updated alarm: " + alarm);
+        } else {
+            // No existing alarm could be located; create one using the intent data.
+            alarm = new Alarm();
+            updateAlarmFromIntent(alarm, intent);
+            alarm.deleteAfterUse = !alarm.daysOfWeek.isRepeating() && skipUi;
+
+            // Save the new alarm.
+            Alarm.addAlarm(cr, alarm);
+
+            Events.sendAlarmEvent(R.string.action_create, R.string.label_intent);
+            LOGGER.i("Created new alarm: " + alarm);
         }
 
-        // Otherwise insert a new alarm.
-        final String message = getMessageFromIntent(intent);
-        final DaysOfWeek daysOfWeek = getDaysFromIntent(intent);
-        final boolean vibrate = intent.getBooleanExtra(AlarmClock.EXTRA_VIBRATE, true);
-        final String alert = intent.getStringExtra(AlarmClock.EXTRA_RINGTONE);
-
-        Alarm alarm = new Alarm(hour, minutes);
-        alarm.enabled = true;
-        alarm.label = message;
-        alarm.daysOfWeek = daysOfWeek;
-        alarm.vibrate = vibrate;
-
-        if (alert != null) {
-            if (AlarmClock.VALUE_RINGTONE_SILENT.equals(alert) || alert.isEmpty()) {
-                alarm.alert = Alarm.NO_RINGTONE_URI;
-            } else {
-                alarm.alert = Uri.parse(alert);
-            }
-        }
-        alarm.deleteAfterUse = !daysOfWeek.isRepeating() && skipUi;
-
-        alarm = Alarm.addAlarm(cr, alarm);
+        // Schedule the next instance.
         final AlarmInstance alarmInstance = alarm.createInstanceAfter(Calendar.getInstance());
         setupInstance(alarmInstance, skipUi);
-        final String time = DateFormat.getTimeFormat(mAppContext).format(
-                alarmInstance.getAlarmTime().getTime());
+
+        final String time = DateFormat.getTimeFormat(this)
+                .format(alarmInstance.getAlarmTime().getTime());
         Voice.notifySuccess(this, getString(R.string.alarm_is_set, time));
-        LogUtils.i("HandleApiCalls set up alarm: %s", alarm);
     }
 
-    private void handleShowAlarms() {
-        // Change to the alarms tab.
-        UiDataModel.getUiDataModel().setSelectedTab(ALARMS);
-
-        // Open DeskClock which is now positioned on the alarms tab.
-        startActivity(new Intent(this, DeskClock.class));
-
+    private void handleShowAlarms(Intent intent) {
         Events.sendAlarmEvent(R.string.action_show, R.string.label_intent);
-        LogUtils.i("HandleApiCalls show alarms");
+
+        // Open DeskClock positioned on the alarms tab.
+        UiDataModel.getUiDataModel().setSelectedTab(ALARMS);
+        startActivity(new Intent(this, DeskClock.class));
+    }
+
+    private void handleShowTimers(Intent intent) {
+        Events.sendTimerEvent(R.string.action_show, R.string.label_intent);
+
+        final Intent showTimersIntent = new Intent(this, DeskClock.class);
+
+        final List<Timer> timers = DataModel.getDataModel().getTimers();
+        if (!timers.isEmpty()) {
+            final Timer newestTimer = timers.get(timers.size() - 1);
+            showTimersIntent.putExtra(TimerService.EXTRA_TIMER_ID, newestTimer.getId());
+        }
+
+        // Open DeskClock positioned on the timers tab.
+        UiDataModel.getUiDataModel().setSelectedTab(TIMERS);
+        startActivity(showTimersIntent);
     }
 
     private void handleSetTimer(Intent intent) {
@@ -361,7 +409,7 @@
 
             // Open DeskClock which is now positioned on the timers tab and show the timer setup.
             startActivity(TimerFragment.createTimerSetupIntent(this));
-            LogUtils.i("HandleApiCalls showing timer setup");
+            LOGGER.i("Showing timer setup");
             return;
         }
 
@@ -369,11 +417,11 @@
         final long lengthMillis = SECOND_IN_MILLIS * intent.getIntExtra(AlarmClock.EXTRA_LENGTH, 0);
         if (lengthMillis < Timer.MIN_LENGTH || lengthMillis > Timer.MAX_LENGTH) {
             Voice.notifyFailure(this, getString(R.string.invalid_timer_length));
-            LogUtils.i("Invalid timer length requested: " + lengthMillis);
+            LOGGER.i("Invalid timer length requested: " + lengthMillis);
             return;
         }
 
-        final String label = getMessageFromIntent(intent);
+        final String label = getLabelFromIntent(intent, "");
         final boolean skipUi = intent.getBooleanExtra(AlarmClock.EXTRA_SKIP_UI, false);
 
         // Attempt to reuse an existing timer that is Reset with the same length and label.
@@ -405,7 +453,7 @@
 
             // Open DeskClock which is now positioned on the timers tab.
             startActivity(new Intent(this, DeskClock.class)
-                    .putExtra(HandleDeskClockApiCalls.EXTRA_TIMER_ID, timer.getId()));
+                    .putExtra(TimerService.EXTRA_TIMER_ID, timer.getId()));
         }
     }
 
@@ -425,12 +473,30 @@
         }
     }
 
-    private static String getMessageFromIntent(Intent intent) {
-        final String message = intent.getStringExtra(AlarmClock.EXTRA_MESSAGE);
+    /**
+     * @param alarm the alarm to be updated
+     * @param intent the intent containing new alarm field values to merge into the {@code alarm}
+     */
+    private static void updateAlarmFromIntent(Alarm alarm, Intent intent) {
+        alarm.enabled = true;
+        alarm.hour = intent.getIntExtra(AlarmClock.EXTRA_HOUR, alarm.hour);
+        alarm.minutes = intent.getIntExtra(AlarmClock.EXTRA_MINUTES, alarm.minutes);
+        alarm.vibrate = intent.getBooleanExtra(AlarmClock.EXTRA_VIBRATE, alarm.vibrate);
+        alarm.alert = getAlertFromIntent(intent, alarm.alert);
+        alarm.label = getLabelFromIntent(intent, alarm.label);
+        alarm.daysOfWeek = getDaysFromIntent(intent, alarm.daysOfWeek.getBitSet());
+    }
+
+    private static String getLabelFromIntent(Intent intent, String defaultLabel) {
+        final String message = intent.getExtras().getString(AlarmClock.EXTRA_MESSAGE, defaultLabel);
         return message == null ? "" : message;
     }
 
-    private static DaysOfWeek getDaysFromIntent(Intent intent) {
+    private static DaysOfWeek getDaysFromIntent(Intent intent, int defaultDaysBitset) {
+        if (!intent.hasExtra(AlarmClock.EXTRA_DAYS)) {
+            return new DaysOfWeek(defaultDaysBitset);
+        }
+
         final DaysOfWeek daysOfWeek = new DaysOfWeek(0);
         final ArrayList<Integer> days = intent.getIntegerArrayListExtra(AlarmClock.EXTRA_DAYS);
         if (days != null) {
@@ -449,6 +515,17 @@
         return daysOfWeek;
     }
 
+    private static Uri getAlertFromIntent(Intent intent, Uri defaultUri) {
+        final String alert = intent.getStringExtra(AlarmClock.EXTRA_RINGTONE);
+        if (alert == null) {
+            return defaultUri;
+        } else if (AlarmClock.VALUE_RINGTONE_SILENT.equals(alert) || alert.isEmpty()) {
+            return Alarm.NO_RINGTONE_URI;
+        }
+
+        return Uri.parse(alert);
+    }
+
     /**
      * Assemble a database where clause to search for an alarm matching the given {@code hour} and
      * {@code minutes} as well as all of the optional information within the {@code intent}
@@ -480,14 +557,13 @@
 
         if (intent.hasExtra(AlarmClock.EXTRA_MESSAGE)) {
             selection.append(" AND ").append(Alarm.LABEL).append("=?");
-            args.add(getMessageFromIntent(intent));
+            args.add(getLabelFromIntent(intent, ""));
         }
 
-        // Days is treated differently that other fields because if days is not specified, it
+        // Days is treated differently than other fields because if days is not specified, it
         // explicitly means "not recurring".
         selection.append(" AND ").append(Alarm.DAYS_OF_WEEK).append("=?");
-        args.add(String.valueOf(intent.hasExtra(AlarmClock.EXTRA_DAYS)
-                ? getDaysFromIntent(intent).getBitSet() : DaysOfWeek.NO_DAYS_SET));
+        args.add(String.valueOf(getDaysFromIntent(intent, DaysOfWeek.NO_DAYS_SET).getBitSet()));
 
         if (intent.hasExtra(AlarmClock.EXTRA_VIBRATE)) {
             selection.append(" AND ").append(Alarm.VIBRATE).append("=?");
@@ -497,15 +573,10 @@
         if (intent.hasExtra(AlarmClock.EXTRA_RINGTONE)) {
             selection.append(" AND ").append(Alarm.RINGTONE).append("=?");
 
-            String ringTone = intent.getStringExtra(AlarmClock.EXTRA_RINGTONE);
-            if (ringTone == null) {
-                // If the intent explicitly specified a NULL ringtone, treat it as the default
-                // ringtone.
-                ringTone = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_ALARM).toString();
-            } else if (AlarmClock.VALUE_RINGTONE_SILENT.equals(ringTone) || ringTone.isEmpty()) {
-                    ringTone = Alarm.NO_RINGTONE;
-            }
-            args.add(ringTone);
+            // If the intent explicitly specified a NULL ringtone, treat it as the default ringtone.
+            final Uri defaultRingtone = DataModel.getDataModel().getDefaultAlarmRingtoneUri();
+            final Uri ringtone = getAlertFromIntent(intent, defaultRingtone);
+            args.add(ringtone.toString());
         }
     }
 }
diff --git a/src/com/android/deskclock/HandleDeskClockApiCalls.java b/src/com/android/deskclock/HandleDeskClockApiCalls.java
deleted file mode 100644
index 23be9b8..0000000
--- a/src/com/android/deskclock/HandleDeskClockApiCalls.java
+++ /dev/null
@@ -1,353 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.deskclock;
-
-import android.app.Activity;
-import android.content.Context;
-import android.content.Intent;
-import android.os.Bundle;
-
-import com.android.deskclock.data.City;
-import com.android.deskclock.data.DataModel;
-import com.android.deskclock.data.Timer;
-import com.android.deskclock.events.Events;
-import com.android.deskclock.uidata.UiDataModel;
-import com.android.deskclock.worldclock.CitySelectionActivity;
-
-import java.util.List;
-import java.util.Set;
-
-import static com.android.deskclock.uidata.UiDataModel.Tab.CLOCKS;
-import static com.android.deskclock.uidata.UiDataModel.Tab.STOPWATCH;
-import static com.android.deskclock.uidata.UiDataModel.Tab.TIMERS;
-
-public class HandleDeskClockApiCalls extends Activity {
-
-    private static final String ACTION_PREFIX = "com.android.deskclock.action.";
-
-    // shows the tab with world clocks
-    public static final String ACTION_SHOW_CLOCK = ACTION_PREFIX + "SHOW_CLOCK";
-    // add a clock of a selected city, if no city is specified opens the city selection screen
-    public static final String ACTION_ADD_CLOCK = ACTION_PREFIX + "ADD_CLOCK";
-    // delete a clock of a selected city, if no city is specified shows CitiesActivity for the user
-    // to choose a city
-    public static final String ACTION_DELETE_CLOCK = ACTION_PREFIX + "DELETE_CLOCK";
-    // extra for ACTION_ADD_CLOCK and ACTION_DELETE_CLOCK
-    public static final String EXTRA_CITY = "com.android.deskclock.extra.clock.CITY";
-
-    // shows the tab with the stopwatch
-    public static final String ACTION_SHOW_STOPWATCH = ACTION_PREFIX + "SHOW_STOPWATCH";
-    // starts the current stopwatch
-    public static final String ACTION_START_STOPWATCH = ACTION_PREFIX + "START_STOPWATCH";
-    // pauses the current stopwatch that's currently running
-    public static final String ACTION_PAUSE_STOPWATCH = ACTION_PREFIX + "PAUSE_STOPWATCH";
-    // laps the stopwatch that's currently running
-    public static final String ACTION_LAP_STOPWATCH = ACTION_PREFIX + "LAP_STOPWATCH";
-    // resets the stopwatch if it's stopped
-    public static final String ACTION_RESET_STOPWATCH = ACTION_PREFIX + "RESET_STOPWATCH";
-
-    // shows the tab with timers; optionally scrolls to a specific timer
-    public static final String ACTION_SHOW_TIMERS = ACTION_PREFIX + "SHOW_TIMERS";
-    // pauses running timers; resets expired timers
-    public static final String ACTION_PAUSE_TIMER = ACTION_PREFIX + "PAUSE_TIMER";
-    // starts the sole timer
-    public static final String ACTION_START_TIMER = ACTION_PREFIX + "START_TIMER";
-    // resets the timer
-    public static final String ACTION_RESET_TIMER = ACTION_PREFIX + "RESET_TIMER";
-    // adds an extra minute to the timer
-    public static final String ACTION_ADD_MINUTE_TIMER = ACTION_PREFIX + "ADD_MINUTE_TIMER";
-
-    // extra for many actions specific to a given timer
-    public static final String EXTRA_TIMER_ID = "com.android.deskclock.extra.TIMER_ID";
-
-    // Describes the entity responsible for the action being performed.
-    public static final String EXTRA_EVENT_LABEL = "com.android.deskclock.extra.EVENT_LABEL";
-
-    private Context mAppContext;
-
-    @Override
-    protected void onCreate(Bundle icicle) {
-        try {
-            super.onCreate(icicle);
-            mAppContext = getApplicationContext();
-
-            final Intent intent = getIntent();
-            if (intent == null) {
-                return;
-            }
-
-            final String action = intent.getAction();
-            LogUtils.i("HandleDeskClockApiCalls " + action);
-
-            switch (action) {
-                case ACTION_START_STOPWATCH:
-                case ACTION_PAUSE_STOPWATCH:
-                case ACTION_LAP_STOPWATCH:
-                case ACTION_SHOW_STOPWATCH:
-                case ACTION_RESET_STOPWATCH:
-                    handleStopwatchIntent(intent);
-                    break;
-                case ACTION_SHOW_TIMERS:
-                case ACTION_RESET_TIMER:
-                case ACTION_PAUSE_TIMER:
-                case ACTION_START_TIMER:
-                    handleTimerIntent(intent);
-                    break;
-                case ACTION_SHOW_CLOCK:
-                case ACTION_ADD_CLOCK:
-                case ACTION_DELETE_CLOCK:
-                    handleClockIntent(intent);
-                    break;
-            }
-        } finally {
-            finish();
-        }
-    }
-
-    private void handleStopwatchIntent(Intent intent) {
-        final String action = intent.getAction();
-
-        // Determine where this intent originated.
-        final int eventLabel = intent.getIntExtra(EXTRA_EVENT_LABEL, R.string.label_intent);
-
-        if (ACTION_SHOW_STOPWATCH.equals(action)) {
-            Events.sendStopwatchEvent(R.string.action_show, eventLabel);
-        } else {
-            final String reason;
-            boolean fail = false;
-            switch (action) {
-                case ACTION_START_STOPWATCH: {
-                    DataModel.getDataModel().startStopwatch();
-                    Events.sendStopwatchEvent(R.string.action_start, eventLabel);
-                    reason = getString(R.string.stopwatch_started);
-                    break;
-                }
-                case ACTION_PAUSE_STOPWATCH: {
-                    DataModel.getDataModel().pauseStopwatch();
-                    Events.sendStopwatchEvent(R.string.action_pause, eventLabel);
-                    reason = getString(R.string.stopwatch_paused);
-                    break;
-                }
-                case ACTION_RESET_STOPWATCH: {
-                    DataModel.getDataModel().resetStopwatch();
-                    Events.sendStopwatchEvent(R.string.action_reset, eventLabel);
-                    reason = getString(R.string.stopwatch_reset);
-                    break;
-                }
-                case ACTION_LAP_STOPWATCH: {
-                    if (!DataModel.getDataModel().getStopwatch().isRunning()) {
-                        fail = true;
-                        reason = getString(R.string.stopwatch_isnt_running);
-                    } else {
-                        DataModel.getDataModel().addLap();
-                        Events.sendStopwatchEvent(R.string.action_lap, eventLabel);
-                        reason = getString(R.string.stopwatch_lapped);
-                    }
-                    break;
-                }
-                default:
-                    throw new IllegalArgumentException("unknown stopwatch action: " + action);
-            }
-
-            if (fail) {
-                Voice.notifyFailure(this, reason);
-            } else {
-                Voice.notifySuccess(this, reason);
-            }
-            LogUtils.i(reason);
-        }
-
-        // Change to the stopwatch tab.
-        UiDataModel.getUiDataModel().setSelectedTab(STOPWATCH);
-
-        // Open DeskClock which is now positioned on the stopwatch tab.
-        startActivity(new Intent(mAppContext, DeskClock.class));
-    }
-
-    private void handleTimerIntent(Intent intent) {
-        final String action = intent.getAction();
-
-        // Determine where this intent originated.
-        final int eventLabel = intent.getIntExtra(EXTRA_EVENT_LABEL, R.string.label_intent);
-        int timerId = intent.getIntExtra(EXTRA_TIMER_ID, -1);
-        Timer timer = null;
-
-        if (ACTION_SHOW_TIMERS.equals(action)) {
-            Events.sendTimerEvent(R.string.action_show, eventLabel);
-        } else {
-            String reason = null;
-            if (timerId == -1) {
-                // No timer id was given explicitly, so check if only one timer exists.
-                final List<Timer> timers =  DataModel.getDataModel().getTimers();
-                if (timers.isEmpty()) {
-                    // No timers exist to control.
-                    reason = getString(R.string.no_timers_exist);
-                } else if (timers.size() > 1) {
-                    // Many timers exist so the control command is ambiguous.
-                    reason = getString(R.string.too_many_timers_exist);
-                } else {
-                    timer = timers.get(0);
-                }
-            } else {
-                // Verify that the given timer does exist.
-                timer = DataModel.getDataModel().getTimer(timerId);
-                if (timer == null) {
-                    reason = getString(R.string.timer_does_not_exist);
-                }
-            }
-
-            if (timer == null) {
-                Voice.notifyFailure(this, reason);
-            } else {
-                timerId = timer.getId();
-
-                // Otherwise the control command can be honored.
-                switch (action) {
-                    case ACTION_RESET_TIMER: {
-                        timer = DataModel.getDataModel().resetOrDeleteTimer(timer, eventLabel);
-                        if (timer == null) {
-                            timerId = -1;
-                            reason = getString(R.string.timer_deleted);
-                        } else {
-                            reason = getString(R.string.timer_was_reset);
-                        }
-                        break;
-                    }
-                    case ACTION_START_TIMER: {
-                        DataModel.getDataModel().startTimer(timer);
-                        Events.sendTimerEvent(R.string.action_start, eventLabel);
-                        reason = getString(R.string.timer_started);
-                        break;
-                    }
-                    case ACTION_PAUSE_TIMER: {
-                        DataModel.getDataModel().pauseTimer(timer);
-                        Events.sendTimerEvent(R.string.action_pause, eventLabel);
-                        reason = getString(R.string.timer_paused);
-                        break;
-                    }
-                    default:
-                        throw new IllegalArgumentException("unknown timer action: " + action);
-                }
-
-                Voice.notifySuccess(this, reason);
-            }
-
-            LogUtils.i(reason);
-        }
-
-        // Change to the timers tab.
-        UiDataModel.getUiDataModel().setSelectedTab(TIMERS);
-
-        // Open DeskClock which is now positioned on the timers tab and show the timer in question.
-        final Intent showTimers = new Intent(mAppContext, DeskClock.class);
-        if (timerId != -1) {
-            showTimers.putExtra(EXTRA_TIMER_ID, timerId);
-        }
-        startActivity(showTimers);
-    }
-
-    private void handleClockIntent(Intent intent) {
-        final String action = intent.getAction();
-
-        final int eventLabel = intent.getIntExtra(EXTRA_EVENT_LABEL, R.string.label_intent);
-        if (ACTION_SHOW_CLOCK.equals(action)) {
-            Events.sendClockEvent(R.string.action_show, eventLabel);
-        } else {
-            switch (action) {
-                case ACTION_ADD_CLOCK:
-                    Events.sendClockEvent(R.string.action_add, eventLabel);
-                    break;
-                case ACTION_DELETE_CLOCK:
-                    Events.sendClockEvent(R.string.action_delete, eventLabel);
-                    break;
-            }
-
-            final String cityName = intent.getStringExtra(EXTRA_CITY);
-
-            final String reason;
-            boolean fail = false;
-
-            // If no city was given, start the city chooser.
-            if (cityName == null) {
-                reason = getString(R.string.no_city_selected);
-                LogUtils.i(reason);
-                Voice.notifySuccess(this, reason);
-                startActivity(new Intent(this, CitySelectionActivity.class)
-                        .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
-                return;
-            }
-
-            // If a city was given, ensure it can be located.
-            final City city = DataModel.getDataModel().getCity(cityName);
-            if (city == null) {
-                reason = getString(R.string.the_city_you_specified_is_not_available);
-                LogUtils.i(reason);
-                Voice.notifyFailure(this, reason);
-                startActivity(new Intent(this, CitySelectionActivity.class)
-                        .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
-                return;
-            }
-
-            final Set<City> selectedCities =
-                    Utils.newArraySet(DataModel.getDataModel().getSelectedCities());
-
-            switch (action) {
-                case ACTION_ADD_CLOCK: {
-                    // Fail if the city is already present.
-                    if (!selectedCities.add(city)) {
-                        fail = true;
-                        reason = getString(R.string.the_city_already_added);
-                        break;
-                    }
-
-                    // Otherwise report the success.
-                    DataModel.getDataModel().setSelectedCities(selectedCities);
-                    reason = getString(R.string.city_added, city.getName());
-                    break;
-                }
-                case ACTION_DELETE_CLOCK: {
-                    // Fail if the city is not present.
-                    if (!selectedCities.remove(city)) {
-                        fail = true;
-                        reason = getString(R.string.the_city_you_specified_is_not_available);
-                        break;
-                    }
-
-                    // Otherwise report the success.
-                    DataModel.getDataModel().setSelectedCities(selectedCities);
-                    reason = getString(R.string.city_deleted, city.getName());
-                    break;
-                }
-                default:
-                    throw new IllegalArgumentException("unknown clock action: " + action);
-            }
-
-            if (fail) {
-                Voice.notifyFailure(this, reason);
-            } else {
-                Voice.notifySuccess(this, reason);
-            }
-            LogUtils.i(reason);
-        }
-
-        // Change to the clocks tab.
-        UiDataModel.getUiDataModel().setSelectedTab(CLOCKS);
-
-        // Open DeskClock which is now positioned on the clocks tab.
-        startActivity(new Intent(mAppContext, DeskClock.class));
-    }
-}
\ No newline at end of file
diff --git a/src/com/android/deskclock/HandleShortcuts.java b/src/com/android/deskclock/HandleShortcuts.java
new file mode 100644
index 0000000..00094d8
--- /dev/null
+++ b/src/com/android/deskclock/HandleShortcuts.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.deskclock;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+
+import com.android.deskclock.data.DataModel;
+import com.android.deskclock.events.Events;
+import com.android.deskclock.stopwatch.StopwatchService;
+import com.android.deskclock.uidata.UiDataModel;
+
+import static com.android.deskclock.uidata.UiDataModel.Tab.STOPWATCH;
+
+public class HandleShortcuts extends Activity {
+
+    private static final LogUtils.Logger LOGGER = new LogUtils.Logger("HandleShortcuts");
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        final Intent intent = getIntent();
+
+        try {
+            final String action = intent.getAction();
+            switch (action) {
+                case StopwatchService.ACTION_PAUSE_STOPWATCH:
+                    Events.sendStopwatchEvent(R.string.action_pause, R.string.label_shortcut);
+                    DataModel.getDataModel().pauseStopwatch();
+
+                    // Open DeskClock positioned on the stopwatch tab.
+                    UiDataModel.getUiDataModel().setSelectedTab(STOPWATCH);
+                    startActivity(new Intent(this, DeskClock.class));
+                    setResult(RESULT_OK);
+                    break;
+                case StopwatchService.ACTION_START_STOPWATCH:
+                    Events.sendStopwatchEvent(R.string.action_start, R.string.label_shortcut);
+                    DataModel.getDataModel().startStopwatch();
+
+                    // Open DeskClock positioned on the stopwatch tab.
+                    UiDataModel.getUiDataModel().setSelectedTab(STOPWATCH);
+                    startActivity(new Intent(this, DeskClock.class));
+                    setResult(RESULT_OK);
+                    break;
+                default:
+                    throw new IllegalArgumentException("Unsupported action: " + action);
+            }
+        } catch (Exception e) {
+            LOGGER.e("Error handling intent: " + intent, e);
+            setResult(RESULT_CANCELED);
+        } finally {
+            finish();
+        }
+    }
+}
diff --git a/src/com/android/deskclock/LabelDialogFragment.java b/src/com/android/deskclock/LabelDialogFragment.java
index 36c08d5..d301b10 100644
--- a/src/com/android/deskclock/LabelDialogFragment.java
+++ b/src/com/android/deskclock/LabelDialogFragment.java
@@ -80,7 +80,10 @@
     @Override
     public void onSaveInstanceState(@NonNull Bundle outState) {
         super.onSaveInstanceState(outState);
-        outState.putString(KEY_LABEL, mLabelBox.getText().toString());
+        // As long as the label box exists, save its state.
+        if (mLabelBox != null) {
+            outState.putString(KEY_LABEL, mLabelBox.getText().toString());
+        }
     }
 
     @Override
diff --git a/src/com/android/deskclock/LogUtils.java b/src/com/android/deskclock/LogUtils.java
index cce4ff4..c679ac2 100644
--- a/src/com/android/deskclock/LogUtils.java
+++ b/src/com/android/deskclock/LogUtils.java
@@ -54,6 +54,10 @@
         DEFAULT_LOGGER.wtf(message, args);
     }
 
+    public static void wtf(Throwable e) {
+        DEFAULT_LOGGER.wtf(e);
+    }
+
     public final static class Logger {
 
         /**
@@ -123,5 +127,11 @@
                         : String.format(message, args));
             }
         }
+
+        public void wtf(Throwable e) {
+            if (isWtfLoggable()) {
+                Log.wtf(logTag, e);
+            }
+        }
     }
 }
\ No newline at end of file
diff --git a/src/com/android/deskclock/RingtonePickerDialogFragment.java b/src/com/android/deskclock/RingtonePickerDialogFragment.java
index e74a684..826566e 100644
--- a/src/com/android/deskclock/RingtonePickerDialogFragment.java
+++ b/src/com/android/deskclock/RingtonePickerDialogFragment.java
@@ -336,7 +336,11 @@
 
             // Force the ringtone manager to load its ringtones. The cursor will be cached
             // internally by the ringtone manager.
-            ringtoneManager.getCursor();
+            try {
+                ringtoneManager.getCursor();
+            } catch (Exception e) {
+                LogUtils.e("Error getting Ringtone Manager cursor", e);
+            }
 
             return ringtoneManager;
         }
@@ -348,7 +352,11 @@
                     mRingtoneCursor.close();
                 }
                 mRingtoneManager = ringtoneManager;
-                mRingtoneCursor = mRingtoneManager.getCursor();
+                try {
+                    mRingtoneCursor = mRingtoneManager.getCursor();
+                } catch (Exception e) {
+                    LogUtils.e("Error getting Ringtone Manager cursor", e);
+                }
             }
             super.deliverResult(ringtoneManager);
         }
@@ -413,7 +421,11 @@
          */
         public RingtoneAdapter setRingtoneManager(RingtoneManager ringtoneManager) {
             mRingtoneManager = ringtoneManager;
-            mRingtoneCursor = ringtoneManager == null ? null : ringtoneManager.getCursor();
+            try {
+                mRingtoneCursor = ringtoneManager == null ? null : ringtoneManager.getCursor();
+            } catch (Exception e) {
+                LogUtils.e("Error getting Ringtone Manager cursor", e);
+            }
             notifyDataSetChanged();
 
             return this;
diff --git a/src/com/android/deskclock/ScreensaverActivity.java b/src/com/android/deskclock/ScreensaverActivity.java
index 7013e8d..8cdf3b8 100644
--- a/src/com/android/deskclock/ScreensaverActivity.java
+++ b/src/com/android/deskclock/ScreensaverActivity.java
@@ -33,6 +33,7 @@
 import android.view.WindowManager;
 import android.widget.TextClock;
 
+import com.android.deskclock.events.Events;
 import com.android.deskclock.uidata.UiDataModel;
 
 import static android.content.Intent.ACTION_BATTERY_CHANGED;
@@ -115,6 +116,12 @@
         Utils.dimClockView(true, mSaverView);
 
         mPositionUpdater = new MoveScreensaverRunnable(mContentView, mSaverView);
+
+        final Intent intent = getIntent();
+        if (intent != null) {
+            final int eventLabel = intent.getIntExtra(Events.EXTRA_EVENT_LABEL, 0);
+            Events.sendScreensaverEvent(R.string.action_show, eventLabel);
+        }
     }
 
     @Override
diff --git a/src/com/android/deskclock/Utils.java b/src/com/android/deskclock/Utils.java
index aee4dc9..0f60da9 100644
--- a/src/com/android/deskclock/Utils.java
+++ b/src/com/android/deskclock/Utils.java
@@ -38,14 +38,13 @@
 import android.os.Build;
 import android.os.Bundle;
 import android.os.Looper;
+import android.os.SystemClock;
 import android.preference.PreferenceManager;
 import android.provider.Settings;
 import android.support.annotation.AnyRes;
 import android.support.annotation.DrawableRes;
 import android.support.graphics.drawable.VectorDrawableCompat;
 import android.support.v4.os.BuildCompat;
-import android.support.v7.widget.LinearLayoutManager;
-import android.support.v7.widget.RecyclerView;
 import android.text.Spannable;
 import android.text.SpannableString;
 import android.text.TextUtils;
@@ -56,7 +55,6 @@
 import android.text.style.TypefaceSpan;
 import android.util.ArraySet;
 import android.view.View;
-import android.widget.AbsListView;
 import android.widget.TextClock;
 import android.widget.TextView;
 
@@ -163,6 +161,13 @@
     }
 
     /**
+     * @return {@code true} if the device is {@link Build.VERSION_CODES#NMR1} or later
+     */
+    public static boolean isNMR1OrLater() {
+        return BuildCompat.isAtLeastNMR1();
+    }
+
+    /**
      * @param resourceId identifies an application resource
      * @return the Uri by which the application resource is accessed
      */
@@ -618,4 +623,12 @@
     public static boolean isLandscape(Context context) {
         return context.getResources().getConfiguration().orientation == ORIENTATION_LANDSCAPE;
     }
+
+    public static long now() {
+        return SystemClock.elapsedRealtime();
+    }
+
+    public static long wallClock() {
+        return System.currentTimeMillis();
+    }
 }
\ No newline at end of file
diff --git a/src/com/android/deskclock/actionbarmenu/NightModeMenuItemController.java b/src/com/android/deskclock/actionbarmenu/NightModeMenuItemController.java
index 745a8f8..8975221 100644
--- a/src/com/android/deskclock/actionbarmenu/NightModeMenuItemController.java
+++ b/src/com/android/deskclock/actionbarmenu/NightModeMenuItemController.java
@@ -22,6 +22,7 @@
 
 import com.android.deskclock.R;
 import com.android.deskclock.ScreensaverActivity;
+import com.android.deskclock.events.Events;
 
 import static android.view.Menu.NONE;
 
@@ -56,7 +57,8 @@
     @Override
     public boolean onOptionsItemSelected(MenuItem item) {
         mContext.startActivity(new Intent(mContext, ScreensaverActivity.class)
-                .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
+                .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+                .putExtra(Events.EXTRA_EVENT_LABEL, R.string.label_deskclock));
         return true;
     }
-}
\ No newline at end of file
+}
diff --git a/src/com/android/deskclock/alarms/AlarmNotifications.java b/src/com/android/deskclock/alarms/AlarmNotifications.java
index 86095ac..a8dcd08 100644
--- a/src/com/android/deskclock/alarms/AlarmNotifications.java
+++ b/src/com/android/deskclock/alarms/AlarmNotifications.java
@@ -41,6 +41,7 @@
 import java.text.DateFormat;
 import java.text.SimpleDateFormat;
 import java.util.Locale;
+import java.util.Objects;
 
 public final class AlarmNotifications {
     public static final String EXTRA_NOTIFICATION_ID = "extra_notification_id";
@@ -75,7 +76,8 @@
      */
     private static final int ALARM_GROUP_MISSED_NOTIFICATION_ID = Integer.MAX_VALUE - 5;
 
-    public static void showLowPriorityNotification(Context context, AlarmInstance instance) {
+    public static synchronized void showLowPriorityNotification(Context context,
+            AlarmInstance instance) {
         LogUtils.v("Displaying low priority notification for alarm instance: " + instance.mId);
 
         NotificationCompat.Builder notification = new NotificationCompat.Builder(context)
@@ -107,7 +109,7 @@
         Intent dismissIntent = AlarmStateManager.createStateChangeIntent(context,
                 AlarmStateManager.ALARM_DISMISS_TAG, instance, AlarmInstance.PREDISMISSED_STATE);
         notification.addAction(R.drawable.ic_alarm_off_24dp,
-                context.getString(R.string.alarm_alert_dismiss_now_text),
+                context.getString(R.string.alarm_alert_dismiss_text),
                 PendingIntent.getService(context, instance.hashCode(),
                         dismissIntent, PendingIntent.FLAG_UPDATE_CURRENT));
 
@@ -118,10 +120,11 @@
 
         NotificationManagerCompat nm = NotificationManagerCompat.from(context);
         nm.notify(instance.hashCode(), notification.build());
-        updateAlarmGroupNotification(context);
+        updateUpcomingAlarmGroupNotification(context);
     }
 
-    public static void showHighPriorityNotification(Context context, AlarmInstance instance) {
+    public static synchronized void showHighPriorityNotification(Context context,
+            AlarmInstance instance) {
         LogUtils.v("Displaying high priority notification for alarm instance: " + instance.mId);
 
         NotificationCompat.Builder notification = new NotificationCompat.Builder(context)
@@ -145,7 +148,7 @@
         Intent dismissIntent = AlarmStateManager.createStateChangeIntent(context,
                 AlarmStateManager.ALARM_DISMISS_TAG, instance, AlarmInstance.PREDISMISSED_STATE);
         notification.addAction(R.drawable.ic_alarm_off_24dp,
-                context.getString(R.string.alarm_alert_dismiss_now_text),
+                context.getString(R.string.alarm_alert_dismiss_text),
                 PendingIntent.getService(context, instance.hashCode(),
                         dismissIntent, PendingIntent.FLAG_UPDATE_CURRENT));
 
@@ -156,79 +159,112 @@
 
         NotificationManagerCompat nm = NotificationManagerCompat.from(context);
         nm.notify(instance.hashCode(), notification.build());
-        updateAlarmGroupNotification(context);
+        updateUpcomingAlarmGroupNotification(context);
     }
 
     @TargetApi(Build.VERSION_CODES.N)
-    private static int getActiveNotificationsCount(Context context, String group) {
-        NotificationManager nm = (NotificationManager) context.getSystemService(
-                Context.NOTIFICATION_SERVICE);
-        StatusBarNotification[] notifications = nm.getActiveNotifications();
-        int count = 0;
-        for (StatusBarNotification statusBarNotification : notifications) {
-            final Notification n = statusBarNotification.getNotification();
-            if ((n.flags & Notification.FLAG_GROUP_SUMMARY) != Notification.FLAG_GROUP_SUMMARY
-                    && group.equals(n.getGroup())) {
-                count++;
-            }
-        }
-        return count;
+    private static boolean isGroupSummary(Notification n) {
+        return (n.flags & Notification.FLAG_GROUP_SUMMARY) == Notification.FLAG_GROUP_SUMMARY;
     }
 
-    public static void updateAlarmGroupNotification(Context context) {
+    @TargetApi(Build.VERSION_CODES.N)
+    private static Notification getFirstActiveNotification(Context context, String group) {
+        final NotificationManager nm =
+                (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
+        final StatusBarNotification[] notifications = nm.getActiveNotifications();
+        Notification firstActiveNotification = null;
+        for (StatusBarNotification statusBarNotification : notifications) {
+            final Notification n = statusBarNotification.getNotification();
+            if (!isGroupSummary(n) && group.equals(n.getGroup())) {
+                if (firstActiveNotification == null
+                        || n.getSortKey().compareTo(firstActiveNotification.getSortKey()) < 0) {
+                    firstActiveNotification = n;
+                }
+            }
+        }
+        return firstActiveNotification;
+    }
+
+    @TargetApi(Build.VERSION_CODES.N)
+    private static Notification getActiveGroupSummaryNotification(Context context, String group) {
+        final NotificationManager nm =
+                (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
+        final StatusBarNotification[] notifications = nm.getActiveNotifications();
+        for (StatusBarNotification statusBarNotification : notifications) {
+            final Notification n = statusBarNotification.getNotification();
+            if (isGroupSummary(n) && group.equals(n.getGroup())) {
+                return n;
+            }
+        }
+        return null;
+    }
+
+    private static void updateUpcomingAlarmGroupNotification(Context context) {
         if (!Utils.isNOrLater()) {
             return;
         }
 
-        NotificationManagerCompat nm = NotificationManagerCompat.from(context);
+        final NotificationManagerCompat nm = NotificationManagerCompat.from(context);
 
-        if (getActiveNotificationsCount(context, UPCOMING_GROUP_KEY) == 0) {
+        final Notification firstUpcoming = getFirstActiveNotification(context, UPCOMING_GROUP_KEY);
+        if (firstUpcoming == null) {
             nm.cancel(ALARM_GROUP_NOTIFICATION_ID);
             return;
         }
 
-        NotificationCompat.Builder summaryNotification = new NotificationCompat.Builder(context)
-                .setShowWhen(false)
-                .setColor(ContextCompat.getColor(context, R.color.default_background))
-                .setSmallIcon(R.drawable.stat_notify_alarm)
-                .setGroup(UPCOMING_GROUP_KEY)
-                .setGroupSummary(true)
-                .setPriority(NotificationCompat.PRIORITY_HIGH)
-                .setCategory(NotificationCompat.CATEGORY_ALARM)
-                .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
-                .setLocalOnly(true);
-
-
-        nm.notify(ALARM_GROUP_NOTIFICATION_ID, summaryNotification.build());
+        Notification summary = getActiveGroupSummaryNotification(context, UPCOMING_GROUP_KEY);
+        if (summary == null
+                || !Objects.equals(summary.contentIntent, firstUpcoming.contentIntent)) {
+            summary = new NotificationCompat.Builder(context)
+                    .setShowWhen(false)
+                    .setContentIntent(firstUpcoming.contentIntent)
+                    .setColor(ContextCompat.getColor(context, R.color.default_background))
+                    .setSmallIcon(R.drawable.stat_notify_alarm)
+                    .setGroup(UPCOMING_GROUP_KEY)
+                    .setGroupSummary(true)
+                    .setPriority(NotificationCompat.PRIORITY_HIGH)
+                    .setCategory(NotificationCompat.CATEGORY_ALARM)
+                    .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
+                    .setLocalOnly(true)
+                    .build();
+            nm.notify(ALARM_GROUP_NOTIFICATION_ID, summary);
+        }
     }
 
-    public static void updateAlarmGroupMissedNotification(Context context) {
+    private static void updateMissedAlarmGroupNotification(Context context) {
         if (!Utils.isNOrLater()) {
             return;
         }
 
-        NotificationManagerCompat nm = NotificationManagerCompat.from(context);
+        final NotificationManagerCompat nm = NotificationManagerCompat.from(context);
 
-        if (getActiveNotificationsCount(context, MISSED_GROUP_KEY) == 0) {
+        final Notification firstMissed = getFirstActiveNotification(context, MISSED_GROUP_KEY);
+        if (firstMissed == null) {
             nm.cancel(ALARM_GROUP_MISSED_NOTIFICATION_ID);
             return;
         }
 
-        NotificationCompat.Builder summaryNotification = new NotificationCompat.Builder(context)
-                .setShowWhen(false)
-                .setColor(ContextCompat.getColor(context, R.color.default_background))
-                .setSmallIcon(R.drawable.stat_notify_alarm)
-                .setGroup(MISSED_GROUP_KEY)
-                .setGroupSummary(true)
-                .setPriority(NotificationCompat.PRIORITY_HIGH)
-                .setCategory(NotificationCompat.CATEGORY_ALARM)
-                .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
-                .setLocalOnly(true);
-
-        nm.notify(ALARM_GROUP_MISSED_NOTIFICATION_ID, summaryNotification.build());
+        Notification summary = getActiveGroupSummaryNotification(context, MISSED_GROUP_KEY);
+        if (summary == null
+                || !Objects.equals(summary.contentIntent, firstMissed.contentIntent)) {
+            summary = new NotificationCompat.Builder(context)
+                    .setShowWhen(false)
+                    .setContentIntent(firstMissed.contentIntent)
+                    .setColor(ContextCompat.getColor(context, R.color.default_background))
+                    .setSmallIcon(R.drawable.stat_notify_alarm)
+                    .setGroup(MISSED_GROUP_KEY)
+                    .setGroupSummary(true)
+                    .setPriority(NotificationCompat.PRIORITY_HIGH)
+                    .setCategory(NotificationCompat.CATEGORY_ALARM)
+                    .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
+                    .setLocalOnly(true)
+                    .build();
+            nm.notify(ALARM_GROUP_MISSED_NOTIFICATION_ID, summary);
+        }
     }
 
-    public static void showSnoozeNotification(Context context, AlarmInstance instance) {
+    public static synchronized void showSnoozeNotification(Context context,
+            AlarmInstance instance) {
         LogUtils.v("Displaying snoozed notification for alarm instance: " + instance.mId);
 
         NotificationCompat.Builder notification = new NotificationCompat.Builder(context)
@@ -264,10 +300,11 @@
 
         NotificationManagerCompat nm = NotificationManagerCompat.from(context);
         nm.notify(instance.hashCode(), notification.build());
-        updateAlarmGroupNotification(context);
+        updateUpcomingAlarmGroupNotification(context);
     }
 
-    public static void showMissedNotification(Context context, AlarmInstance instance) {
+    public static synchronized void showMissedNotification(Context context,
+            AlarmInstance instance) {
         LogUtils.v("Displaying missed notification for alarm instance: " + instance.mId);
 
         String label = instance.mLabel;
@@ -307,10 +344,10 @@
 
         NotificationManagerCompat nm = NotificationManagerCompat.from(context);
         nm.notify(hashCode, notification.build());
-        updateAlarmGroupMissedNotification(context);
+        updateMissedAlarmGroupNotification(context);
     }
 
-    public static void showAlarmNotification(Service service, AlarmInstance instance) {
+    public static synchronized void showAlarmNotification(Service service, AlarmInstance instance) {
         LogUtils.v("Displaying alarm notification for alarm instance: " + instance.mId);
 
         Resources resources = service.getResources();
@@ -368,12 +405,12 @@
         service.startForeground(instance.hashCode(), notification.build());
     }
 
-    public static void clearNotification(Context context, AlarmInstance instance) {
+    public static synchronized void clearNotification(Context context, AlarmInstance instance) {
         LogUtils.v("Clearing notifications for alarm instance: " + instance.mId);
         NotificationManagerCompat nm = NotificationManagerCompat.from(context);
         nm.cancel(instance.hashCode());
-        updateAlarmGroupNotification(context);
-        updateAlarmGroupMissedNotification(context);
+        updateUpcomingAlarmGroupNotification(context);
+        updateMissedAlarmGroupNotification(context);
     }
 
     /**
@@ -418,4 +455,4 @@
         final boolean missedAlarm = instance.mAlarmState == AlarmInstance.MISSED_STATE;
         return missedAlarm ? ("MISSED " + timeKey) : timeKey;
     }
-}
\ No newline at end of file
+}
diff --git a/src/com/android/deskclock/alarms/AlarmStateManager.java b/src/com/android/deskclock/alarms/AlarmStateManager.java
index c26386b..357dd0b 100644
--- a/src/com/android/deskclock/alarms/AlarmStateManager.java
+++ b/src/com/android/deskclock/alarms/AlarmStateManager.java
@@ -50,6 +50,8 @@
 import java.util.Comparator;
 import java.util.List;
 
+import static android.provider.Settings.System.NEXT_ALARM_FORMATTED;
+
 /**
  * This class handles all the state changes for alarm instances. You need to
  * register all alarm instances with the state manager if you want them to
@@ -171,12 +173,10 @@
     }
 
     /**
-     * Find and notify system what the next alarm that will fire. This is used
-     * to update text in the system and widgets.
-     *
-     * @param context application context
+     * Update the next alarm stored in framework. This value is also displayed in digital widgets
+     * and the clock tab in this app.
      */
-    public static void updateNextAlarm(Context context) {
+    private static void updateNextAlarm(Context context) {
         final AlarmInstance nextAlarm = getNextFiringAlarm(context);
 
         if (Utils.isPreL()) {
@@ -212,23 +212,24 @@
     @SuppressWarnings("deprecation")
     @TargetApi(Build.VERSION_CODES.KITKAT)
     private static void updateNextAlarmInSystemSettings(Context context, AlarmInstance nextAlarm) {
-        // Send broadcast message so pre-L AppWidgets will recognize an update
-        String timeString = "";
-        boolean showStatusIcon = false;
+        // Format the next alarm time if an alarm is scheduled.
+        String time = "";
         if (nextAlarm != null) {
-            timeString = AlarmUtils.getFormattedTime(context, nextAlarm.getAlarmTime());
-            showStatusIcon = true;
+            time = AlarmUtils.getFormattedTime(context, nextAlarm.getAlarmTime());
         }
 
-        // Set and notify next alarm text to system
-        LogUtils.i("Displaying next alarm time: \'" + timeString + '\'');
-        // Write directly to NEXT_ALARM_FORMATTED in all pre-L versions
-        Settings.System.putString(context.getContentResolver(),
-                Settings.System.NEXT_ALARM_FORMATTED,
-                timeString);
-        Intent alarmChanged = new Intent(ACTION_ALARM_CHANGED);
-        alarmChanged.putExtra("alarmSet", showStatusIcon);
-        context.sendBroadcast(alarmChanged);
+        try {
+            // Write directly to NEXT_ALARM_FORMATTED in all pre-L versions
+            Settings.System.putString(context.getContentResolver(), NEXT_ALARM_FORMATTED, time);
+
+            LogUtils.i("Updated next alarm time to: \'" + time + '\'');
+
+            // Send broadcast message so pre-L AppWidgets will recognize an update.
+            context.sendBroadcast(new Intent(ACTION_ALARM_CHANGED));
+        } catch (SecurityException se) {
+            // The user has most likely revoked WRITE_SETTINGS.
+            LogUtils.e("Unable to update next alarm to: \'" + time + '\'', se);
+        }
     }
 
     /**
@@ -1015,7 +1016,7 @@
         public void scheduleInstanceStateChange(Context context, Calendar time,
                 AlarmInstance instance, int newState) {
             final long timeInMillis = time.getTimeInMillis();
-            LogUtils.v("Scheduling state change %d to instance %d at %s (%d)", newState,
+            LogUtils.i("Scheduling state change %d to instance %d at %s (%d)", newState,
                     instance.mId, AlarmUtils.getFormattedTime(context, time), timeInMillis);
             final Intent stateChangeIntent =
                     createStateChangeIntent(context, ALARM_MANAGER_TAG, instance, newState);
diff --git a/src/com/android/deskclock/alarms/TimePickerCompat.java b/src/com/android/deskclock/alarms/TimePickerCompat.java
index 38ff651..59da1e5 100644
--- a/src/com/android/deskclock/alarms/TimePickerCompat.java
+++ b/src/com/android/deskclock/alarms/TimePickerCompat.java
@@ -21,7 +21,6 @@
 import android.app.DialogFragment;
 import android.app.Fragment;
 import android.app.FragmentManager;
-import android.app.FragmentTransaction;
 import android.os.Bundle;
 import android.text.format.DateFormat;
 import android.widget.TimePicker;
@@ -146,14 +145,9 @@
      */
     public static void showTimeEditDialog(Fragment targetFragment, Alarm alarm,
             boolean use24hourFormat) {
-        // Make sure the dialog isn't already added.
         final FragmentManager manager = targetFragment.getFragmentManager();
-        final FragmentTransaction ft = manager.beginTransaction();
-        final Fragment prev = manager.findFragmentByTag(FRAG_TAG_TIME_PICKER);
-        if (prev != null) {
-            ft.remove(prev);
-        }
-        ft.commit();
+        // Make sure the dialog isn't already added.
+        removeTimeEditDialog(manager);
 
         if (Utils.isLOrLater()) {
             final TimePickerPostL picker = new TimePickerPostL();
@@ -177,4 +171,15 @@
             }
         }
     }
+
+    public static void removeTimeEditDialog(FragmentManager manager) {
+        if (manager == null) {
+            return;
+        }
+        final Fragment prev = manager.findFragmentByTag(FRAG_TAG_TIME_PICKER);
+        if (prev != null) {
+            manager.beginTransaction().remove(prev).commit();
+        }
+    }
+
 }
diff --git a/src/com/android/deskclock/alarms/dataadapter/AlarmItemViewHolder.java b/src/com/android/deskclock/alarms/dataadapter/AlarmItemViewHolder.java
index 0fe87da..d34faba 100644
--- a/src/com/android/deskclock/alarms/dataadapter/AlarmItemViewHolder.java
+++ b/src/com/android/deskclock/alarms/dataadapter/AlarmItemViewHolder.java
@@ -51,12 +51,13 @@
         preemptiveDismissContainer = itemView.findViewById(R.id.preemptive_dismiss_container);
         preemptiveDismissButton =
                 (TextView) itemView.findViewById(R.id.preemptive_dismiss_button);
-
         preemptiveDismissButton.setOnClickListener(new View.OnClickListener() {
             @Override
             public void onClick(View v) {
                 final AlarmInstance alarmInstance = getItemHolder().getAlarmInstance();
-                getItemHolder().getAlarmTimeClickHandler().dismissAlarmInstance(alarmInstance);
+                if (alarmInstance != null) {
+                    getItemHolder().getAlarmTimeClickHandler().dismissAlarmInstance(alarmInstance);
+                }
             }
         });
         onOff.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@@ -96,10 +97,12 @@
             final String dismissText = alarm.instanceState == AlarmInstance.SNOOZE_STATE
                     ? context.getString(R.string.alarm_alert_snooze_until,
                             AlarmUtils.getAlarmText(context, alarmInstance, false))
-                    : context.getString(R.string.alarm_alert_dismiss_now_text);
+                    : context.getString(R.string.alarm_alert_dismiss_text);
             preemptiveDismissButton.setText(dismissText);
+            preemptiveDismissButton.setClickable(true);
         } else {
             preemptiveDismissContainer.setVisibility(View.GONE);
+            preemptiveDismissButton.setClickable(false);
         }
         return canBind;
     }
diff --git a/src/com/android/deskclock/controller/Controller.java b/src/com/android/deskclock/controller/Controller.java
new file mode 100644
index 0000000..ad15db6
--- /dev/null
+++ b/src/com/android/deskclock/controller/Controller.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.deskclock.controller;
+
+import android.content.Context;
+
+import com.android.deskclock.Utils;
+
+public final class Controller {
+
+    private static final Controller sController = new Controller();
+
+    private Context mContext;
+
+    private ShortcutController mShortcutController;
+
+    private Controller() {}
+
+    public static Controller getController() {
+        return sController;
+    }
+
+    public void setContext(Context context) {
+        if (mContext != null) {
+            throw new IllegalStateException("context has already been set");
+        }
+        mContext = context;
+        if (Utils.isNMR1OrLater()) {
+            mShortcutController = new ShortcutController(mContext);
+        }
+    }
+
+    public void updateShortcuts() {
+        Utils.enforceMainLooper();
+        if (mShortcutController != null) {
+            mShortcutController.updateShortcuts();
+        }
+    }
+}
diff --git a/src/com/android/deskclock/controller/ShortcutController.java b/src/com/android/deskclock/controller/ShortcutController.java
new file mode 100644
index 0000000..87ca2d8
--- /dev/null
+++ b/src/com/android/deskclock/controller/ShortcutController.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.deskclock.controller;
+
+import android.annotation.TargetApi;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ShortcutInfo;
+import android.content.pm.ShortcutManager;
+import android.graphics.drawable.Icon;
+import android.os.Build;
+import android.os.UserManager;
+import android.provider.AlarmClock;
+import android.support.annotation.StringRes;
+
+import com.android.deskclock.DeskClock;
+import com.android.deskclock.HandleApiCalls;
+import com.android.deskclock.HandleShortcuts;
+import com.android.deskclock.LogUtils;
+import com.android.deskclock.R;
+import com.android.deskclock.ScreensaverActivity;
+import com.android.deskclock.data.DataModel;
+import com.android.deskclock.data.Lap;
+import com.android.deskclock.data.Stopwatch;
+import com.android.deskclock.data.StopwatchListener;
+import com.android.deskclock.events.Events;
+import com.android.deskclock.events.ShortcutEventTracker;
+import com.android.deskclock.stopwatch.StopwatchService;
+import com.android.deskclock.uidata.UiDataModel;
+
+import java.util.Arrays;
+import java.util.Collections;
+
+@TargetApi(Build.VERSION_CODES.N_MR1)
+class ShortcutController {
+
+    private final Context mContext;
+    private final ComponentName mComponentName;
+    private final ShortcutManager mShortcutManager;
+    private final UserManager mUserManager;
+
+    ShortcutController(Context context) {
+        mContext = context;
+        mComponentName = new ComponentName(mContext, DeskClock.class);
+        mShortcutManager = mContext.getSystemService(ShortcutManager.class);
+        mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
+        Events.addEventTracker(new ShortcutEventTracker(mContext));
+        DataModel.getDataModel().addStopwatchListener(new StopwatchWatcher());
+    }
+
+    void updateShortcuts() {
+        if (!mUserManager.isUserUnlocked()) {
+            LogUtils.i("Skipping shortcut update because user is locked.");
+            return;
+        }
+        try {
+            final ShortcutInfo alarm = createNewAlarmShortcut();
+            final ShortcutInfo timer = createNewTimerShortcut();
+            final ShortcutInfo stopwatch = createStopwatchShortcut();
+            final ShortcutInfo screensaver = createScreensaverShortcut();
+            mShortcutManager.setDynamicShortcuts(
+                    Arrays.asList(alarm, timer, stopwatch, screensaver));
+        } catch (IllegalStateException e) {
+            LogUtils.wtf(e);
+        }
+    }
+
+    private ShortcutInfo createNewAlarmShortcut() {
+        final Intent intent = new Intent(AlarmClock.ACTION_SET_ALARM)
+                .setClass(mContext, HandleApiCalls.class)
+                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+                .putExtra(Events.EXTRA_EVENT_LABEL, R.string.label_shortcut);
+        final String setAlarmShortcut = UiDataModel.getUiDataModel()
+                .getShortcutId(R.string.category_alarm, R.string.action_create);
+        return new ShortcutInfo.Builder(mContext, setAlarmShortcut)
+                .setIcon(Icon.createWithResource(mContext, R.drawable.shortcut_new_alarm))
+                .setActivity(mComponentName)
+                .setShortLabel(mContext.getString(R.string.shortcut_new_alarm_short))
+                .setLongLabel(mContext.getString(R.string.shortcut_new_alarm_long))
+                .setIntent(intent)
+                .setRank(0)
+                .build();
+    }
+
+    private ShortcutInfo createNewTimerShortcut() {
+        final Intent intent = new Intent(AlarmClock.ACTION_SET_TIMER)
+                .setClass(mContext, HandleApiCalls.class)
+                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+                .putExtra(Events.EXTRA_EVENT_LABEL, R.string.label_shortcut);
+        final String setTimerShortcut = UiDataModel.getUiDataModel()
+                .getShortcutId(R.string.category_timer, R.string.action_create);
+        return new ShortcutInfo.Builder(mContext, setTimerShortcut)
+                .setIcon(Icon.createWithResource(mContext, R.drawable.shortcut_new_timer))
+                .setActivity(mComponentName)
+                .setShortLabel(mContext.getString(R.string.shortcut_new_timer_short))
+                .setLongLabel(mContext.getString(R.string.shortcut_new_timer_long))
+                .setIntent(intent)
+                .setRank(1)
+                .build();
+    }
+
+    private ShortcutInfo createStopwatchShortcut() {
+        final @StringRes int action = DataModel.getDataModel().getStopwatch().isRunning()
+                ? R.string.action_pause : R.string.action_start;
+        final String shortcutId = UiDataModel.getUiDataModel()
+                .getShortcutId(R.string.category_stopwatch, action);
+        final ShortcutInfo.Builder shortcut = new ShortcutInfo.Builder(mContext, shortcutId)
+                .setIcon(Icon.createWithResource(mContext, R.drawable.shortcut_stopwatch))
+                .setActivity(mComponentName)
+                .setRank(2);
+        final Intent intent;
+        if (DataModel.getDataModel().getStopwatch().isRunning()) {
+            intent = new Intent(StopwatchService.ACTION_PAUSE_STOPWATCH)
+                    .putExtra(Events.EXTRA_EVENT_LABEL, R.string.label_shortcut);
+            shortcut.setShortLabel(mContext.getString(R.string.shortcut_pause_stopwatch_short))
+                    .setLongLabel(mContext.getString(R.string.shortcut_pause_stopwatch_long));
+        } else {
+            intent = new Intent(StopwatchService.ACTION_START_STOPWATCH)
+                    .putExtra(Events.EXTRA_EVENT_LABEL, R.string.label_shortcut);
+            shortcut.setShortLabel(mContext.getString(R.string.shortcut_start_stopwatch_short))
+                    .setLongLabel(mContext.getString(R.string.shortcut_start_stopwatch_long));
+        }
+        intent.setClass(mContext, HandleShortcuts.class)
+                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        return shortcut
+                .setIntent(intent)
+                .build();
+    }
+
+    private ShortcutInfo createScreensaverShortcut() {
+        final Intent intent = new Intent(Intent.ACTION_MAIN)
+                .setClass(mContext, ScreensaverActivity.class)
+                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+                .putExtra(Events.EXTRA_EVENT_LABEL, R.string.label_shortcut);
+        final String screensaverShortcut = UiDataModel.getUiDataModel()
+                .getShortcutId(R.string.category_screensaver, R.string.action_show);
+        return new ShortcutInfo.Builder(mContext, screensaverShortcut)
+                .setIcon(Icon.createWithResource(mContext, R.drawable.shortcut_screensaver))
+                .setActivity(mComponentName)
+                .setShortLabel((mContext.getString(R.string.shortcut_start_screensaver_short)))
+                .setLongLabel((mContext.getString(R.string.shortcut_start_screensaver_long)))
+                .setIntent(intent)
+                .setRank(3)
+                .build();
+    }
+
+    private class StopwatchWatcher implements StopwatchListener {
+
+        @Override
+        public void stopwatchUpdated(Stopwatch before, Stopwatch after) {
+            if (!mUserManager.isUserUnlocked()) {
+                LogUtils.i("Skipping stopwatch shortcut update because user is locked.");
+                return;
+            }
+            try {
+                mShortcutManager.updateShortcuts(
+                        Collections.singletonList(createStopwatchShortcut()));
+            } catch (IllegalStateException e) {
+                LogUtils.wtf(e);
+            }
+        }
+
+        @Override
+        public void lapAdded(Lap lap) {
+        }
+    }
+}
diff --git a/src/com/android/deskclock/data/City.java b/src/com/android/deskclock/data/City.java
index 8ce700f..7a41d08 100644
--- a/src/com/android/deskclock/data/City.java
+++ b/src/com/android/deskclock/data/City.java
@@ -18,6 +18,7 @@
 
 import java.text.Collator;
 import java.util.Comparator;
+import java.util.Locale;
 import java.util.TimeZone;
 
 /**
@@ -102,7 +103,8 @@
 
     @Override
     public String toString() {
-        return String.format("City {id=%s, index=%d, indexString=%s, name=%s, phonetic=%s, tz=%s}",
+        return String.format(Locale.US,
+                "City {id=%s, index=%d, indexString=%s, name=%s, phonetic=%s, tz=%s}",
                 mId, mIndex, mIndexString, mName, mPhoneticName, mTimeZone.getID());
     }
 
@@ -129,7 +131,7 @@
      */
     public static final class UtcOffsetComparator implements Comparator<City> {
 
-        private final Comparator<City> mDelegate1 = new UtcOffsetIndexComparator();;
+        private final Comparator<City> mDelegate1 = new UtcOffsetIndexComparator();
 
         private final Comparator<City> mDelegate2 = new NameComparator();
 
diff --git a/src/com/android/deskclock/data/CityListener.java b/src/com/android/deskclock/data/CityListener.java
new file mode 100644
index 0000000..37f1b52
--- /dev/null
+++ b/src/com/android/deskclock/data/CityListener.java
@@ -0,0 +1,10 @@
+package com.android.deskclock.data;
+
+import java.util.List;
+
+/**
+ * The interface through which interested parties are notified of changes to the world cities list.
+ */
+public interface CityListener {
+    void citiesChanged(List<City> oldCities, List<City> newCities);
+}
diff --git a/src/com/android/deskclock/data/CityModel.java b/src/com/android/deskclock/data/CityModel.java
index b34c447..fafd48b 100644
--- a/src/com/android/deskclock/data/CityModel.java
+++ b/src/com/android/deskclock/data/CityModel.java
@@ -58,6 +58,9 @@
     @SuppressWarnings("FieldCanBeLocal")
     private final BroadcastReceiver mLocaleChangedReceiver = new LocaleChangedReceiver();
 
+    /** List of listeners to invoke upon world city list change */
+    private final List<CityListener> mCityListeners = new ArrayList<>();
+
     /** Maps city ID to city instance. */
     private Map<String, City> mCityMap;
 
@@ -86,6 +89,14 @@
         prefs.registerOnSharedPreferenceChangeListener(mPreferenceListener);
     }
 
+    void addCityListener(CityListener cityListener) {
+        mCityListeners.add(cityListener);
+    }
+
+    void removeCityListener(CityListener cityListener) {
+        mCityListeners.remove(cityListener);
+    }
+
     /**
      * @return a list of all cities in their display order
      */
@@ -178,6 +189,7 @@
      * @param cities the new collection of cities selected for display by the user
      */
     void setSelectedCities(Collection<City> cities) {
+        final List<City> oldCities = getAllCities();
         CityDAO.setSelectedCities(mContext, cities);
 
         // Clear caches affected by this update.
@@ -186,7 +198,7 @@
         mUnselectedCities = null;
 
         // Broadcast the change to the selected cities for the benefit of widgets.
-        sendCitiesChangedBroadcast();
+        fireCitiesChanged(oldCities, getAllCities());
     }
 
     /**
@@ -219,14 +231,6 @@
         mUnselectedCities = null;
     }
 
-    /**
-     * @param cityId identifies a city
-     * @return the corresponding city
-     */
-    City getCityById(String cityId) {
-        return getCityMap().get(cityId);
-    }
-
     private Map<String, City> getCityMap() {
         if (mCityMap == null) {
             mCityMap = CityDAO.getCities(mContext);
@@ -244,8 +248,11 @@
         throw new IllegalStateException("unexpected city sort: " + citySort);
     }
 
-    private void sendCitiesChangedBroadcast() {
+    private void fireCitiesChanged(List<City> oldCities, List<City> newCities) {
         mContext.sendBroadcast(new Intent(DataModel.ACTION_WORLD_CITIES_CHANGED));
+        for (CityListener cityListener : mCityListeners) {
+            cityListener.citiesChanged(oldCities, newCities);
+        }
     }
 
     /**
@@ -273,7 +280,8 @@
                 case SettingsActivity.KEY_HOME_TZ:
                     mHomeCity = null;
                 case SettingsActivity.KEY_AUTO_HOME_CLOCK:
-                    sendCitiesChangedBroadcast();
+                    final List<City> cities = getAllCities();
+                    fireCitiesChanged(cities, cities);
                     break;
             }
         }
diff --git a/src/com/android/deskclock/data/DataModel.java b/src/com/android/deskclock/data/DataModel.java
index ec681da..7def051 100644
--- a/src/com/android/deskclock/data/DataModel.java
+++ b/src/com/android/deskclock/data/DataModel.java
@@ -23,6 +23,8 @@
 import android.os.Looper;
 import android.support.annotation.StringRes;
 
+import com.android.deskclock.timer.TimerService;
+
 import java.util.Collection;
 import java.util.Comparator;
 import java.util.List;
@@ -106,6 +108,24 @@
     }
 
     /**
+     * Updates all timers and the stopwatch after the device has shutdown and restarted.
+     */
+    public void updateAfterReboot() {
+        enforceMainLooper();
+        mTimerModel.updateTimersAfterReboot();
+        mStopwatchModel.setStopwatch(getStopwatch().updateAfterReboot());
+    }
+
+    /**
+     * Updates all timers and the stopwatch after the device's time has changed.
+     */
+    public void updateAfterTimeSet() {
+        enforceMainLooper();
+        mTimerModel.updateTimersAfterTimeSet();
+        mStopwatchModel.setStopwatch(getStopwatch().updateAfterTimeSet());
+    }
+
+    /**
      * Posts a runnable to the main thread and blocks until the runnable executes. Used to access
      * the data model from the main thread.
      */
@@ -151,6 +171,7 @@
 
             // Refresh all notifications in response to a change in app open state.
             mTimerModel.updateNotification();
+            mTimerModel.updateMissedNotification();
             mStopwatchModel.updateNotification();
         }
     }
@@ -170,6 +191,7 @@
     public void updateAllNotifications() {
         enforceMainLooper();
         mTimerModel.updateNotification();
+        mTimerModel.updateMissedNotification();
         mStopwatchModel.updateNotification();
     }
 
@@ -250,6 +272,22 @@
         mCityModel.toggleCitySort();
     }
 
+    /**
+     * @param cityListener listener to be notified when the world city list changes
+     */
+    public void addCityListener(CityListener cityListener) {
+        enforceMainLooper();
+        mCityModel.addCityListener(cityListener);
+    }
+
+    /**
+     * @param cityListener listener that no longer needs to be notified of world city list changes
+     */
+    public void removeCityListener(CityListener cityListener) {
+        enforceMainLooper();
+        mCityModel.removeCityListener(cityListener);
+    }
+
     //
     // Timers
     //
@@ -327,8 +365,24 @@
      * @param timer the timer to be started
      */
     public void startTimer(Timer timer) {
+        startTimer(null, timer);
+    }
+
+    /**
+     * @param service used to start foreground notifications for expired timers
+     * @param timer the timer to be started
+     */
+    public void startTimer(Service service, Timer timer) {
         enforceMainLooper();
-        mTimerModel.updateTimer(timer.start());
+        final Timer started = timer.start();
+        mTimerModel.updateTimer(started);
+        if (timer.getRemainingTime() <= 0) {
+            if (service != null) {
+                expireTimer(service, started);
+            } else {
+                mContext.startService(TimerService.createTimerExpiredIntent(mContext, started));
+            }
+        }
     }
 
     /**
@@ -349,6 +403,15 @@
     }
 
     /**
+     * @param timer the timer to be reset
+     * @return the reset {@code timer}
+     */
+    public Timer resetTimer(Timer timer) {
+        enforceMainLooper();
+        return mTimerModel.resetTimer(timer, false /* allowDelete */, 0 /* eventLabelId */);
+    }
+
+    /**
      * If the given {@code timer} is expired and marked for deletion after use then this method
      * removes the the timer. The timer is otherwise transitioned to the reset state and continues
      * to exist.
@@ -359,17 +422,7 @@
      */
     public Timer resetOrDeleteTimer(Timer timer, @StringRes int eventLabelId) {
         enforceMainLooper();
-        return mTimerModel.resetOrDeleteTimer(timer, eventLabelId);
-    }
-
-    /**
-     * Resets all timers.
-     *
-     * @param eventLabelId the label of the timer event to send; 0 if no event should be sent
-     */
-    public void resetTimers(@StringRes int eventLabelId) {
-        enforceMainLooper();
-        mTimerModel.resetTimers(eventLabelId);
+        return mTimerModel.resetTimer(timer, true /* allowDelete */, eventLabelId);
     }
 
     /**
@@ -393,6 +446,17 @@
     }
 
     /**
+     * Resets all missed timers.
+     *
+     * @param eventLabelId the label of the timer event to send; 0 if no event should be sent
+     */
+    public void resetMissedTimers(@StringRes int eventLabelId) {
+        enforceMainLooper();
+        mTimerModel.resetMissedTimers(eventLabelId);
+    }
+
+
+    /**
      * @param timer the timer to which a minute should be added to the remaining time
      */
     public void addTimerMinute(Timer timer) {
@@ -410,6 +474,29 @@
     }
 
     /**
+     * @param timer  the timer whose {@code length} to change
+     * @param length the new length of the timer in milliseconds
+     */
+    public void setTimerLength(Timer timer, long length) {
+        enforceMainLooper();
+        mTimerModel.updateTimer(timer.setLength(length));
+    }
+
+    /**
+     * @param timer         the timer whose {@code remainingTime} to change
+     * @param remainingTime the new remaining time of the timer in milliseconds
+     */
+    public void setRemainingTime(Timer timer, long remainingTime) {
+        enforceMainLooper();
+
+        final Timer updated = timer.setRemainingTime(remainingTime);
+        mTimerModel.updateTimer(updated);
+        if (timer.isRunning() && timer.getRemainingTime() <= 0) {
+            mContext.startService(TimerService.createTimerExpiredIntent(mContext, updated));
+        }
+    }
+
+    /**
      * Updates the timer notifications to be current.
      */
     public void updateTimerNotification() {
diff --git a/src/com/android/deskclock/data/NotificationModel.java b/src/com/android/deskclock/data/NotificationModel.java
index 586127c..f722fb9 100644
--- a/src/com/android/deskclock/data/NotificationModel.java
+++ b/src/com/android/deskclock/data/NotificationModel.java
@@ -40,6 +40,10 @@
     //
     // Notification IDs
     //
+    // Used elsewhere:
+    // Integer.MAX_VALUE - 4
+    // Integer.MAX_VALUE - 5
+    //
 
     /**
      * @return a value that identifies the stopwatch notification
@@ -62,13 +66,19 @@
         return Integer.MAX_VALUE - 3;
     }
 
-    // Used elsewhere:
-    // Integer.MAX_VALUE - 4;
-    // Integer.MAX_VALUE - 5;
+    /**
+     * @return a value that identifies the notification for missed timers
+     */
+    int getMissedTimerNotificationId() {
+        return Integer.MAX_VALUE - 6;
+    }
 
     //
-    // Notification Group IDs
+    // Notification Group keys
     //
+    // Used elsewhere:
+    // "1"
+    // "4"
 
     /**
      * @return the group key for the stopwatch notification
@@ -84,7 +94,21 @@
         return "2";
     }
 
-    // Used elsewhere:
-    // "1"
-    // "4"
+    //
+    // Notification Sort keys
+    //
+
+    /**
+     * @return the sort key for the timer notification
+     */
+    String getTimerNotificationSortKey() {
+        return "0";
+    }
+
+    /**
+     * @return the sort key for the missed timer notification
+     */
+    String getTimerNotificationMissedSortKey() {
+        return "1";
+    }
 }
\ No newline at end of file
diff --git a/src/com/android/deskclock/data/Stopwatch.java b/src/com/android/deskclock/data/Stopwatch.java
index bfb7fba..11482b5 100644
--- a/src/com/android/deskclock/data/Stopwatch.java
+++ b/src/com/android/deskclock/data/Stopwatch.java
@@ -16,11 +16,8 @@
 
 package com.android.deskclock.data;
 
-import android.net.Uri;
-import android.os.SystemClock;
-
-import com.android.deskclock.provider.ClockContract;
-
+import static com.android.deskclock.Utils.now;
+import static com.android.deskclock.Utils.wallClock;
 import static com.android.deskclock.data.Stopwatch.State.PAUSED;
 import static com.android.deskclock.data.Stopwatch.State.RESET;
 import static com.android.deskclock.data.Stopwatch.State.RUNNING;
@@ -32,36 +29,33 @@
 
     public enum State { RESET, RUNNING, PAUSED }
 
-    /**
-     * The content:// style URI for the stopwatch.
-     */
-    public static final Uri CONTENT_URI =
-            Uri.parse("content://" + ClockContract.AUTHORITY + "/stopwatch");
+    static final long UNUSED = Long.MIN_VALUE;
 
     /** The single, immutable instance of a reset stopwatch. */
-    private static final Stopwatch RESET_STOPWATCH = new Stopwatch(RESET, Long.MIN_VALUE, 0);
+    private static final Stopwatch RESET_STOPWATCH = new Stopwatch(RESET, UNUSED, UNUSED, 0);
 
     /** Current state of this stopwatch. */
     private final State mState;
 
-    /** Elapsed time in ms the stopwatch was last started; {@link Long#MIN_VALUE} if not running. */
+    /** Elapsed time in ms the stopwatch was last started; {@link #UNUSED} if not running. */
     private final long mLastStartTime;
 
+    /** The time since epoch at which the stopwatch was last started. */
+    private final long mLastStartWallClockTime;
+
     /** Elapsed time in ms this stopwatch has accumulated while running. */
     private final long mAccumulatedTime;
 
-    Stopwatch(State state, long lastStartTime, long accumulatedTime) {
+    Stopwatch(State state, long lastStartTime, long lastWallClockTime, long accumulatedTime) {
         mState = state;
         mLastStartTime = lastStartTime;
+        mLastStartWallClockTime = lastWallClockTime;
         mAccumulatedTime = accumulatedTime;
     }
 
-    public Uri getContentUri() {
-        return CONTENT_URI;
-    }
-
     public State getState() { return mState; }
     public long getLastStartTime() { return mLastStartTime; }
+    public long getLastWallClockTime() { return mLastStartWallClockTime; }
     public boolean isReset() { return mState == RESET; }
     public boolean isPaused() { return mState == PAUSED; }
     public boolean isRunning() { return mState == RUNNING; }
@@ -96,7 +90,7 @@
             return this;
         }
 
-        return new Stopwatch(RUNNING, now(), getTotalTime());
+        return new Stopwatch(RUNNING, now(), wallClock(), getTotalTime());
     }
 
     /**
@@ -107,7 +101,7 @@
             return this;
         }
 
-        return new Stopwatch(PAUSED, Long.MIN_VALUE, getTotalTime());
+        return new Stopwatch(PAUSED, UNUSED, UNUSED, getTotalTime());
     }
 
     /**
@@ -117,7 +111,31 @@
         return RESET_STOPWATCH;
     }
 
-    private static long now() {
-        return SystemClock.elapsedRealtime();
+    Stopwatch updateAfterReboot() {
+        if (mState != RUNNING) {
+            return this;
+        }
+        final long timeSinceBoot = now();
+        final long wallClockTime = wallClock();
+        // Avoid negative time deltas. They can happen in practice, but they can't be used. Simply
+        // update the recorded times and proceed with no change in accumulated time.
+        final long delta = Math.max(0, wallClockTime - mLastStartWallClockTime);
+        return new Stopwatch(mState, timeSinceBoot, wallClockTime, mAccumulatedTime + delta);
+    }
+
+    Stopwatch updateAfterTimeSet() {
+        if (mState != RUNNING) {
+            return this;
+        }
+        final long timeSinceBoot = now();
+        final long wallClockTime = wallClock();
+        final long delta = timeSinceBoot - mLastStartTime;
+        if (delta < 0) {
+            // Avoid negative time deltas. They typically happen following reboots when TIME_SET is
+            // broadcast before BOOT_COMPLETED. Simply ignore the time update and hope
+            // updateAfterReboot() can successfully correct the data at a later time.
+            return this;
+        }
+        return new Stopwatch(mState, timeSinceBoot, wallClockTime, mAccumulatedTime + delta);
     }
 }
diff --git a/src/com/android/deskclock/data/StopwatchDAO.java b/src/com/android/deskclock/data/StopwatchDAO.java
index a230481..48ca3dd 100644
--- a/src/com/android/deskclock/data/StopwatchDAO.java
+++ b/src/com/android/deskclock/data/StopwatchDAO.java
@@ -40,6 +40,9 @@
     /** Key to a preference that stores the last start time of the stopwatch. */
     private static final String LAST_START_TIME = "sw_start_time";
 
+    /** Key to a preference that stores the epoch time when the stopwatch last started. */
+    private static final String LAST_WALL_CLOCK_TIME = "sw_wall_clock_time";
+
     /** Key to a preference that stores the accumulated elapsed time of the stopwatch. */
     private static final String ACCUMULATED_TIME = "sw_accum_time";
 
@@ -58,9 +61,10 @@
         final SharedPreferences prefs = Utils.getDefaultSharedPreferences(context);
         final int stateIndex = prefs.getInt(STATE, RESET.ordinal());
         final State state = State.values()[stateIndex];
-        final long lastStartTime = prefs.getLong(LAST_START_TIME, Long.MIN_VALUE);
+        final long lastStartTime = prefs.getLong(LAST_START_TIME, Stopwatch.UNUSED);
+        final long lastWallClockTime = prefs.getLong(LAST_WALL_CLOCK_TIME, Stopwatch.UNUSED);
         final long accumulatedTime = prefs.getLong(ACCUMULATED_TIME, 0);
-        return new Stopwatch(state, lastStartTime, accumulatedTime);
+        return new Stopwatch(state, lastStartTime, lastWallClockTime, accumulatedTime);
     }
 
     /**
@@ -73,10 +77,12 @@
         if (stopwatch.isReset()) {
             editor.remove(STATE)
                     .remove(LAST_START_TIME)
+                    .remove(LAST_WALL_CLOCK_TIME)
                     .remove(ACCUMULATED_TIME);
         } else {
             editor.putInt(STATE, stopwatch.getState().ordinal())
                     .putLong(LAST_START_TIME, stopwatch.getLastStartTime())
+                    .putLong(LAST_WALL_CLOCK_TIME, stopwatch.getLastWallClockTime())
                     .putLong(ACCUMULATED_TIME, stopwatch.getAccumulatedTime());
         }
 
diff --git a/src/com/android/deskclock/data/StopwatchModel.java b/src/com/android/deskclock/data/StopwatchModel.java
index 022c123..13790e4 100644
--- a/src/com/android/deskclock/data/StopwatchModel.java
+++ b/src/com/android/deskclock/data/StopwatchModel.java
@@ -133,7 +133,7 @@
      * @return a newly recorded lap completed now; {@code null} if no more laps can be added
      */
     Lap addLap() {
-        if (!canAddMoreLaps()) {
+        if (!mStopwatch.isRunning() || !canAddMoreLaps()) {
             return null;
         }
 
@@ -274,4 +274,4 @@
          */
         Notification build(Context context, NotificationModel nm, Stopwatch stopwatch);
     }
-}
\ No newline at end of file
+}
diff --git a/src/com/android/deskclock/data/StopwatchNotificationBuilderN.java b/src/com/android/deskclock/data/StopwatchNotificationBuilderN.java
index a81f7c2..e8e40b1 100644
--- a/src/com/android/deskclock/data/StopwatchNotificationBuilderN.java
+++ b/src/com/android/deskclock/data/StopwatchNotificationBuilderN.java
@@ -29,9 +29,9 @@
 import android.support.v4.content.ContextCompat;
 import android.widget.RemoteViews;
 
-import com.android.deskclock.HandleDeskClockApiCalls;
 import com.android.deskclock.R;
 import com.android.deskclock.Utils;
+import com.android.deskclock.events.Events;
 import com.android.deskclock.stopwatch.StopwatchService;
 
 import java.util.ArrayList;
@@ -51,12 +51,11 @@
         @StringRes final int eventLabel = R.string.label_notification;
 
         // Intent to load the app when the notification is tapped.
-        final Intent showApp = new Intent(context, HandleDeskClockApiCalls.class)
-                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
-                .setAction(HandleDeskClockApiCalls.ACTION_SHOW_STOPWATCH)
-                .putExtra(HandleDeskClockApiCalls.EXTRA_EVENT_LABEL, eventLabel);
+        final Intent showApp = new Intent(context, StopwatchService.class)
+                .setAction(StopwatchService.ACTION_SHOW_STOPWATCH)
+                .putExtra(Events.EXTRA_EVENT_LABEL, eventLabel);
 
-        final PendingIntent pendingShowApp = PendingIntent.getActivity(context, 0, showApp,
+        final PendingIntent pendingShowApp = PendingIntent.getService(context, 0, showApp,
                 PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_UPDATE_CURRENT);
 
         // Compute some values required below.
@@ -73,8 +72,8 @@
         if (running) {
             // Left button: Pause
             final Intent pause = new Intent(context, StopwatchService.class)
-                    .setAction(HandleDeskClockApiCalls.ACTION_PAUSE_STOPWATCH)
-                    .putExtra(HandleDeskClockApiCalls.EXTRA_EVENT_LABEL, eventLabel);
+                    .setAction(StopwatchService.ACTION_PAUSE_STOPWATCH)
+                    .putExtra(Events.EXTRA_EVENT_LABEL, eventLabel);
 
             final Icon icon1 = Icon.createWithResource(context, R.drawable.ic_pause_24dp);
             final CharSequence title1 = res.getText(R.string.sw_pause_button);
@@ -84,8 +83,8 @@
             // Right button: Add Lap
             if (DataModel.getDataModel().canAddMoreLaps()) {
                 final Intent lap = new Intent(context, StopwatchService.class)
-                        .setAction(HandleDeskClockApiCalls.ACTION_LAP_STOPWATCH)
-                        .putExtra(HandleDeskClockApiCalls.EXTRA_EVENT_LABEL, eventLabel);
+                        .setAction(StopwatchService.ACTION_LAP_STOPWATCH)
+                        .putExtra(Events.EXTRA_EVENT_LABEL, eventLabel);
 
                 final Icon icon2 = Icon.createWithResource(context, R.drawable.ic_sw_lap_24dp);
                 final CharSequence title2 = res.getText(R.string.sw_lap_button);
@@ -106,8 +105,8 @@
         } else {
             // Left button: Start
             final Intent start = new Intent(context, StopwatchService.class)
-                    .setAction(HandleDeskClockApiCalls.ACTION_START_STOPWATCH)
-                    .putExtra(HandleDeskClockApiCalls.EXTRA_EVENT_LABEL, eventLabel);
+                    .setAction(StopwatchService.ACTION_START_STOPWATCH)
+                    .putExtra(Events.EXTRA_EVENT_LABEL, eventLabel);
 
             final Icon icon1 = Icon.createWithResource(context, R.drawable.ic_start_24dp);
             final CharSequence title1 = res.getText(R.string.sw_start_button);
@@ -116,8 +115,8 @@
 
             // Right button: Reset (dismisses notification and resets stopwatch)
             final Intent reset = new Intent(context, StopwatchService.class)
-                    .setAction(HandleDeskClockApiCalls.ACTION_RESET_STOPWATCH)
-                    .putExtra(HandleDeskClockApiCalls.EXTRA_EVENT_LABEL, eventLabel);
+                    .setAction(StopwatchService.ACTION_RESET_STOPWATCH)
+                    .putExtra(Events.EXTRA_EVENT_LABEL, eventLabel);
 
             final Icon icon2 = Icon.createWithResource(context, R.drawable.ic_reset_24dp);
             final CharSequence title2 = res.getText(R.string.sw_reset_button);
@@ -129,11 +128,6 @@
             content.setViewVisibility(R.id.state, VISIBLE);
         }
 
-        // Swipe away will reset the stopwatch without bringing forward the app.
-        final Intent reset = new Intent(context, StopwatchService.class)
-                .setAction(HandleDeskClockApiCalls.ACTION_RESET_STOPWATCH)
-                .putExtra(HandleDeskClockApiCalls.EXTRA_EVENT_LABEL, eventLabel);
-
         return new Notification.Builder(context)
                 .setLocalOnly(true)
                 .setOngoing(running)
@@ -144,9 +138,8 @@
                 .setSmallIcon(R.drawable.stat_notify_stopwatch)
                 .setGroup(nm.getStopwatchNotificationGroupKey())
                 .setStyle(new Notification.DecoratedCustomViewStyle())
-                .setDeleteIntent(Utils.pendingServiceIntent(context, reset))
                 .setActions(actions.toArray(new Notification.Action[actions.size()]))
                 .setColor(ContextCompat.getColor(context, R.color.default_background))
                 .build();
     }
-}
\ No newline at end of file
+}
diff --git a/src/com/android/deskclock/data/StopwatchNotificationBuilderPreN.java b/src/com/android/deskclock/data/StopwatchNotificationBuilderPreN.java
index f89d254..50489bc 100644
--- a/src/com/android/deskclock/data/StopwatchNotificationBuilderPreN.java
+++ b/src/com/android/deskclock/data/StopwatchNotificationBuilderPreN.java
@@ -28,9 +28,9 @@
 import android.support.v4.content.ContextCompat;
 import android.widget.RemoteViews;
 
-import com.android.deskclock.HandleDeskClockApiCalls;
 import com.android.deskclock.R;
 import com.android.deskclock.Utils;
+import com.android.deskclock.events.Events;
 import com.android.deskclock.stopwatch.StopwatchService;
 
 import static android.view.View.GONE;
@@ -48,12 +48,11 @@
         @StringRes final int eventLabel = R.string.label_notification;
 
         // Intent to load the app when the notification is tapped.
-        final Intent showApp = new Intent(context, HandleDeskClockApiCalls.class)
-                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
-                .setAction(HandleDeskClockApiCalls.ACTION_SHOW_STOPWATCH)
-                .putExtra(HandleDeskClockApiCalls.EXTRA_EVENT_LABEL, eventLabel);
+        final Intent showApp = new Intent(context, StopwatchService.class)
+                .setAction(StopwatchService.ACTION_SHOW_STOPWATCH)
+                .putExtra(Events.EXTRA_EVENT_LABEL, eventLabel);
 
-        final PendingIntent pendingShowApp = PendingIntent.getActivity(context, 0, showApp,
+        final PendingIntent pendingShowApp = PendingIntent.getService(context, 0, showApp,
                 PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_UPDATE_CURRENT);
 
         // Compute some values required below.
@@ -64,12 +63,10 @@
 
         final RemoteViews collapsed = new RemoteViews(pname, R.layout.stopwatch_notif_collapsed);
         collapsed.setChronometer(R.id.swn_collapsed_chronometer, base, null, running);
-        collapsed.setOnClickPendingIntent(R.id.swn_collapsed_hitspace, pendingShowApp);
         collapsed.setImageViewResource(R.id.notification_icon, R.drawable.stat_notify_stopwatch);
 
         final RemoteViews expanded = new RemoteViews(pname, R.layout.stopwatch_notif_expanded);
         expanded.setChronometer(R.id.swn_expanded_chronometer, base, null, running);
-        expanded.setOnClickPendingIntent(R.id.swn_expanded_hitspace, pendingShowApp);
         expanded.setImageViewResource(R.id.notification_icon, R.drawable.stat_notify_stopwatch);
 
         @IdRes final int leftButtonId = R.id.swn_left_button;
@@ -79,8 +76,8 @@
             expanded.setTextViewText(leftButtonId, res.getText(R.string.sw_pause_button));
             setTextViewDrawable(expanded, leftButtonId, R.drawable.ic_pause_24dp);
             final Intent pause = new Intent(context, StopwatchService.class)
-                    .setAction(HandleDeskClockApiCalls.ACTION_PAUSE_STOPWATCH)
-                    .putExtra(HandleDeskClockApiCalls.EXTRA_EVENT_LABEL, eventLabel);
+                    .setAction(StopwatchService.ACTION_PAUSE_STOPWATCH)
+                    .putExtra(Events.EXTRA_EVENT_LABEL, eventLabel);
             final PendingIntent pendingPause = Utils.pendingServiceIntent(context, pause);
             expanded.setOnClickPendingIntent(leftButtonId, pendingPause);
 
@@ -90,8 +87,8 @@
                 setTextViewDrawable(expanded, rightButtonId, R.drawable.ic_sw_lap_24dp);
 
                 final Intent lap = new Intent(context, StopwatchService.class)
-                        .setAction(HandleDeskClockApiCalls.ACTION_LAP_STOPWATCH)
-                        .putExtra(HandleDeskClockApiCalls.EXTRA_EVENT_LABEL, eventLabel);
+                        .setAction(StopwatchService.ACTION_LAP_STOPWATCH)
+                        .putExtra(Events.EXTRA_EVENT_LABEL, eventLabel);
                 final PendingIntent pendingLap = Utils.pendingServiceIntent(context, lap);
                 expanded.setOnClickPendingIntent(rightButtonId, pendingLap);
                 expanded.setViewVisibility(rightButtonId, VISIBLE);
@@ -117,8 +114,8 @@
             expanded.setTextViewText(leftButtonId, res.getText(R.string.sw_start_button));
             setTextViewDrawable(expanded, leftButtonId, R.drawable.ic_start_24dp);
             final Intent start = new Intent(context, StopwatchService.class)
-                    .setAction(HandleDeskClockApiCalls.ACTION_START_STOPWATCH)
-                    .putExtra(HandleDeskClockApiCalls.EXTRA_EVENT_LABEL, eventLabel);
+                    .setAction(StopwatchService.ACTION_START_STOPWATCH)
+                    .putExtra(Events.EXTRA_EVENT_LABEL, eventLabel);
             final PendingIntent pendingStart = Utils.pendingServiceIntent(context, start);
             expanded.setOnClickPendingIntent(leftButtonId, pendingStart);
 
@@ -127,8 +124,8 @@
             expanded.setTextViewText(rightButtonId, res.getText(R.string.sw_reset_button));
             setTextViewDrawable(expanded, rightButtonId, R.drawable.ic_reset_24dp);
             final Intent reset = new Intent(context, StopwatchService.class)
-                    .setAction(HandleDeskClockApiCalls.ACTION_RESET_STOPWATCH)
-                    .putExtra(HandleDeskClockApiCalls.EXTRA_EVENT_LABEL, eventLabel);
+                    .setAction(StopwatchService.ACTION_RESET_STOPWATCH)
+                    .putExtra(Events.EXTRA_EVENT_LABEL, eventLabel);
             final PendingIntent pendingReset = Utils.pendingServiceIntent(context, reset);
             expanded.setOnClickPendingIntent(rightButtonId, pendingReset);
 
@@ -139,19 +136,14 @@
             expanded.setViewVisibility(R.id.swn_expanded_laps, VISIBLE);
         }
 
-        // Swipe away will reset the stopwatch without bringing forward the app.
-        final Intent reset = new Intent(context, StopwatchService.class)
-                .setAction(HandleDeskClockApiCalls.ACTION_RESET_STOPWATCH)
-                .putExtra(HandleDeskClockApiCalls.EXTRA_EVENT_LABEL, eventLabel);
-
         final Notification notification = new NotificationCompat.Builder(context)
                 .setLocalOnly(true)
                 .setOngoing(running)
                 .setContent(collapsed)
+                .setContentIntent(pendingShowApp)
                 .setAutoCancel(stopwatch.isPaused())
                 .setPriority(NotificationCompat.PRIORITY_MAX)
                 .setSmallIcon(R.drawable.stat_notify_stopwatch)
-                .setDeleteIntent(Utils.pendingServiceIntent(context, reset))
                 .setColor(ContextCompat.getColor(context, R.color.default_background))
                 .build();
         notification.bigContentView = expanded;
@@ -161,4 +153,4 @@
     private static void setTextViewDrawable(RemoteViews rv, int viewId, int drawableId) {
         rv.setTextViewCompoundDrawablesRelative(viewId, drawableId, 0, 0, 0);
     }
-}
\ No newline at end of file
+}
diff --git a/src/com/android/deskclock/data/Timer.java b/src/com/android/deskclock/data/Timer.java
index 27c944d..11e25cc 100644
--- a/src/com/android/deskclock/data/Timer.java
+++ b/src/com/android/deskclock/data/Timer.java
@@ -16,13 +16,8 @@
 
 package com.android.deskclock.data;
 
-import android.content.ContentUris;
-import android.net.Uri;
-import android.os.SystemClock;
 import android.text.TextUtils;
 
-import com.android.deskclock.provider.ClockContract;
-
 import java.util.Arrays;
 import java.util.Comparator;
 import java.util.List;
@@ -30,7 +25,10 @@
 import static android.text.format.DateUtils.HOUR_IN_MILLIS;
 import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
 import static android.text.format.DateUtils.SECOND_IN_MILLIS;
+import static com.android.deskclock.Utils.now;
+import static com.android.deskclock.Utils.wallClock;
 import static com.android.deskclock.data.Timer.State.EXPIRED;
+import static com.android.deskclock.data.Timer.State.MISSED;
 import static com.android.deskclock.data.Timer.State.PAUSED;
 import static com.android.deskclock.data.Timer.State.RESET;
 import static com.android.deskclock.data.Timer.State.RUNNING;
@@ -41,7 +39,7 @@
 public final class Timer {
 
     public enum State {
-        RUNNING(1), PAUSED(2), EXPIRED(3), RESET(4);
+        RUNNING(1), PAUSED(2), EXPIRED(3), RESET(4), MISSED(5);
 
         /** The value assigned to this State in prior releases. */
         private final int mValue;
@@ -71,12 +69,6 @@
         }
     }
 
-    /**
-     * The content:// style URI for timers.
-     */
-    public static final Uri CONTENT_URI =
-            Uri.parse("content://" + ClockContract.AUTHORITY + "/timers");
-
     /** The minimum duration of a timer. */
     public static final long MIN_LENGTH = SECOND_IN_MILLIS;
 
@@ -84,7 +76,9 @@
     public static final long MAX_LENGTH =
             99 * HOUR_IN_MILLIS + 99 * MINUTE_IN_MILLIS + 99 * SECOND_IN_MILLIS;
 
-    /** A unique identifier for the city. */
+    static final long UNUSED = Long.MIN_VALUE;
+
+    /** A unique identifier for the timer. */
     private final int mId;
 
     /** The current state of the timer. */
@@ -96,9 +90,12 @@
     /** The length of the timer in milliseconds including additional time added by the user. */
     private final long mTotalLength;
 
-    /** The time at which the timer was last started; {@link Long#MIN_VALUE} when not running. */
+    /** The time at which the timer was last started; {@link #UNUSED} when not running. */
     private final long mLastStartTime;
 
+    /** The time since epoch at which the timer was last started. */
+    private final long mLastStartWallClockTime;
+
     /** The time at which the timer is scheduled to expire; negative if it is already expired. */
     private final long mRemainingTime;
 
@@ -109,12 +106,13 @@
     private final boolean mDeleteAfterUse;
 
     Timer(int id, State state, long length, long totalLength, long lastStartTime,
-            long remainingTime, String label, boolean deleteAfterUse) {
+          long lastWallClockTime, long remainingTime, String label, boolean deleteAfterUse) {
         mId = id;
         mState = state;
         mLength = length;
         mTotalLength = totalLength;
         mLastStartTime = lastStartTime;
+        mLastStartWallClockTime = lastWallClockTime;
         mRemainingTime = remainingTime;
         mLabel = label;
         mDeleteAfterUse = deleteAfterUse;
@@ -130,13 +128,7 @@
     public boolean isRunning() { return mState == RUNNING; }
     public boolean isPaused() { return mState == PAUSED; }
     public boolean isExpired() { return mState == EXPIRED; }
-
-    /**
-     * @return the {@link Uri} identifying the timer instance.
-     */
-    public Uri getContentUri() {
-        return ContentUris.withAppendedId(CONTENT_URI, mId);
-    }
+    public boolean isMissed() { return mState == MISSED; }
 
     /**
      * @return the amount of remaining time when the timer was last started or paused.
@@ -146,11 +138,11 @@
     }
 
     /**
-     * @return the total amount of time remaining up to this moment; expired timers will return a
-     *      negative amount
+     * @return the total amount of time remaining up to this moment; expired and missed timers will
+     * return a negative amount
      */
     public long getRemainingTime() {
-        if (mState == RUNNING || mState == EXPIRED) {
+        if (mState == RUNNING || mState == EXPIRED || mState == MISSED) {
             return mRemainingTime - (now() - mLastStartTime);
         }
 
@@ -158,10 +150,10 @@
     }
 
     /**
-     * @return the time at which this timer will or did expire
+     * @return the elapsed realtime at which this timer will or did expire
      */
     public long getExpirationTime() {
-        if (mState != RUNNING && mState != EXPIRED) {
+        if (mState != RUNNING && mState != EXPIRED && mState != MISSED) {
             throw new IllegalStateException("cannot compute expiration time in state " + mState);
         }
 
@@ -169,6 +161,17 @@
     }
 
     /**
+     * @return the wall clock time at which this timer will or did expire
+     */
+    public long getWallClockExpirationTime() {
+        if (mState != RUNNING && mState != EXPIRED && mState != MISSED) {
+            throw new IllegalStateException("cannot compute expiration time in state " + mState);
+        }
+
+        return mLastStartWallClockTime + mRemainingTime;
+    }
+
+    /**
      *
      * @return the total amount of time elapsed up to this moment; expired timers will report more
      *      than the {@link #getTotalLength() total length}
@@ -178,17 +181,18 @@
     }
 
     long getLastStartTime() { return mLastStartTime; }
+    long getLastWallClockTime() { return mLastStartWallClockTime; }
 
     /**
-     * @return a copy of this timer that is running or expired
+     * @return a copy of this timer that is running, expired or missed
      */
     Timer start() {
-        if (mState == RUNNING || mState == EXPIRED) {
+        if (mState == RUNNING || mState == EXPIRED || mState == MISSED) {
             return this;
         }
 
-        return new Timer(mId, RUNNING, mLength, mTotalLength, now(), mRemainingTime, mLabel,
-                mDeleteAfterUse);
+        return new Timer(mId, RUNNING, mLength, mTotalLength, now(), wallClock(), mRemainingTime,
+                mLabel, mDeleteAfterUse);
     }
 
     /**
@@ -197,25 +201,39 @@
     Timer pause() {
         if (mState == PAUSED || mState == RESET) {
             return this;
-        } else if (mState == EXPIRED) {
+        } else if (mState == EXPIRED || mState == MISSED) {
             return reset();
         }
 
         final long remainingTime = getRemainingTime();
-        return new Timer(mId, PAUSED, mLength, mTotalLength, Long.MIN_VALUE, remainingTime, mLabel,
+        return new Timer(mId, PAUSED, mLength, mTotalLength, UNUSED, UNUSED, remainingTime, mLabel,
                 mDeleteAfterUse);
     }
 
     /**
-     * @return a copy of this timer that is expired or reset
+     * @return a copy of this timer that is expired, missed or reset
      */
     Timer expire() {
-        if (mState == EXPIRED || mState == RESET) {
+        if (mState == EXPIRED || mState == RESET || mState == MISSED) {
             return this;
         }
 
-        return new Timer(mId, EXPIRED, mLength, mTotalLength, mLastStartTime, mRemainingTime,
-                mLabel, mDeleteAfterUse);
+        final long remainingTime = Math.min(0L, getRemainingTime());
+        return new Timer(mId, EXPIRED, mLength, 0L, now(), wallClock(), remainingTime, mLabel,
+                mDeleteAfterUse);
+    }
+
+    /**
+     * @return a copy of this timer that is missed or reset
+     */
+    Timer miss() {
+        if (mState == RESET || mState == MISSED) {
+            return this;
+        }
+
+        final long remainingTime = Math.min(0L, getRemainingTime());
+        return new Timer(mId, MISSED, mLength, 0L, now(), wallClock(), remainingTime, mLabel,
+                mDeleteAfterUse);
     }
 
     /**
@@ -226,11 +244,51 @@
             return this;
         }
 
-        return new Timer(mId, RESET, mLength, mLength, Long.MIN_VALUE, mLength, mLabel,
+        return new Timer(mId, RESET, mLength, mLength, UNUSED, UNUSED, mLength, mLabel,
                 mDeleteAfterUse);
     }
 
     /**
+     * @return a copy of this timer that has its times adjusted after a reboot
+     */
+    Timer updateAfterReboot() {
+        if (mState == RESET || mState == PAUSED) {
+            return this;
+        }
+
+        final long timeSinceBoot = now();
+        final long wallClockTime = wallClock();
+        // Avoid negative time deltas. They can happen in practice, but they can't be used. Simply
+        // update the recorded times and proceed with no change in accumulated time.
+        final long delta = Math.max(0, wallClockTime - mLastStartWallClockTime);
+        final long remainingTime = mRemainingTime - delta;
+        return new Timer(mId, mState, mLength, mTotalLength, timeSinceBoot, wallClockTime,
+                remainingTime, mLabel, mDeleteAfterUse);
+    }
+
+    /**
+     * @return a copy of this timer that has its times adjusted after time has been set
+     */
+    Timer updateAfterTimeSet() {
+        if (mState == RESET || mState == PAUSED) {
+            return this;
+        }
+
+        final long timeSinceBoot = now();
+        final long wallClockTime = wallClock();
+        final long delta = timeSinceBoot - mLastStartTime;
+        final long remainingTime = mRemainingTime - delta;
+        if (delta < 0) {
+            // Avoid negative time deltas. They typically happen following reboots when TIME_SET is
+            // broadcast before BOOT_COMPLETED. Simply ignore the time update and hope
+            // updateAfterReboot() can successfully correct the data at a later time.
+            return this;
+        }
+        return new Timer(mId, mState, mLength, mTotalLength, timeSinceBoot, wallClockTime,
+                remainingTime, mLabel, mDeleteAfterUse);
+    }
+
+    /**
      * @return a copy of this timer with the given {@code label}
      */
     Timer setLabel(String label) {
@@ -238,8 +296,61 @@
             return this;
         }
 
-        return new Timer(mId, mState, mLength, mTotalLength, mLastStartTime, mRemainingTime, label,
-                mDeleteAfterUse);
+        return new Timer(mId, mState, mLength, mTotalLength, mLastStartTime,
+                mLastStartWallClockTime, mRemainingTime, label, mDeleteAfterUse);
+    }
+
+    /**
+     * @return a copy of this timer with the given {@code length}
+     */
+    Timer setLength(long length) {
+        if (mLength == length
+                || length <= 0L
+                || length > MAX_LENGTH) {
+            return this;
+        }
+
+        final long totalLength;
+        final long remainingTime;
+        if (mState == RESET) {
+            totalLength = length;
+            remainingTime = length;
+        } else {
+            totalLength = mTotalLength;
+            remainingTime = mRemainingTime;
+        }
+
+        return new Timer(mId, mState, length, totalLength, mLastStartTime,
+                mLastStartWallClockTime, remainingTime, mLabel, mDeleteAfterUse);
+    }
+
+    /**
+     * @return a copy of this timer with the given {@code remainingTime}
+     */
+    Timer setRemainingTime(long remainingTime) {
+        // Do not allow the remaining time to exceed the maximum.
+        if (mRemainingTime == remainingTime || remainingTime > MAX_LENGTH) {
+            return this;
+        }
+
+        final long delta = remainingTime - mRemainingTime;
+        final long totalLength = mTotalLength + delta;
+
+        final long lastStartTime;
+        final long lastWallClockTime;
+        final State state;
+        if (remainingTime > 0 && (mState == EXPIRED || mState == MISSED)) {
+            state = RUNNING;
+            lastStartTime = now();
+            lastWallClockTime = wallClock();
+        } else {
+            state = mState;
+            lastStartTime = mLastStartTime;
+            lastWallClockTime = mLastStartWallClockTime;
+        }
+
+        return new Timer(mId, state, mLength, totalLength, lastStartTime,
+                lastWallClockTime, remainingTime, mLabel, mDeleteAfterUse);
     }
 
     /**
@@ -247,29 +358,8 @@
      *      length, or this Timer if adding a minute would exceed the maximum timer duration
      */
     Timer addMinute() {
-        final long lastStartTime;
-        final long remainingTime;
-        final long totalLength;
-        final State state;
-        if (mState == EXPIRED) {
-            state = RUNNING;
-            lastStartTime = now();
-            totalLength = MINUTE_IN_MILLIS;
-            remainingTime = MINUTE_IN_MILLIS;
-        } else {
-            state = mState;
-            lastStartTime = mLastStartTime;
-            totalLength = mRemainingTime + MINUTE_IN_MILLIS;
-            remainingTime = mRemainingTime + MINUTE_IN_MILLIS;
-        }
-
-        // Do not allow the remaining time to exceed the maximum.
-        if (remainingTime > MAX_LENGTH) {
-            return this;
-        }
-
-        return new Timer(mId, state, mLength, totalLength, lastStartTime, remainingTime, mLabel,
-                mDeleteAfterUse);
+        final long remainingTime = (mState == EXPIRED || mState == MISSED) ? 0L : mRemainingTime;
+        return setRemainingTime(remainingTime + MINUTE_IN_MILLIS);
     }
 
     @Override
@@ -288,10 +378,6 @@
         return mId;
     }
 
-    private static long now() {
-        return SystemClock.elapsedRealtime();
-    }
-
     /**
      * Orders timers by their IDs. Oldest timers are at the bottom. Newest timers are at the top.
      */
@@ -306,6 +392,7 @@
      * Orders timers by their expected/actual expiration time. The general order is:
      *
      * <ol>
+     *     <li>{@link State#MISSED MISSED} timers; ties broken by {@link #getRemainingTime()}</li>
      *     <li>{@link State#EXPIRED EXPIRED} timers; ties broken by {@link #getRemainingTime()}</li>
      *     <li>{@link State#RUNNING RUNNING} timers; ties broken by {@link #getRemainingTime()}</li>
      *     <li>{@link State#PAUSED PAUSED} timers; ties broken by {@link #getRemainingTime()}</li>
@@ -314,7 +401,8 @@
      */
     public static Comparator<Timer> EXPIRY_COMPARATOR = new Comparator<Timer>() {
 
-        private final List<State> stateExpiryOrder = Arrays.asList(EXPIRED, RUNNING, PAUSED, RESET);
+        private final List<State> stateExpiryOrder = Arrays.asList(MISSED, EXPIRED, RUNNING, PAUSED,
+                RESET);
 
         @Override
         public int compare(Timer timer1, Timer timer2) {
diff --git a/src/com/android/deskclock/data/TimerDAO.java b/src/com/android/deskclock/data/TimerDAO.java
index 2ea6fd8..462f4d9 100644
--- a/src/com/android/deskclock/data/TimerDAO.java
+++ b/src/com/android/deskclock/data/TimerDAO.java
@@ -54,6 +54,9 @@
     /** Prefix for a key to a preference that stores the last start time of the timer. */
     private static final String LAST_START_TIME = "timer_start_time_";
 
+    /** Prefix for a key to a preference that stores the epoch time when the timer last started. */
+    private static final String LAST_WALL_CLOCK_TIME = "timer_wall_clock_time_";
+
     /** Prefix for a key to a preference that stores the remaining time before expiry. */
     private static final String REMAINING_TIME = "timer_time_left_";
 
@@ -86,12 +89,14 @@
             if (state != null) {
                 final long length = prefs.getLong(LENGTH + id, Long.MIN_VALUE);
                 final long totalLength = prefs.getLong(TOTAL_LENGTH + id, Long.MIN_VALUE);
-                final long lastStartTime = prefs.getLong(LAST_START_TIME + id, Long.MIN_VALUE);
+                final long lastStartTime = prefs.getLong(LAST_START_TIME + id, Timer.UNUSED);
+                final long lastWallClockTime = prefs.getLong(LAST_WALL_CLOCK_TIME + id,
+                        Timer.UNUSED);
                 final long remainingTime = prefs.getLong(REMAINING_TIME + id, totalLength);
                 final String label = prefs.getString(LABEL + id, null);
                 final boolean deleteAfterUse = prefs.getBoolean(DELETE_AFTER_USE + id, false);
-                timers.add(new Timer(id, state, length, totalLength, lastStartTime, remainingTime,
-                        label, deleteAfterUse));
+                timers.add(new Timer(id, state, length, totalLength, lastStartTime,
+                        lastWallClockTime, remainingTime, label, deleteAfterUse));
             }
         }
 
@@ -119,6 +124,7 @@
         editor.putLong(LENGTH + id, timer.getLength());
         editor.putLong(TOTAL_LENGTH + id, timer.getTotalLength());
         editor.putLong(LAST_START_TIME + id, timer.getLastStartTime());
+        editor.putLong(LAST_WALL_CLOCK_TIME + id, timer.getLastWallClockTime());
         editor.putLong(REMAINING_TIME + id, timer.getRemainingTime());
         editor.putString(LABEL + id, timer.getLabel());
         editor.putBoolean(DELETE_AFTER_USE + id, timer.getDeleteAfterUse());
@@ -127,8 +133,8 @@
 
         // Return a new timer with the generated timer id present.
         return new Timer(id, timer.getState(), timer.getLength(), timer.getTotalLength(),
-                timer.getLastStartTime(), timer.getRemainingTime(), timer.getLabel(),
-                timer.getDeleteAfterUse());
+                timer.getLastStartTime(), timer.getLastWallClockTime(), timer.getRemainingTime(),
+                timer.getLabel(), timer.getDeleteAfterUse());
     }
 
     /**
@@ -144,6 +150,7 @@
         editor.putLong(LENGTH + id, timer.getLength());
         editor.putLong(TOTAL_LENGTH + id, timer.getTotalLength());
         editor.putLong(LAST_START_TIME + id, timer.getLastStartTime());
+        editor.putLong(LAST_WALL_CLOCK_TIME + id, timer.getLastWallClockTime());
         editor.putLong(REMAINING_TIME + id, timer.getRemainingTime());
         editor.putString(LABEL + id, timer.getLabel());
         editor.putBoolean(DELETE_AFTER_USE + id, timer.getDeleteAfterUse());
@@ -175,6 +182,7 @@
         editor.remove(LENGTH + id);
         editor.remove(TOTAL_LENGTH + id);
         editor.remove(LAST_START_TIME + id);
+        editor.remove(LAST_WALL_CLOCK_TIME + id);
         editor.remove(REMAINING_TIME + id);
         editor.remove(LABEL + id);
         editor.remove(DELETE_AFTER_USE + id);
diff --git a/src/com/android/deskclock/data/TimerModel.java b/src/com/android/deskclock/data/TimerModel.java
index a07ab50..f0adbcb 100644
--- a/src/com/android/deskclock/data/TimerModel.java
+++ b/src/com/android/deskclock/data/TimerModel.java
@@ -49,6 +49,7 @@
 import java.util.Set;
 
 import static android.app.AlarmManager.ELAPSED_REALTIME_WAKEUP;
+import static android.text.format.DateUtils.*;
 import static com.android.deskclock.data.Timer.State.EXPIRED;
 import static com.android.deskclock.data.Timer.State.RESET;
 
@@ -57,6 +58,12 @@
  */
 final class TimerModel {
 
+    /**
+     * Running timers less than this threshold are left running/expired; greater than this
+     * threshold are considered missed.
+     */
+    private static final long MISSED_THRESHOLD = -MINUTE_IN_MILLIS;
+
     private final Context mContext;
 
     /** The alarm manager system service that calls back when timers expire. */
@@ -105,6 +112,9 @@
     /** A mutable copy of the expired timers. */
     private List<Timer> mExpiredTimers;
 
+    /** A mutable copy of the missed timers. */
+    private List<Timer> mMissedTimers;
+
     /** Delegate that builds platform-specific timer notifications. */
     private NotificationBuilder mNotificationBuilder;
 
@@ -161,6 +171,13 @@
     }
 
     /**
+     * @return all missed timers in their expiration order
+     */
+    List<Timer> getMissedTimers() {
+        return Collections.unmodifiableList(getMutableMissedTimers());
+    }
+
+    /**
      * @param timerId identifies the timer to return
      * @return the timer with the given {@code timerId}
      */
@@ -191,8 +208,8 @@
      */
     Timer addTimer(long length, String label, boolean deleteAfterUse) {
         // Create the timer instance.
-        Timer timer = new Timer(-1, RESET, length, length, Long.MIN_VALUE, length, label,
-                deleteAfterUse);
+        Timer timer = new Timer(-1, RESET, length, length, Timer.UNUSED, Timer.UNUSED, length,
+                label, deleteAfterUse);
 
         // Add the timer to permanent storage.
         timer = TimerDAO.addTimer(mContext, timer);
@@ -254,9 +271,10 @@
         doRemoveTimer(timer);
 
         // Update the timer notifications after removing the timer data.
-        updateNotification();
         if (timer.isExpired()) {
             updateHeadsUpNotification();
+        } else {
+            updateNotification();
         }
     }
 
@@ -265,37 +283,55 @@
      * removes the the timer. The timer is otherwise transitioned to the reset state and continues
      * to exist.
      *
-     * @param timer the timer to be reset
+     * @param timer        the timer to be reset
+     * @param allowDelete  {@code true} if the timer is allowed to be deleted instead of reset
+     *                     (e.g. one use timers)
      * @param eventLabelId the label of the timer event to send; 0 if no event should be sent
      * @return the reset {@code timer} or {@code null} if the timer was deleted
      */
-    Timer resetOrDeleteTimer(Timer timer, @StringRes int eventLabelId) {
-        final Timer result = doResetOrDeleteTimer(timer, eventLabelId);
+    Timer resetTimer(Timer timer, boolean allowDelete, @StringRes int eventLabelId) {
+        final Timer result = doResetOrDeleteTimer(timer, allowDelete, eventLabelId);
 
         // Update the notification after updating the timer data.
-        updateNotification();
-
-        // If the timer stopped being expired, update the heads-up notification.
-        if (timer.isExpired()) {
+        if (timer.isMissed()) {
+            updateMissedNotification();
+        } else if (timer.isExpired()) {
             updateHeadsUpNotification();
+        } else {
+            updateNotification();
         }
 
         return result;
     }
 
     /**
-     * Reset all timers.
-     *
-     * @param eventLabelId the label of the timer event to send; 0 if no event should be sent
+     * Update timers after system reboot.
      */
-    void resetTimers(@StringRes int eventLabelId) {
+    void updateTimersAfterReboot() {
         final List<Timer> timers = new ArrayList<>(getTimers());
         for (Timer timer : timers) {
-            doResetOrDeleteTimer(timer, eventLabelId);
+            doUpdateAfterRebootTimer(timer);
         }
 
-        // Update the notifications once after all timers are reset.
+        // Update the notifications once after all timers are updated.
         updateNotification();
+        updateMissedNotification();
+        updateHeadsUpNotification();
+    }
+
+
+    /**
+     * Update timers after time set.
+     */
+    void updateTimersAfterTimeSet() {
+        final List<Timer> timers = new ArrayList<>(getTimers());
+        for (Timer timer : timers) {
+            doUpdateAfterTimeSetTimer(timer);
+        }
+
+        // Update the notifications once after all timers are updated.
+        updateNotification();
+        updateMissedNotification();
         updateHeadsUpNotification();
     }
 
@@ -308,16 +344,32 @@
         final List<Timer> timers = new ArrayList<>(getTimers());
         for (Timer timer : timers) {
             if (timer.isExpired()) {
-                doResetOrDeleteTimer(timer, eventLabelId);
+                doResetOrDeleteTimer(timer, true /* allowDelete */, eventLabelId);
             }
         }
 
         // Update the notifications once after all timers are updated.
-        updateNotification();
         updateHeadsUpNotification();
     }
 
     /**
+     * Reset all missed timers.
+     *
+     * @param eventLabelId the label of the timer event to send; 0 if no event should be sent
+     */
+    void resetMissedTimers(@StringRes int eventLabelId) {
+        final List<Timer> timers = new ArrayList<>(getTimers());
+        for (Timer timer : timers) {
+            if (timer.isMissed()) {
+                doResetOrDeleteTimer(timer, true /* allowDelete */, eventLabelId);
+            }
+        }
+
+        // Update the notifications once after all timers are updated.
+        updateMissedNotification();
+    }
+
+    /**
      * Reset all unexpired timers.
      *
      * @param eventLabelId the label of the timer event to send; 0 if no event should be sent
@@ -326,7 +378,7 @@
         final List<Timer> timers = new ArrayList<>(getTimers());
         for (Timer timer : timers) {
             if (timer.isRunning() || timer.isPaused()) {
-                doResetOrDeleteTimer(timer, eventLabelId);
+                doResetOrDeleteTimer(timer, true /* allowDelete */, eventLabelId);
             }
         }
 
@@ -430,6 +482,21 @@
         return mExpiredTimers;
     }
 
+    private List<Timer> getMutableMissedTimers() {
+        if (mMissedTimers == null) {
+            mMissedTimers = new ArrayList<>();
+
+            for (Timer timer : getMutableTimers()) {
+                if (timer.isMissed()) {
+                    mMissedTimers.add(timer);
+                }
+            }
+            Collections.sort(mMissedTimers, Timer.EXPIRY_COMPARATOR);
+        }
+
+        return mMissedTimers;
+    }
+
     /**
      * This method updates timer data without updating notifications. This is useful in bulk-update
      * scenarios so the notifications are only rebuilt once.
@@ -458,6 +525,10 @@
         if (before.isExpired() || timer.isExpired()) {
             mExpiredTimers = null;
         }
+        // Clear the cache of missed timers if the timer changed to/from missed.
+        if (before.isMissed() || timer.isMissed()) {
+            mMissedTimers = null;
+        }
 
         // Update the timer expiration callback.
         updateAlarmManager();
@@ -499,6 +570,11 @@
             mExpiredTimers = null;
         }
 
+        // Clear the cache of missed timers if a new missed timer was added.
+        if (timer.isMissed()) {
+            mMissedTimers = null;
+        }
+
         // Update the timer expiration callback.
         updateAlarmManager();
 
@@ -520,11 +596,16 @@
      * to exist.
      *
      * @param timer the timer to be reset
+     * @param allowDelete  {@code true} if the timer is allowed to be deleted instead of reset
+     *                     (e.g. one use timers)
      * @param eventLabelId the label of the timer event to send; 0 if no event should be sent
      * @return the reset {@code timer} or {@code null} if the timer was deleted
      */
-    private Timer doResetOrDeleteTimer(Timer timer, @StringRes int eventLabelId) {
-        if (timer.isExpired() && timer.getDeleteAfterUse()) {
+    private Timer doResetOrDeleteTimer(Timer timer, boolean allowDelete,
+            @StringRes int eventLabelId) {
+        if (allowDelete
+                && (timer.isExpired() || timer.isMissed())
+                && timer.getDeleteAfterUse()) {
             doRemoveTimer(timer);
             if (eventLabelId != 0) {
                 Events.sendTimerEvent(R.string.action_delete, eventLabelId);
@@ -543,6 +624,25 @@
     }
 
     /**
+     * This method updates/removes timer data after a reboot without updating notifications.
+     *
+     * @param timer the timer to be updated
+     */
+    private void doUpdateAfterRebootTimer(Timer timer) {
+        Timer updated = timer.updateAfterReboot();
+        if (updated.getRemainingTime() < MISSED_THRESHOLD && updated.isRunning()) {
+            updated = updated.miss();
+        }
+        doUpdateTimer(updated);
+    }
+
+    private void doUpdateAfterTimeSetTimer(Timer timer) {
+        final Timer updated = timer.updateAfterTimeSet();
+        doUpdateTimer(updated);
+    }
+
+
+    /**
      * Updates the callback given to this application from the {@link AlarmManager} that signals the
      * expiration of the next timer. If no timers are currently set to expire (i.e. no running
      * timers exist) then this method clears the expiration callback from AlarmManager.
@@ -641,6 +741,31 @@
                 getNotificationBuilder().build(mContext, mNotificationModel, unexpired);
         final int notificationId = mNotificationModel.getUnexpiredTimerNotificationId();
         mNotificationManager.notify(notificationId, notification);
+
+    }
+
+    /**
+     * Updates the notification controlling missed timers. This notification is only displayed when
+     * the application is not open.
+     */
+    void updateMissedNotification() {
+        // Notifications should be hidden if the app is open.
+        if (mNotificationModel.isApplicationInForeground()) {
+            mNotificationManager.cancel(mNotificationModel.getMissedTimerNotificationId());
+            return;
+        }
+
+        final List<Timer> missed = getMissedTimers();
+
+        if (missed.isEmpty()) {
+            mNotificationManager.cancel(mNotificationModel.getMissedTimerNotificationId());
+            return;
+        }
+
+        final Notification notification = getNotificationBuilder().buildMissed(mContext,
+                mNotificationModel, missed);
+        final int notificationId = mNotificationModel.getMissedTimerNotificationId();
+        mNotificationManager.notify(notificationId, notification);
     }
 
     /**
@@ -687,6 +812,7 @@
         @Override
         public void onReceive(Context context, Intent intent) {
             updateNotification();
+            updateMissedNotification();
             updateHeadsUpNotification();
         }
     }
@@ -720,6 +846,10 @@
      * An API for building platform-specific timer notifications.
      */
     public interface NotificationBuilder {
+
+        int REQUEST_CODE_UPCOMING = 0;
+        int REQUEST_CODE_MISSING = 1;
+
         /**
          * @param context a context to use for fetching resources
          * @param nm from which notification data are fetched
@@ -734,5 +864,12 @@
          * @return a heads-up notification reporting the state of the {@code expiredTimers}
          */
         Notification buildHeadsUp(Context context, List<Timer> expiredTimers);
+
+        /**
+         * @param context a context to use for fetching resources
+         * @param missedTimers all missed timers
+         * @return a heads-up notification reporting the state of the {@code missedTimers}
+         */
+        Notification buildMissed(Context context, NotificationModel nm, List<Timer> missedTimers);
     }
-}
\ No newline at end of file
+}
diff --git a/src/com/android/deskclock/data/TimerNotificationBuilderN.java b/src/com/android/deskclock/data/TimerNotificationBuilderN.java
index 9015c89..a2009ee 100644
--- a/src/com/android/deskclock/data/TimerNotificationBuilderN.java
+++ b/src/com/android/deskclock/data/TimerNotificationBuilderN.java
@@ -29,9 +29,9 @@
 import android.text.TextUtils;
 import android.widget.RemoteViews;
 
-import com.android.deskclock.HandleDeskClockApiCalls;
 import com.android.deskclock.R;
 import com.android.deskclock.Utils;
+import com.android.deskclock.events.Events;
 import com.android.deskclock.timer.ExpiredTimersActivity;
 import com.android.deskclock.timer.TimerService;
 
@@ -75,8 +75,8 @@
 
                 // Left button: Pause
                 final Intent pause = new Intent(context, TimerService.class)
-                        .setAction(HandleDeskClockApiCalls.ACTION_PAUSE_TIMER)
-                        .putExtra(HandleDeskClockApiCalls.EXTRA_TIMER_ID, timer.getId());
+                        .setAction(TimerService.ACTION_PAUSE_TIMER)
+                        .putExtra(TimerService.EXTRA_TIMER_ID, timer.getId());
 
                 final Icon icon1 = Icon.createWithResource(context, R.drawable.ic_pause_24dp);
                 final CharSequence title1 = res.getText(R.string.timer_pause);
@@ -85,8 +85,8 @@
 
                 // Right Button: +1 Minute
                 final Intent addMinute = new Intent(context, TimerService.class)
-                        .setAction(HandleDeskClockApiCalls.ACTION_ADD_MINUTE_TIMER)
-                        .putExtra(HandleDeskClockApiCalls.EXTRA_TIMER_ID, timer.getId());
+                        .setAction(TimerService.ACTION_ADD_MINUTE_TIMER)
+                        .putExtra(TimerService.EXTRA_TIMER_ID, timer.getId());
 
                 final Icon icon2 = Icon.createWithResource(context, R.drawable.ic_add_24dp);
                 final CharSequence title2 = res.getText(R.string.timer_plus_1_min);
@@ -99,8 +99,8 @@
 
                 // Left button: Start
                 final Intent start = new Intent(context, TimerService.class)
-                        .setAction(HandleDeskClockApiCalls.ACTION_START_TIMER)
-                        .putExtra(HandleDeskClockApiCalls.EXTRA_TIMER_ID, timer.getId());
+                        .setAction(TimerService.ACTION_START_TIMER)
+                        .putExtra(TimerService.EXTRA_TIMER_ID, timer.getId());
 
                 final Icon icon1 = Icon.createWithResource(context, R.drawable.ic_start_24dp);
                 final CharSequence title1 = res.getText(R.string.sw_resume_button);
@@ -109,8 +109,8 @@
 
                 // Right Button: Reset
                 final Intent reset = new Intent(context, TimerService.class)
-                        .setAction(HandleDeskClockApiCalls.ACTION_RESET_TIMER)
-                        .putExtra(HandleDeskClockApiCalls.EXTRA_TIMER_ID, timer.getId());
+                        .setAction(TimerService.ACTION_RESET_TIMER)
+                        .putExtra(TimerService.EXTRA_TIMER_ID, timer.getId());
 
                 final Icon icon2 = Icon.createWithResource(context, R.drawable.ic_reset_24dp);
                 final CharSequence title2 = res.getText(R.string.sw_reset_button);
@@ -137,13 +137,13 @@
         content.setTextViewText(R.id.state, stateText);
 
         // Intent to load the app and show the timer when the notification is tapped.
-        final Intent showApp = new Intent(context, HandleDeskClockApiCalls.class)
-                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
-                .setAction(HandleDeskClockApiCalls.ACTION_SHOW_TIMERS)
-                .putExtra(HandleDeskClockApiCalls.EXTRA_TIMER_ID, timer.getId())
-                .putExtra(HandleDeskClockApiCalls.EXTRA_EVENT_LABEL, R.string.label_notification);
+        final Intent showApp = new Intent(context, TimerService.class)
+                .setAction(TimerService.ACTION_SHOW_TIMER)
+                .putExtra(TimerService.EXTRA_TIMER_ID, timer.getId())
+                .putExtra(Events.EXTRA_EVENT_LABEL, R.string.label_notification);
 
-        final PendingIntent pendingShowApp = PendingIntent.getActivity(context, 0, showApp,
+        final PendingIntent pendingShowApp =
+                PendingIntent.getService(context, REQUEST_CODE_UPCOMING, showApp,
                 PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_UPDATE_CURRENT);
 
         return new Notification.Builder(context)
@@ -157,6 +157,7 @@
                 .setCategory(Notification.CATEGORY_ALARM)
                 .setSmallIcon(R.drawable.stat_notify_timer)
                 .setGroup(nm.getTimerNotificationGroupKey())
+                .setSortKey(nm.getTimerNotificationSortKey())
                 .setVisibility(Notification.VISIBILITY_PUBLIC)
                 .setStyle(new Notification.DecoratedCustomViewStyle())
                 .setActions(actions.toArray(new Notification.Action[actions.size()]))
@@ -230,11 +231,89 @@
                 .setCustomContentView(contentView)
                 .setPriority(Notification.PRIORITY_MAX)
                 .setDefaults(Notification.DEFAULT_LIGHTS)
-                .setColor(ContextCompat.getColor(context, R.color.default_background))
                 .setSmallIcon(R.drawable.stat_notify_timer)
                 .setFullScreenIntent(pendingFullScreen, true)
                 .setStyle(new Notification.DecoratedCustomViewStyle())
                 .setActions(actions.toArray(new Notification.Action[actions.size()]))
+                .setColor(ContextCompat.getColor(context, R.color.default_background))
+                .build();
+    }
+
+    @Override
+    public Notification buildMissed(Context context, NotificationModel nm,
+            List<Timer> missedTimers) {
+        final Timer timer = missedTimers.get(0);
+        final int count = missedTimers.size();
+
+        // Compute some values required below.
+        final long base = getChronometerBase(timer);
+        final String pName = context.getPackageName();
+        final Resources res = context.getResources();
+
+        final RemoteViews content = new RemoteViews(pName, R.layout.chronometer_notif_content);
+        content.setChronometerCountDown(R.id.chronometer, true);
+        content.setChronometer(R.id.chronometer, base, null, true);
+
+        final List<Notification.Action> actions = new ArrayList<>(1);
+
+        final CharSequence stateText;
+        if (count == 1) {
+            // Single timer is missed.
+            if (TextUtils.isEmpty(timer.getLabel())) {
+                stateText = res.getString(R.string.missed_timer_notification_label);
+            } else {
+                stateText = res.getString(R.string.missed_named_timer_notification_label,
+                        timer.getLabel());
+            }
+
+            // Reset button
+            final Intent reset = new Intent(context, TimerService.class)
+                    .setAction(TimerService.ACTION_RESET_TIMER)
+                    .putExtra(TimerService.EXTRA_TIMER_ID, timer.getId());
+
+            final Icon icon1 = Icon.createWithResource(context, R.drawable.ic_reset_24dp);
+            final CharSequence title1 = res.getText(R.string.timer_reset);
+            final PendingIntent intent1 = Utils.pendingServiceIntent(context, reset);
+            actions.add(new Notification.Action.Builder(icon1, title1, intent1).build());
+        } else {
+            // Multiple missed timers.
+            stateText = res.getString(R.string.timer_multi_missed, count);
+
+            final Intent reset = TimerService.createResetMissedTimersIntent(context);
+
+            final Icon icon1 = Icon.createWithResource(context, R.drawable.ic_reset_24dp);
+            final CharSequence title1 = res.getText(R.string.timer_reset_all);
+            final PendingIntent intent1 = Utils.pendingServiceIntent(context, reset);
+            actions.add(new Notification.Action.Builder(icon1, title1, intent1).build());
+        }
+
+        content.setTextViewText(R.id.state, stateText);
+
+        // Intent to load the app and show the timer when the notification is tapped.
+        final Intent showApp = new Intent(context, TimerService.class)
+                .setAction(TimerService.ACTION_SHOW_TIMER)
+                .putExtra(TimerService.EXTRA_TIMER_ID, timer.getId())
+                .putExtra(Events.EXTRA_EVENT_LABEL, R.string.label_notification);
+
+        final PendingIntent pendingShowApp =
+                PendingIntent.getService(context, REQUEST_CODE_MISSING, showApp,
+                PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_UPDATE_CURRENT);
+
+        return new Notification.Builder(context)
+                .setLocalOnly(true)
+                .setShowWhen(false)
+                .setAutoCancel(false)
+                .setCustomContentView(content)
+                .setContentIntent(pendingShowApp)
+                .setPriority(Notification.PRIORITY_HIGH)
+                .setCategory(Notification.CATEGORY_ALARM)
+                .setSmallIcon(R.drawable.stat_notify_timer)
+                .setGroup(nm.getTimerNotificationGroupKey())
+                .setVisibility(Notification.VISIBILITY_PUBLIC)
+                .setSortKey(nm.getTimerNotificationMissedSortKey())
+                .setStyle(new Notification.DecoratedCustomViewStyle())
+                .setActions(actions.toArray(new Notification.Action[actions.size()]))
+                .setColor(ContextCompat.getColor(context, R.color.default_background))
                 .build();
     }
 
diff --git a/src/com/android/deskclock/data/TimerNotificationBuilderPreN.java b/src/com/android/deskclock/data/TimerNotificationBuilderPreN.java
index bbe8928..b10bcfb 100644
--- a/src/com/android/deskclock/data/TimerNotificationBuilderPreN.java
+++ b/src/com/android/deskclock/data/TimerNotificationBuilderPreN.java
@@ -29,9 +29,10 @@
 import android.support.v4.content.ContextCompat;
 import android.text.TextUtils;
 
-import com.android.deskclock.HandleDeskClockApiCalls;
+import com.android.deskclock.AlarmUtils;
 import com.android.deskclock.R;
 import com.android.deskclock.Utils;
+import com.android.deskclock.events.Events;
 import com.android.deskclock.timer.ExpiredTimersActivity;
 import com.android.deskclock.timer.TimerService;
 
@@ -71,14 +72,14 @@
                 firstActionIconId = R.drawable.ic_pause_24dp;
                 firstActionTitleId = R.string.timer_pause;
                 firstActionIntent = new Intent(context, TimerService.class)
-                        .setAction(HandleDeskClockApiCalls.ACTION_PAUSE_TIMER)
-                        .putExtra(HandleDeskClockApiCalls.EXTRA_TIMER_ID, timer.getId());
+                        .setAction(TimerService.ACTION_PAUSE_TIMER)
+                        .putExtra(TimerService.EXTRA_TIMER_ID, timer.getId());
 
                 secondActionIconId = R.drawable.ic_add_24dp;
                 secondActionTitleId = R.string.timer_plus_1_min;
                 secondActionIntent = new Intent(context, TimerService.class)
-                        .setAction(HandleDeskClockApiCalls.ACTION_ADD_MINUTE_TIMER)
-                        .putExtra(HandleDeskClockApiCalls.EXTRA_TIMER_ID, timer.getId());
+                        .setAction(TimerService.ACTION_ADD_MINUTE_TIMER)
+                        .putExtra(TimerService.EXTRA_TIMER_ID, timer.getId());
             } else {
                 // Single timer is paused.
                 contentTitle = context.getString(R.string.timer_paused);
@@ -86,14 +87,14 @@
                 firstActionIconId = R.drawable.ic_start_24dp;
                 firstActionTitleId = R.string.sw_resume_button;
                 firstActionIntent = new Intent(context, TimerService.class)
-                        .setAction(HandleDeskClockApiCalls.ACTION_START_TIMER)
-                        .putExtra(HandleDeskClockApiCalls.EXTRA_TIMER_ID, timer.getId());
+                        .setAction(TimerService.ACTION_START_TIMER)
+                        .putExtra(TimerService.EXTRA_TIMER_ID, timer.getId());
 
                 secondActionIconId = R.drawable.ic_reset_24dp;
                 secondActionTitleId = R.string.sw_reset_button;
                 secondActionIntent = new Intent(context, TimerService.class)
-                        .setAction(HandleDeskClockApiCalls.ACTION_RESET_TIMER)
-                        .putExtra(HandleDeskClockApiCalls.EXTRA_TIMER_ID, timer.getId());
+                        .setAction(TimerService.ACTION_RESET_TIMER)
+                        .putExtra(TimerService.EXTRA_TIMER_ID, timer.getId());
             }
         } else {
             if (timer.isRunning()) {
@@ -113,13 +114,12 @@
         }
 
         // Intent to load the app and show the timer when the notification is tapped.
-        final Intent showApp = new Intent(context, HandleDeskClockApiCalls.class)
-                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
-                .setAction(HandleDeskClockApiCalls.ACTION_SHOW_TIMERS)
-                .putExtra(HandleDeskClockApiCalls.EXTRA_TIMER_ID, timer.getId())
-                .putExtra(HandleDeskClockApiCalls.EXTRA_EVENT_LABEL, R.string.label_notification);
+        final Intent showApp = new Intent(context, TimerService.class)
+                .setAction(TimerService.ACTION_SHOW_TIMER)
+                .putExtra(TimerService.EXTRA_TIMER_ID, timer.getId())
+                .putExtra(Events.EXTRA_EVENT_LABEL, R.string.label_notification);
 
-        final PendingIntent pendingShowApp = PendingIntent.getActivity(context, 0, showApp,
+        final PendingIntent pendingShowApp = PendingIntent.getService(context, 0, showApp,
                 PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_UPDATE_CURRENT);
 
         final NotificationCompat.Builder builder = new NotificationCompat.Builder(context)
@@ -131,6 +131,7 @@
                 .setContentTitle(contentTitle)
                 .setContentIntent(pendingShowApp)
                 .setSmallIcon(R.drawable.stat_notify_timer)
+                .setSortKey(nm.getTimerNotificationSortKey())
                 .setPriority(NotificationCompat.PRIORITY_HIGH)
                 .setCategory(NotificationCompat.CATEGORY_ALARM)
                 .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
@@ -150,8 +151,9 @@
         final Intent updateNotification = TimerService.createUpdateNotificationIntent(context);
         if (timer.isRunning() && remainingTime > MINUTE_IN_MILLIS) {
             // Schedule a callback to update the time-sensitive information of the running timer.
-            final PendingIntent pi = PendingIntent.getService(context, 0, updateNotification,
-                    PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_UPDATE_CURRENT);
+            final PendingIntent pi =
+                    PendingIntent.getService(context, REQUEST_CODE_UPCOMING, updateNotification,
+                            PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_UPDATE_CURRENT);
 
             final long nextMinuteChange = remainingTime % MINUTE_IN_MILLIS;
             final long triggerTime = SystemClock.elapsedRealtime() + nextMinuteChange;
@@ -218,12 +220,12 @@
                 .setContentText(contentText)
                 .setContentTitle(contentTitle)
                 .setContentIntent(pendingContent)
-                .setColor(ContextCompat.getColor(context, R.color.default_background))
                 .setSmallIcon(R.drawable.stat_notify_timer)
                 .setFullScreenIntent(pendingFullScreen, true)
                 .setPriority(NotificationCompat.PRIORITY_MAX)
                 .setDefaults(NotificationCompat.DEFAULT_LIGHTS)
-                .addAction(R.drawable.ic_stop_24dp, resetActionTitle, pendingReset);
+                .addAction(R.drawable.ic_stop_24dp, resetActionTitle, pendingReset)
+                .setColor(ContextCompat.getColor(context, R.color.default_background));
 
         // Add a second action if only a single timer is expired.
         if (count == 1) {
@@ -237,6 +239,74 @@
         return builder.build();
     }
 
+    @Override
+    public Notification buildMissed(Context context, NotificationModel nm,
+                                    List<Timer> missedTimers) {
+        final Timer timer = missedTimers.get(0);
+        final long expirationTime = timer.getWallClockExpirationTime();
+
+        // Generate a title, and some actions based on timer states.
+        final String contentTitle;
+        final String contentText;
+        final @DrawableRes int firstActionIconId;
+        final @StringRes int firstActionTitleId;
+        final Intent firstActionIntent;
+
+        if (missedTimers.size() == 1) {
+            // Single timer is missed.
+            contentText = AlarmUtils.getFormattedTime(context, expirationTime);
+            if (TextUtils.isEmpty(timer.getLabel())) {
+                contentTitle = context.getString(R.string.missed_timer_notification_label);
+            } else {
+                contentTitle = context.getString(R.string.missed_named_timer_notification_label,
+                        timer.getLabel());
+            }
+
+            firstActionIconId = R.drawable.ic_reset_24dp;
+            firstActionTitleId = R.string.timer_reset;
+            firstActionIntent = new Intent(context, TimerService.class)
+                    .setAction(TimerService.ACTION_RESET_TIMER)
+                    .putExtra(TimerService.EXTRA_TIMER_ID, timer.getId());
+        } else {
+            // Multiple missed timers.
+            contentText = AlarmUtils.getFormattedTime(context, expirationTime);
+            contentTitle = context.getString(R.string.timer_multi_missed, missedTimers.size());
+
+            firstActionIconId = R.drawable.ic_reset_24dp;
+            firstActionTitleId = R.string.timer_reset_all;
+            firstActionIntent = TimerService.createResetMissedTimersIntent(context);
+        }
+
+        // Intent to load the app and show the timer when the notification is tapped.
+        final Intent showApp = new Intent(context, TimerService.class)
+                .setAction(TimerService.ACTION_SHOW_TIMER)
+                .putExtra(TimerService.EXTRA_TIMER_ID, timer.getId())
+                .putExtra(Events.EXTRA_EVENT_LABEL, R.string.label_notification);
+
+        final PendingIntent pendingShowApp = PendingIntent.getService(context, 0, showApp,
+                PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_UPDATE_CURRENT);
+
+        final NotificationCompat.Builder builder = new NotificationCompat.Builder(context)
+                .setLocalOnly(true)
+                .setShowWhen(false)
+                .setAutoCancel(false)
+                .setContentText(contentText)
+                .setContentTitle(contentTitle)
+                .setContentIntent(pendingShowApp)
+                .setSmallIcon(R.drawable.stat_notify_timer)
+                .setPriority(NotificationCompat.PRIORITY_HIGH)
+                .setCategory(NotificationCompat.CATEGORY_ALARM)
+                .setSortKey(nm.getTimerNotificationMissedSortKey())
+                .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
+                .setColor(ContextCompat.getColor(context, R.color.default_background));
+
+        final PendingIntent action1 = Utils.pendingServiceIntent(context, firstActionIntent);
+        final String action1Title = context.getString(firstActionTitleId);
+        builder.addAction(firstActionIconId, action1Title, action1);
+
+        return builder.build();
+    }
+
     /**
      * Format "7 hours 52 minutes remaining"
      */
diff --git a/src/com/android/deskclock/events/EventTracker.java b/src/com/android/deskclock/events/EventTracker.java
index d0bc079..97955c9 100644
--- a/src/com/android/deskclock/events/EventTracker.java
+++ b/src/com/android/deskclock/events/EventTracker.java
@@ -20,13 +20,6 @@
 
 public interface EventTracker {
     /**
-     * Send screen view tracking to Log system.
-     *
-     * @param screenName Screen name to be logged
-     */
-    void sendView(String screenName);
-
-    /**
      * Send category, action and label describing an event to Log system.
      *
      * @param category string resource id indicating Alarm, Clock, Timer or Stopwatch or 0 for no
@@ -37,13 +30,4 @@
      *              e.g. DeskClock (UI), Intent, Notification, etc. or 0 for no label
      */
     void sendEvent(@StringRes int category, @StringRes int action, @StringRes int label);
-
-    /**
-     * Send category, action and label describing an event to Log system.
-     *
-     * @param category Alarm, Clock, Timer or Stopwatch
-     * @param action how the entity was altered; e.g. create, delete, fire, etc
-     * @param label where the action originated; e.g. DeskClock (UI), Intent, Notification, etc.
-     */
-    void sendEvent(String category, String action, String label);
 }
diff --git a/src/com/android/deskclock/events/Events.java b/src/com/android/deskclock/events/Events.java
index 1a5d540..66b4bf6 100644
--- a/src/com/android/deskclock/events/Events.java
+++ b/src/com/android/deskclock/events/Events.java
@@ -25,6 +25,9 @@
 
 public final class Events {
 
+    /** Extra describing the entity responsible for the action being performed. */
+    public static final String EXTRA_EVENT_LABEL = "com.android.deskclock.extra.EVENT_LABEL";
+
     private static final Collection<EventTracker> sEventTrackers = new ArrayList<>();
 
     public static void addEventTracker(EventTracker eventTracker) {
@@ -56,7 +59,7 @@
     }
 
     /**
-     * Tracks an timer event.
+     * Tracks a timer event.
      *
      * @param action resource id of event action
      * @param label resource id of event label
@@ -66,7 +69,7 @@
     }
 
     /**
-     * Tracks an stopwatch event.
+     * Tracks a stopwatch event.
      *
      * @param action resource id of event action
      * @param label resource id of event label
@@ -76,6 +79,16 @@
     }
 
     /**
+     * Tracks a screensaver event.
+     *
+     * @param action resource id of event action
+     * @param label resource id of event label
+     */
+    public static void sendScreensaverEvent(@StringRes int action, @StringRes int label) {
+        sendEvent(R.string.category_screensaver, action, label);
+    }
+
+    /**
      * Tracks an event. Events have a category, action, label and value. This
      * method can be used to track events such as button presses or other user
      * interactions with your application (value is not used in this app).
@@ -90,32 +103,4 @@
             eventTracker.sendEvent(category, action, label);
         }
     }
-
-    /**
-     * Tracks an event. Events have a category, action, label and value. This
-     * method can be used to track events such as button presses or other user
-     * interactions with your application (value is not used in this app).
-     *
-     * @param category the event category
-     * @param action the event action
-     * @param label the event label
-     */
-    public static void sendEvent(String category, String action, String label) {
-        if (category != null && action != null) {
-            for (EventTracker eventTracker : sEventTrackers) {
-                eventTracker.sendEvent(category, action, label);
-            }
-        }
-    }
-
-    /**
-     * Tracks entering a view with a new app screen name.
-     *
-     * @param screenName the new app screen name
-     */
-    public static void sendView(String screenName) {
-        for (EventTracker eventTracker : sEventTrackers) {
-            eventTracker.sendView(screenName);
-        }
-    }
-}
\ No newline at end of file
+}
diff --git a/src/com/android/deskclock/events/LogEventTracker.java b/src/com/android/deskclock/events/LogEventTracker.java
index 08cabcb..70dfc9b 100644
--- a/src/com/android/deskclock/events/LogEventTracker.java
+++ b/src/com/android/deskclock/events/LogEventTracker.java
@@ -31,21 +31,12 @@
     }
 
     @Override
-    public void sendView(String screenName) {
-        LOGGER.d("viewing screen %s", screenName);
-    }
-
-    @Override
     public void sendEvent(@StringRes int category, @StringRes int action, @StringRes int label) {
-        sendEvent(safeGetString(category), safeGetString(action), safeGetString(label));
-    }
-
-    @Override
-    public void sendEvent(String category, String action, String label) {
-        if (label == null) {
-            LOGGER.d("[%s] [%s]", category, action);
+        if (label == 0) {
+            LOGGER.d("[%s] [%s]", safeGetString(category), safeGetString(action));
         } else {
-            LOGGER.d("[%s] [%s] [%s]", category, action, label);
+            LOGGER.d("[%s] [%s] [%s]", safeGetString(category), safeGetString(action),
+                    safeGetString(label));
         }
     }
 
diff --git a/src/com/android/deskclock/events/ShortcutEventTracker.java b/src/com/android/deskclock/events/ShortcutEventTracker.java
new file mode 100644
index 0000000..444a476
--- /dev/null
+++ b/src/com/android/deskclock/events/ShortcutEventTracker.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.deskclock.events;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.content.pm.ShortcutManager;
+import android.os.Build;
+import android.support.annotation.StringRes;
+import android.util.ArraySet;
+
+import com.android.deskclock.R;
+import com.android.deskclock.uidata.UiDataModel;
+
+import java.util.Set;
+
+@TargetApi(Build.VERSION_CODES.N_MR1)
+public final class ShortcutEventTracker implements EventTracker {
+
+    private final Context mContext;
+    private final ShortcutManager mShortcutManager;
+    private final Set<String> shortcuts = new ArraySet<>(5);
+
+    public ShortcutEventTracker(Context context) {
+        mContext = context;
+        mShortcutManager = mContext.getSystemService(ShortcutManager.class);
+        final UiDataModel uidm = UiDataModel.getUiDataModel();
+        shortcuts.add(uidm.getShortcutId(R.string.category_alarm, R.string.action_create));
+        shortcuts.add(uidm.getShortcutId(R.string.category_timer, R.string.action_create));
+        shortcuts.add(uidm.getShortcutId(R.string.category_stopwatch, R.string.action_pause));
+        shortcuts.add(uidm.getShortcutId(R.string.category_stopwatch, R.string.action_start));
+        shortcuts.add(uidm.getShortcutId(R.string.category_screensaver, R.string.action_show));
+    }
+
+    @Override
+    public void sendEvent(@StringRes int category, @StringRes int action, @StringRes int label) {
+        final String shortcutId = UiDataModel.getUiDataModel().getShortcutId(category, action);
+        if (shortcuts.contains(shortcutId)) {
+            mShortcutManager.reportShortcutUsed(shortcutId);
+        }
+    }
+
+    /**
+     * @return Resource string represented by a given resource id, null if resId is invalid (0).
+     */
+    private String safeGetString(@StringRes int resId) {
+        return resId == 0 ? null : mContext.getString(resId);
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/deskclock/provider/Alarm.java b/src/com/android/deskclock/provider/Alarm.java
index 258a50b..d96d683 100644
--- a/src/com/android/deskclock/provider/Alarm.java
+++ b/src/com/android/deskclock/provider/Alarm.java
@@ -133,10 +133,10 @@
     }
 
     public static Intent createIntent(Context context, Class<?> cls, long alarmId) {
-        return new Intent(context, cls).setData(getUri(alarmId));
+        return new Intent(context, cls).setData(getContentUri(alarmId));
     }
 
-    public static Uri getUri(long alarmId) {
+    public static Uri getContentUri(long alarmId) {
         return ContentUris.withAppendedId(CONTENT_URI, alarmId);
     }
 
@@ -158,12 +158,12 @@
     /**
      * Get alarm by id.
      *
-     * @param cr to perform the query on.
+     * @param cr provides access to the content model
      * @param alarmId for the desired alarm.
      * @return alarm if found, null otherwise
      */
     public static Alarm getAlarm(ContentResolver cr, long alarmId) {
-        try (Cursor cursor = cr.query(getUri(alarmId), QUERY_COLUMNS, null, null, null)) {
+        try (Cursor cursor = cr.query(getContentUri(alarmId), QUERY_COLUMNS, null, null, null)) {
             if (cursor.moveToFirst()) {
                 return new Alarm(cursor);
             }
@@ -171,11 +171,21 @@
 
         return null;
     }
+    /**
+     * Get alarm for the {@code contentUri}.
+     *
+     * @param cr provides access to the content model
+     * @param contentUri the {@link #getContentUri deeplink} for the desired alarm
+     * @return instance if found, null otherwise
+     */
+    public static Alarm getAlarm(ContentResolver cr, Uri contentUri) {
+        return getAlarm(cr, ContentUris.parseId(contentUri));
+    }
 
     /**
      * Get all alarms given conditions.
      *
-     * @param cr to perform the query on.
+     * @param cr provides access to the content model
      * @param selection A filter declaring which rows to return, formatted as an
      *         SQL WHERE clause (excluding the WHERE itself). Passing null will
      *         return all rows for the given URI.
@@ -221,13 +231,13 @@
     public static boolean updateAlarm(ContentResolver contentResolver, Alarm alarm) {
         if (alarm.id == Alarm.INVALID_ID) return false;
         ContentValues values = createContentValues(alarm);
-        long rowsUpdated = contentResolver.update(getUri(alarm.id), values, null, null);
+        long rowsUpdated = contentResolver.update(getContentUri(alarm.id), values, null, null);
         return rowsUpdated == 1;
     }
 
     public static boolean deleteAlarm(ContentResolver contentResolver, long alarmId) {
         if (alarmId == INVALID_ID) return false;
-        int deletedRows = contentResolver.delete(getUri(alarmId), "", null);
+        int deletedRows = contentResolver.delete(getContentUri(alarmId), "", null);
         return deletedRows == 1;
     }
 
@@ -307,6 +317,13 @@
         deleteAfterUse = p.readInt() == 1;
     }
 
+    /**
+     * @return the deeplink that identifies this alarm
+     */
+    public Uri getContentUri() {
+        return getContentUri(id);
+    }
+
     public String getLabelOrDefault(Context context) {
         return label.isEmpty() ? context.getString(R.string.default_label) : label;
     }
diff --git a/src/com/android/deskclock/provider/AlarmInstance.java b/src/com/android/deskclock/provider/AlarmInstance.java
index 5be3c03..1c7650e 100644
--- a/src/com/android/deskclock/provider/AlarmInstance.java
+++ b/src/com/android/deskclock/provider/AlarmInstance.java
@@ -24,7 +24,6 @@
 import android.database.Cursor;
 import android.media.RingtoneManager;
 import android.net.Uri;
-import android.preference.PreferenceManager;
 
 import com.android.deskclock.LogUtils;
 import com.android.deskclock.R;
@@ -120,30 +119,33 @@
     }
 
     public static Intent createIntent(String action, long instanceId) {
-        return new Intent(action).setData(getUri(instanceId));
+        return new Intent(action).setData(getContentUri(instanceId));
     }
 
     public static Intent createIntent(Context context, Class<?> cls, long instanceId) {
-        return new Intent(context, cls).setData(getUri(instanceId));
+        return new Intent(context, cls).setData(getContentUri(instanceId));
     }
 
     public static long getId(Uri contentUri) {
         return ContentUris.parseId(contentUri);
     }
 
-    public static Uri getUri(long instanceId) {
+    /**
+     * @return the {@link Uri} identifying the alarm instance
+     */
+    public static Uri getContentUri(long instanceId) {
         return ContentUris.withAppendedId(CONTENT_URI, instanceId);
     }
 
     /**
      * Get alarm instance from instanceId.
      *
-     * @param cr to perform the query on.
+     * @param cr provides access to the content model
      * @param instanceId for the desired instance.
      * @return instance if found, null otherwise
      */
     public static AlarmInstance getInstance(ContentResolver cr, long instanceId) {
-        try (Cursor cursor = cr.query(getUri(instanceId), QUERY_COLUMNS, null, null, null)) {
+        try (Cursor cursor = cr.query(getContentUri(instanceId), QUERY_COLUMNS, null, null, null)) {
             if (cursor != null && cursor.moveToFirst()) {
                 return new AlarmInstance(cursor, false /* joinedTable */);
             }
@@ -153,9 +155,21 @@
     }
 
     /**
+     * Get alarm instance for the {@code contentUri}.
+     *
+     * @param cr provides access to the content model
+     * @param contentUri the {@link #getContentUri deeplink} for the desired instance
+     * @return instance if found, null otherwise
+     */
+    public static AlarmInstance getInstance(ContentResolver cr, Uri contentUri) {
+        final long instanceId = ContentUris.parseId(contentUri);
+        return getInstance(cr, instanceId);
+    }
+
+    /**
      * Get an alarm instances by alarmId.
      *
-     * @param contentResolver to perform the query on.
+     * @param contentResolver provides access to the content model
      * @param alarmId of instances desired.
      * @return list of alarms instances that are owned by alarmId.
      */
@@ -166,7 +180,7 @@
 
     /**
      * Get the next instance of an alarm given its alarmId
-     * @param contentResolver to perform query on
+     * @param contentResolver provides access to the content model
      * @param alarmId of instance desired
      * @return the next instance of an alarm by alarmId.
      */
@@ -205,7 +219,7 @@
     /**
      * Get a list of instances given selection.
      *
-     * @param cr to perform the query on.
+     * @param cr provides access to the content model
      * @param selection A filter declaring which rows to return, formatted as an
      *         SQL WHERE clause (excluding the WHERE itself). Passing null will
      *         return all rows for the given URI.
@@ -218,7 +232,7 @@
                                                    String... selectionArgs) {
         final List<AlarmInstance> result = new LinkedList<>();
         try (Cursor cursor = cr.query(CONTENT_URI, QUERY_COLUMNS, selection, selectionArgs, null)) {
-            if (cursor.moveToFirst()) {
+            if (cursor != null && cursor.moveToFirst()) {
                 do {
                     result.add(new AlarmInstance(cursor, false /* joinedTable */));
                 } while (cursor.moveToNext());
@@ -254,19 +268,19 @@
     public static boolean updateInstance(ContentResolver contentResolver, AlarmInstance instance) {
         if (instance.mId == INVALID_ID) return false;
         ContentValues values = createContentValues(instance);
-        long rowsUpdated = contentResolver.update(getUri(instance.mId), values, null, null);
+        long rowsUpdated = contentResolver.update(getContentUri(instance.mId), values, null, null);
         return rowsUpdated == 1;
     }
 
     public static boolean deleteInstance(ContentResolver contentResolver, long instanceId) {
         if (instanceId == INVALID_ID) return false;
-        int deletedRows = contentResolver.delete(getUri(instanceId), "", null);
+        int deletedRows = contentResolver.delete(getContentUri(instanceId), "", null);
         return deletedRows == 1;
     }
 
     /**
      * @param context
-     * @param contentResolver to access the content provider
+     * @param contentResolver provides access to the content model
      * @param alarmId identifies the alarm in question
      * @param instanceId identifies the instance to keep; all other instances will be removed
      */
@@ -356,6 +370,13 @@
         mAlarmState = c.getInt(ALARM_STATE_INDEX);
     }
 
+    /**
+     * @return the deeplink that identifies this alarm instance
+     */
+    public Uri getContentUri() {
+        return getContentUri(mId);
+    }
+
     public String getLabelOrDefault(Context context) {
         return mLabel.isEmpty() ? context.getString(R.string.default_label) : mLabel;
     }
diff --git a/src/com/android/deskclock/settings/AlarmVolumePreference.java b/src/com/android/deskclock/settings/AlarmVolumePreference.java
index 67f232c..990b895 100644
--- a/src/com/android/deskclock/settings/AlarmVolumePreference.java
+++ b/src/com/android/deskclock/settings/AlarmVolumePreference.java
@@ -31,6 +31,9 @@
 import com.android.deskclock.RingtonePreviewKlaxon;
 import com.android.deskclock.data.DataModel;
 
+import static android.content.Context.AUDIO_SERVICE;
+import static android.media.AudioManager.STREAM_ALARM;
+
 public class AlarmVolumePreference extends Preference {
 
     private static final long ALARM_PREVIEW_DURATION_MS = 2000;
@@ -47,24 +50,23 @@
     public void onBindViewHolder(PreferenceViewHolder holder) {
         super.onBindViewHolder(holder);
 
+        final Context context = getContext();
+        final AudioManager audioManager = (AudioManager) context.getSystemService(AUDIO_SERVICE);
+
         // Disable click feedback for this preference.
         holder.itemView.setClickable(false);
 
-        final Context context = getContext();
-        final AudioManager audioManager =
-                (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
         mSeekbar = (SeekBar) holder.findViewById(R.id.alarm_volume_slider);
-        mSeekbar.setMax(audioManager.getStreamMaxVolume(AudioManager.STREAM_ALARM));
-        mSeekbar.setProgress(audioManager.getStreamVolume(AudioManager.STREAM_ALARM));
+        mSeekbar.setMax(audioManager.getStreamMaxVolume(STREAM_ALARM));
+        mSeekbar.setProgress(audioManager.getStreamVolume(STREAM_ALARM));
         mAlarmIcon = (ImageView) holder.findViewById(R.id.alarm_icon);
-        updateIcon();
+        onSeekbarChanged();
 
         final ContentObserver volumeObserver = new ContentObserver(mSeekbar.getHandler()) {
             @Override
             public void onChange(boolean selfChange) {
                 // Volume was changed elsewhere, update our slider.
-                mSeekbar.setProgress(audioManager.getStreamVolume(
-                        AudioManager.STREAM_ALARM));
+                mSeekbar.setProgress(audioManager.getStreamVolume(STREAM_ALARM));
             }
         };
 
@@ -81,14 +83,13 @@
             }
         });
 
-
         mSeekbar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
             @Override
             public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
                 if (fromUser) {
-                    audioManager.setStreamVolume(AudioManager.STREAM_ALARM, progress, 0);
+                    audioManager.setStreamVolume(STREAM_ALARM, progress, 0);
                 }
-                updateIcon();
+                onSeekbarChanged();
             }
 
             @Override
@@ -114,7 +115,7 @@
         });
     }
 
-    private void updateIcon() {
+    private void onSeekbarChanged() {
         mAlarmIcon.setImageResource(mSeekbar.getProgress() == 0 ?
                 R.drawable.ic_alarm_off_24dp : R.drawable.ic_alarm_small);
     }
diff --git a/src/com/android/deskclock/stopwatch/LapsAdapter.java b/src/com/android/deskclock/stopwatch/LapsAdapter.java
index 62496bd..4bdb1e9 100644
--- a/src/com/android/deskclock/stopwatch/LapsAdapter.java
+++ b/src/com/android/deskclock/stopwatch/LapsAdapter.java
@@ -61,7 +61,7 @@
     /** Used to determine when the time format for the total time column has changed length. */
     private int mLastFormattedAccumulatedTimeLength;
 
-    public LapsAdapter(Context context) {
+    LapsAdapter(Context context) {
         mContext = context;
         mInflater = LayoutInflater.from(context);
         setHasStableIds(true);
@@ -232,16 +232,22 @@
      */
     @VisibleForTesting
     static String formatTime(long maxTime, long time, String separator) {
-        final int hours = (int) (time / DateUtils.HOUR_IN_MILLIS);
-        int remainder = (int) (time % DateUtils.HOUR_IN_MILLIS);
+        final int hours, minutes, seconds, hundredths;
+        if (time <= 0) {
+            // A negative time should be impossible, but is tolerated to avoid crashing the app.
+            hours = minutes = seconds = hundredths = 0;
+        } else {
+            hours = (int) (time / DateUtils.HOUR_IN_MILLIS);
+            int remainder = (int) (time % DateUtils.HOUR_IN_MILLIS);
 
-        final int minutes = (int) (remainder / DateUtils.MINUTE_IN_MILLIS);
-        remainder = (int) (remainder % DateUtils.MINUTE_IN_MILLIS);
+            minutes = (int) (remainder / DateUtils.MINUTE_IN_MILLIS);
+            remainder = (int) (remainder % DateUtils.MINUTE_IN_MILLIS);
 
-        final int seconds = (int) (remainder / DateUtils.SECOND_IN_MILLIS);
-        remainder = (int) (remainder % DateUtils.SECOND_IN_MILLIS);
+            seconds = (int) (remainder / DateUtils.SECOND_IN_MILLIS);
+            remainder = (int) (remainder % DateUtils.SECOND_IN_MILLIS);
 
-        final int hundredths = remainder / 10;
+            hundredths = remainder / 10;
+        }
 
         final char decimalSeparator = DecimalFormatSymbols.getInstance().getDecimalSeparator();
 
@@ -334,7 +340,7 @@
         private final TextView lapTime;
         private final TextView accumulatedTime;
 
-        public LapItemHolder(View itemView) {
+        LapItemHolder(View itemView) {
             super(itemView);
 
             lapTime = (TextView) itemView.findViewById(R.id.lap_time);
diff --git a/src/com/android/deskclock/stopwatch/StopwatchService.java b/src/com/android/deskclock/stopwatch/StopwatchService.java
index 53fcd05..f7f9425 100644
--- a/src/com/android/deskclock/stopwatch/StopwatchService.java
+++ b/src/com/android/deskclock/stopwatch/StopwatchService.java
@@ -20,10 +20,13 @@
 import android.content.Intent;
 import android.os.IBinder;
 
-import com.android.deskclock.HandleDeskClockApiCalls;
+import com.android.deskclock.DeskClock;
 import com.android.deskclock.R;
 import com.android.deskclock.data.DataModel;
 import com.android.deskclock.events.Events;
+import com.android.deskclock.uidata.UiDataModel;
+
+import static com.android.deskclock.uidata.UiDataModel.Tab.STOPWATCH;
 
 /**
  * This service exists solely to allow the stopwatch notification to alter the state of the
@@ -34,6 +37,19 @@
  */
 public final class StopwatchService extends Service {
 
+    private static final String ACTION_PREFIX = "com.android.deskclock.action.";
+
+    // shows the tab with the stopwatch
+    public static final String ACTION_SHOW_STOPWATCH = ACTION_PREFIX + "SHOW_STOPWATCH";
+    // starts the current stopwatch
+    public static final String ACTION_START_STOPWATCH = ACTION_PREFIX + "START_STOPWATCH";
+    // pauses the current stopwatch that's currently running
+    public static final String ACTION_PAUSE_STOPWATCH = ACTION_PREFIX + "PAUSE_STOPWATCH";
+    // laps the stopwatch that's currently running
+    public static final String ACTION_LAP_STOPWATCH = ACTION_PREFIX + "LAP_STOPWATCH";
+    // resets the stopwatch if it's stopped
+    public static final String ACTION_RESET_STOPWATCH = ACTION_PREFIX + "RESET_STOPWATCH";
+
     @Override
     public IBinder onBind(Intent intent) {
         return null;
@@ -41,29 +57,41 @@
 
     @Override
     public int onStartCommand(Intent intent, int flags, int startId) {
-        switch (intent.getAction()) {
-            case HandleDeskClockApiCalls.ACTION_START_STOPWATCH: {
+        final String action = intent.getAction();
+        final int label = intent.getIntExtra(Events.EXTRA_EVENT_LABEL, R.string.label_intent);
+        switch (action) {
+            case ACTION_SHOW_STOPWATCH: {
+                Events.sendStopwatchEvent(R.string.action_show, label);
+
+                // Open DeskClock positioned on the stopwatch tab.
+                UiDataModel.getUiDataModel().setSelectedTab(STOPWATCH);
+                final Intent showStopwatch = new Intent(this, DeskClock.class)
+                        .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+                startActivity(showStopwatch);
+                break;
+            }
+            case ACTION_START_STOPWATCH: {
+                Events.sendStopwatchEvent(R.string.action_start, label);
                 DataModel.getDataModel().startStopwatch();
-                Events.sendStopwatchEvent(R.string.action_start, R.string.label_notification);
                 break;
             }
-            case HandleDeskClockApiCalls.ACTION_PAUSE_STOPWATCH: {
+            case ACTION_PAUSE_STOPWATCH: {
+                Events.sendStopwatchEvent(R.string.action_pause, label);
                 DataModel.getDataModel().pauseStopwatch();
-                Events.sendStopwatchEvent(R.string.action_pause, R.string.label_notification);
                 break;
             }
-            case HandleDeskClockApiCalls.ACTION_RESET_STOPWATCH: {
+            case ACTION_RESET_STOPWATCH: {
+                Events.sendStopwatchEvent(R.string.action_reset, label);
                 DataModel.getDataModel().resetStopwatch();
-                Events.sendStopwatchEvent(R.string.action_reset, R.string.label_notification);
                 break;
             }
-            case HandleDeskClockApiCalls.ACTION_LAP_STOPWATCH: {
+            case ACTION_LAP_STOPWATCH: {
+                Events.sendStopwatchEvent(R.string.action_lap, label);
                 DataModel.getDataModel().addLap();
-                Events.sendStopwatchEvent(R.string.action_lap, R.string.label_notification);
                 break;
             }
         }
 
         return START_NOT_STICKY;
     }
-}
\ No newline at end of file
+}
diff --git a/src/com/android/deskclock/timer/ExpiredTimersActivity.java b/src/com/android/deskclock/timer/ExpiredTimersActivity.java
index 82f24f7..3c1c48d 100644
--- a/src/com/android/deskclock/timer/ExpiredTimersActivity.java
+++ b/src/com/android/deskclock/timer/ExpiredTimersActivity.java
@@ -30,6 +30,7 @@
 import android.widget.TextView;
 
 import com.android.deskclock.BaseActivity;
+import com.android.deskclock.LogUtils;
 import com.android.deskclock.R;
 import com.android.deskclock.data.DataModel;
 import com.android.deskclock.data.Timer;
@@ -60,6 +61,15 @@
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
 
+        final List<Timer> expiredTimers = getExpiredTimers();
+
+        // If no expired timers, finish
+        if (expiredTimers.size() == 0) {
+            LogUtils.i("No expired timers, skipping display.");
+            finish();
+            return;
+        }
+
         setContentView(R.layout.expired_timers_activity);
 
         mExpiredTimersView = (ViewGroup) findViewById(R.id.expired_timers_list);
@@ -85,7 +95,7 @@
         }
 
         // Create views for each of the expired timers.
-        for (Timer timer : getExpiredTimers()) {
+        for (Timer timer : expiredTimers) {
             addTimer(timer);
         }
 
diff --git a/src/com/android/deskclock/timer/TimerFragment.java b/src/com/android/deskclock/timer/TimerFragment.java
index 0803936..831a38e 100644
--- a/src/com/android/deskclock/timer/TimerFragment.java
+++ b/src/com/android/deskclock/timer/TimerFragment.java
@@ -38,7 +38,6 @@
 
 import com.android.deskclock.DeskClock;
 import com.android.deskclock.DeskClockFragment;
-import com.android.deskclock.HandleDeskClockApiCalls;
 import com.android.deskclock.R;
 import com.android.deskclock.data.DataModel;
 import com.android.deskclock.data.Timer;
@@ -146,8 +145,8 @@
             createTimer = intent.getBooleanExtra(EXTRA_TIMER_SETUP, false);
             intent.removeExtra(EXTRA_TIMER_SETUP);
 
-            showTimerId = intent.getIntExtra(HandleDeskClockApiCalls.EXTRA_TIMER_ID, -1);
-            intent.removeExtra(HandleDeskClockApiCalls.EXTRA_TIMER_ID);
+            showTimerId = intent.getIntExtra(TimerService.EXTRA_TIMER_ID, -1);
+            intent.removeExtra(TimerService.EXTRA_TIMER_ID);
         }
 
         // Choose the view to display in this fragment.
@@ -185,6 +184,28 @@
     }
 
     @Override
+    public void onResume() {
+        super.onResume();
+
+        // We may have received a new intent while paused.
+        final Intent intent = getActivity().getIntent();
+        if (intent != null && intent.hasExtra(TimerService.EXTRA_TIMER_ID)) {
+            // This extra is single-use; remove after honoring it.
+            final int showTimerId = intent.getIntExtra(TimerService.EXTRA_TIMER_ID, -1);
+            intent.removeExtra(TimerService.EXTRA_TIMER_ID);
+
+            final Timer timer = DataModel.getDataModel().getTimer(showTimerId);
+            if (timer != null) {
+                // A specific timer must be shown; show the list of timers.
+                final int index = DataModel.getDataModel().getTimers().indexOf(timer);
+                mViewPager.setCurrentItem(index);
+
+                animateToView(mTimersView, null);
+            }
+        }
+    }
+
+    @Override
     public void onStop() {
         super.onStop();
 
@@ -231,6 +252,7 @@
                     fab.setImageResource(R.drawable.ic_start_white_24dp);
                     fab.setContentDescription(fab.getResources().getString(R.string.timer_start));
                     break;
+                case MISSED:
                 case EXPIRED:
                     fab.setImageResource(R.drawable.ic_stop_white_24dp);
                     fab.setContentDescription(fab.getResources().getString(R.string.timer_stop));
@@ -292,6 +314,7 @@
                     DataModel.getDataModel().startTimer(timer);
                     Events.sendTimerEvent(R.string.action_start, R.string.label_deskclock);
                     break;
+                case MISSED:
                 case EXPIRED:
                     DataModel.getDataModel().resetOrDeleteTimer(timer, R.string.label_deskclock);
                     break;
@@ -346,7 +369,9 @@
 
     @Override
     public void onRightButtonClick(@NonNull ImageButton right) {
-        animateToView(mCreateTimerView, null);
+        if (mCurrentView != mCreateTimerView) {
+            animateToView(mCreateTimerView, null);
+        }
     }
 
     @Override
@@ -507,7 +532,7 @@
      */
     private void animateToView(View toView, final Timer timerToRemove) {
         if (mCurrentView == toView) {
-            throw new IllegalStateException("toView is already the current view");
+            return;
         }
 
         final boolean toTimers = toView == mTimersView;
@@ -522,17 +547,17 @@
         rotateFrom.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationEnd(Animator animation) {
-                if (timerToRemove != null) {
-                    DataModel.getDataModel().removeTimer(timerToRemove);
-                    Events.sendTimerEvent(R.string.action_delete, R.string.label_deskclock);
-                }
-
                 mCurrentView.setScaleX(1);
                 if (toTimers) {
                     showTimersView(FAB_AND_BUTTONS_SHRINK_AND_EXPAND);
                 } else {
                     showCreateTimerView(FAB_AND_BUTTONS_SHRINK_AND_EXPAND);
                 }
+
+                if (timerToRemove != null) {
+                    DataModel.getDataModel().removeTimer(timerToRemove);
+                    Events.sendTimerEvent(R.string.action_delete, R.string.label_deskclock);
+                }
             }
         });
 
@@ -615,23 +640,11 @@
     private class TimerWatcher implements TimerListener {
         @Override
         public void timerAdded(Timer timer) {
-            // The user interface should not be updated unless the fragment is resumed. It will be
-            // refreshed during onResume later if it is not currently resumed.
-            if (!isResumed()) {
-                return;
-            }
-
             updatePageIndicators();
         }
 
         @Override
         public void timerUpdated(Timer before, Timer after) {
-            // The user interface should not be updated unless the fragment is resumed. It will be
-            // refreshed during onResume later if it is not currently resumed.
-            if (!isResumed()) {
-                return;
-            }
-
             // If the timer started, animate the timers.
             if (before.isReset() && !after.isReset()) {
                 startUpdatingTime();
@@ -652,14 +665,15 @@
 
         @Override
         public void timerRemoved(Timer timer) {
-            // The user interface should not be updated unless the fragment is resumed. It will be
-            // refreshed during onResume later if it is not currently resumed.
-            if (!isResumed()) {
-                return;
-            }
-
             updatePageIndicators();
-            updateFab(FAB_AND_BUTTONS_IMMEDIATE);
+
+            if (mCurrentView == mTimersView) {
+                if (mAdapter.getCount() == 0) {
+                    animateToView(mCreateTimerView, null);
+                } else {
+                    updateFab(FAB_AND_BUTTONS_IMMEDIATE);
+                }
+            }
         }
     }
-}
\ No newline at end of file
+}
diff --git a/src/com/android/deskclock/timer/TimerItem.java b/src/com/android/deskclock/timer/TimerItem.java
index 9bf03c8..9eadadb 100644
--- a/src/com/android/deskclock/timer/TimerItem.java
+++ b/src/com/android/deskclock/timer/TimerItem.java
@@ -81,7 +81,7 @@
         // Update visibility of things that may blink.
         final boolean blinkOff = SystemClock.elapsedRealtime() % 1000 < 500;
         if (mCircleView != null) {
-            final boolean hideCircle = timer.isExpired() && blinkOff;
+            final boolean hideCircle = (timer.isExpired() || timer.isMissed()) && blinkOff;
             mCircleView.setVisibility(hideCircle ? INVISIBLE : VISIBLE);
 
             if (!hideCircle) {
@@ -116,6 +116,7 @@
                     mTimerText.setTimeStrTextColor(false, true);
                     break;
                 }
+                case MISSED:
                 case EXPIRED: {
                     final String addTimeDesc = getResources().getString(R.string.timer_plus_one);
                     mResetAddButton.setImageResource(R.drawable.ic_plusone);
diff --git a/src/com/android/deskclock/timer/TimerItemFragment.java b/src/com/android/deskclock/timer/TimerItemFragment.java
index 3a9d70b..8174997 100644
--- a/src/com/android/deskclock/timer/TimerItemFragment.java
+++ b/src/com/android/deskclock/timer/TimerItemFragment.java
@@ -96,7 +96,7 @@
             final Timer timer = getTimer();
             if (timer.isPaused()) {
                 DataModel.getDataModel().resetOrDeleteTimer(timer, R.string.label_deskclock);
-            } else if (timer.isRunning() || timer.isExpired()) {
+            } else if (timer.isRunning() || timer.isExpired() || timer.isMissed()) {
                 DataModel.getDataModel().addTimerMinute(timer);
                 Events.sendTimerEvent(R.string.action_add_minute, R.string.label_deskclock);
             }
diff --git a/src/com/android/deskclock/timer/TimerPagerAdapter.java b/src/com/android/deskclock/timer/TimerPagerAdapter.java
index 536024c..c4892df 100644
--- a/src/com/android/deskclock/timer/TimerPagerAdapter.java
+++ b/src/com/android/deskclock/timer/TimerPagerAdapter.java
@@ -16,6 +16,7 @@
 
 package com.android.deskclock.timer;
 
+import android.annotation.SuppressLint;
 import android.app.Fragment;
 import android.app.FragmentManager;
 import android.app.FragmentTransaction;
@@ -72,6 +73,7 @@
     }
 
     @Override
+    @SuppressLint("CommitTransaction")
     public Fragment instantiateItem(ViewGroup container, int position) {
         if (mCurrentTransaction == null) {
             mCurrentTransaction = mFragmentManager.beginTransaction();
@@ -102,6 +104,7 @@
     }
 
     @Override
+    @SuppressLint("CommitTransaction")
     public void destroyItem(ViewGroup container, int position, Object object) {
         final TimerItemFragment fragment = (TimerItemFragment) object;
 
diff --git a/src/com/android/deskclock/timer/TimerService.java b/src/com/android/deskclock/timer/TimerService.java
index de82b50..8671d90 100644
--- a/src/com/android/deskclock/timer/TimerService.java
+++ b/src/com/android/deskclock/timer/TimerService.java
@@ -21,11 +21,14 @@
 import android.content.Intent;
 import android.os.IBinder;
 
-import com.android.deskclock.HandleDeskClockApiCalls;
+import com.android.deskclock.DeskClock;
 import com.android.deskclock.R;
 import com.android.deskclock.data.DataModel;
 import com.android.deskclock.data.Timer;
 import com.android.deskclock.events.Events;
+import com.android.deskclock.uidata.UiDataModel;
+
+import static com.android.deskclock.uidata.UiDataModel.Tab.TIMERS;
 
 /**
  * <p>This service exists solely to allow {@link android.app.AlarmManager} and timer notifications
@@ -42,18 +45,36 @@
 
     private static final String ACTION_PREFIX = "com.android.deskclock.action.";
 
-    private static final String ACTION_TIMER_EXPIRED = ACTION_PREFIX + "TIMER_EXPIRED";
-    private static final String ACTION_UPDATE_NOTIFICATION = ACTION_PREFIX + "UPDATE_NOTIFICATION";
-    private static final String ACTION_RESET_EXPIRED_TIMERS = ACTION_PREFIX +
-            "RESET_EXPIRED_TIMERS";
-    private static final String ACTION_RESET_UNEXPIRED_TIMERS = ACTION_PREFIX +
-            "RESET_UNEXPIRED_TIMERS";
+    /** Shows the tab with timers; scrolls to a specific timer. */
+    public static final String ACTION_SHOW_TIMER = ACTION_PREFIX + "SHOW_TIMER";
+    /** Pauses running timers; resets expired timers. */
+    public static final String ACTION_PAUSE_TIMER = ACTION_PREFIX + "PAUSE_TIMER";
+    /** Starts the sole timer. */
+    public static final String ACTION_START_TIMER = ACTION_PREFIX + "START_TIMER";
+    /** Resets the timer. */
+    public static final String ACTION_RESET_TIMER = ACTION_PREFIX + "RESET_TIMER";
+    /** Adds an extra minute to the timer. */
+    public static final String ACTION_ADD_MINUTE_TIMER = ACTION_PREFIX + "ADD_MINUTE_TIMER";
+
+    /** Extra for many actions specific to a given timer. */
+    public static final String EXTRA_TIMER_ID = "com.android.deskclock.extra.TIMER_ID";
+
+    private static final String ACTION_TIMER_EXPIRED =
+            ACTION_PREFIX + "TIMER_EXPIRED";
+    private static final String ACTION_UPDATE_NOTIFICATION =
+            ACTION_PREFIX + "UPDATE_NOTIFICATION";
+    private static final String ACTION_RESET_EXPIRED_TIMERS =
+            ACTION_PREFIX + "RESET_EXPIRED_TIMERS";
+    private static final String ACTION_RESET_UNEXPIRED_TIMERS =
+            ACTION_PREFIX + "RESET_UNEXPIRED_TIMERS";
+    private static final String ACTION_RESET_MISSED_TIMERS =
+            ACTION_PREFIX + "RESET_MISSED_TIMERS";
 
     public static Intent createTimerExpiredIntent(Context context, Timer timer) {
         final int timerId = timer == null ? -1 : timer.getId();
         return new Intent(context, TimerService.class)
                 .setAction(ACTION_TIMER_EXPIRED)
-                .putExtra(HandleDeskClockApiCalls.EXTRA_TIMER_ID, timerId);
+                .putExtra(EXTRA_TIMER_ID, timerId);
     }
 
     public static Intent createResetExpiredTimersIntent(Context context) {
@@ -66,10 +87,16 @@
                 .setAction(ACTION_RESET_UNEXPIRED_TIMERS);
     }
 
+    public static Intent createResetMissedTimersIntent(Context context) {
+        return new Intent(context, TimerService.class)
+                .setAction(ACTION_RESET_MISSED_TIMERS);
+    }
+
+
     public static Intent createAddMinuteTimerIntent(Context context, int timerId) {
         return new Intent(context, TimerService.class)
-                .setAction(HandleDeskClockApiCalls.ACTION_ADD_MINUTE_TIMER)
-                .putExtra(HandleDeskClockApiCalls.EXTRA_TIMER_ID, timerId);
+                .setAction(ACTION_ADD_MINUTE_TIMER)
+                .putExtra(EXTRA_TIMER_ID, timerId);
     }
 
     public static Intent createUpdateNotificationIntent(Context context) {
@@ -85,23 +112,29 @@
     @Override
     public int onStartCommand(Intent intent, int flags, int startId) {
         try {
-            switch (intent.getAction()) {
+            final String action = intent.getAction();
+            final int label = intent.getIntExtra(Events.EXTRA_EVENT_LABEL, R.string.label_intent);
+            switch (action) {
                 case ACTION_UPDATE_NOTIFICATION: {
                     DataModel.getDataModel().updateTimerNotification();
                     return START_NOT_STICKY;
                 }
                 case ACTION_RESET_EXPIRED_TIMERS: {
-                    DataModel.getDataModel().resetExpiredTimers(R.string.label_notification);
+                    DataModel.getDataModel().resetExpiredTimers(label);
                     return START_NOT_STICKY;
                 }
                 case ACTION_RESET_UNEXPIRED_TIMERS: {
-                    DataModel.getDataModel().resetUnexpiredTimers(R.string.label_notification);
+                    DataModel.getDataModel().resetUnexpiredTimers(label);
+                    return START_NOT_STICKY;
+                }
+                case ACTION_RESET_MISSED_TIMERS: {
+                    DataModel.getDataModel().resetMissedTimers(label);
                     return START_NOT_STICKY;
                 }
             }
 
             // Look up the timer in question.
-            final int timerId = intent.getIntExtra(HandleDeskClockApiCalls.EXTRA_TIMER_ID, -1);
+            final int timerId = intent.getIntExtra(EXTRA_TIMER_ID, -1);
             final Timer timer = DataModel.getDataModel().getTimer(timerId);
 
             // If the timer cannot be located, ignore the action.
@@ -110,26 +143,40 @@
             }
 
             // Perform the action on the timer.
-            switch (intent.getAction()) {
-                case HandleDeskClockApiCalls.ACTION_START_TIMER:
-                    DataModel.getDataModel().startTimer(timer);
-                    Events.sendTimerEvent(R.string.action_start, R.string.label_notification);
+            switch (action) {
+                case ACTION_SHOW_TIMER: {
+                    Events.sendTimerEvent(R.string.action_show, label);
+
+                    // Change to the timers tab.
+                    UiDataModel.getUiDataModel().setSelectedTab(TIMERS);
+
+                    // Open DeskClock which is now positioned on the timers tab and show the timer
+                    // in question.
+                    final Intent showTimers = new Intent(this, DeskClock.class)
+                            .putExtra(EXTRA_TIMER_ID, timerId)
+                            .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+                    startActivity(showTimers);
                     break;
-                case HandleDeskClockApiCalls.ACTION_PAUSE_TIMER:
+                } case ACTION_START_TIMER: {
+                    Events.sendTimerEvent(R.string.action_start, label);
+                    DataModel.getDataModel().startTimer(this, timer);
+                    break;
+                } case ACTION_PAUSE_TIMER: {
+                    Events.sendTimerEvent(R.string.action_pause, label);
                     DataModel.getDataModel().pauseTimer(timer);
-                    Events.sendTimerEvent(R.string.action_pause, R.string.label_notification);
                     break;
-                case HandleDeskClockApiCalls.ACTION_ADD_MINUTE_TIMER:
+                } case ACTION_ADD_MINUTE_TIMER: {
+                    Events.sendTimerEvent(R.string.action_add_minute, label);
                     DataModel.getDataModel().addTimerMinute(timer);
-                    Events.sendTimerEvent(R.string.action_add_minute, R.string.label_notification);
                     break;
-                case HandleDeskClockApiCalls.ACTION_RESET_TIMER:
-                    DataModel.getDataModel().resetOrDeleteTimer(timer, R.string.label_notification);
+                } case ACTION_RESET_TIMER: {
+                    DataModel.getDataModel().resetOrDeleteTimer(timer, label);
                     break;
-                case ACTION_TIMER_EXPIRED:
+                } case ACTION_TIMER_EXPIRED: {
+                    Events.sendTimerEvent(R.string.action_fire, label);
                     DataModel.getDataModel().expireTimer(this, timer);
-                    Events.sendTimerEvent(R.string.action_fire, R.string.label_intent);
                     break;
+                }
             }
         } finally {
             // This service is foreground when expired timers exist and stopped when none exist.
@@ -140,4 +187,4 @@
 
         return START_NOT_STICKY;
     }
-}
\ No newline at end of file
+}
diff --git a/src/com/android/deskclock/timer/TimerSetupView.java b/src/com/android/deskclock/timer/TimerSetupView.java
index c3b240c..6e79637 100644
--- a/src/com/android/deskclock/timer/TimerSetupView.java
+++ b/src/com/android/deskclock/timer/TimerSetupView.java
@@ -193,7 +193,7 @@
             updateTime();
 
             // Update talkback to read the number being deleted or its original description.
-            final String number = mInputPointer < 0 ? "" : Integer.toString(mInput[mInputPointer]);
+            final String number = mInputPointer < 0 ? "" : Integer.toString(mInput[0]);
             final String cd = getResources().getString(R.string.timer_descriptive_delete, number);
             mDelete.setContentDescription(cd);
         }
@@ -262,7 +262,10 @@
 
     private void updateTime() {
         final int seconds = mInput[1] * 10 + mInput[0];
+        final int minutes = mInput[3] * 10 + mInput[2];
+        final int hours = mInput[5] * 10 + mInput[4];
         mEnteredTime.setTime(mInput[5], mInput[4], mInput[3], mInput[2], seconds);
+        mEnteredTime.setContentDescription(createContentDescription(hours, minutes, seconds));
     }
 
     private void updateDeleteButtonAndDivider() {
@@ -274,4 +277,17 @@
     private void updateFab() {
         mFabContainer.updateFab(FAB_ONLY_SHRINK_AND_EXPAND);
     }
+
+    private CharSequence createContentDescription(int hours, int minutes, int seconds) {
+        final Resources r = getResources();
+        final String hoursFormatted = r.getQuantityString(
+                R.plurals.Nhours_description, hours, hours);
+        final String minutesFormatted = r.getQuantityString(
+                R.plurals.Nminutes_description, minutes, minutes);
+        final String secondsFormatted = r.getQuantityString(
+                R.plurals.Nseconds_description, seconds, seconds);
+        return (hours == 0 ? "" : hoursFormatted + ", ")
+                + (minutes == 0 ? "" : minutesFormatted + ", ")
+                + secondsFormatted;
+    }
 }
diff --git a/src/com/android/deskclock/uidata/UiDataModel.java b/src/com/android/deskclock/uidata/UiDataModel.java
index 1690448..a3c09fd 100644
--- a/src/com/android/deskclock/uidata/UiDataModel.java
+++ b/src/com/android/deskclock/uidata/UiDataModel.java
@@ -350,6 +350,22 @@
     }
 
     //
+    // Shortcut Ids
+    //
+
+    /**
+     * @param category which category of shortcut of which to get the id
+     * @param action the desired action to perform
+     * @return the id of the shortcut
+     */
+    public String getShortcutId(@StringRes int category, @StringRes int action) {
+        if (category == R.string.category_stopwatch) {
+            return mContext.getString(category);
+        }
+        return mContext.getString(category) + "_" + mContext.getString(action);
+    }
+
+    //
     // Timed Callbacks
     //
 
diff --git a/src/com/android/deskclock/widget/toast/LinearLayoutWithSnackbarBehavior.java b/src/com/android/deskclock/widget/toast/LinearLayoutWithSnackbarBehavior.java
deleted file mode 100644
index 3b9c61f..0000000
--- a/src/com/android/deskclock/widget/toast/LinearLayoutWithSnackbarBehavior.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.deskclock.widget.toast;
-
-import android.content.Context;
-import android.support.design.widget.CoordinatorLayout;
-import android.support.design.widget.Snackbar;
-import android.util.AttributeSet;
-import android.view.View;
-import android.widget.LinearLayout;
-
-/**
- * LinearLayout Behavior that slides up and down when snackbar appears/disappears.
- */
-public final class LinearLayoutWithSnackbarBehavior
-        extends CoordinatorLayout.Behavior<LinearLayout> {
-
-    public LinearLayoutWithSnackbarBehavior(Context context, AttributeSet attrs) {}
-
-    @Override
-    public boolean layoutDependsOn(CoordinatorLayout parent, LinearLayout child, View dependency) {
-        return dependency instanceof Snackbar.SnackbarLayout;
-    }
-
-    @Override
-    public boolean onDependentViewChanged(CoordinatorLayout parent, LinearLayout child,
-            View dependency) {
-        final float translationY = Math.min(0,
-                dependency.getTranslationY() - dependency.getHeight());
-        child.setTranslationY(translationY);
-        return true;
-    }
-}
diff --git a/src/com/android/deskclock/widget/toast/SnackbarSlidingBehavior.java b/src/com/android/deskclock/widget/toast/SnackbarSlidingBehavior.java
new file mode 100644
index 0000000..3685bfe
--- /dev/null
+++ b/src/com/android/deskclock/widget/toast/SnackbarSlidingBehavior.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.deskclock.widget.toast;
+
+import android.content.Context;
+import android.support.annotation.Keep;
+import android.support.design.widget.CoordinatorLayout;
+import android.support.design.widget.Snackbar;
+import android.util.AttributeSet;
+import android.view.View;
+
+/**
+ * Custom {@link CoordinatorLayout.Behavior} that slides with the {@link Snackbar}.
+ */
+@Keep
+public final class SnackbarSlidingBehavior extends CoordinatorLayout.Behavior<View> {
+
+    public SnackbarSlidingBehavior(Context context, AttributeSet attrs) {
+    }
+
+    @Override
+    public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
+        return dependency instanceof Snackbar.SnackbarLayout;
+    }
+
+    @Override
+    public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {
+        updateTranslationY(parent, child);
+        return false;
+    }
+
+    @Override
+    public void onDependentViewRemoved(CoordinatorLayout parent, View child, View dependency) {
+        updateTranslationY(parent, child);
+    }
+
+    @Override
+    public boolean onLayoutChild(CoordinatorLayout parent, View child, int layoutDirection) {
+        updateTranslationY(parent, child);
+        return false;
+    }
+
+    private void updateTranslationY(CoordinatorLayout parent, View child) {
+        float translationY = 0f;
+        for (View dependency : parent.getDependencies(child)) {
+            translationY = Math.min(translationY, dependency.getY() - child.getBottom());
+        }
+        child.setTranslationY(translationY);
+    }
+}