Compare commits

33 Commits
temp ... master

Author SHA1 Message Date
tim
a1ed6eb45b Fix integer overflow in milliseconds. 2014-09-13 19:17:53 +02:00
tim
517ae884b4 - New package name, because of a certificate error with the first uploaded version. 2014-08-19 09:34:39 +02:00
tim
45e324fe8c - Release changes 2014-08-18 23:00:10 +02:00
tim
c4c612c0ce - Release changes 2014-08-18 22:42:46 +02:00
tim
118e6cf34a - Fix debugging options 2014-07-09 18:28:14 +02:00
tim
093de96c68 - Remove the TIME_SET receiver, as this could reset the alarm whenever the network decides to reset the phone time.
- Recalculate the grace period notification progress, based on current time rather than once per update. This was causing the notification to persist when the phone went to sleep, past the time that the grace period had actually expired.
- Upgrade to latest libraries.
2014-07-09 18:23:31 +02:00
tim
7810231e19 Prevent alarm cancellation from triggering null value exception, remove unused function, remove unnecessary imports. 2014-04-20 23:04:32 +02:00
tim
2bb2bdc3bc Change the link to one that SMS apps should parse as being for the web. 2014-04-18 17:51:03 +02:00
tim
6c386d5baf Only display toast of text message if debugging is on; get user to approve location queries when starting the app, not when sending text messages; re-add wakelock to AlarmService, otherwise snoozes don't happen. 2014-04-18 17:26:03 +02:00
tim
55e8777d42 Bug fixes for Alarm service, to make sure we don't remove an inactive alarm service (thus preventing it from ever starting next time). Also add commented-out code for a radial timepicker, which can be reenabled when betterpickers releases 1.5.3 (but test it). 2014-04-18 16:07:19 +02:00
tim
048d56c84d Alarm service bug fixes, to ensure that the service isn't prematurely cancelled. Also some commented-out code to enable a radial timepicker. This can be re-enabled once 1.5.3 of betterpickers is released, but needs testing. 2014-04-18 16:02:17 +02:00
tim
615b81e295 Minor tweaks to MainActivity 2014-04-17 16:24:52 +02:00
tim
20f99f2b5f Prevent subsequent alarms from snoozing; make pre-alarm an hour earlier; correct boot receiver bug. 2014-04-17 13:40:28 +02:00
tim
571df2f2c2 - Deal with time and timezone changes 2014-04-15 13:26:29 +02:00
tim
54bd082e89 - Final commit. 2014-04-15 00:46:28 +02:00
tim
6eb1ebe188 - Simplify alarm setting, cancellation, and resetting code
- Lintian cleanups
2014-04-15 00:24:49 +02:00
tim
db6c41fe72 - Always activate the date when starting. 2014-04-14 16:50:01 +02:00
tim
74994b99ee - Fix some date issues that crept in. 2014-04-14 16:47:07 +02:00
tim
b98934c15d - Some lintian checks. 2014-04-14 15:49:26 +02:00
tim
66e615bb20 - Activate for smaller screens and older devices (Samsung Galaxy S Mini, 2.3.6 / API 10).
- Turn off debugging
2014-04-14 15:16:39 +02:00
tim
e6266d4ed6 - Fix a bug where hitting the back button would snooze multiple times. 2014-04-14 13:55:47 +02:00
tim
6532182fb8 - Convert alarm activity from being in the receiver to a service of its own. 2014-04-14 11:45:32 +02:00
tim
f9204e52f3 - Allow user to select ringtone
- Display readable alarm time in pre-alarm notification
2014-04-14 10:55:54 +02:00
tim
fb886aabe9 - Simplify the audio, since we don't trigger the alarm if in a call. 2014-04-14 00:10:15 +02:00
tim
36b796fd13 - If user is in a phone call, they're already awake, so ignore the alarm and reset it for tomorrow. 2014-04-13 23:59:16 +02:00
tim
4c1e6a5a1a - Delay the alarm if in a phone call 2014-04-13 23:51:49 +02:00
tim
c36a1ab907 - Check for incoming phone calls and mute alarm
- Run alarm at lower volume if in a phone call
- Re-enable alarm noises.
2014-04-13 22:53:33 +02:00
tim
a0c2f37b07 - Add location capabilities, using Google Play Services.
- Shift to using GlowPadBackport maven library
2014-04-13 22:27:21 +02:00
tim
ad051bc1c2 - Add a pre-alarm notification
- Initial location stuff.
2014-04-13 18:51:05 +02:00
tim
249a185649 - Disable SeekArc and enable GlowPadView
- Correct fullscreen ability
- Add debugging options
2014-04-13 15:38:16 +02:00
tim
d7bf2832ea Minor revisions (comments). 2014-03-27 21:59:12 +02:00
tim
1813f3faa6 Refactor to allow for snooze reminders (snooze when user hits the back or home buttons). 2014-03-27 21:15:27 +02:00
tim
c2806f2231 Add some SDK checks. 2014-03-26 16:43:24 +02:00
76 changed files with 2059 additions and 1169 deletions

3
.idea/dictionaries/tim.xml generated Normal file
View File

@ -0,0 +1,3 @@
<component name="ProjectDictionaryState">
<dictionary name="tim" />
</component>

View File

@ -1,10 +0,0 @@
<component name="libraryTable">
<library name="appcompat-v7-19.0.1">
<CLASSES>
<root url="file://$PROJECT_DIR$/HypoAlarm/build/exploded-aar/com.android.support/appcompat-v7/19.0.1/res" />
<root url="jar://$PROJECT_DIR$/HypoAlarm/build/exploded-aar/com.android.support/appcompat-v7/19.0.1/classes.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

13
.idea/libraries/appcompat_v7_20_0_0.xml generated Normal file
View File

@ -0,0 +1,13 @@
<component name="libraryTable">
<library name="appcompat-v7-20.0.0">
<ANNOTATIONS>
<root url="jar://$PROJECT_DIR$/HypoAlarm/build/intermediates/exploded-aar/com.android.support/appcompat-v7/20.0.0/annotations.zip!/" />
</ANNOTATIONS>
<CLASSES>
<root url="jar://$PROJECT_DIR$/HypoAlarm/build/intermediates/exploded-aar/com.android.support/appcompat-v7/20.0.0/classes.jar!/" />
<root url="file://$PROJECT_DIR$/HypoAlarm/build/intermediates/exploded-aar/com.android.support/appcompat-v7/20.0.0/res" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

View File

@ -0,0 +1,10 @@
<component name="libraryTable">
<library name="glowpadbackport-2.1.0">
<CLASSES>
<root url="file://$PROJECT_DIR$/HypoAlarm/build/intermediates/exploded-aar/net.frakbot.glowpadbackport/glowpadbackport/2.1.0/res" />
<root url="jar://$PROJECT_DIR$/HypoAlarm/build/intermediates/exploded-aar/net.frakbot.glowpadbackport/glowpadbackport/2.1.0/classes.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

10
.idea/libraries/play_services_5_0_77.xml generated Normal file
View File

@ -0,0 +1,10 @@
<component name="libraryTable">
<library name="play-services-5.0.77">
<CLASSES>
<root url="file://$PROJECT_DIR$/HypoAlarm/build/intermediates/exploded-aar/com.google.android.gms/play-services/5.0.77/res" />
<root url="jar://$PROJECT_DIR$/HypoAlarm/build/intermediates/exploded-aar/com.google.android.gms/play-services/5.0.77/classes.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

View File

@ -1,11 +1,11 @@
<component name="libraryTable">
<library name="support-v4-19.0.1">
<library name="support-annotations-20.0.0">
<CLASSES>
<root url="jar://$APPLICATION_HOME_DIR$/sdk/extras/android/m2repository/com/android/support/support-v4/19.0.1/support-v4-19.0.1.jar!/" />
<root url="jar://$APPLICATION_HOME_DIR$/sdk/extras/android/m2repository/com/android/support/support-annotations/20.0.0/support-annotations-20.0.0.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$APPLICATION_HOME_DIR$/sdk/extras/android/m2repository/com/android/support/support-v4/19.0.1/support-v4-19.0.1-sources.jar!/" />
<root url="jar://$APPLICATION_HOME_DIR$/sdk/extras/android/m2repository/com/android/support/support-annotations/20.0.0/support-annotations-20.0.0-sources.jar!/" />
</SOURCES>
</library>
</component>

11
.idea/libraries/support_v4_20_0_0.xml generated Normal file
View File

@ -0,0 +1,11 @@
<component name="libraryTable">
<library name="support-v4-20.0.0">
<CLASSES>
<root url="file://$PROJECT_DIR$/HypoAlarm/build/intermediates/exploded-aar/com.android.support/support-v4/20.0.0/res" />
<root url="jar://$PROJECT_DIR$/HypoAlarm/build/intermediates/exploded-aar/com.android.support/support-v4/20.0.0/libs/internal_impl-20.0.0.jar!/" />
<root url="jar://$PROJECT_DIR$/HypoAlarm/build/intermediates/exploded-aar/com.android.support/support-v4/20.0.0/classes.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

View File

@ -1,76 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$/.." external.system.id="GRADLE" external.system.module.group="HypoAlarm" external.system.module.version="unspecified" type="JAVA_MODULE" version="4">
<component name="FacetManager">
<facet type="android-gradle" name="Android-Gradle">
<configuration>
<option name="GRADLE_PROJECT_PATH" value=":GlowPadBackport" />
</configuration>
</facet>
<facet type="android" name="Android">
<configuration>
<option name="SELECTED_BUILD_VARIANT" value="debug" />
<option name="ASSEMBLE_TASK_NAME" value="assembleDebug" />
<option name="COMPILE_JAVA_TASK_NAME" value="compileDebugJava" />
<option name="ASSEMBLE_TEST_TASK_NAME" value="assembleDebugTest" />
<option name="SOURCE_GEN_TASK_NAME" value="generateDebugSources" />
<option name="ALLOW_USER_CONFIGURATION" value="false" />
<option name="MANIFEST_FILE_RELATIVE_PATH" value="/src/main/AndroidManifest.xml" />
<option name="RES_FOLDER_RELATIVE_PATH" value="/src/main/res" />
<option name="RES_FOLDERS_RELATIVE_PATH" value="file://$MODULE_DIR$/src/main/res" />
<option name="ASSETS_FOLDER_RELATIVE_PATH" value="/src/main/assets" />
<option name="LIBRARY_PROJECT" value="true" />
</configuration>
</facet>
</component>
<component name="NewModuleRootManager" inherit-compiler-output="false">
<output url="file://$MODULE_DIR$/build/classes/debug" />
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/build/source/r/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/source/aidl/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/source/buildConfig/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/source/rs/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/res/rs/debug" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/build/source/r/test/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/source/aidl/test/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/source/buildConfig/test/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/source/rs/test/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/res/rs/test/debug" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/res" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/resources" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/aidl" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/assets" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/java" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/jni" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/rs" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/res" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/main/aidl" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/assets" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/jni" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/rs" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/res" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/resources" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/aidl" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/assets" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/java" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/jni" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/rs" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/build/apk" />
<excludeFolder url="file://$MODULE_DIR$/build/assets" />
<excludeFolder url="file://$MODULE_DIR$/build/bundles" />
<excludeFolder url="file://$MODULE_DIR$/build/classes" />
<excludeFolder url="file://$MODULE_DIR$/build/dependency-cache" />
<excludeFolder url="file://$MODULE_DIR$/build/incremental" />
<excludeFolder url="file://$MODULE_DIR$/build/libs" />
<excludeFolder url="file://$MODULE_DIR$/build/manifests" />
<excludeFolder url="file://$MODULE_DIR$/build/res" />
<excludeFolder url="file://$MODULE_DIR$/build/symbols" />
<excludeFolder url="file://$MODULE_DIR$/build/tmp" />
</content>
<orderEntry type="jdk" jdkName="Android API 19 Platform" jdkType="Android SDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

View File

@ -1,19 +0,0 @@
apply plugin: 'android-library'
android {
compileSdkVersion 19
buildToolsVersion "19.0.1"
defaultConfig {
minSdkVersion 10
targetSdkVersion 19
versionCode 1
versionName "1.0"
}
buildTypes {
release {
runProguard false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
}
}
}

View File

@ -1,17 +0,0 @@
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in /home/tim/sources/android-studio/sdk/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the ProGuard
# include property in project.properties.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}

View File

@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="net.sebastianopoggi.ui.GlowPadBackport">
</manifest>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

View File

@ -1,3 +0,0 @@
<resources>
<string name="app_name">GlowPadBackport</string>
</resources>

View File

