From 51c39ba2d7487a305d9f6fc8724d254bce687e7d Mon Sep 17 00:00:00 2001 From: Matt Garnes Date: Wed, 29 Jul 2015 17:19:03 -0700 Subject: [PATCH] Add API to SettingsManager to set zen mode. - Add a new permission cyanogenmod.permission.MODIFY_SOUND_SETTINGS. - Only allowed when the caller holds cyanogenmod.permission.MODIFY_SOUND_SETTINGS. - Allows the user to set zen mode to the off, priority interruptions only, or no interruptions modes. - For each mode change, the end condition will be set to null so that the mode remains active indefinitely. Change-Id: Id465509a8cc8d98953bf8cbe06cacff02b9f75a0 --- api/cm_current.txt | 70 +++++++++++++++++++ .../internal/SettingsManagerService.java | 57 +++++++++++++++ cm/res/AndroidManifest.xml | 7 ++ cm/res/res/values/strings.xml | 3 + .../cyanogenmod/app/ISettingsManager.aidl | 1 + src/java/cyanogenmod/app/SettingsManager.java | 56 +++++++++++++++ system-api/cm_system-current.txt | 70 +++++++++++++++++++ tests/AndroidManifest.xml | 1 + .../tests/settings/CMSettingsManagerTest.java | 17 ++++- 9 files changed, 281 insertions(+), 1 deletion(-) diff --git a/api/cm_current.txt b/api/cm_current.txt index 179dd37..bfb69fe 100644 --- a/api/cm_current.txt +++ b/api/cm_current.txt @@ -1,3 +1,69 @@ +package cyanogenmod.alarmclock { + + public final class ClockContract { + field public static final java.lang.String AUTHORITY = "com.android.deskclock"; + } + + public static abstract interface ClockContract.AlarmSettingColumns { + field public static final java.lang.String INCREASING_VOLUME = "incvol"; + field public static final java.lang.String LABEL = "label"; + field public static final java.lang.String NO_RINGTONE; + field public static final android.net.Uri NO_RINGTONE_URI; + field public static final java.lang.String PROFILE = "profile"; + field public static final java.lang.String RINGTONE = "ringtone"; + field public static final java.lang.String VIBRATE = "vibrate"; + } + + public static abstract interface ClockContract.AlarmsColumns implements cyanogenmod.alarmclock.ClockContract.AlarmSettingColumns { + field public static final android.net.Uri CONTENT_URI; + field public static final java.lang.String DAYS_OF_WEEK = "daysofweek"; + field public static final java.lang.String DELETE_AFTER_USE = "delete_after_use"; + field public static final java.lang.String ENABLED = "enabled"; + field public static final java.lang.String HOUR = "hour"; + field public static final java.lang.String MINUTES = "minutes"; + } + + public static abstract interface ClockContract.CitiesColumns { + field public static final java.lang.String CITY_ID = "city_id"; + field public static final java.lang.String CITY_NAME = "city_name"; + field public static final android.net.Uri CONTENT_URI; + field public static final java.lang.String TIMEZONE_NAME = "timezone_name"; + field public static final java.lang.String TIMEZONE_OFFSET = "timezone_offset"; + } + + public static abstract interface ClockContract.InstancesColumns implements cyanogenmod.alarmclock.ClockContract.AlarmSettingColumns { + field public static final java.lang.String ALARM_ID = "alarm_id"; + field public static final java.lang.String ALARM_STATE = "alarm_state"; + field public static final android.net.Uri CONTENT_URI; + field public static final java.lang.String DAY = "day"; + field public static final int DISMISSED_STATE = 7; // 0x7 + field public static final int FIRED_STATE = 5; // 0x5 + field public static final int HIDE_NOTIFICATION_STATE = 2; // 0x2 + field public static final int HIGH_NOTIFICATION_STATE = 3; // 0x3 + field public static final java.lang.String HOUR = "hour"; + field public static final int LOW_NOTIFICATION_STATE = 1; // 0x1 + field public static final java.lang.String MINUTES = "minutes"; + field public static final int MISSED_STATE = 6; // 0x6 + field public static final java.lang.String MONTH = "month"; + field public static final int POWER_OFF_ALARM_STATE = -1; // 0xffffffff + field public static final int SILENT_STATE = 0; // 0x0 + field public static final int SNOOZE_STATE = 4; // 0x4 + field public static final java.lang.String YEAR = "year"; + } + + public class CyanogenModAlarmClock { + ctor public CyanogenModAlarmClock(); + method public static android.content.Intent createAlarmIntent(); + field public static final java.lang.String ACTION_SET_ALARM_ENABLED = "cyanogenmod.alarmclock.SET_ALARM_ENABLED"; + field public static final java.lang.String EXTRA_ALARM_ID = "cyanogenmod.intent.extra.alarmclock.ID"; + field public static final java.lang.String EXTRA_ENABLED = "cyanogenmod.intent.extra.alarmclock.ENABLED"; + field public static final java.lang.String MODIFY_ALARMS_PERMISSION = "cyanogenmod.alarmclock.permission.MODIFY_ALARMS"; + field public static final java.lang.String READ_ALARMS_PERMISSION = "cyanogenmod.alarmclock.permission.READ_ALARMS"; + field public static final java.lang.String WRITE_ALARMS_PERMISSION = "cyanogenmod.alarmclock.permission.WRITE_ALARMS"; + } + +} + package cyanogenmod.app { public class CMStatusBarManager { @@ -241,7 +307,11 @@ package cyanogenmod.app { method public void rebootDevice(); method public void setAirplaneModeEnabled(boolean); method public void setMobileDataEnabled(boolean); + method public boolean setZenMode(int); method public void shutdownDevice(); + field public static final int ZEN_MODE_IMPORTANT_INTERRUPTIONS = 1; // 0x1 + field public static final int ZEN_MODE_NO_INTERRUPTIONS = 2; // 0x2 + field public static final int ZEN_MODE_OFF = 0; // 0x0 } public class StatusBarPanelCustomTile implements android.os.Parcelable { diff --git a/cm/lib/main/java/org/cyanogenmod/platform/internal/SettingsManagerService.java b/cm/lib/main/java/org/cyanogenmod/platform/internal/SettingsManagerService.java index 2aa57b9..ae50755 100644 --- a/cm/lib/main/java/org/cyanogenmod/platform/internal/SettingsManagerService.java +++ b/cm/lib/main/java/org/cyanogenmod/platform/internal/SettingsManagerService.java @@ -16,6 +16,8 @@ package org.cyanogenmod.platform.internal; +import android.app.INotificationManager; +import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.pm.PackageInfo; @@ -48,6 +50,7 @@ public class SettingsManagerService extends SystemService { private Context mContext; private TelephonyManager mTelephonyManager; + private INotificationManager mNotificationManager; public SettingsManagerService(Context context) { super(context); @@ -59,6 +62,8 @@ public class SettingsManagerService extends SystemService { public void onStart() { mTelephonyManager = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE); + mNotificationManager = INotificationManager.Stub.asInterface( + ServiceManager.getService(Context.NOTIFICATION_SERVICE)); } private void enforceModifyNetworkSettingsPermission() { @@ -66,6 +71,11 @@ public class SettingsManagerService extends SystemService { "You do not have permissions to change system network settings."); } + private void enforceModifySoundSettingsPermission() { + mContext.enforceCallingOrSelfPermission(SettingsManager.MODIFY_SOUND_SETTINGS_PERMISSION, + "You do not have permissions to change system sound settings."); + } + private void enforceShutdownPermission() { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.REBOOT, "You do not have permissions to shut down the device."); @@ -124,6 +134,20 @@ public class SettingsManagerService extends SystemService { shutdownInternal(true); restoreCallingIdentity(token); } + + @Override + public boolean setZenMode(int mode) { + enforceModifySoundSettingsPermission(); + /* + * We need to clear the caller's identity in order to + * allow this method call to modify settings + * not allowed by the caller's permissions. + */ + long token = clearCallingIdentity(); + boolean success = setZenModeInternal(mode); + restoreCallingIdentity(token); + return success; + } }; private void setAirplaneModeEnabledInternal(boolean enabled) { @@ -154,5 +178,38 @@ public class SettingsManagerService extends SystemService { Log.d(TAG, "Unable to shutdown."); } } + + private boolean setZenModeInternal(int mode) { + ContentResolver contentResolver = mContext.getContentResolver(); + int zenModeValue = -1; + switch(mode) { + case SettingsManager.ZEN_MODE_IMPORTANT_INTERRUPTIONS: + zenModeValue = Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS; + break; + case SettingsManager.ZEN_MODE_OFF: + zenModeValue = Settings.Global.ZEN_MODE_OFF; + break; + case SettingsManager.ZEN_MODE_NO_INTERRUPTIONS: + zenModeValue = Settings.Global.ZEN_MODE_NO_INTERRUPTIONS; + break; + default: + // Invalid mode parameter + Log.w(TAG, "setZenMode() called with invalid mode: " + mode); + return false; + } + Settings.Global.putInt(contentResolver, + Settings.Global.ZEN_MODE, + zenModeValue); + try { + // Setting the exit condition to null signifies "indefinitely" + mNotificationManager.setZenModeCondition(null); + } catch (RemoteException e) { + // An error occurred, return false since the + // condition failed to set. + Log.e(TAG, "setZenMode() failed for mode: " + mode); + return false; + } + return true; + } } diff --git a/cm/res/AndroidManifest.xml b/cm/res/AndroidManifest.xml index 003c920..7a7249d 100644 --- a/cm/res/AndroidManifest.xml +++ b/cm/res/AndroidManifest.xml @@ -42,6 +42,13 @@ android:icon="@drawable/ic_launcher_cyanogenmod" android:protectionLevel="system|signature" /> + + + change system network settings Allows an app to make changes to a restricted set of system network settings. + change system sound settings + Allows an app to make changes to a restricted set of system sound settings. + bind to a custom tile listener service Allows the app to bind to the top-level interface of a custom tile listener service. diff --git a/src/java/cyanogenmod/app/ISettingsManager.aidl b/src/java/cyanogenmod/app/ISettingsManager.aidl index 83d7bf0..86fa36c 100644 --- a/src/java/cyanogenmod/app/ISettingsManager.aidl +++ b/src/java/cyanogenmod/app/ISettingsManager.aidl @@ -22,6 +22,7 @@ interface ISettingsManager { void setAirplaneModeEnabled(boolean enabled); void setMobileDataEnabled(boolean enabled); + boolean setZenMode(int mode); void shutdown(); void reboot(); } diff --git a/src/java/cyanogenmod/app/SettingsManager.java b/src/java/cyanogenmod/app/SettingsManager.java index 2ab871b..b8a5802 100644 --- a/src/java/cyanogenmod/app/SettingsManager.java +++ b/src/java/cyanogenmod/app/SettingsManager.java @@ -28,6 +28,30 @@ import android.util.Log; *

*/ public class SettingsManager { + /** + * Turn off zen mode. This restores the original ring and interruption + * settings that the user had set before zen mode was enabled. + * + * @see #setZenMode + */ + public static final int ZEN_MODE_OFF = 0; + /** + * Sets zen mode to important interruptions mode, so that + * only notifications that the user has chosen in Settings + * to be of high importance will cause the user's device to notify them. + * + * This condition is held indefinitely until changed again. + * + * @see #setZenMode + */ + public static final int ZEN_MODE_IMPORTANT_INTERRUPTIONS = 1; + /** + * Sets zen mode so that no interruptions will be allowed. The device is + * effectively silenced indefinitely, until the mode is changed again. + * + * @see #setZenMode + */ + public static final int ZEN_MODE_NO_INTERRUPTIONS = 2; private static ISettingsManager sService; @@ -40,6 +64,12 @@ public class SettingsManager { public static final String MODIFY_NETWORK_SETTINGS_PERMISSION = "cyanogenmod.permission.MODIFY_NETWORK_SETTINGS"; + /** + * Allows an application to change system sound settings, such as the zen mode. + */ + public static final String MODIFY_SOUND_SETTINGS_PERMISSION = + "cyanogenmod.permission.MODIFY_SOUND_SETTINGS"; + private static final String TAG = "SettingsManager"; private static SettingsManager sSettingsManagerInstance; @@ -116,6 +146,32 @@ public class SettingsManager { } } + /** + * Set the zen mode for the device. + * + * You will need {@link #MODIFY_SOUND_SETTINGS_PERMISSION} + * to utilize this functionality. + * + * @see #ZEN_MODE_IMPORTANT_INTERRUPTIONS + * @see #ZEN_MODE_NO_INTERRUPTIONS + * @see #ZEN_MODE_OFF + * @param mode The zen mode to set the device to. + * One of {@link #ZEN_MODE_IMPORTANT_INTERRUPTIONS}, + * {@link #ZEN_MODE_NO_INTERRUPTIONS} or + * {@link #ZEN_MODE_OFF}. + */ + public boolean setZenMode(int mode) { + if (sService == null) { + return false; + } + try { + return getService().setZenMode(mode); + } catch (RemoteException e) { + Log.e(TAG, e.getLocalizedMessage(), e); + } + return false; + } + /** * Shuts down the device, immediately. * diff --git a/system-api/cm_system-current.txt b/system-api/cm_system-current.txt index 179dd37..bfb69fe 100644 --- a/system-api/cm_system-current.txt +++ b/system-api/cm_system-current.txt @@ -1,3 +1,69 @@ +package cyanogenmod.alarmclock { + + public final class ClockContract { + field public static final java.lang.String AUTHORITY = "com.android.deskclock"; + } + + public static abstract interface ClockContract.AlarmSettingColumns { + field public static final java.lang.String INCREASING_VOLUME = "incvol"; + field public static final java.lang.String LABEL = "label"; + field public static final java.lang.String NO_RINGTONE; + field public static final android.net.Uri NO_RINGTONE_URI; + field public static final java.lang.String PROFILE = "profile"; + field public static final java.lang.String RINGTONE = "ringtone"; + field public static final java.lang.String VIBRATE = "vibrate"; + } + + public static abstract interface ClockContract.AlarmsColumns implements cyanogenmod.alarmclock.ClockContract.AlarmSettingColumns { + field public static final android.net.Uri CONTENT_URI; + field public static final java.lang.String DAYS_OF_WEEK = "daysofweek"; + field public static final java.lang.String DELETE_AFTER_USE = "delete_after_use"; + field public static final java.lang.String ENABLED = "enabled"; + field public static final java.lang.String HOUR = "hour"; + field public static final java.lang.String MINUTES = "minutes"; + } + + public static abstract interface ClockContract.CitiesColumns { + field public static final java.lang.String CITY_ID = "city_id"; + field public static final java.lang.String CITY_NAME = "city_name"; + field public static final android.net.Uri CONTENT_URI; + field public static final java.lang.String TIMEZONE_NAME = "timezone_name"; + field public static final java.lang.String TIMEZONE_OFFSET = "timezone_offset"; + } + + public static abstract interface ClockContract.InstancesColumns implements cyanogenmod.alarmclock.ClockContract.AlarmSettingColumns { + field public static final java.lang.String ALARM_ID = "alarm_id"; + field public static final java.lang.String ALARM_STATE = "alarm_state"; + field public static final android.net.Uri CONTENT_URI; + field public static final java.lang.String DAY = "day"; + field public static final int DISMISSED_STATE = 7; // 0x7 + field public static final int FIRED_STATE = 5; // 0x5 + field public static final int HIDE_NOTIFICATION_STATE = 2; // 0x2 + field public static final int HIGH_NOTIFICATION_STATE = 3; // 0x3 + field public static final java.lang.String HOUR = "hour"; + field public static final int LOW_NOTIFICATION_STATE = 1; // 0x1 + field public static final java.lang.String MINUTES = "minutes"; + field public static final int MISSED_STATE = 6; // 0x6 + field public static final java.lang.String MONTH = "month"; + field public static final int POWER_OFF_ALARM_STATE = -1; // 0xffffffff + field public static final int SILENT_STATE = 0; // 0x0 + field public static final int SNOOZE_STATE = 4; // 0x4 + field public static final java.lang.String YEAR = "year"; + } + + public class CyanogenModAlarmClock { + ctor public CyanogenModAlarmClock(); + method public static android.content.Intent createAlarmIntent(); + field public static final java.lang.String ACTION_SET_ALARM_ENABLED = "cyanogenmod.alarmclock.SET_ALARM_ENABLED"; + field public static final java.lang.String EXTRA_ALARM_ID = "cyanogenmod.intent.extra.alarmclock.ID"; + field public static final java.lang.String EXTRA_ENABLED = "cyanogenmod.intent.extra.alarmclock.ENABLED"; + field public static final java.lang.String MODIFY_ALARMS_PERMISSION = "cyanogenmod.alarmclock.permission.MODIFY_ALARMS"; + field public static final java.lang.String READ_ALARMS_PERMISSION = "cyanogenmod.alarmclock.permission.READ_ALARMS"; + field public static final java.lang.String WRITE_ALARMS_PERMISSION = "cyanogenmod.alarmclock.permission.WRITE_ALARMS"; + } + +} + package cyanogenmod.app { public class CMStatusBarManager { @@ -241,7 +307,11 @@ package cyanogenmod.app { method public void rebootDevice(); method public void setAirplaneModeEnabled(boolean); method public void setMobileDataEnabled(boolean); + method public boolean setZenMode(int); method public void shutdownDevice(); + field public static final int ZEN_MODE_IMPORTANT_INTERRUPTIONS = 1; // 0x1 + field public static final int ZEN_MODE_NO_INTERRUPTIONS = 2; // 0x2 + field public static final int ZEN_MODE_OFF = 0; // 0x0 } public class StatusBarPanelCustomTile implements android.os.Parcelable { diff --git a/tests/AndroidManifest.xml b/tests/AndroidManifest.xml index e4ad867..43f4925 100644 --- a/tests/AndroidManifest.xml +++ b/tests/AndroidManifest.xml @@ -7,6 +7,7 @@ + diff --git a/tests/src/org/cyanogenmod/tests/settings/CMSettingsManagerTest.java b/tests/src/org/cyanogenmod/tests/settings/CMSettingsManagerTest.java index adbf459..f0bd97f 100644 --- a/tests/src/org/cyanogenmod/tests/settings/CMSettingsManagerTest.java +++ b/tests/src/org/cyanogenmod/tests/settings/CMSettingsManagerTest.java @@ -49,6 +49,21 @@ public class CMSettingsManagerTest extends TestActivity { public void run() { mSettingsManager.shutdownDevice(); } - } + }, + new Test("Test set zen mode to important interruptions") { + public void run() { + mSettingsManager.setZenMode(SettingsManager.ZEN_MODE_IMPORTANT_INTERRUPTIONS); + } + }, + new Test("Test set zen mode to no interruptions") { + public void run() { + mSettingsManager.setZenMode(SettingsManager.ZEN_MODE_NO_INTERRUPTIONS); + } + }, + new Test("Test turn zen mode off") { + public void run() { + mSettingsManager.setZenMode(SettingsManager.ZEN_MODE_OFF); + } + }, }; }