From 9e6ec9e8ba2b880d0d005a01090aa6ca3ca3e465 Mon Sep 17 00:00:00 2001 From: Matt Garnes Date: Mon, 20 Jul 2015 15:28:17 -0700 Subject: Add AlarmClock support for CM DeskClock manipulation. - In order to externalize the AlarmClock provider within DeskClock, move the database contract, ClockContract, into the SDK so that interested parties can reference it. - Add CyanogenModAlarmClock to add new utilities for turning existing alarms on/off and creating new alarms. Change-Id: I1f11ccc3988bdef10d721e2038b2c7d69a4ae598 --- Android.mk | 4 +- src/java/cyanogenmod/alarmclock/ClockContract.java | 309 +++++++++++++++++++++ .../alarmclock/CyanogenModAlarmClock.java | 163 +++++++++++ tests/AndroidManifest.xml | 10 + tests/res/values/strings.xml | 1 + .../tests/alarmclock/CMAlarmClockTest.java | 135 +++++++++ 6 files changed, 620 insertions(+), 2 deletions(-) create mode 100644 src/java/cyanogenmod/alarmclock/ClockContract.java create mode 100644 src/java/cyanogenmod/alarmclock/CyanogenModAlarmClock.java create mode 100644 tests/src/org/cyanogenmod/tests/alarmclock/CMAlarmClockTest.java diff --git a/Android.mk b/Android.mk index cca2bf4..bac2019 100644 --- a/Android.mk +++ b/Android.mk @@ -155,7 +155,7 @@ LOCAL_DROIDDOC_CUSTOM_TEMPLATE_DIR:= build/tools/droiddoc/templates-sdk LOCAL_DROIDDOC_OPTIONS:= \ -stubs $(TARGET_OUT_COMMON_INTERMEDIATES)/JAVA_LIBRARIES/cmsdk_stubs_current_intermediates/src \ - -stubpackages cyanogenmod.app:cyanogenmod.os:cyanogenmod.profiles:cyanogenmod.platform:org.cyanogenmod.platform \ + -stubpackages cyanogenmod.alarmclock:cyanogenmod.app:cyanogenmod.os:cyanogenmod.profiles:cyanogenmod.platform:org.cyanogenmod.platform \ -api $(INTERNAL_CM_PLATFORM_API_FILE) \ -removedApi $(INTERNAL_CM_PLATFORM_REMOVED_API_FILE) \ -nodocs \ @@ -184,7 +184,7 @@ LOCAL_MODULE := cm-system-api-stubs LOCAL_DROIDDOC_OPTIONS:=\ -stubs $(TARGET_OUT_COMMON_INTERMEDIATES)/JAVA_LIBRARIES/cmsdk_system_stubs_current_intermediates/src \ - -stubpackages cyanogenmod.app:cyanogenmod.os:cyanogenmod.profiles:cyanogenmod.platform:org.cyanogenmod.platform \ + -stubpackages cyanogenmod.alarmclock:cyanogenmod.app:cyanogenmod.os:cyanogenmod.profiles:cyanogenmod.platform:org.cyanogenmod.platform \ -showAnnotation android.annotation.SystemApi \ -api $(INTERNAL_CM_PLATFORM_SYSTEM_API_FILE) \ -removedApi $(INTERNAL_CM_PLATFORM_SYSTEM_REMOVED_API_FILE) \ diff --git a/src/java/cyanogenmod/alarmclock/ClockContract.java b/src/java/cyanogenmod/alarmclock/ClockContract.java new file mode 100644 index 0000000..66ba4af --- /dev/null +++ b/src/java/cyanogenmod/alarmclock/ClockContract.java @@ -0,0 +1,309 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package cyanogenmod.alarmclock; + +import android.net.Uri; +import android.provider.BaseColumns; + +/** + *

+ * The contract between the clock provider and desk clock. Contains + * definitions for the supported URIs and data columns. + *

+ *

Overview

+ *

+ * ClockContract defines the data model of clock related information. + * This data is stored in a number of tables: + *

+ * + * + *

+ * Requires {@link cyanogenmod.alarmclock.CyanogenModAlarmClock#READ_ALARMS_PERMISSION} + * to read from the provider. + * Requires {@link cyanogenmod.alarmclock.CyanogenModAlarmClock#WRITE_ALARMS_PERMISSION} to write + * to the provider. + *

+ */ +public final class ClockContract { + /** + * This authority is used for writing to or querying from the clock + * provider. + */ + public static final String AUTHORITY = "com.android.deskclock"; + + /** + * This utility class cannot be instantiated + */ + private ClockContract() {} + + /** + * Constants for tables with AlarmSettings. + */ + public interface AlarmSettingColumns extends BaseColumns { + /** + * This string is used to indicate no ringtone. + */ + public static final Uri NO_RINGTONE_URI = Uri.EMPTY; + + /** + * This string is used to indicate no ringtone. + */ + public static final String NO_RINGTONE = NO_RINGTONE_URI.toString(); + + /** + * True if alarm should vibrate + *

Type: BOOLEAN

+ */ + public static final String VIBRATE = "vibrate"; + + /** + * Alarm label. + * + *

Type: STRING

+ */ + public static final String LABEL = "label"; + + /** + * Audio alert to play when alarm triggers. Null entry + * means use system default and entry that equal + * Uri.EMPTY.toString() means no ringtone. + * + *

Type: STRING

+ */ + public static final String RINGTONE = "ringtone"; + + /** + * True if alarm should start off quiet and slowly increase volume + *

Type: BOOLEAN

+ */ + public static final String INCREASING_VOLUME = "incvol"; + + /** + * Profile to change to when alarm triggers + *

Type: STRING

+ */ + public static final String PROFILE = "profile"; + } + + /** + * Constants for the Alarms table, which contains the user created alarms. + */ + public interface AlarmsColumns extends AlarmSettingColumns, BaseColumns { + /** + * The content:// style URL for this table. + */ + public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/alarms"); + + /** + * Hour in 24-hour localtime 0 - 23. + *

Type: INTEGER

+ */ + public static final String HOUR = "hour"; + + /** + * Minutes in localtime 0 - 59. + *

Type: INTEGER

+ */ + public static final String MINUTES = "minutes"; + + /** + * Days of the week encoded as a bit set. + *

Type: INTEGER

+ * + */ + public static final String DAYS_OF_WEEK = "daysofweek"; + + /** + * True if alarm is active. + *

Type: BOOLEAN

+ */ + public static final String ENABLED = "enabled"; + + /** + * Determine if alarm is deleted after it has been used. + *

Type: INTEGER

+ */ + public static final String DELETE_AFTER_USE = "delete_after_use"; + } + + /** + * Constants for the Instance table, which contains the state of each alarm. + */ + public interface InstancesColumns extends AlarmSettingColumns, BaseColumns { + /** + * The content:// style URL for this table. + */ + public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/instances"); + + + /** + * Alarm state for rtc power off alarm + */ + public static final int POWER_OFF_ALARM_STATE = -1; + + /** + * Alarm state when to show no notification. + * + * Can transitions to: + * LOW_NOTIFICATION_STATE + */ + public static final int SILENT_STATE = 0; + + /** + * Alarm state to show low priority alarm notification. + * + * Can transitions to: + * HIDE_NOTIFICATION_STATE + * HIGH_NOTIFICATION_STATE + * DISMISSED_STATE + */ + public static final int LOW_NOTIFICATION_STATE = 1; + + /** + * Alarm state to hide low priority alarm notification. + * + * Can transitions to: + * HIGH_NOTIFICATION_STATE + */ + public static final int HIDE_NOTIFICATION_STATE = 2; + + /** + * Alarm state to show high priority alarm notification. + * + * Can transitions to: + * DISMISSED_STATE + * FIRED_STATE + */ + public static final int HIGH_NOTIFICATION_STATE = 3; + + /** + * Alarm state when alarm is in snooze. + * + * Can transitions to: + * DISMISSED_STATE + * FIRED_STATE + */ + public static final int SNOOZE_STATE = 4; + + /** + * Alarm state when alarm is being fired. + * + * Can transitions to: + * DISMISSED_STATE + * SNOOZED_STATE + * MISSED_STATE + */ + public static final int FIRED_STATE = 5; + + /** + * Alarm state when alarm has been missed. + * + * Can transitions to: + * DISMISSED_STATE + */ + public static final int MISSED_STATE = 6; + + /** + * Alarm state when alarm is done. + */ + public static final int DISMISSED_STATE = 7; + + /** + * Alarm year. + * + *

Type: INTEGER

+ */ + public static final String YEAR = "year"; + + /** + * Alarm month in year. + * + *

Type: INTEGER

+ */ + public static final String MONTH = "month"; + + /** + * Alarm day in month. + * + *

Type: INTEGER

+ */ + public static final String DAY = "day"; + + /** + * Alarm hour in 24-hour localtime 0 - 23. + *

Type: INTEGER

+ */ + public static final String HOUR = "hour"; + + /** + * Alarm minutes in localtime 0 - 59 + *

Type: INTEGER

+ */ + public static final String MINUTES = "minutes"; + + /** + * Foreign key to Alarms table + *

Type: INTEGER (long)

+ */ + public static final String ALARM_ID = "alarm_id"; + + /** + * Alarm state + *

Type: INTEGER

+ */ + public static final String ALARM_STATE = "alarm_state"; + } + + /** + * Constants for the Cities table, which contains all selectable cities. + */ + public interface CitiesColumns { + /** + * The content:// style URL for this table. + */ + public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/cities"); + + /** + * Primary id for city. + *

Type: STRING

+ */ + public static final String CITY_ID = "city_id"; + + /** + * City name. + *

Type: STRING

+ */ + public static final String CITY_NAME = "city_name"; + + /** + * Timezone name of city. + *

Type: STRING

+ */ + public static final String TIMEZONE_NAME = "timezone_name"; + + /** + * Timezone offset. + *

Type: INTEGER

+ */ + public static final String TIMEZONE_OFFSET = "timezone_offset"; + } +} diff --git a/src/java/cyanogenmod/alarmclock/CyanogenModAlarmClock.java b/src/java/cyanogenmod/alarmclock/CyanogenModAlarmClock.java new file mode 100644 index 0000000..76508a9 --- /dev/null +++ b/src/java/cyanogenmod/alarmclock/CyanogenModAlarmClock.java @@ -0,0 +1,163 @@ +/** + * Copyright (c) 2015, The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package cyanogenmod.alarmclock; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.provider.AlarmClock; + +import java.util.List; + +/** + * The CyanogenModAlarmClock class contains utilities for interacting with + * a variety of Alarm features that the CyanogenMod AlarmClock application + * (based on AOSP DeskClock) supports. + */ +public class CyanogenModAlarmClock { + /** + * The package name of the CyanogenMod DeskClock application. + */ + private static final String DESKCLOCK_PACKAGE = "com.android.deskclock"; + + /** + * Allows an application to make modifications to existing alarms, + * such as turning them on or off. + * + * @see #ACTION_SET_ALARM_ENABLED + */ + public static final String MODIFY_ALARMS_PERMISSION + = "cyanogenmod.alarmclock.permission.MODIFY_ALARMS"; + + /** + * Allows an application to have read access to all alarms in the + * CyanogenMod DeskClock application. + * + * @see cyanogenmod.alarmclock.ClockContract + */ + public static final String READ_ALARMS_PERMISSION + = "cyanogenmod.alarmclock.permission.READ_ALARMS"; + + /** + * Allows an application to have write access to all alarms in the + * CyanogenMod DeskClock application. This is a system level permission. + * + * @see cyanogenmod.alarmclock.ClockContract + * @hide + */ + public static final String WRITE_ALARMS_PERMISSION + = "cyanogenmod.alarmclock.permission.WRITE_ALARMS"; + + /** + * Service Action: Set an existing alarm to be either enabled or disabled. + *

+ * This action sets an alarm to be enabled or disabled. + *

+ * This action requests an alarm with the id specified by {@link #EXTRA_ALARM_ID} + * be set to enabled or disabled, depending on the value set with {@link #EXTRA_ENABLED}. + *

+ * + *

Requires permission {@link #MODIFY_ALARMS_PERMISSION} to launch this + * intent. + *

+ * + *

Always set the package name of the Intent that will launch this action + * to {@link #DESKCLOCK_PACKAGE} explicitly, for security.

+ * + *

Request parameters

+ * + */ + public static final String ACTION_SET_ALARM_ENABLED + = "cyanogenmod.alarmclock.SET_ALARM_ENABLED"; + + /** + * Bundle extra: The id of the alarm. + *

+ * Used by {@link #ACTION_SET_ALARM_ENABLED}. + *

+ * This extra is required. + *

+ * The value is an {@link Long} and is the ID stored in + * {@link cyanogenmod.alarmclock.ClockContract.AlarmsColumns#_ID} for this alarm. + *

+ * + * @see #ACTION_SET_ALARM_ENABLED + * @see #EXTRA_ENABLED + */ + public static final String EXTRA_ALARM_ID = "cyanogenmod.intent.extra.alarmclock.ID"; + + /** + * Bundle extra: Whether to set the alarm to enabled to disabled. + *

+ * Used by {@link #ACTION_SET_ALARM_ENABLED}. + *

+ * This extra is required. + *

+ * The value is an {@link Boolean} and if true, will set the alarm specified by + * {@link #EXTRA_ALARM_ID} to be enabled. Otherwise, the alarm will be disabled. + *

+ * + * @see #ACTION_SET_ALARM_ENABLED + * @see #EXTRA_ALARM_ID + */ + public static final String EXTRA_ENABLED = "cyanogenmod.intent.extra.alarmclock.ENABLED"; + + /** + *

+ * Retrieves an Intent that is prepopulated with the proper action and ComponentName to + * create a new alarm in the CyanogenMod DeskClock application. + *

+ *

The action will be set to {@link android.provider.AlarmClock#ACTION_SET_ALARM}. Use the + * Intent extras contained at {@link android.provider.AlarmClock} to configure the alarm. + *

+ *

Requires permission {@link android.Manifest.permission#SET_ALARM} to launch this + * intent. + *

+ * + * @see android.provider.AlarmClock#ACTION_SET_ALARM + * @return The Intent to create a new alarm with the CyanogenMod DeskClock application. + */ + public static Intent createAlarmIntent(Context context) { + Intent intent = new Intent(); + intent.setAction(AlarmClock.ACTION_SET_ALARM); + + // Retrieve the ComponentName of the best result + // for ACTION_SET_ALARM within system applications only. + // This will exclude third party alarm apps that have been installed. + PackageManager pm = context.getPackageManager(); + List resolves = pm.queryIntentActivities(intent, 0); + ComponentName selectedSystemComponent = null; + for (ResolveInfo info : resolves) { + if ((info.activityInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) { + selectedSystemComponent = new ComponentName(info.activityInfo.packageName, + info.activityInfo.name); + break; + } + } + if (selectedSystemComponent != null) { + intent.setComponent(selectedSystemComponent); + } + return intent; + } +} diff --git a/tests/AndroidManifest.xml b/tests/AndroidManifest.xml index 8aae204..e4ad867 100644 --- a/tests/AndroidManifest.xml +++ b/tests/AndroidManifest.xml @@ -8,6 +8,9 @@ + + + @@ -25,6 +28,13 @@ + + + + + + diff --git a/tests/res/values/strings.xml b/tests/res/values/strings.xml index e07e6c1..6195f2b 100644 --- a/tests/res/values/strings.xml +++ b/tests/res/values/strings.xml @@ -2,4 +2,5 @@ CM Platform Tests CM Platform Settings Tests + CM Platform Alarm Clock Tests diff --git a/tests/src/org/cyanogenmod/tests/alarmclock/CMAlarmClockTest.java b/tests/src/org/cyanogenmod/tests/alarmclock/CMAlarmClockTest.java new file mode 100644 index 0000000..83a1c1f --- /dev/null +++ b/tests/src/org/cyanogenmod/tests/alarmclock/CMAlarmClockTest.java @@ -0,0 +1,135 @@ +package org.cyanogenmod.tests.alarmclock; + +import android.content.Intent; +import android.database.Cursor; +import android.database.DatabaseUtils; +import android.net.Uri; +import android.provider.AlarmClock; +import android.util.Log; +import android.widget.Toast; +import cyanogenmod.alarmclock.ClockContract; +import cyanogenmod.alarmclock.CyanogenModAlarmClock; +import org.cyanogenmod.tests.TestActivity; + +/** + * Tests functionality added in {@link cyanogenmod.alarmclock.CyanogenModAlarmClock} + */ +public class CMAlarmClockTest extends TestActivity { + private static final String TAG = "CMAlarmClockTest"; + + private static final String[] ALARM_QUERY_COLUMNS = { + ClockContract.AlarmsColumns._ID, + ClockContract.AlarmsColumns.LABEL, + ClockContract.AlarmsColumns.VIBRATE, + ClockContract.AlarmsColumns.RINGTONE, + ClockContract.AlarmsColumns.INCREASING_VOLUME, + ClockContract.AlarmsColumns.PROFILE, + ClockContract.AlarmsColumns.ENABLED + }; + + @Override + protected String tag() { + return null; + } + + @Override + protected Test[] tests() { + return mTests; + } + + private Test[] mTests = new Test[] { + new Test("Test query alarms and dump to log") { + public void run() { + Uri clockUri = ClockContract.AlarmsColumns.CONTENT_URI; + Cursor allAlarms = getContentResolver().query(clockUri, + ALARM_QUERY_COLUMNS, null, null, null); + Log.d(TAG, "All alarms: " + DatabaseUtils.dumpCursorToString(allAlarms)); + if (allAlarms != null && !allAlarms.isClosed()) { + allAlarms.close(); + } + } + }, + new Test("Test create alarm") { + public void run() { + Intent intent = CyanogenModAlarmClock.createAlarmIntent(CMAlarmClockTest.this); + intent.putExtra(AlarmClock.EXTRA_HOUR, 13); + intent.putExtra(AlarmClock.EXTRA_MINUTES, 35); + intent.putExtra(AlarmClock.EXTRA_MESSAGE, "Test from third party!"); + intent.putExtra(AlarmClock.EXTRA_SKIP_UI, true); + startActivityForResult(intent, 0); + } + }, + new Test("Enable the first alarm if it exists") { + public void run() { + setAlarmEnabledAtIndex(0, true); + } + }, + new Test("Disable the first alarm if it exists") { + public void run() { + setAlarmEnabledAtIndex(0, false); + } + }, + new Test("Enable the second alarm if it exists") { + public void run() { + setAlarmEnabledAtIndex(1, true); + } + }, + new Test("Disable the second alarm if it exists") { + public void run() { + setAlarmEnabledAtIndex(1, false); + } + }, + }; + + /** + * Retrieve the id of the alarm within the Alarms table at the given index. + * @param index The index of the alarm for which to retrieve the id, beginning at zero. + * @return The ID of the alarm at the given index or -1L if + * no alarm exists at that index. + */ + private long getAlarmIdAtIndex(int index) { + Uri clockUri = ClockContract.AlarmsColumns.CONTENT_URI; + Cursor allAlarms = getContentResolver().query(clockUri, + new String[]{ClockContract.AlarmsColumns._ID}, null, null, null); + long theIdToReturn = -1L; + int current = 0; + int idColumnIndex = allAlarms.getColumnIndex(ClockContract.AlarmsColumns._ID); + allAlarms.moveToFirst(); + while(!allAlarms.isAfterLast()) { + if (current == index) { + theIdToReturn = allAlarms.getLong(idColumnIndex); + break; + } + current++; + allAlarms.moveToNext(); + } + if (allAlarms != null && !allAlarms.isClosed()) { + allAlarms.close(); + } + return theIdToReturn; + } + + /** + * Construct a new Intent that will launch a DeskClock IntentService to + * set an alarm's state to enabled or disabled. + * @param alarmId The ID of the alarm that we will toggle. + * @param enabledState The new state of the alarm, whether it will be enabled or disabled. + * @return The Intent to launch that will perform this action. + */ + private Intent getIntentToSetAlarmEnabled(long alarmId, boolean enabledState) { + Intent intent = new Intent(CyanogenModAlarmClock.ACTION_SET_ALARM_ENABLED); + intent.setPackage("com.android.deskclock"); + intent.putExtra(CyanogenModAlarmClock.EXTRA_ALARM_ID, alarmId); + intent.putExtra(CyanogenModAlarmClock.EXTRA_ENABLED, enabledState); + return intent; + } + + private void setAlarmEnabledAtIndex(int index, boolean enabled) { + long firstAlarmId = getAlarmIdAtIndex(index); + if (firstAlarmId == -1L) { + Toast.makeText(this, "Alarm not found!", Toast.LENGTH_SHORT); + } else { + startService(getIntentToSetAlarmEnabled(firstAlarmId, enabled)); + } + } +} -- cgit v1.1