@ -8,11 +8,10 @@
</facet>
<facet type="android" name="Android">
<configuration>
<option name="SELECTED_BUILD_VARIANT" value="debug" />
<option name="ASSEMBLE_TASK_NAME" value="assembleDebug" />
<option name="COMPILE_JAVA_TASK_NAME" value="compileDebugJava" />
<option name="ASSEMBLE_TEST_TASK_NAME" value="assembleDebugTest" />
<option name="SOURCE_GEN_TASK_NAME" value="generateDebugSources" />
<option name="SELECTED_BUILD_VARIANT" value="release" />
<option name="ASSEMBLE_TASK_NAME" value="assembleRelease" />
<option name="COMPILE_JAVA_TASK_NAME" value="compileReleaseJava" />
<option name="SOURCE_GEN_TASK_NAME" value="generateReleaseSources" />
<option name="ALLOW_USER_CONFIGURATION" value="false" />
<option name="MANIFEST_FILE_RELATIVE_PATH" value="/src/main/AndroidManifest.xml" />
<option name="RES_FOLDER_RELATIVE_PATH" value="/src/main/res" />
@ -22,56 +21,63 @@
</facet>
</component>
<component name="NewModuleRootManager" inherit-compiler-output="false">
<output url="file://$MODULE_DIR$/build/classes/debug" />
<output url="file://$MODULE_DIR$/build/intermediates/classes/release" />
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/build/source/r/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/source/aidl/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/source/buildConfig/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/source/rs/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/res/rs/debug" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/build/source/r/test/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/source/aidl/test/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/source/buildConfig/test/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/source/rs/test/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/res/rs/test/debug" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/res" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/resources" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/aidl" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/assets" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/java" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/jni" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/rs" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/r/release" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/aidl/release" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/buildConfig/release" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/rs/release" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/rs/release" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/release/res" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/release/resources" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/release/assets" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/release/aidl" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/release/java" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/release/jni" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/release/rs" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/res" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/main/assets" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/main/aidl" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/assets" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/jni" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/rs" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/res" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/resources" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/assets" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/aidl" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/assets" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/java" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/jni" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/rs" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/build/apk" />
<excludeFolder url="file://$MODULE_DIR$/build/assets" />
<excludeFolder url="file://$MODULE_DIR$/build/bundles" />
<excludeFolder url="file://$MODULE_DIR$/build/classes" />
<excludeFolder url="file://$MODULE_DIR$/build/dependency-cache" />
<excludeFolder url="file://$MODULE_DIR$/build/incremental" />
<excludeFolder url="file://$MODULE_DIR$/build/libs" />
<excludeFolder url="file://$MODULE_DIR$/build/manifests" />
<excludeFolder url="file://$MODULE_DIR$/build/res" />
<excludeFolder url="file://$MODULE_DIR$/build/symbols" />
<excludeFolder url="file://$MODULE_DIR$/build/tmp" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/assets" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/bundles" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/classes" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/coverage-instrumented-classes" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/dependency-cache" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/dex" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/dex-cache" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/jacoco" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/javaResources" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/libs" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/lint" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/manifests" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/ndk" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/pre-dexed" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/proguard" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/res" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/rs" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/symbols" />
<excludeFolder url="file://$MODULE_DIR$/build/outputs" />
</content>
<orderEntry type="jdk" jdkName="Android API 19 Platform" jdkType="Android SDK" />
<orderEntry type="jdk" jdkName="Android API 20 Platform" jdkType="Android SDK" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" exported="" name="appcompat-v7-19.0.1" level="project" />
<orderEntry type="library" exported="" name="support-v4-19.0.1" level="project" />
<orderEntry type="library" exported="" name="appcompat-v7-20.0.0" level="project" />
<orderEntry type="library" exported="" name="play-services-5.0.77" level="project" />
<orderEntry type="library" exported="" name="glowpadbackport-2.1.0" level="project" />
<orderEntry type="library" exported="" name="support-annotations-20.0.0" level="project" />
<orderEntry type="library" exported="" name="support-v4-20.0.0" level="project" />
<orderEntry type="library" exported="" name="library-2.4.0" level="project" />
</component>
</module>

View File

@ -1,27 +1,41 @@
apply plugin: 'android'
android {
compileSdkVersion 19
buildToolsVersion '19.0.1'
signingConfigs {
config {
keyAlias 'druid-android-keystore'
keyPassword 'android56Loops'
storeFile file('/home/tim/.android-apk-keystore.jks')
storePassword 'PurpleApes&20Flowers'
}
}
compileSdkVersion 20
buildToolsVersion '20.0.0'
defaultConfig {
minSdkVersion 10
targetSdkVersion 19
versionCode 1
versionName "1.0"
targetSdkVersion 20
versionCode 2
versionName '1.0'
signingConfig signingConfigs.config
}
buildTypes {
release {
runProguard false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
signingConfig signingConfigs.config
}
}
productFlavors {
}
}
dependencies {
compile 'com.android.support:support-v4:19.0.1'
compile 'com.android.support:appcompat-v7:19.0.1'
compile 'com.nineoldandroids:library:2.4.0'
compile fileTree(dir: 'libs', include: ['*.aar'])
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.android.support:appcompat-v7:20.+'
compile 'com.android.support:support-v4:20.+'
compile 'com.google.android.gms:play-services:+'
//compile 'com.google.android.gms:play-services:4.2.42'
compile 'net.frakbot.glowpadbackport:glowpadbackport:2.1.0'
//compile 'com.doomonafireball.betterpickers:library:+'
//compile 'com.doomonafireball.betterpickers:library:1.5.2'
//compile 'com.doomonafireball.betterpickers:library:1.5.3-SNAPSHOT'
//compile 'com.github.flavienlaurent.datetimepicker:library:+'
//compile 'com.github.flavienlaurent.datetimepicker:library:0.0.1'
}

View File

@ -1,6 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="za.org.treehouse.hypoalarm" >
package="za.org.treehouse.HypoAlarm" >
<!-- permission required to read contacts -->
<uses-permission android:name="android.permission.READ_CONTACTS" />
<!-- permission required to tell if a phone call is active -->
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<!-- permission required to set an alarm -->
<uses-permission android:name="com.android.alarm.permission.SET_ALARM" />
<!-- permission required to send an SMS -->
<uses-permission android:name="android.permission.SEND_SMS" />
<!-- permission required to message the user's location -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<!-- permission to restart the alarm on device boot -->
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<!-- permission required to vibrate -->
<uses-permission android:name="android.permission.VIBRATE" />
<!-- permission required for AlarmService to keep the CPU awake -->
<uses-permission android:name="android.permission.WAKE_LOCK" />
<application
android:allowBackup="true"
@ -8,45 +25,48 @@
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name=".MainActivity"
android:name="za.org.treehouse.HypoAlarm.MainActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".AlarmAlertActivity"
android:label="@string/alarm_alert"
android:launchMode="singleInstance"
android:noHistory="true">
<activity
android:name="za.org.treehouse.HypoAlarm.AlarmAlertActivity"
android:label="@string/alarm_alert"
android:launchMode="singleInstance"
android:noHistory="true" >
</activity>
<receiver android:name=".AlarmReceiver"/>
<receiver android:name=".GraceReceiver"/>
<receiver android:name=".CancelGraceReceiver"/>
<receiver android:name=".BootReceiver"
<receiver android:name="za.org.treehouse.HypoAlarm.PreAlarmReceiver" />
<receiver android:name="za.org.treehouse.HypoAlarm.CancelAlarmReceiver" />
<receiver android:name="za.org.treehouse.HypoAlarm.AlarmReceiver" />
<receiver android:name="za.org.treehouse.HypoAlarm.GraceReceiver" />
<receiver android:name="za.org.treehouse.HypoAlarm.CancelGraceReceiver" />
<receiver
android:name="za.org.treehouse.HypoAlarm.AlarmChangeReceiver"
android:enabled="false" >
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
<action android:name="android.intent.action.BOOT_COMPLETED" />
<action android:name="android.intent.action.TIMEZONE_CHANGED" />
<!--action android:name="android.intent.action.TIME_SET" /-->
</intent-filter>
</receiver>
<service
android:name=".AlarmNotify"
android:label="@string/alarm_notification" >
</service>
android:name="za.org.treehouse.HypoAlarm.AlarmService" />
<service
android:name="za.org.treehouse.HypoAlarm.AlarmNotify"
android:label="@string/alarm_notification" />
<service
android:name="za.org.treehouse.HypoAlarm.PreAlarmNotify"
android:label="@string/pre_alarm_notification" />
<meta-data
android:name="com.google.android.gms.version"
android:value="@integer/google_play_services_version" />
</application>
<!-- permission required to read contacts -->
<uses-permission android:name="android.permission.READ_CONTACTS"/>
<!-- permission required to set an alarm -->
<uses-permission android:name="com.android.alarm.permission.SET_ALARM"/>
<!-- permission required to Send SMS -->
<uses-permission android:name="android.permission.SEND_SMS"/>
<!-- permission to restart the alarm on device boot -->
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<!-- permission required to vibrate -->
<uses-permission android:name="android.permission.VIBRATE"/>
<!-- permission required to wake the device -->
<uses-permission android:name="android.permission.WAKE_LOCK"/>
</manifest>

View File

@ -0,0 +1,103 @@
package za.org.treehouse.HypoAlarm;
import android.app.Activity;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import net.frakbot.glowpadbackport.GlowPadView;
public class AlarmAlertActivity extends Activity {
public static Activity alertActivity;
@Override
protected void onDestroy() {
super.onDestroy();
alertActivity = null;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Self-reference so we can run finish() from other classes
alertActivity = this;
requestWindowFeature(Window.FEATURE_NO_TITLE);
final Window window = getWindow();
// Set to use the full screen
window.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
| WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD
| WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
| WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
| WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON
| WindowManager.LayoutParams.FLAG_FULLSCREEN
);
if (Build.VERSION.SDK_INT >= 11) {
window.getDecorView().
setSystemUiVisibility(View.SYSTEM_UI_FLAG_LOW_PROFILE);
}
setContentView(R.layout.alarm_alert);
Intent notifyIntent = new Intent(getApplicationContext(), AlarmNotify.class);
// Disable any current notifications (if we're snoozing)
stopService(notifyIntent);
}
@Override
public void onStart() {
super.onStart();
final GlowPadView cancelGlowPad = (GlowPadView) findViewById(R.id.cancel_glowpad);
cancelGlowPad.setOnTriggerListener(new GlowPadView.OnTriggerListener() {
@Override
public void onGrabbed(View v, int handle) {
// Do nothing
}
@Override
public void onReleased(View v, int handle) {
// Do nothing
}
@Override
public void onTrigger(View v, int target) {
// if (target == "")
AlarmService.dismissAlarm(alertActivity);
}
@Override
public void onGrabbedStateChange(View v, int handle) {
// Do nothing
}
@Override
public void onFinishFinalAnimation() {
// Do nothing
}
});
}
/**
* Handle the user pressing the back/return and home buttons
*/
@Override
public void onBackPressed() {
if (!AlarmService.alarmStatus.contentEquals(AlarmService.ALARM_SNOOZE_RUNNING)) {
AlarmService.setAlarmStatus(AlarmService.ALARM_IGNORED);
}
AlarmService.snoozeAlarm(this);
}
public void onUserLeaveHint() {
if (!AlarmService.alarmStatus.contentEquals(AlarmService.ALARM_SNOOZE_RUNNING)) {
AlarmService.setAlarmStatus(AlarmService.ALARM_IGNORED);
}
AlarmService.snoozeAlarm(this);
}
}

View File

@ -0,0 +1,26 @@
package za.org.treehouse.HypoAlarm;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.util.Log;
import java.util.Calendar;
public class AlarmChangeReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(context);
// Don't set this to happen on TIME_SET, or it'll restart the alarm every time the network (or NTP) sets the time.
// intent.getAction().equals("android.intent.action.TIME_SET")
if (intent.getAction().equals("android.intent.action.BOOT_COMPLETED") ||
intent.getAction().equals("android.intent.action.TIMEZONE_CHANGED")) {
String alarmTimeStr = sharedPref.getString(context.getString(R.string.AlarmTimePref), MainActivity.defaultTimeStr);
Calendar cal = MainActivity.TimeStringToCalendar(alarmTimeStr);
Log.d("AlarmChangeReceiver", intent.getAction() + ": resetting alarm for " + MainActivity.debugDate(cal));
MainActivity.setAlarm(context, cal);
}
}
}

View File

@ -0,0 +1,139 @@
package za.org.treehouse.HypoAlarm;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.res.AssetFileDescriptor;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.media.RingtoneManager;
import android.net.Uri;
import android.os.Vibrator;
import android.preference.PreferenceManager;
import android.telephony.PhoneStateListener;
import android.telephony.TelephonyManager;
import android.util.Log;
import java.io.IOException;
public class AlarmKlaxon {
private static Boolean klaxonActive = false;
private static final long[] vPattern = {500, 500};
// Volume modification for alarms while a phone call is active, from com.android.deskclock.alarms
private static final float IN_CALL_VOLUME = 0.125f;
private static MediaPlayer mediaPlayer = null;
private static TelephonyManager telephonyManager;
private static PhoneStateListener phoneStateListener;
private static int initialCallState;
private static Vibrator vibrator;
public static void start(final Context context) {
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(context);
if (klaxonActive) {
stop(context);
}
klaxonActive = true;
vibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
vibrator.cancel();
vibrator.vibrate(vPattern, 0);
telephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
initialCallState = telephonyManager.getCallState();
phoneStateListener = new PhoneStateListener() {
@Override
public void onCallStateChanged(int state, String ignored) {
// The user might already be in a call when the alarm fires. When
// we register onCallStateChanged, we get the initial in-call state
// which kills the alarm. Check against the initial call state so
// we don't kill the alarm during a call.
if (state != TelephonyManager.CALL_STATE_IDLE && state != initialCallState) {
if (mediaPlayer != null) {
mediaPlayer.setVolume(IN_CALL_VOLUME, IN_CALL_VOLUME);
}
// or just stop the audio entirely...
//stopAudio(context);
}
}
};
telephonyManager.listen(phoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
stopAudio(context);
mediaPlayer = new MediaPlayer();
mediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {
@Override
public boolean onError(MediaPlayer mp, int what, int extra) {
Log.e("AlarmKlaxon", "Error occurred while playing audio. Stopping alarm.");
stopAudio(context);
return true;
}
});
// Get preferred ringtone, or fall back to the default alarm tone
String ringtoneStr = sharedPref.getString(context.getString(R.string.RingtonePref), RingtoneManager.getDefaultUri(RingtoneManager.TYPE_ALARM).toString());
Uri ringtoneUri = Uri.parse(ringtoneStr);
try {
mediaPlayer.setDataSource(context, ringtoneUri);
startAudio(context);
} catch (Exception ex) {
// The ringtoneUri may be on the sd card which could be busy right
// now. Use the fallback ringtone.
try {
// Reset the media player to clear the error state.
mediaPlayer.reset();
setDataSourceFromResource(context, mediaPlayer, R.raw.fallbackring);
startAudio(context);
} catch (Exception ex2) {
// At this point we just don't play anything.
Log.e("AlarmKlaxon", "Failed to play fallback ringtone", ex2);
}
}
}
public static void stop(final Context context) {
if (klaxonActive) {
vibrator.cancel();
stopAudio(context);
telephonyManager.listen(phoneStateListener, PhoneStateListener.LISTEN_NONE);
klaxonActive = false;
}
}
private static void startAudio(final Context context) throws IOException {
AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
// do not play alarms if stream volume is 0 (typically because ringer mode is silent).
if (audioManager.getStreamVolume(AudioManager.STREAM_ALARM) != 0) {
mediaPlayer.setAudioStreamType(AudioManager.STREAM_ALARM);
mediaPlayer.setLooping(true);
try {
mediaPlayer.prepare();
} catch (Exception e) {
Log.e("AlarmAlertActivity", "Prepare failed. Exiting", e);
return;
}
audioManager.requestAudioFocus(null,
AudioManager.STREAM_ALARM, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
mediaPlayer.start();
}
}
private static void stopAudio(final Context context) {
if (mediaPlayer != null) {
mediaPlayer.stop();
AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
audioManager.abandonAudioFocus(null);
mediaPlayer.release();
mediaPlayer = null;
}
}
// Load ringtone from a resource
private static void setDataSourceFromResource(Context context, MediaPlayer player, int res) throws IOException {
AssetFileDescriptor afd = context.getResources().openRawResourceFd(res);
if (afd != null) {
player.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
afd.close();
}
}
}

View File

@ -1,4 +1,4 @@
package za.org.treehouse.hypoalarm;
package za.org.treehouse.HypoAlarm;
import android.app.Notification;
import android.app.NotificationManager;
@ -9,13 +9,15 @@ import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Build;
import android.os.IBinder;
import android.preference.PreferenceManager;
import android.support.v4.app.NotificationCompat;
import android.util.Log;
public class AlarmNotify extends Service {
public static final int notifyID = 1;
public volatile boolean notificationRunning = false;
private static final int notifyID = 1;
private volatile boolean notificationRunning = false;
@Override
public IBinder onBind(Intent intent) {
@ -26,10 +28,10 @@ public class AlarmNotify extends Service {
public void onDestroy() {
// If the notification is cancelled, stop updating.
notificationRunning = false;
Log.d("AlarmNotify", "1: Setting notificationRunning to false");
// Remove the notification in the notification bar
NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
nm.cancel(notifyID);
Log.d("AlarmNotify", "Notification stopped.");
}
@Override
@ -37,21 +39,23 @@ public class AlarmNotify extends Service {
final int UPDATE_INTERVAL = 10*1000; // Timer is updated six times a minute
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this);
Log.d("AlarmNotify", "Notification started.");
//final String phoneNumber = sharedPref.getString(getString(R.string.PhoneNumberPref), null);
final int gracePeriod = sharedPref.getInt(getString(R.string.GracePeriodPref), 60);
// convert gracePeriod to milliseconds and calculate when it'll fire
final long endTime = System.currentTimeMillis() + (gracePeriod * 60 * 1000);
final int gracePeriod = sharedPref.getInt(getString(R.string.GracePeriodPref), MainActivity.defaultGracePeriod);
Bitmap bm = BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher_grey);
final NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
final Notification.Builder notification = new Notification.Builder(this)
final NotificationCompat.Builder notification = new NotificationCompat.Builder(this)
.setContentTitle(getString(R.string.app_name))
.setContentText(String.format(getString(R.string.notificationText), MainActivity.MinutesToGracePeriodStr(gracePeriod)))
.setSmallIcon(R.drawable.alarm_notification)
.setLargeIcon(bm)
.setOnlyAlertOnce(true)
.setAutoCancel(false)
.setPriority(Notification.PRIORITY_HIGH);
.setAutoCancel(false);
if (Build.VERSION.SDK_INT >= 16) {
notification.setPriority(Notification.PRIORITY_HIGH);
}
// Set up dismiss action
Intent cancellerIntent = new Intent(getBaseContext(), CancelGraceReceiver.class);
@ -64,15 +68,6 @@ public class AlarmNotify extends Service {
// Allow the user to cancel by selecting the ContentText or ContentTitle
notification.setContentIntent(cancellerPendingIntent);
/**
* TODO load alert activity (without sound or vibration) on select?
* TODO This would allow the user to test competence
Intent alertActivityIntent = new Intent(this, AlarmAlertActivity.class);
alertActivityIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
notification.setContentIntent(alertActivityIntent);
*/
nm.cancel(notifyID);
nm.notify(notifyID, notification.build());
@ -80,42 +75,45 @@ public class AlarmNotify extends Service {
@Override
public void run() {
notificationRunning = true;
Log.d("AlarmNotify", "2: Setting notificationRunning to true");
// Make progress out of 1000 and not just 100, for greater resolution
int max = 1000;
// Convert endTime from milliseconds to seconds, and translate to time remaining
int secondsLeft = (int) ((endTime - System.currentTimeMillis())) / (1000);
int gracePeriodSeconds = gracePeriod * 60;
// Multiply each int by 1000 for greater progress resolution
int progress = (((gracePeriodSeconds * 1000) - (secondsLeft * 1000)) * max) / (gracePeriodSeconds * 1000);
// Count in milliseconds for greater progress resolution
long graceEndTime = AlarmService.graceEndTime;
long milliSecondsLeft = ((graceEndTime - System.currentTimeMillis()));
long gracePeriodMilliSeconds = gracePeriod * 60 * 1000;
int progress = (int) (((gracePeriodMilliSeconds - milliSecondsLeft) * max) / gracePeriodMilliSeconds);
while (progress < max) {
// Stop the thread if cancelled elsewhere
Log.d("AlarmNotify", "notificationRunning is "+notificationRunning);
// Stop the thread if the notification is cancelled elsewhere
if (!notificationRunning) {
return;
}
int minutesLeft = secondsLeft / 60;
notification.setContentText(String.format(getString(R.string.notificationText), MainActivity.MinutesToGracePeriodStr(minutesLeft)));
notification.setProgress(max, progress, false);
// Update the notification
nm.notify(notifyID, notification.build());
// Prepare secondsLeft and progress for the next loop
secondsLeft = secondsLeft - (UPDATE_INTERVAL / 1000);
// Multiply each int by 1000 for greater progress resolution
progress = (((gracePeriodSeconds * 1000) - (secondsLeft * 1000)) * max) / (gracePeriodSeconds * 1000);
Log.d("AlarmNotify", "secondsLeft is "+secondsLeft+" and progress is "+progress+" (gracePeriodSeconds is "+gracePeriodSeconds+")");
// Sleeps the thread, simulating an operation
// that takes time
// Recalculate milliSecondsLeft so that progress gets updated correctly.
milliSecondsLeft = ((graceEndTime - System.currentTimeMillis()));
// Recalculate the progress
progress = (int) (((gracePeriodMilliSeconds - milliSecondsLeft) * max) / gracePeriodMilliSeconds);
int minutesLeft = (int) ((milliSecondsLeft / 1000) / 60);
if (Build.VERSION.SDK_INT >= 11) {
notification.setContentText(String.format(getString(R.string.notificationText), MainActivity.MinutesToGracePeriodStr(minutesLeft)));
notification.setProgress(max, progress, false);
// Update the notification
nm.notify(notifyID, notification.build());
}
//Log.d("AlarmNotify", "milliSecondsLeft is " + milliSecondsLeft + " and gracePeriodMilliSeconds is " + gracePeriodMilliSeconds);
//Log.d("AlarmNotify", "progress is " + progress + "; max is " + max);
// Sleep until we need to update again
try {
Thread.sleep(UPDATE_INTERVAL);
} catch (InterruptedException e) {
Log.d("AlarmNotify", "sleep failure");
Log.d("AlarmNotify", "sleep failure: " + e.toString());
}
}
stopSelf(); // stop notification service
}
}).start();
return super.onStartCommand(intent, flags, startId);
return super.onStartCommand(intent, flags, startId);
}
}

View File

@ -0,0 +1,15 @@
package za.org.treehouse.HypoAlarm;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
public class AlarmReceiver extends BroadcastReceiver {
@Override
public void onReceive(final Context context, final Intent intent) {
Intent alarmIntent = new Intent(context, AlarmService.class);
context.startService(alarmIntent);
}
}

View File

@ -0,0 +1,194 @@
package za.org.treehouse.HypoAlarm;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Handler;
import android.os.IBinder;
import android.os.PowerManager;
import android.preference.PreferenceManager;
import android.telephony.TelephonyManager;
import android.util.Log;
import java.util.Calendar;
public class AlarmService extends Service {
private static final int SNOOZE_TIME = 1000*60*5; // Snooze for 5 minutes if need be
private static final int ALERT_LIFE = 1000*60*2; // 2 minutes
private static PowerManager.WakeLock wl;
private static Intent alarmServiceIntent, alertActivityIntent, notifyIntent;
private static Boolean alarmStarted = false;
public static final String ALARM_RUNNING = "ALARM_RUNNING";
public static final String ALARM_DISMISSED = "ALARM_DISMISSED";
public static final String ALARM_IGNORED = "ALARM_IGNORED";
public static final String ALARM_SNOOZED = "ALARM_SNOOZED";
public static final String ALARM_SNOOZE_RUNNING = "ALARM_SNOOZE_RUNNING";
public static volatile String alarmStatus = ALARM_DISMISSED; // Register ALARM_DISMISSED and its brethren here
public static long graceEndTime;
@Override
public void onCreate() {
// Ensure that CPU runs while the service is running, so we don't miss an alert after snoozing
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "AlarmService");
wl.acquire();
}
@Override
public void onDestroy() {
Log.d("AlarmService", "Destroying alarm (alarmStarted: " + alarmStarted + ")");
if (alarmStarted) {
stopAlert(getApplicationContext());
alarmStarted = false;
}
if (wl.isHeld()) {
wl.release();
}
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
alarmServiceIntent = intent;
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this);
final Boolean alarmActive = sharedPref.getBoolean(getString(R.string.AlarmActivePref), MainActivity.defaultActive);
final String alarmTimeStr = sharedPref.getString(getString(R.string.AlarmTimePref), MainActivity.defaultTimeStr);
final int gracePeriod = sharedPref.getInt(getString(R.string.GracePeriodPref), MainActivity.defaultGracePeriod);
if (alarmActive) {
// Cancel the pre-alarm notification, if it exists
stopService(new Intent(this, PreAlarmNotify.class));
// Set up intents for later use
notifyIntent = new Intent(this, AlarmNotify.class);
alertActivityIntent = new Intent(this, AlarmAlertActivity.class);
alertActivityIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
if (alarmStarted) {
stopService(notifyIntent);
}
alarmStarted = true;
// if nothing else happens, assume the alert was ignored.
alarmStatus = ALARM_RUNNING;
// Reset for tomorrow; as of API 19, setRepeating() is inexact, so we use setExact()
Calendar cal = MainActivity.TimeStringToCalendar(alarmTimeStr);
// Advance the calendar to tomorrow (and make sure the calendar hasn't already been advanced)
if (cal.before(Calendar.getInstance())) {
cal.add(Calendar.DAY_OF_MONTH, 1);
}
MainActivity.setAlarm(getApplicationContext(), cal);
Log.d("AlarmService", "Alarm reset for next period");
// If dialing, active in a phone call, or on hold, don't bother with today's alarm, just reset it for tomorrow
TelephonyManager telephonyManager = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
if (telephonyManager.getCallState() != TelephonyManager.CALL_STATE_OFFHOOK) {
// set grace period, which sends the text message upon firing
MainActivity.setGraceAlarm(getApplicationContext());
// Calculate when the grace period (converted from minutes to milliseconds) ends
graceEndTime = System.currentTimeMillis() + (gracePeriod * 60 * 1000);
// Allow user to acknowledge alarm and cancel grace alarm
startAlert(this);
}
}
return super.onStartCommand(intent, flags, startId);
}
private static void startAlert(final Context context) {
Log.d("AlarmService", "Starting alert; status is " + alarmStatus);
// Turn off any notifications first
context.stopService(notifyIntent);
context.startActivity(alertActivityIntent);
AlarmKlaxon.start(context);
// Turn off the alert activity after a period, and switch to a notification
new Handler().postDelayed(new Runnable() {
public void run() {
Log.d("AlarmService", "Closing alert activity, status is " + alarmStatus);
// Close the dialogue and switch to notification
// if the Activity has not been closed by the user
// (that is, snoozeAlert and dismissAlert have not been called)
if (alarmStatus.contentEquals(ALARM_DISMISSED) ||
alarmStatus.contentEquals(ALARM_SNOOZED)) {
// Do nothing
// Stop if we've already run the snooze alert
} else if (alarmStatus.contentEquals(ALARM_SNOOZE_RUNNING)) {
alarmStatus = ALARM_IGNORED;
context.stopService(alarmServiceIntent);
} else {
alarmStatus = ALARM_IGNORED; // This is true, although we are about to switch to ALARM_SNOOZED
snoozeAlarm(context);
}
}
}, ALERT_LIFE);
}
private static void stopAlert(final Context context) {
Log.d("AlarmService", "Stopping alert; status is " + alarmStatus);
if (alarmStarted) {
AlarmKlaxon.stop(context);
if (AlarmAlertActivity.alertActivity != null) {
AlarmAlertActivity.alertActivity.finish();
}
if (!alarmStatus.contentEquals(ALARM_DISMISSED)) {
context.startService(notifyIntent);
}
}
}
public static void dismissAlarm(final Context context) {
Log.d("AlarmService", "Dismissing alarm");
alarmStatus = ALARM_DISMISSED;
// Cancel the graceAlarm
MainActivity.cancelGraceAlarm(context);
// Stop this service, along with the alert and all notifications
context.stopService(alarmServiceIntent);
}
public static void snoozeAlarm(final Context context) {
Log.d("AlarmService", "Snoozing alarm");
// Close the alert, stop the klaxon, and start the notification,
// but only if there's time left before the gracePeriod triggers,
// and we haven't snoozed before
if (((System.currentTimeMillis() + SNOOZE_TIME) < graceEndTime) &&
(!alarmStatus.contentEquals(ALARM_SNOOZE_RUNNING)) &&
(!alarmStatus.contentEquals(ALARM_SNOOZED)) &&
(!alarmStatus.contentEquals(ALARM_DISMISSED))) {
stopAlert(context);
new Handler().postDelayed(new Runnable() {
public void run() {
Log.d("AlarmService", "Resuming after snooze; status is " + alarmStatus);
// Don't run if the alarm was dismissed before the timer ran out
// (because a notification was acknowledged)
if (!alarmStatus.contentEquals(ALARM_DISMISSED)) {
alarmStatus = ALARM_SNOOZE_RUNNING;
startAlert(context);
}
}
}, SNOOZE_TIME);
// Change alarm status from ignored to snoozed
alarmStatus = ALARM_SNOOZED;
} else {
Log.d("AlarmService", "Actually, not snoozing!");
context.stopService(alarmServiceIntent);
}
}
public static void setAlarmStatus (String status) {
Log.d("AlarmService", "Setting alarm status to " + status);
alarmStatus = status;
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
}

View File

@ -0,0 +1,41 @@
package za.org.treehouse.HypoAlarm;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.util.Log;
import android.widget.Toast;
import java.util.Calendar;
public class CancelAlarmReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(context);
String alarmTimeStr = sharedPref.getString(context.getString(R.string.AlarmTimePref), MainActivity.defaultTimeStr);
// Cancel alarm. This isn't technically necessary, as it'll happen in setAlarm
MainActivity.cancelAlarm(context);
// Reset for tomorrow. setAlarm will also advance the day, but
// make it explicit here.
Calendar cal = MainActivity.TimeStringToCalendar(alarmTimeStr);
cal.add(Calendar.DAY_OF_MONTH, 1);
// Reset alarm for tomorrow
MainActivity.setAlarm(context, cal);
// Display toast
Toast.makeText(context, context.getString(R.string.alarmCancelToast), Toast.LENGTH_LONG).show();
// Stop any snoozed/existing alarms that may have started
AlarmService.setAlarmStatus(AlarmService.ALARM_DISMISSED);
context.stopService(new Intent(context, AlarmService.class));
// Remove notification
context.stopService(new Intent(context, PreAlarmNotify.class));
}
}

View File

@ -0,0 +1,21 @@
package za.org.treehouse.HypoAlarm;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
import android.widget.Toast;
public class CancelGraceReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
// Ensure that any snoozes that are pending never happen.
AlarmService.setAlarmStatus(AlarmService.ALARM_DISMISSED);
context.stopService(new Intent(context, AlarmService.class));
MainActivity.cancelGraceAlarm(context);
// Display toast
Toast.makeText(context, context.getString(R.string.graceCancelToast), Toast.LENGTH_LONG).show();
}
}

View File

@ -0,0 +1,93 @@
package za.org.treehouse.HypoAlarm;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.location.Location;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.telephony.SmsManager;
import android.util.Log;
import android.widget.Toast;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GooglePlayServicesClient;
import com.google.android.gms.common.GooglePlayServicesUtil;
import com.google.android.gms.location.LocationClient;
/*
* TODO: translate geographic coordinates into an address?
* import android.location.Address;
* import android.location.Geocoder;
*/
public class GraceReceiver extends BroadcastReceiver {
private static LocationClient locationClient = null;
private static String phoneNumber;
private static String message;
@Override
public void onReceive(final Context context, Intent intent) {
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(context);
Boolean alarmActive = sharedPref.getBoolean(context.getString(R.string.AlarmActivePref), MainActivity.defaultActive);
phoneNumber = sharedPref.getString(context.getString(R.string.PhoneNumberPref), null);
message = sharedPref.getString(context.getString(R.string.MessagePref), context.getString(R.string.defaultMessage));
// Stop any lingering alarm service
context.stopService(new Intent(context, AlarmService.class));
if (alarmActive) {
if (GooglePlayServicesUtil.isGooglePlayServicesAvailable(context) == ConnectionResult.SUCCESS) {
locationClient = new LocationClient(context,
new GooglePlayServicesClient.ConnectionCallbacks() {
@Override
public void onConnected(Bundle bundle) {
Location location = locationClient.getLastLocation();
if (location != null) {
// uri must begin with a space
String uri = " http://maps.google.com/maps/?q=loc:" + location.getLatitude() + "," + location.getLongitude();
message += uri;
sendText(context);
} else {
Log.e("GraceReceiver", "No location data available. Sending text message anyway.");
sendText(context);
}
locationClient.disconnect();
}
@Override
public void onDisconnected() {
}
},
new GooglePlayServicesClient.OnConnectionFailedListener() {
@Override
public void onConnectionFailed(ConnectionResult connectionResult) {
Log.e("GraceReceiver", "Failed connection to location manager " + connectionResult.toString() + ". Sending text message anyway.");
sendText(context);
}
}
);
locationClient.connect();
} else {
Log.e("GraceReceiver", "Google Play Services is not available. Sending text message anyway.");
sendText(context);
}
}
}
private void sendText(Context context) {
SmsManager sms = SmsManager.getDefault();
if (phoneNumber == null || phoneNumber.isEmpty()) {
message = "You have not specified a phone number. No text message will be sent.";
Toast.makeText(context, message, Toast.LENGTH_LONG).show();
Log.e("GraceReceiver", "ERROR: " + message);
} else {
if (!MainActivity.HYPOALARM_DEBUG) {
sms.sendTextMessage(phoneNumber, null, message, null, null);
} else {
Toast.makeText(context, message, Toast.LENGTH_LONG).show();
}
Log.d("GraceReceiver", "Sending sms to " + phoneNumber + " with message: " + message);
}
}
}

View File

@ -0,0 +1,719 @@
package za.org.treehouse.HypoAlarm;
import android.app.AlarmManager;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.PendingIntent;
import android.app.TimePickerDialog;
import android.content.ComponentName;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.location.Location;
import android.media.Ringtone;
import android.media.RingtoneManager;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.provider.ContactsContract;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.Fragment;
import android.support.v7.app.ActionBarActivity;
import android.text.InputType;
import android.text.format.DateFormat;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.InputMethodManager;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.CompoundButton;
import android.widget.EditText;
import android.widget.Spinner;
import android.widget.TimePicker;
import android.widget.Toast;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GooglePlayServicesClient;
import com.google.android.gms.common.GooglePlayServicesUtil;
import com.google.android.gms.location.LocationClient;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
// Settings: alarm time, phone number, custom message, grace period length
// Set alarm
// Display alarm notification
// Display countdown (in seconds) or time till alarm fires (in minutes)
// Reset alarm
// Send SMS
// Possible settings:
// More than one phone number?
// Alerts via Whatsapp and other protocols?
public class MainActivity extends ActionBarActivity {
private static LocationClient locationClient = null;
public static final int ALARM_REQUEST = 1;
public static final int GRACE_REQUEST = 2;
public static final int PRE_NOTIFY_REQUEST = 3;
public static final int CANCEL_GRACE_REQUEST = 4;
public static final int CANCEL_ALARM_REQUEST = 5;
public static final int PHONE_NUMBER_REQUEST = 6;
public static final int RINGTONE_REQUEST = 7;
public static final String TIMEPICKER_TAG = "alarmTimePicker";
public static final String defaultTimeStr = "09:00";
public static final int defaultGracePeriod = 60;
public static final Boolean defaultActive = true;
// Write a message to the screen instead of sending a text as normal
public static final Boolean HYPOALARM_DEBUG = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (savedInstanceState == null) {
getSupportFragmentManager().beginTransaction()
.add(R.id.container, new MainFragment())
.commit();
}
}
// Main screen
/**
* To use a radial timepicker, uncomment this line instead
public static class MainFragment extends Fragment implements RadialTimePickerDialog.OnTimeSetListener {
*/
public static class MainFragment extends Fragment {
public MainFragment() {
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_main, container, false);
}
public void onStart() {
final SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(getActivity());
super.onStart();
/**
* To use a radial timepicker, uncomment here:
final RadialTimePickerDialog timePickerDialog = RadialTimePickerDialog.newInstance(this, 0, 0, DateFormat.is24HourFormat(getActivity()));
*/
// Set alarm time
String alarmTimeStr = verifyTimeString(sharedPref.getString(getString(R.string.AlarmTimePref), defaultTimeStr));
final Button alarmTimeButton = (Button) getActivity().findViewById(R.id.alarm_time);
alarmTimeButton.setText(alarmTimeStr);
alarmTimeButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
DialogFragment alarmFragment = new TimePickerFragment();
alarmFragment.show(getActivity().getSupportFragmentManager(), TIMEPICKER_TAG);
/**
* To use a radial time picker, uncomment here:
// Use the current set time as the default value for the picker
String alarmTimeStr = verifyTimeString(sharedPref.getString(getString(R.string.AlarmTimePref), defaultTimeStr));
timePickerDialog.setStartTime(hour, minute);
timePickerDialog.setThemeDark(true); // available from version 1.5.3...
timePickerDialog.show(getActivity().getSupportFragmentManager(), TIMEPICKER_TAG);
*/
}
});
// Allow alarm to activate
Boolean alarmActive = sharedPref.getBoolean(getString(R.string.AlarmActivePref), defaultActive);
final CompoundButton alarmActiveSwitch = (CompoundButton) getActivity().findViewById(R.id.alarm_active_switch);
alarmActiveSwitch.setChecked(alarmActive);
alarmActiveSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton compoundButton, boolean active) {
SharedPreferences.Editor editor = sharedPref.edit();
editor.putBoolean(getString(R.string.AlarmActivePref), active);
editor.commit();
if (!active) {
// Prevent any snoozed alarm from returning
if (AlarmService.alarmStatus.contentEquals(AlarmService.ALARM_SNOOZE_RUNNING) ||
AlarmService.alarmStatus.contentEquals(AlarmService.ALARM_SNOOZED) ||
AlarmService.alarmStatus.contentEquals(AlarmService.ALARM_RUNNING)) {
AlarmService.setAlarmStatus(AlarmService.ALARM_DISMISSED);
}
// Cancel any alarms
cancelAllAlarms(getActivity());
Toast.makeText(getActivity(), getString(R.string.alarmCancelled), Toast.LENGTH_SHORT).show();
} else {
String alarmTimeStr = verifyTimeString(sharedPref.getString(getString(R.string.AlarmTimePref), defaultTimeStr));
Calendar cal = TimeStringToCalendar(alarmTimeStr);
setAlarm(getActivity(), cal);
}
}
});
// Activate the time (after setting alarmActive) when starting the app (but don't cancel any existing grace period alarms)
Calendar cal = TimeStringToCalendar(alarmTimeStr);
setAlarm(getActivity(), cal);
// Set grace period
int gracePeriod = sharedPref.getInt(getString(R.string.GracePeriodPref), defaultGracePeriod);
final Spinner gracePeriodSpinner = (Spinner) getActivity().findViewById(R.id.grace_period);
gracePeriodSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
final SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(getActivity());
@Override
public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) {
Object value = parent.getItemAtPosition(pos);
if (value != null) {
SharedPreferences.Editor editor = sharedPref.edit();
int minutes = GracePeriodToMinutes(value.toString());
editor.putInt(getString(R.string.GracePeriodPref), minutes);
editor.commit();
}
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
}
});
// Set value of drop-down list to value of gracePeriodStr
ArrayAdapter<String> gracePeriodSpinnerAdapter = (ArrayAdapter<String>) gracePeriodSpinner.getAdapter();
int spinnerPosition = gracePeriodSpinnerAdapter.getPosition(MinutesToGracePeriodStr(gracePeriod));
gracePeriodSpinner.setSelection(spinnerPosition);
// Allow user to select ringtone
final Button ringtoneButton = (Button) getActivity().findViewById(R.id.ringtone);
String ringtoneStr = sharedPref.getString(getString(R.string.RingtonePref), RingtoneManager.getDefaultUri(RingtoneManager.TYPE_ALARM).toString());
final Uri ringtoneUri = Uri.parse(ringtoneStr);
Ringtone currentRingtone = RingtoneManager.getRingtone(getActivity().getApplicationContext(), ringtoneUri);
ringtoneButton.setText(currentRingtone.getTitle(getActivity().getApplicationContext()));
ringtoneButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent ringtoneIntent = new Intent(RingtoneManager.ACTION_RINGTONE_PICKER);
ringtoneIntent.putExtra(RingtoneManager.EXTRA_RINGTONE_TITLE, "Select ringtone:");
ringtoneIntent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, false);
ringtoneIntent.putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, RingtoneManager.TYPE_ALARM);
ringtoneIntent.putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, ringtoneUri);
startActivityForResult(ringtoneIntent, RINGTONE_REQUEST);
}
});
// Set phone number
String phoneNumberStr = sharedPref.getString(getString(R.string.PhoneNumberPref), null);
final EditText phoneNumberButton = (EditText) getActivity().findViewById(R.id.phone_number);
phoneNumberButton.setText(phoneNumberStr);
phoneNumberButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
DialogFragment phoneFragment = new PhonePickerFragment();
phoneFragment.show(getActivity().getSupportFragmentManager(), "phoneNumberPicker");
}
});
// Set message
String messageStr = sharedPref.getString(getString(R.string.MessagePref), getString(R.string.defaultMessage));
final EditText messageButton = (EditText) getActivity().findViewById(R.id.message);
messageButton.setText(messageStr);
messageButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
final SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(getActivity());
final InputMethodManager imm = (InputMethodManager) getActivity().getSystemService(Context.INPUT_METHOD_SERVICE);
AlertDialog.Builder alert = new AlertDialog.Builder(getActivity());
final EditText messageInput = new EditText(getActivity());
messageInput.setInputType(InputType.TYPE_CLASS_TEXT |
InputType.TYPE_TEXT_FLAG_MULTI_LINE |
InputType.TYPE_TEXT_FLAG_CAP_SENTENCES |
InputType.TYPE_TEXT_FLAG_AUTO_CORRECT);
if (messageButton.getText() != null) {
messageInput.setText(messageButton.getText().toString());
}
if (messageInput.getText() != null) {
messageInput.setSelection(messageInput.getText().length());
}
alert.setView(messageInput);
alert.setMessage(getString(R.string.setMessage));
alert.setPositiveButton("OK", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
String value = messageInput.getText().toString();
if (value.compareTo("") == 0) {
value = getString(R.string.defaultMessage);
}
SharedPreferences.Editor editor = sharedPref.edit();
editor.putString(getString(R.string.MessagePref), value);
editor.commit();
messageButton.setText(value);
imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0);
}
});
alert.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0);
// Canceled.
}
});
alert.show();
messageInput.requestFocus();
imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0);
}
});
// Set time change and boot receiver, so alarm restarts on boot
ComponentName bootReceiver = new ComponentName(getActivity(), AlarmChangeReceiver.class);
PackageManager pm = getActivity().getPackageManager();
if (pm != null) {
pm.setComponentEnabledSetting(bootReceiver,
PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
PackageManager.DONT_KILL_APP);
Log.d("MainActivity", "Setting boot receiver");
}
// Get the location now, so that user has to grant permission for location now, not when
// the text message is sent.
if (GooglePlayServicesUtil.isGooglePlayServicesAvailable(getActivity()) == ConnectionResult.SUCCESS) {
locationClient = new LocationClient(getActivity(),
new GooglePlayServicesClient.ConnectionCallbacks() {
@Override
public void onConnected(Bundle bundle) {
Location location = locationClient.getLastLocation();
if (location == null) {
Log.e("MainActivity", "No location data available.");
}
locationClient.disconnect();
}
@Override
public void onDisconnected() {
}
},
new GooglePlayServicesClient.OnConnectionFailedListener() {
@Override
public void onConnectionFailed(ConnectionResult connectionResult) {
Log.e("MainActivity", "Failed connection to location manager " + connectionResult.toString());
}
}
);
locationClient.connect();
} else {
Log.e("GraceReceiver", "Google Play Services is not available.");
}
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
// If we're answering a ringtone dialogue, update the correct button
if (requestCode == RINGTONE_REQUEST && resultCode == RESULT_OK) {
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(getActivity());
SharedPreferences.Editor editor = sharedPref.edit();
Button ringtoneButton = (Button) getActivity().findViewById(R.id.ringtone);
Uri uri = data.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI);
if (uri == null) {
editor.putString(getString(R.string.RingtonePref), null);
} else {
editor.putString(getString(R.string.RingtonePref), uri.toString());
}
editor.commit();
Ringtone currentRingtone = RingtoneManager.getRingtone(getActivity().getApplicationContext(), uri);
ringtoneButton.setText(currentRingtone.getTitle(getActivity().getApplicationContext()));
}
}
/**
* To use a radial timepicker, uncomment this function
*
@Override
public void onTimeSet(RadialPickerLayout view, int hourOfDay, int minute) {
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(getActivity());
// Set time preference
Calendar cal = Calendar.getInstance();
cal.set(Calendar.HOUR_OF_DAY, hourOfDay);
cal.set(Calendar.MINUTE, minute);
cal.set(Calendar.SECOND, 0);
String alarmStr = CalendarToTimeString(cal);
SharedPreferences.Editor editor = sharedPref.edit();
editor.putString(getString(R.string.AlarmTimePref), alarmStr);
editor.commit();
Button alarm_time = (Button) getActivity().findViewById(R.id.alarm_time);
alarm_time.setText(alarmStr);
// Set actual alarm
setAlarm(getActivity(), cal);
// Display toast
CharSequence text = getString(R.string.alarmSetToast) + " " + CalendarToTimeString(cal);
Toast.makeText(getActivity(), text, Toast.LENGTH_SHORT).show();
}
*/
}
/**
* Cancel main alarm, but not grace alarm.
* This should be run whenever the alarm is set.
*/
public static void cancelAlarm(Context context) {
AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
// Cancel any current alarm
Intent alarmIntent = new Intent(context, AlarmReceiver.class);
PendingIntent alarmPendingIntent = PendingIntent.getBroadcast(context, ALARM_REQUEST, alarmIntent, 0);
alarmManager.cancel(alarmPendingIntent);
// Cancel any pre-alarm notification that may fire
Intent preNotifyIntent = new Intent(context, PreAlarmReceiver.class);
PendingIntent preNotifyPendingIntent = PendingIntent.getBroadcast(context, PRE_NOTIFY_REQUEST, preNotifyIntent, 0);
alarmManager.cancel(preNotifyPendingIntent);
// Stop any existing pre-alarm notification that has already fired
context.stopService(new Intent(context, PreAlarmNotify.class));
Log.d("MainActivity", "Alarm cancelled");
}
/**
* Cancel grace alarm and notifications
*/
public static void cancelGraceAlarm(Context context) {
AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
// Cancel any grace period
Intent graceIntent = new Intent(context, GraceReceiver.class);
PendingIntent gracePendingIntent = PendingIntent.getBroadcast(context, GRACE_REQUEST, graceIntent, 0);
alarmManager.cancel(gracePendingIntent);
// Stop any notification of grace period expiry
context.stopService(new Intent(context, AlarmNotify.class));
Log.d("MainActivity", "Grace alarm cancelled");
}
/**
* Cancels main alarm and grace alarm
* This should only be run when we're disabling the alarm entirely.
*/
public static void cancelAllAlarms(Context context) {
cancelAlarm(context);
cancelGraceAlarm(context);
Log.d("MainActivity", "All alarms cancelled");
}
/**
* Set the alarm.
*
* @param context Context
* @param cal Time at which to fire alarm
*/
public static void setAlarm(Context context, Calendar cal) {
AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
PendingIntent alarmPendingIntent, preNotifyPendingIntent;
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(context);
Boolean alarmActive = sharedPref.getBoolean(context.getString(R.string.AlarmActivePref), defaultActive);
cancelAlarm(context);
// Advance cal to tomorrow if setting a time earlier than now
if (cal.before(Calendar.getInstance())) {
cal.add(Calendar.DAY_OF_MONTH, 1);
}
if (alarmActive) {
// Initialise alarm, which displays a dialog and system alert, and
// calls AlarmManager with grace_period as the delay
// which in turn, sends SMS if dialog is not exited.
Intent alarmIntent = new Intent(context, AlarmReceiver.class);
alarmPendingIntent = PendingIntent.getBroadcast(context, ALARM_REQUEST, alarmIntent, 0);
// Set or reset alarm
if (Build.VERSION.SDK_INT >= 19) {
alarmManager.setExact(AlarmManager.RTC_WAKEUP, cal.getTimeInMillis(), alarmPendingIntent);
} else {
alarmManager.set(AlarmManager.RTC_WAKEUP, cal.getTimeInMillis(), alarmPendingIntent);
}
Log.d("MainActivity", "Setting alarm for " + debugDate(cal));
// Set an alarm for the pre-alarm notification, an hour before the alarm
Calendar preNotifyCal = (Calendar) cal.clone();
preNotifyCal.add(Calendar.MINUTE, -60);
Intent preNotifyIntent = new Intent(context, PreAlarmReceiver.class);
preNotifyPendingIntent = PendingIntent.getBroadcast(context, PRE_NOTIFY_REQUEST, preNotifyIntent, 0);
// Set or reset alarm
if (Build.VERSION.SDK_INT >= 19) {
alarmManager.setExact(AlarmManager.RTC_WAKEUP, preNotifyCal.getTimeInMillis(), preNotifyPendingIntent);
} else {
alarmManager.set(AlarmManager.RTC_WAKEUP, preNotifyCal.getTimeInMillis(), preNotifyPendingIntent);
}
Log.d("MainActivity", "Setting pre-alarm for " + debugDate(preNotifyCal));
}
}
/**
* Set the grace alarm, to fire at the end of the grace period and send a text message.
*/
public static void setGraceAlarm(Context context) {
AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(context);
Boolean alarmActive = sharedPref.getBoolean(context.getString(R.string.AlarmActivePref), defaultActive);
int gracePeriod = sharedPref.getInt(context.getString(R.string.GracePeriodPref), defaultGracePeriod);
cancelGraceAlarm(context);
if (alarmActive) {
// Set a grace period alarm to send SMS
Calendar graceCal = Calendar.getInstance();
graceCal.set(Calendar.SECOND, 0);
graceCal.add(Calendar.MINUTE, gracePeriod);
Intent graceIntent = new Intent(context, GraceReceiver.class);
PendingIntent gracePendingIntent = PendingIntent.getBroadcast(context, GRACE_REQUEST, graceIntent, 0);
if (Build.VERSION.SDK_INT >= 19) {
alarmManager.setExact(AlarmManager.RTC_WAKEUP, graceCal.getTimeInMillis(), gracePendingIntent);
} else {
alarmManager.set(AlarmManager.RTC_WAKEUP, graceCal.getTimeInMillis(), gracePendingIntent);
}
Log.d("MainActivity", "Setting grace alarm for " + debugDate(graceCal));
}
}
public static class TimePickerFragment extends DialogFragment implements TimePickerDialog.OnTimeSetListener {
SharedPreferences sharedPref;
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
sharedPref = PreferenceManager.getDefaultSharedPreferences(getActivity());
// Use the current set time as the default value for the picker
String alarmTimeStr = verifyTimeString(sharedPref.getString(getString(R.string.AlarmTimePref), defaultTimeStr));
// For selecting a time, the date doesn't matter, just the hour and minute
Calendar cal = TimeStringToCalendar(alarmTimeStr);
int hour = cal.get(Calendar.HOUR_OF_DAY);
int minute = cal.get(Calendar.MINUTE);
// Create a new instance of TimePickerDialog and return it
return new TimePickerDialog(getActivity(), this, hour, minute, DateFormat.is24HourFormat(getActivity()));
}
public void onTimeSet(TimePicker view, int hourOfDay, int minute) {
// Set time preference
Calendar cal = Calendar.getInstance();
cal.set(Calendar.HOUR_OF_DAY, hourOfDay);
cal.set(Calendar.MINUTE, minute);
cal.set(Calendar.SECOND, 0);
String alarmStr = CalendarToTimeString(cal);
SharedPreferences.Editor editor = sharedPref.edit();
editor.putString(getString(R.string.AlarmTimePref), alarmStr);
editor.commit();
Button alarm_time = (Button) getActivity().findViewById(R.id.alarm_time);
alarm_time.setText(alarmStr);
// Set actual alarm
setAlarm(getActivity(), cal);
// Display toast
CharSequence text = getString(R.string.alarmSetToast) + " " + CalendarToTimeString(cal);
Toast.makeText(getActivity(), text, Toast.LENGTH_SHORT).show();
}
}
public static class PhonePickerFragment extends DialogFragment {
SharedPreferences sharedPref;
EditText phoneInput;
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
sharedPref = PreferenceManager.getDefaultSharedPreferences(getActivity());
AlertDialog.Builder alert = new AlertDialog.Builder(getActivity());
alert.setMessage(R.string.setPhoneNumber);
LayoutInflater inflater = getActivity().getLayoutInflater();
// Pass null instead of the parent ViewGroup, as the parent View is a
// ScrollView, which can only have one direct child
View dialogView = inflater.inflate(R.layout.phone_dialog, null);
alert.setView(dialogView);
final EditText phoneButton = (EditText) getActivity().findViewById(R.id.phone_number);
phoneInput = (EditText) dialogView.findViewById(R.id.dialog_phone_number);
phoneInput.setText(phoneButton.getText().toString());
phoneInput.setSelection(phoneInput.getText().length());
final Button contactsButton = (Button) dialogView.findViewById(R.id.dialog_contacts_button);
alert.setPositiveButton("OK", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
String value = phoneInput.getText().toString();
SharedPreferences.Editor editor = sharedPref.edit();
editor.putString(getString(R.string.PhoneNumberPref), value);
editor.commit();
phoneButton.setText(value);
}
});
alert.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
// Cancelled
}
});
contactsButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent = new Intent(Intent.ACTION_PICK, ContactsContract.Contacts.CONTENT_URI);
intent.setType(ContactsContract.CommonDataKinds.Phone.CONTENT_TYPE);
startActivityForResult(intent, PHONE_NUMBER_REQUEST);
}
});
return alert.create();
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == PHONE_NUMBER_REQUEST && resultCode == RESULT_OK && data != null) {
Uri uri = data.getData();
if (uri != null) {
Cursor c = null;
try {
c = getActivity().getContentResolver().query(uri, new String[]{
ContactsContract.CommonDataKinds.Phone.NUMBER,
ContactsContract.CommonDataKinds.Phone.TYPE},
null, null, null
);
if (c != null && c.moveToFirst()) {
String number = c.getString(0);
phoneInput.setText(number);
}
} finally {
if (c != null) {
c.close();
}
}
}
}
}
}
public static String verifyTimeString(String timeStr) {
return CalendarToTimeString(TimeStringToCalendar(timeStr));
}
public static Calendar TimeStringToCalendar(String dateString) {
Date date;
Calendar cal;
final String FORMAT = "HH:mm";
SimpleDateFormat sdf = new SimpleDateFormat(FORMAT, Locale.getDefault());
sdf.format(new Date());
try {
date = sdf.parse(dateString);
// date has the correct hour and minute, but the incorrect day,
// month and year information. To get information out of it, we need to
// convert it to a Calendar.
Calendar dateCal = Calendar.getInstance();
dateCal.setTime(date);
// Create a new calendar with the correct day, month and year,
// and set the hour and minute by hand.
cal = Calendar.getInstance();
cal.set(Calendar.HOUR_OF_DAY, dateCal.get(Calendar.HOUR_OF_DAY));
cal.set(Calendar.MINUTE, dateCal.get(Calendar.MINUTE));
cal.set(Calendar.SECOND, 0);
return cal;
} catch (ParseException e) {
e.printStackTrace();
return null;
}
}
public static String CalendarToTimeString(Calendar cal) {
final String FORMAT = "HH:mm";
SimpleDateFormat sdf = new SimpleDateFormat(FORMAT, Locale.getDefault());
return sdf.format(cal.getTime());
}
public static String formattedTime(Context context, Calendar cal) {
SimpleDateFormat print;
if (DateFormat.is24HourFormat(context)) {
print = new SimpleDateFormat("E HH:mm");
} else {
print = new SimpleDateFormat("E hh:mm a");
}
return print.format(cal.getTime());
}
public static int GracePeriodToMinutes(String gracePeriod) {
Pattern p = Pattern.compile("(?:(\\d+)\\s+hours?)?\\s*(?:(\\d+)\\s+minutes?)?");
Matcher m = p.matcher(gracePeriod);
if (m.find()) {
int hours = 0;
int minutes = 0;
if (m.group(1) != null) {
hours = Integer.parseInt(m.group(1));
}
if (m.group(2) != null) {
minutes = Integer.parseInt(m.group(2));
}
minutes = minutes + (hours * 60);
return minutes;
}
return 0;
}
public static String MinutesToGracePeriodStr(int minutes) {
int hours = minutes / 60;
int remMinutes = minutes % 60;
String hourStr = " hours";
if (hours == 1) {
hourStr = " hour";
}
String minStr = " minutes";
if (remMinutes == 1) {
minStr = " minute";
}
if (hours > 0) {
if (remMinutes == 0) {
return hours + hourStr;
}
return hours + hourStr + " " + remMinutes + minStr;
}
return remMinutes + minStr;
}
public static String debugDate(Calendar cal) {
SimpleDateFormat print = new SimpleDateFormat("dd-MM-yyyy HH:mm:ssZ");
return print.format(cal.getTime());
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
}
}

View File

@ -0,0 +1,77 @@
package za.org.treehouse.HypoAlarm;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Build;
import android.os.IBinder;
import android.preference.PreferenceManager;
import android.support.v4.app.NotificationCompat;
import android.util.Log;
import java.util.Calendar;
public class PreAlarmNotify extends Service {
private static final int preNotifyID = 2;
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onDestroy() {
// Remove the notification in the notification bar
NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
nm.cancel(preNotifyID);
Log.d("AlarmNotify", "Pre-notification stopped.");
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this);
Log.d("PreAlarmNotify", "Pre-notification started.");
// Get alarm time, and convert it into something readable.
String alarmTimeStr = sharedPref.getString(getString(R.string.AlarmTimePref), MainActivity.defaultTimeStr);
Calendar alarmCal = MainActivity.TimeStringToCalendar(alarmTimeStr);
alarmTimeStr = MainActivity.formattedTime(getApplicationContext(), alarmCal);
Bitmap bm = BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher_grey);
final NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
final NotificationCompat.Builder notification = new NotificationCompat.Builder(this)
.setContentTitle(getString(R.string.preNotificationTitle))
.setContentText(alarmTimeStr)
.setSmallIcon(R.drawable.alarm_notification)
.setLargeIcon(bm)
.setOnlyAlertOnce(true)
.setAutoCancel(false);
if (Build.VERSION.SDK_INT >= 16) {
notification.setPriority(Notification.PRIORITY_DEFAULT);
}
// Set up dismiss action
Intent cancelAlarmIntent = new Intent(getBaseContext(), CancelAlarmReceiver.class);
PendingIntent cancelAlarmPendingIntent = PendingIntent.getBroadcast(getBaseContext(), MainActivity.CANCEL_ALARM_REQUEST, cancelAlarmIntent, PendingIntent.FLAG_CANCEL_CURRENT);
// Cancel the grace period if the user clears the notification
notification.setDeleteIntent(cancelAlarmPendingIntent);
// Allow the user to cancel by clicking a "Cancel" button
notification.addAction(android.R.drawable.ic_menu_close_clear_cancel, getString(R.string.preNotificationCancellation), cancelAlarmPendingIntent);
// Allow the user to cancel by selecting the ContentText or ContentTitle
notification.setContentIntent(cancelAlarmPendingIntent);
nm.cancel(preNotifyID);
nm.notify(preNotifyID, notification.build());
return super.onStartCommand(intent, flags, startId);
}
}

View File

@ -0,0 +1,22 @@
package za.org.treehouse.HypoAlarm;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
public class PreAlarmReceiver extends BroadcastReceiver {
@Override
public void onReceive(final Context context, Intent intent) {
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(context);
Boolean alarmActive = sharedPref.getBoolean(context.getString(R.string.AlarmActivePref), MainActivity.defaultActive);
if (alarmActive) {
// Create notification
Intent preNotifyIntent = new Intent(context, PreAlarmNotify.class);
// The PreAlarmNotify service can either be stopped by calling CancelAlarmReceiver, or by waiting for AlarmReceiver to run
context.startService(preNotifyIntent);
}
}
}

View File

@ -1,116 +0,0 @@
package za.org.treehouse.hypoalarm;
import android.app.Activity;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.Vibrator;
import android.util.Log;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.widget.Button;
// TODO See GlowPad.
// TODO sound audible alarm -- see AlarmKlaxon.java
// TODO Lower alarm volume if in phone call (and detect phone call!)
// TODO Snooze? set another alarm for the next half-hour (or grace_period / 2)?
public class AlarmAlertActivity extends Activity {
// TODO correct alert lifetime
private static final int ALERT_LIFE = 1000*10; //1000*60*2; // 2 minutes
private static final long[] vPattern = {500, 500};
private static Boolean userCancelled;
private static Vibrator vibrator;
private static Intent notifyIntent;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
Window window = getWindow();
// Set to use the full screen
int fullScreen = WindowManager.LayoutParams.FLAG_FULLSCREEN;
// TODO if KitKat, mimic the main alarm
fullScreen = WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS;
window.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
| WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
| WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
| fullScreen
);
setContentView(R.layout.alarm_alert);
notifyIntent = new Intent(getApplicationContext(), AlarmNotify.class);
// Disable any current notifications
stopService(notifyIntent);
// Turn off the alert activity, and switch to a notification
new Handler().postDelayed(new Runnable() {
public void run() {
// Close the dialogue and switch to notification
// if the Activity has not been closed by the user
if (!userCancelled) {
startService(notifyIntent);
finish();
}
}
}, ALERT_LIFE);
}
@Override
public void onStart() {
super.onStart();
userCancelled = false;
vibrator = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
vibrator.vibrate(vPattern, 0);
// TODO change button to slide...
// TODO change slide to circle slide? https://github.com/JesusM/HoloCircleSeekBar
Button cancelButton = (Button) findViewById(R.id.cancel_dialog_button);
cancelButton.setOnClickListener (new View.OnClickListener() {
@Override
public void onClick(View view) {
cancelGraceAlarm();
}
});
}
/**
* Handle the user pressing the back/return button
* TODO Do we want this to cancel the alarm and grace period?
* TODO Or do we trigger the notification and finish() right away?
* TODO probably most intuitive to cancel the alarms...
*/
@Override
public void onBackPressed() {
cancelGraceAlarm();
/* OR:
// Ensure that the we don't trigger the notification a second time
userCancelled = true;
startService(notifyIntent);
finish();
*/
}
public void onStop() {
vibrator.cancel();
super.onStop();
}
private void cancelGraceAlarm() {
AlarmManager graceManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
Intent graceIntent = new Intent(getApplicationContext(), GraceReceiver.class);
PendingIntent gracePendingIntent = PendingIntent.getBroadcast(getApplicationContext(), MainActivity.GRACE_REQUEST, graceIntent, 0);
graceManager.cancel(gracePendingIntent);
Log.d("AlarmAlertActivity", "Cancelled grace alarm.");
// Ensure we don't load a notification now
userCancelled = true;
// Close the dialogue (stop vibration &c)
finish();
}
}

View File

@ -1,59 +0,0 @@
package za.org.treehouse.hypoalarm;
import android.app.AlarmManager;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.util.Log;
import java.util.Calendar;
public class AlarmReceiver extends BroadcastReceiver {
private static SharedPreferences sharedPref;
private static AlarmManager alarmManager, graceManager;
private static PendingIntent alarmPendingIntent, gracePendingIntent;
@Override
public void onReceive(Context context, Intent intent) {
sharedPref = PreferenceManager.getDefaultSharedPreferences(context);
Boolean alarmActive = sharedPref.getBoolean(context.getString(R.string.AlarmActivePref), true);
if (alarmActive) {
// Set a grace period alarm to send SMS
int gracePeriod = sharedPref.getInt(context.getString(R.string.GracePeriodPref), 60);
Calendar graceCal = Calendar.getInstance();
graceCal.set(Calendar.SECOND, 0);
graceCal.add(Calendar.MINUTE, gracePeriod);
graceManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
Intent graceIntent = new Intent(context, GraceReceiver.class);
gracePendingIntent = PendingIntent.getBroadcast(context, MainActivity.GRACE_REQUEST, graceIntent, 0);
graceManager.cancel(gracePendingIntent);
graceManager.setExact(AlarmManager.RTC_WAKEUP, graceCal.getTimeInMillis(), gracePendingIntent);
Log.d("AlarmReceiver", "Setting grace alarm for " + MainActivity.debugDate(graceCal));
// Allow user to acknowledge alarm and cancel grace alarm
Intent alertActivityIntent = new Intent(context, AlarmAlertActivity.class);
alertActivityIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
context.startActivity(alertActivityIntent);
// Reset for tomorrow; as of API 19, setRepeating() is inexact, so we use setExact()
String alarmTimeStr = sharedPref.getString(context.getString(R.string.AlarmTimePref), null);
// Calendar automatically advances the day since alarmTimeStr is now in the past.
Calendar cal = MainActivity.TimeStringToCalendar(alarmTimeStr);
alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
alarmPendingIntent = PendingIntent.getBroadcast(context, MainActivity.ALARM_REQUEST, intent, 0);
alarmManager.cancel(alarmPendingIntent);
// TODO use set() for older APIs
alarmManager.setExact(AlarmManager.RTC_WAKEUP, cal.getTimeInMillis(), alarmPendingIntent);
Log.d("AlarmReceiver", "Resetting alarm for " + MainActivity.debugDate(cal));
}
}
}

View File

@ -1,36 +0,0 @@
package za.org.treehouse.hypoalarm;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.util.Log;
import java.util.Calendar;
public class BootReceiver extends BroadcastReceiver {
private static SharedPreferences sharedPref;
private static AlarmManager alarmManager;
private static PendingIntent alarmIntent;
@Override
public void onReceive(Context context, Intent intent) {
sharedPref = PreferenceManager.getDefaultSharedPreferences(context);
if (intent.getAction().equals("android.intent.action.BOOT_COMPLETED")) {
// Reset for tomorrow; as of API 19, setRepeating() is inexact, so we use setExact()
String alarmTimeStr = sharedPref.getString(context.getString(R.string.AlarmTimePref), null);
if (alarmTimeStr != null) {
// If it's later than alarmTimeStr, Calendar automatically advances the day.
Calendar cal = MainActivity.TimeStringToCalendar(alarmTimeStr);
alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
alarmIntent = PendingIntent.getBroadcast(context, 0, intent, 0);
// TODO use set() for older APIs
alarmManager.setExact(AlarmManager.RTC_WAKEUP, cal.getTimeInMillis(), alarmIntent);
Log.d("BootReceiver", "Setting alarm for "+MainActivity.debugDate(cal));
}
}
}
}

View File

@ -1,27 +0,0 @@
package za.org.treehouse.hypoalarm;
import android.app.AlarmManager;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
import android.widget.Toast;
public class CancelGraceReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
AlarmManager graceManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
Intent graceIntent = new Intent(context, GraceReceiver.class);
PendingIntent gracePendingIntent = PendingIntent.getBroadcast(context, MainActivity.GRACE_REQUEST, graceIntent, 0);
graceManager.cancel(gracePendingIntent);
Log.d("CancelGraceReceiver", "Cancelled grace alarm");
// Display toast
Toast.makeText(context, context.getString(R.string.alarmCancelToast), Toast.LENGTH_LONG).show();
// Remove notification
context.stopService(new Intent(context, AlarmNotify.class));
}
}

View File

@ -1,30 +0,0 @@
package za.org.treehouse.hypoalarm;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.PowerManager;
import android.preference.PreferenceManager;
import android.telephony.SmsManager;
import android.util.Log;
public class GraceReceiver extends BroadcastReceiver {
private static SharedPreferences sharedPref;
@Override
public void onReceive(Context context, Intent intent) {
sharedPref = PreferenceManager.getDefaultSharedPreferences(context);
Boolean alarmActive = sharedPref.getBoolean(context.getString(R.string.AlarmActivePref), true);
if (alarmActive) {
String phoneNumber = sharedPref.getString(context.getString(R.string.PhoneNumberPref), null);
String message = sharedPref.getString(context.getString(R.string.MessagePref), null);
SmsManager sms = SmsManager.getDefault();
// TODO uncomment this:
//sms.sendTextMessage(phoneNumber, null, message, null, null);
Log.d("GraceReceiver", "Sending sms to " + phoneNumber);
}
}
}

View File

@ -1,493 +0,0 @@
package za.org.treehouse.hypoalarm;
import android.app.AlarmManager;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.PendingIntent;
import android.app.TimePickerDialog;
import android.content.ComponentName;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.provider.ContactsContract;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.Fragment;
import android.support.v7.app.ActionBarActivity;
import android.text.InputType;
import android.text.format.DateFormat;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.InputMethodManager;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.CompoundButton;
import android.widget.EditText;
import android.widget.Spinner;
import android.widget.Switch;
import android.widget.TimePicker;
import android.widget.Toast;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
// Settings: alarm time, phone number, custom message, grace period length
// Set alarm
// Display alarm notification
// Display countdown (in seconds) or time till alarm fires (in minutes)
// Reset alarm
// Send SMS
// Possible settings:
// More than one phone number?
// Alerts via Whatsapp and other protocols?
// TODO: klaxon
// TODO: glowpad dismissal (or circular seekbar)
public class MainActivity extends ActionBarActivity {
public static int ALARM_REQUEST = 1;
public static int GRACE_REQUEST = 2;
public static int CANCEL_GRACE_REQUEST = 3;
private static Switch alarmActiveSwitch;
private static Button alarmTimeButton;
private static Spinner gracePeriodSpinner;
private static EditText phoneNumberButton;
private static EditText messageButton;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (savedInstanceState == null) {
getSupportFragmentManager().beginTransaction()
.add(R.id.container, new MainFragment())
.commit();
}
}
// Main screen
public static class MainFragment extends Fragment {
public MainFragment() {
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_main, container, false);
return rootView;
}
public void onStart() {
super.onStart();
final SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(getActivity());
// Allow alarm to activate
Boolean alarmActive = sharedPref.getBoolean(getString(R.string.AlarmActivePref), true);
alarmActiveSwitch = (Switch) getActivity().findViewById(R.id.alarm_active_switch);
alarmActiveSwitch.setChecked(alarmActive);
alarmActiveSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
AlarmManager alarmManager, graceManager;
PendingIntent alarmPendingIntent, gracePendingIntent;
SharedPreferences.Editor editor = sharedPref.edit();
editor.putBoolean(getString(R.string.AlarmActivePref), b);
editor.commit();
if (!b) {
// Cancel any current alarm
alarmManager = (AlarmManager) getActivity().getSystemService(Context.ALARM_SERVICE);
Intent alarmIntent = new Intent(getActivity(), AlarmReceiver.class);
alarmPendingIntent = PendingIntent.getBroadcast(getActivity(), ALARM_REQUEST, alarmIntent, 0);
alarmManager.cancel(alarmPendingIntent);
// Cancel any grace period
graceManager = (AlarmManager) getActivity().getSystemService(Context.ALARM_SERVICE);
Intent graceIntent = new Intent(getActivity(), GraceReceiver.class);
gracePendingIntent = PendingIntent.getBroadcast(getActivity(), GRACE_REQUEST, graceIntent, 0);
graceManager.cancel(gracePendingIntent);
// Stop any notifications
getActivity().stopService(new Intent(getActivity(), AlarmNotify.class));
Log.d("MainActivity", "Alarms cancelled");
Toast.makeText(getActivity().getApplicationContext(), getString(R.string.alarmCancelled), Toast.LENGTH_SHORT).show();
} else {
// Activate the alarm
DialogFragment alarmFragment = new TimePickerFragment();
alarmFragment.show(getActivity().getSupportFragmentManager(), "alarmTimePicker");
alarmFragment.dismiss();
}
}
});
// Set alarm time
String defaultTimeStr = "09:00";
String alarmTimeStr = verifyTimeString(sharedPref.getString(getString(R.string.AlarmTimePref), defaultTimeStr));
alarmTimeButton = (Button) getActivity().findViewById(R.id.alarm_time);
// TODO remove this testing stuff
Calendar c = Calendar.getInstance();
c.add(Calendar.MINUTE, 1);
alarmTimeStr = CalendarToTimeString(c);
alarmTimeButton.setText(alarmTimeStr);
alarmTimeButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
DialogFragment alarmFragment = new TimePickerFragment();
alarmFragment.show(getActivity().getSupportFragmentManager(), "alarmTimePicker");
}
});
// TODO remove these simulated clicks
DialogFragment alarmFragment = new TimePickerFragment();
alarmFragment.show(getActivity().getSupportFragmentManager(), "alarmTimePicker");
alarmFragment.dismiss();
// Set grace period
int defaultGrace = 60;
int gracePeriod = sharedPref.getInt(getString(R.string.GracePeriodPref), defaultGrace);
gracePeriodSpinner = (Spinner) getActivity().findViewById(R.id.grace_period);
gracePeriodSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
final SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(getActivity());
@Override
public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) {
Object value = parent.getItemAtPosition(pos);
SharedPreferences.Editor editor = sharedPref.edit();
int minutes = GracePeriodToMinutes(value.toString());
editor.putInt(getString(R.string.GracePeriodPref), minutes);
editor.commit();
/*
CharSequence text = "Grace period is " + GracePeriodToMinutes(value.toString()) + " or " + MinutesToGracePeriodStr(GracePeriodToMinutes(value.toString()))+ ".";
Toast.makeText(getActivity().getApplicationContext(), text, Toast.LENGTH_SHORT).show();
*/
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
}
});
// Set value of drop-down list to value of gracePeriodStr
ArrayAdapter gracePeriodSpinnerAdapter = (ArrayAdapter) gracePeriodSpinner.getAdapter();
int spinnerPosition = gracePeriodSpinnerAdapter.getPosition(MinutesToGracePeriodStr(gracePeriod));
gracePeriodSpinner.setSelection(spinnerPosition);
// Set phone number
String phoneNumberStr = sharedPref.getString(getString(R.string.PhoneNumberPref), null);
phoneNumberButton = (EditText) getActivity().findViewById(R.id.phone_number);
phoneNumberButton.setText(phoneNumberStr);
phoneNumberButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
DialogFragment phoneFragment = new PhonePickerFragment();
phoneFragment.show(getActivity().getSupportFragmentManager(), "phoneNumberPicker");
}
});
// Set message
String messageStr = sharedPref.getString(getString(R.string.MessagePref), getString(R.string.defaultMessage));
messageButton = (EditText) getActivity().findViewById(R.id.message);
messageButton.setText(messageStr);
messageButton.setOnClickListener(new View.OnClickListener() {
SharedPreferences sharedPref;
@Override
public void onClick(View view) {
final SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(getActivity());
String messageStr = sharedPref.getString(getString(R.string.MessagePref), getString(R.string.defaultMessage));
final InputMethodManager imm = (InputMethodManager) getActivity().getSystemService(Context.INPUT_METHOD_SERVICE);
AlertDialog.Builder alert = new AlertDialog.Builder(getActivity());
final EditText messageInput = new EditText(getActivity());
messageInput.setInputType(InputType.TYPE_CLASS_TEXT |
InputType.TYPE_TEXT_FLAG_MULTI_LINE |
InputType.TYPE_TEXT_FLAG_CAP_SENTENCES |
InputType.TYPE_TEXT_FLAG_AUTO_CORRECT);
if (messageButton != null) {
messageInput.setText(messageButton.getText().toString());
}
messageInput.setSelection(messageInput.getText().length());
alert.setView(messageInput);
alert.setMessage(getString(R.string.setMessage));
alert.setPositiveButton("OK", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
String value = messageInput.getText().toString();
if (value.compareTo("") == 0) {
value = getString(R.string.defaultMessage);
}
SharedPreferences.Editor editor = sharedPref.edit();
editor.putString(getString(R.string.MessagePref), value);
editor.commit();
messageButton.setText(value);
imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0);
}
});
alert.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0);
// Canceled.
}
});
alert.show();
messageInput.requestFocus();
imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0);
}
});
}
}
// Alarm time picker
public static class TimePickerFragment extends DialogFragment implements TimePickerDialog.OnTimeSetListener {
SharedPreferences sharedPref;
private AlarmManager alarmManager;
private PendingIntent alarmPendingIntent;
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
sharedPref = PreferenceManager.getDefaultSharedPreferences(getActivity());
// Use the current set time as the default value for the picker
String alarmTimeStr = alarmTimeButton.getText().toString();
Calendar cal = TimeStringToCalendar(alarmTimeStr);
int hour = cal.get(Calendar.HOUR_OF_DAY);
int minute = cal.get(Calendar.MINUTE);
// Create a new instance of TimePickerDialog and return it
return new TimePickerDialog(getActivity(), this, hour, minute,
DateFormat.is24HourFormat(getActivity()));
}
public void onTimeSet(TimePicker view, int hourOfDay, int minute) {
String alarmStr = HourMinuteToTimeString(hourOfDay, minute);
Boolean alarmActive = sharedPref.getBoolean(getString(R.string.AlarmActivePref), true);
// Set time preference
SharedPreferences.Editor editor = sharedPref.edit();
editor.putString(getString(R.string.AlarmTimePref), alarmStr);
editor.commit();
Button alarm_time = (Button) getActivity().findViewById(R.id.alarm_time);
alarm_time.setText(alarmStr);
Calendar cal = Calendar.getInstance();
cal.setTimeInMillis(System.currentTimeMillis());
cal.set(Calendar.HOUR_OF_DAY, hourOfDay);
cal.set(Calendar.MINUTE, minute);
cal.set(Calendar.SECOND, 0);
if (alarmActive) {
// Initialise alarm, which displays a dialog and system alert, and
// calls AlarmManager with grace_period as the delay
// which in turn, sends SMS if dialog is not exited.
alarmManager = (AlarmManager) getActivity().getSystemService(Context.ALARM_SERVICE);
Intent alarmIntent = new Intent(getActivity(), AlarmReceiver.class);
alarmPendingIntent = PendingIntent.getBroadcast(getActivity(), ALARM_REQUEST, alarmIntent, 0);
// Cancel any existing alarms
alarmManager.cancel(alarmPendingIntent);
// Set or reset alarm
// TODO use set() for older APIs
alarmManager.setExact(AlarmManager.RTC_WAKEUP, cal.getTimeInMillis(), alarmPendingIntent);
Log.d("MainActivity", "Setting alarm for " + MainActivity.debugDate(cal));
// Set boot receiver, so alarm restarts on boot
ComponentName receiver = new ComponentName(getActivity(), BootReceiver.class);
PackageManager pm = getActivity().getPackageManager();
pm.setComponentEnabledSetting(receiver,
PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
PackageManager.DONT_KILL_APP);
// Display toast
CharSequence text = getString(R.string.alarmSetToast) + " " + alarmStr;
Toast.makeText(getActivity().getApplicationContext(), text, Toast.LENGTH_SHORT).show();
}
}
}
public static class PhonePickerFragment extends DialogFragment {
SharedPreferences sharedPref;
EditText phoneButton;
EditText phoneInput;
Button contactsButton;
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
final SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(getActivity());
AlertDialog.Builder alert = new AlertDialog.Builder(getActivity());
alert.setMessage(R.string.setPhoneNumber);
LayoutInflater inflater = getActivity().getLayoutInflater();
View dialogView = inflater.inflate(R.layout.phone_dialog, null);
alert.setView(dialogView);
phoneButton = (EditText) getActivity().findViewById(R.id.phone_number);
phoneInput = (EditText) dialogView.findViewById(R.id.dialog_phone_number);
phoneInput.setText(phoneButton.getText().toString());
phoneInput.setSelection(phoneInput.getText().length());
contactsButton = (Button) dialogView.findViewById(R.id.dialog_contacts_button);
alert.setPositiveButton("OK", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
String value = phoneInput.getText().toString();
SharedPreferences.Editor editor = sharedPref.edit();
editor.putString(getString(R.string.PhoneNumberPref), value);
editor.commit();
phoneButton.setText(value);
}
});
alert.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
// Cancelled
}
});
contactsButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent = new Intent(Intent.ACTION_PICK, ContactsContract.Contacts.CONTENT_URI);
intent.setType(ContactsContract.CommonDataKinds.Phone.CONTENT_TYPE);
startActivityForResult(intent, 1);
}
});
return alert.create();
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (data != null) {
Uri uri = data.getData();
if (uri != null) {
Cursor c = null;
try {
c = getActivity().getContentResolver().query(uri, new String[]{
ContactsContract.CommonDataKinds.Phone.NUMBER,
ContactsContract.CommonDataKinds.Phone.TYPE },
null, null, null);
if (c != null && c.moveToFirst()) {
String number = c.getString(0);
phoneInput.setText(number);
}
} finally {
if (c != null) {
c.close();
}
}
}
}
}
}
public static String verifyTimeString(String timeStr) {
return CalendarToTimeString(TimeStringToCalendar(timeStr));
}
public static String HourMinuteToTimeString(int hour, int minute) {
return CalendarToTimeString(TimeStringToCalendar(hour + ":" + minute));
}
public static Calendar TimeStringToCalendar(String dateString) {
Date date;
Calendar cal;
final String FORMAT="HH:mm"; // z = time zone
SimpleDateFormat sdf = new SimpleDateFormat(FORMAT, Locale.getDefault());
sdf.format(new Date());
try {
date = sdf.parse(dateString);
// date has the correct hour and minute, but the incorrect day,
// month and year information. To get information out of it, we need to
// convert it to a Calendar.
Calendar dateCal = Calendar.getInstance();
dateCal.setTime(date);
// Create a new calendar with the correct day, month and year,
// and set the hour and minute by hand.
cal = Calendar.getInstance();
cal.set(Calendar.HOUR, dateCal.get(Calendar.HOUR));
cal.set(Calendar.MINUTE, dateCal.get(Calendar.MINUTE));
cal.set(Calendar.SECOND, 0);
// Ensure that this date is in the future
if (date.before(new Date())) {
cal.add(Calendar.DAY_OF_MONTH, 1);
}
return cal;
} catch (ParseException e) {
e.printStackTrace();
return null;
}
}
public static String CalendarToTimeString(Calendar cal) {
final String FORMAT="HH:mm";
SimpleDateFormat sdf = new SimpleDateFormat(FORMAT, Locale.getDefault());
return sdf.format(cal.getTime());
}
public static String debugDate(Calendar cal) {
SimpleDateFormat print = new SimpleDateFormat("dd-MM-yyyy HH:mm:ssZ");
return print.format(cal.getTime());
}
public static int GracePeriodToMinutes(String gracePeriod) {
Pattern p = Pattern.compile("(?:(\\d+)\\s+hours?)?\\s*(?:(\\d+)\\s+minutes?)?");
Matcher m = p.matcher(gracePeriod);
if (m.find()) {
int hours = 0;
int minutes = 0;
if (m.group(1) != null) {
hours = Integer.parseInt(m.group(1));
}
if (m.group(2) != null) {
minutes = Integer.parseInt(m.group(2));
}
minutes = minutes + (hours * 60);
return minutes;
}
return 0;
}
public static String MinutesToGracePeriodStr(int minutes) {
int hours = minutes / 60;
int remMinutes = minutes % 60;
String hourStr = " hours";
if (hours == 1) {
hourStr = " hour";
}
String minStr = " minutes";
if (remMinutes == 1) {
minStr = " minute";
}
if (hours > 0) {
if (remMinutes == 0) {
return hours + hourStr;
}
return hours + hourStr + " " + remMinutes + minStr;
}
return remMinutes + minStr;
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View File

Before

Width:  |  Height:  |  Size: 738 B

After

Width:  |  Height:  |  Size: 738 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

View File

Before

Width:  |  Height:  |  Size: 538 B

After

Width:  |  Height:  |  Size: 538 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

Before

Width:  |  Height:  |  Size: 964 B

After

Width:  |  Height:  |  Size: 964 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

View File

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2012 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.
-->
<!-- Used with alarm widget. -->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:state_enabled="true" android:state_active="false" android:state_focused="false"
android:drawable="@drawable/ic_alarm_normal_layer"/>
<item
android:state_enabled="true" android:state_active="true" android:state_focused="false"
android:drawable="@drawable/ic_alarm_activated_layer" />
<item
android:state_enabled="true" android:state_active="false" android:state_focused="true"
android:drawable="@drawable/ic_alarm_activated_layer" />
</selector>

View File

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2012 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.
-->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval">
<solid android:color="#58c233"/>
<size
android:width="@dimen/alarm_widget_circle_size"
android:height="@dimen/alarm_widget_circle_size" />
</shape>
</item>
<item
android:top="@dimen/alarm_widget_asset_margin"
android:right="@dimen/alarm_widget_asset_margin"
android:bottom="@dimen/alarm_widget_asset_margin"
android:left="@dimen/alarm_widget_asset_margin"
android:drawable="@drawable/ic_alarm_activated" />
</layer-list>

View File

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2012 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.
-->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<!-- A fake circle to fix the size of this layer asset. -->
<item>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval">
<solid android:color="#00000000"/>
<size
android:width="@dimen/alarm_widget_circle_size"
android:height="@dimen/alarm_widget_circle_size" />
</shape>
</item>
<item
android:top="@dimen/alarm_widget_asset_margin"
android:right="@dimen/alarm_widget_asset_margin"
android:bottom="@dimen/alarm_widget_asset_margin"
android:left="@dimen/alarm_widget_asset_margin"
android:drawable="@drawable/ic_alarm_normal" />
</layer-list>

View File

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2011 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.
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval"
>
<size android:height="@dimen/alarm_outerring_diameter"
android:width="@dimen/alarm_outerring_diameter" />
<solid android:color="#00000000" />
<stroke android:color="#1affffff" android:width="2dp" />
</shape>

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<Switch
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/alarm_active_switch"
android:layout_gravity="left" />
</merge>

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<ToggleButton
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/alarm_active_switch"
android:layout_gravity="left" />
</merge>

View File

@ -1,40 +1,39 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
tools:context=".AlarmNotificationActivity"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".AlarmNotificationActivity">
android:background="@android:color/black">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/cancel_dialog_imageView"
android:src="@drawable/hypoalarm"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
android:layout_marginTop="60dp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceLarge"
android:text="@string/app_name"
android:id="@+id/cancel_dialog_title"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
android:layout_marginTop="@dimen/alarm_widget_vertical_margin" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceLarge"
android:text="HypoAlarm!"
android:id="@+id/cancel_dialog_title"
android:layout_below="@+id/cancel_dialog_imageView"
android:layout_centerHorizontal="true"
android:layout_marginTop="25dp" />
<net.frakbot.glowpadbackport.GlowPadView
android:id="@+id/cancel_glowpad"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_below="@+id/cancel_dialog_title"
android:background="@android:color/black"
android:visibility="visible"
android:gravity="center"
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="I'm awake"
android:id="@+id/cancel_dialog_button"
android:layout_marginTop="25dp"
android:layout_below="@+id/cancel_dialog_title"
android:layout_centerHorizontal="true" />
</RelativeLayout>
</FrameLayout>
app:targetDrawables="@array/glowpad_target"
app:handleDrawable="@drawable/ic_alarm_activated"
app:innerRadius="@dimen/glowpadview_inner_radius"
app:outerRadius="@dimen/glowpadview_target_placement_radius"
app:outerRingDrawable="@drawable/ic_outerring"
app:snapMargin="@dimen/glowpadview_snap_margin"
app:glowRadius="@dimen/glowpadview_glow_radius"
app:pointDrawable="@drawable/ic_glowdot" />
</RelativeLayout>

View File

@ -1,138 +1,165 @@
<?xml version="1.0" encoding="utf-8"?>
<TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:stretchColumns="0"
android:shrinkColumns="1"
android:dividerPadding="10dp"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingBottom="@dimen/activity_vertical_margin">
android:fillViewport="true" >
<TableRow
<TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
android:layout_height="wrap_content"
android:stretchColumns="0"
android:shrinkColumns="1"
android:dividerPadding="10dp"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingBottom="@dimen/activity_vertical_margin">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/description"
android:text="@string/description"
android:layout_span="2"
android:layout_column="0" />
</TableRow>
<TableRow
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<TableRow
android:layout_marginTop="10dp"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/description"
android:text="@string/description"
android:layout_span="2"
android:layout_column="0" />
</TableRow>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/alarm_active_text"
android:id="@+id/alarm_active_text"
android:layout_column="0" />
<TableRow
android:layout_marginTop="10dp"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<Switch
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/alarm_active_switch"
android:layout_column="1"
android:layout_gravity="left" />
</TableRow>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/alarm_active_text"
android:id="@+id/alarm_active_text"
android:layout_column="0" />
<TableRow
android:layout_marginTop="10dp"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/alarm_time_text"
android:id="@+id/alarm_time_text"
android:layout_column="0" />
<include
layout="@layout/activeswitch"
android:layout_column="1" />
</TableRow>
<TableRow
android:layout_marginTop="10dp"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/alarm_time_text"
android:id="@+id/alarm_time_text"
android:layout_column="0" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/alarm_time"
android:gravity="center_vertical"
style="?android:attr/borderlessButtonStyle"
android:layout_column="1" />
</TableRow>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/alarm_time"
android:gravity="center_vertical"
style="@style/borderless_button"
android:layout_column="1" />
</TableRow>
<TableRow
android:layout_marginTop="10dp"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/grace_period_text"
android:id="@+id/grace_period_text"
android:layout_column="0" />
<TableRow
android:layout_marginTop="10dp"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/grace_period_text"
android:id="@+id/grace_period_text"
android:layout_column="0" />
<Spinner
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/grace_period"
android:entries="@array/grace_period_array"
android:gravity="left"
android:layout_column="1" />
</TableRow>
<Spinner
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/grace_period"
android:entries="@array/grace_period_array"
android:gravity="left"
android:layout_column="1" />
</TableRow>
<TableRow
android:layout_marginTop="10dp"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/phone_number_text"
android:id="@+id/phone_number_text"
android:layout_column="0" />
<TableRow
android:layout_marginTop="10dp"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/phone_number_text"
android:id="@+id/phone_number_text"
android:layout_column="0" />
<EditText
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/phone_number"
android:inputType="phone"
android:focusable="false"
android:focusableInTouchMode="false"
android:clickable="true"
android:gravity="left"
android:layout_column="1" />
</TableRow>
<EditText
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/phone_number"
android:inputType="phone"
android:focusable="false"
android:focusableInTouchMode="false"
android:clickable="true"
android:gravity="left"
android:layout_column="1" />
</TableRow>
<TableRow
android:layout_marginTop="10dp"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/message_text"
android:id="@+id/message_text"
android:layout_span="2"
android:layout_column="0" />
</TableRow>
<TableRow
android:layout_marginTop="10dp"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/ringtone_text"
android:id="@+id/ringtone_text"
android:layout_column="0" />
<TableRow
android:layout_marginTop="10dp"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<EditText
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/message"
android:focusable="false"
android:focusableInTouchMode="false"
android:clickable="true"
android:layout_span="2"
android:layout_column="0" />
</TableRow>
</TableLayout>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/ringtone"
android:focusable="false"
android:focusableInTouchMode="false"
android:clickable="true"
android:gravity="left"
android:layout_column="1" />
</TableRow>
<TableRow
android:layout_marginTop="10dp"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/message_text"
android:id="@+id/message_text"
android:layout_span="2"
android:layout_column="0" />
</TableRow>
<TableRow
android:layout_marginTop="10dp"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<EditText
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/message"
android:inputType="textShortMessage|textMultiLine|textCapSentences"
android:focusable="false"
android:focusableInTouchMode="false"
android:clickable="true"
android:layout_span="2"
android:layout_column="0" />
</TableRow>
</TableLayout>
</ScrollView>

Binary file not shown.

View File

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Default screen margins, per the Android Design guidelines. -->
<dimen name="activity_horizontal_margin">8dp</dimen>
<dimen name="activity_vertical_margin">8dp</dimen>
<!-- Default target placement radius for GlowPadView. Should be 1/2 of outerring diameter. -->
<dimen name="glowpadview_target_placement_radius">110dip</dimen>
<!-- Default glow radius for GlowPadView -->
<dimen name="glowpadview_glow_radius">50dip</dimen>
<!-- Default distance beyond which GlowPadView snaps to the matching target -->
<dimen name="glowpadview_snap_margin">20dip</dimen>
<!-- Default distance from each snap target that GlowPadView considers a "hit" -->
<dimen name="glowpadview_inner_radius">10dip</dimen>
<!-- Size of lockscreen outerring -->
<dimen name="alarm_outerring_diameter">220dp</dimen>
<!-- Circle size for incoming call widget's each item. -->
<dimen name="alarm_widget_circle_size">72dp</dimen>
<!-- Margin used for alarm widget's icon for each item.
This should be same as "(alarm_widget_circle_size - icon_size)/2".
Right now answer/decline/reject icons have 38dp width/height.
So, (72 - 38)/2 ==> 17dp -->
<dimen name="alarm_widget_asset_margin">11dp</dimen>
<dimen name="alarm_widget_vertical_margin">20dp</dimen>
</resources>

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="borderless_button"></style>
</resources>

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<array name="glowpad_target">
<item>@null</item>
<item>@null</item>
<item>@null</item>
<item>@drawable/ic_alarm</item>
</array>
</resources>

View File

@ -3,4 +3,31 @@
<!-- Default screen margins, per the Android Design guidelines. -->
<dimen name="activity_horizontal_margin">16dp</dimen>
<dimen name="activity_vertical_margin">16dp</dimen>
<!-- Default target placement radius for GlowPadView. Should be 1/2 of outerring diameter. -->
<dimen name="glowpadview_target_placement_radius">135dip</dimen>
<!-- Default glow radius for GlowPadView -->
<dimen name="glowpadview_glow_radius">75dip</dimen>
<!-- Default distance beyond which GlowPadView snaps to the matching target -->
<dimen name="glowpadview_snap_margin">40dip</dimen>
<!-- Default distance from each snap target that GlowPadView considers a "hit" -->
<dimen name="glowpadview_inner_radius">15dip</dimen>
<!-- Size of lockscreen outerring -->
<dimen name="alarm_outerring_diameter">270dp</dimen>
<!-- Circle size for incoming call widget's each item. -->
<dimen name="alarm_widget_circle_size">94dp</dimen>
<!-- Margin used for alarm widget's icon for each item.
This should be same as "(alarm_widget_circle_size - icon_size)/2".
Right now answer/decline/reject icons have 38dp width/height.
So, (94 - 38)/2 ==> 28dp -->
<dimen name="alarm_widget_asset_margin">11dp</dimen>
<dimen name="alarm_widget_vertical_margin">60dp</dimen>
</resources>

View File

@ -7,6 +7,8 @@
<string name="alarm_notification">Text message pending</string>
<string name="pre_alarm_notification">Upcoming alarm</string>
<string-array name="grace_period_array">
<item>10 minutes</item>
<item>15 minutes</item>
@ -27,6 +29,8 @@
<string name="phone_number_text">Phone number</string>
<string name="ringtone_text">Alarm Tone</string>
<string name="message_text">Message</string>
<string name="AlarmActivePref">AlarmActive</string>
@ -35,6 +39,8 @@
<string name="GracePeriodPref">GracePeriod</string>
<string name="RingtonePref">Ringtone</string>
<string name="PhoneNumberPref">PhoneNumber</string>
<string name="MessagePref">Message</string>
@ -47,7 +53,9 @@
<string name="alarmSetToast">HypoAlarm set to </string>
<string name="alarmCancelToast">HypoAlarm text message cancelled</string>
<string name="alarmCancelToast">HypoAlarm cancelled</string>
<string name="graceCancelToast">HypoAlarm text message cancelled</string>
<string name="defaultMessage">Hi, I haven\'t responded to my alarm today. Please contact me to make sure I\'m awake.</string>
@ -55,6 +63,10 @@
<string name="notificationCancellation">Cancel</string>
<string name="preNotificationTitle">Upcoming HypoAlarm</string>
<string name="preNotificationCancellation">Dismiss Now</string>
<string name="alarmCancelled">All HypoAlarms cancelled</string>
</resources>

View File

@ -1,8 +1,8 @@
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat">
<!-- Customize your theme here. -->
</style>
<style name="borderless_button">?android:attr/borderlessButtonStyle</style>
</resources>

View File

@ -1 +1,3 @@
include ':HypoAlarm', ':GlowPadBackport'
//include ':HypoAlarm', ':GlowPadBackport', ':SeekArc'
//include ':HypoAlarm', ':GlowPadBackport'
include ':HypoAlarm'