aboutsummaryrefslogtreecommitdiffstats
path: root/sdk
diff options
context:
space:
mode:
Diffstat (limited to 'sdk')
-rw-r--r--sdk/res/res/values/attrs.xml25
-rw-r--r--sdk/src/java/cyanogenmod/alarmclock/ClockContract.java314
-rw-r--r--sdk/src/java/cyanogenmod/alarmclock/CyanogenModAlarmClock.java163
-rw-r--r--sdk/src/java/cyanogenmod/app/BaseLiveLockManagerService.java226
-rw-r--r--sdk/src/java/cyanogenmod/app/CMContextConstants.java218
-rw-r--r--sdk/src/java/cyanogenmod/app/CMStatusBarManager.java247
-rw-r--r--sdk/src/java/cyanogenmod/app/CMTelephonyManager.java358
-rw-r--r--sdk/src/java/cyanogenmod/app/CustomTile.aidl19
-rw-r--r--sdk/src/java/cyanogenmod/app/CustomTile.java1106
-rw-r--r--sdk/src/java/cyanogenmod/app/CustomTileListenerService.java232
-rw-r--r--sdk/src/java/cyanogenmod/app/ICMStatusBarManager.aidl38
-rw-r--r--sdk/src/java/cyanogenmod/app/ICMTelephonyManager.aidl38
-rw-r--r--sdk/src/java/cyanogenmod/app/ICustomTileListener.aidl29
-rw-r--r--sdk/src/java/cyanogenmod/app/ILiveLockScreenChangeListener.aidl27
-rw-r--r--sdk/src/java/cyanogenmod/app/ILiveLockScreenManager.aidl73
-rw-r--r--sdk/src/java/cyanogenmod/app/ILiveLockScreenManagerProvider.aidl65
-rw-r--r--sdk/src/java/cyanogenmod/app/IPartnerInterface.aidl30
-rw-r--r--sdk/src/java/cyanogenmod/app/IProfileManager.aidl51
-rw-r--r--sdk/src/java/cyanogenmod/app/LiveLockScreenInfo.aidl19
-rw-r--r--sdk/src/java/cyanogenmod/app/LiveLockScreenInfo.java189
-rw-r--r--sdk/src/java/cyanogenmod/app/LiveLockScreenManager.java182
-rw-r--r--sdk/src/java/cyanogenmod/app/PartnerInterface.java262
-rw-r--r--sdk/src/java/cyanogenmod/app/Profile.aidl19
-rwxr-xr-xsdk/src/java/cyanogenmod/app/Profile.java1365
-rw-r--r--sdk/src/java/cyanogenmod/app/ProfileGroup.java395
-rw-r--r--sdk/src/java/cyanogenmod/app/ProfileManager.java555
-rw-r--r--sdk/src/java/cyanogenmod/app/StatusBarPanelCustomTile.aidl20
-rw-r--r--sdk/src/java/cyanogenmod/app/StatusBarPanelCustomTile.java256
-rw-r--r--sdk/src/java/cyanogenmod/app/ThemeComponent.java38
-rw-r--r--sdk/src/java/cyanogenmod/app/ThemeVersion.java206
-rw-r--r--sdk/src/java/cyanogenmod/app/suggest/AppSuggestManager.java150
-rw-r--r--sdk/src/java/cyanogenmod/app/suggest/ApplicationSuggestion.aidl22
-rw-r--r--sdk/src/java/cyanogenmod/app/suggest/ApplicationSuggestion.java110
-rw-r--r--sdk/src/java/cyanogenmod/app/suggest/IAppSuggestManager.aidl30
-rw-r--r--sdk/src/java/cyanogenmod/app/suggest/IAppSuggestProvider.aidl30
-rw-r--r--sdk/src/java/cyanogenmod/content/Intent.java144
-rw-r--r--sdk/src/java/cyanogenmod/externalviews/ExternalView.java247
-rw-r--r--sdk/src/java/cyanogenmod/externalviews/ExternalViewProperties.java95
-rw-r--r--sdk/src/java/cyanogenmod/externalviews/ExternalViewProviderService.java236
-rw-r--r--sdk/src/java/cyanogenmod/externalviews/IExternalViewProvider.aidl32
-rw-r--r--sdk/src/java/cyanogenmod/externalviews/IExternalViewProviderFactory.aidl25
-rw-r--r--sdk/src/java/cyanogenmod/externalviews/IKeyguardExternalViewCallbacks.aidl30
-rw-r--r--sdk/src/java/cyanogenmod/externalviews/IKeyguardExternalViewProvider.aidl42
-rw-r--r--sdk/src/java/cyanogenmod/externalviews/KeyguardExternalView.java493
-rw-r--r--sdk/src/java/cyanogenmod/externalviews/KeyguardExternalViewProviderService.java636
-rw-r--r--sdk/src/java/cyanogenmod/hardware/CMHardwareManager.java857
-rw-r--r--sdk/src/java/cyanogenmod/hardware/DisplayMode.aidl19
-rw-r--r--sdk/src/java/cyanogenmod/hardware/DisplayMode.java109
-rw-r--r--sdk/src/java/cyanogenmod/hardware/ICMHardwareService.aidl61
-rw-r--r--sdk/src/java/cyanogenmod/hardware/IThermalListenerCallback.aidl21
-rw-r--r--sdk/src/java/cyanogenmod/hardware/ThermalListenerCallback.java44
-rw-r--r--sdk/src/java/cyanogenmod/media/MediaRecorder.java84
-rw-r--r--sdk/src/java/cyanogenmod/os/Build.java148
-rw-r--r--sdk/src/java/cyanogenmod/os/Concierge.java153
-rw-r--r--sdk/src/java/cyanogenmod/power/IPerformanceManager.aidl31
-rw-r--r--sdk/src/java/cyanogenmod/power/PerformanceManager.java206
-rw-r--r--sdk/src/java/cyanogenmod/power/PerformanceManagerInternal.java29
-rw-r--r--sdk/src/java/cyanogenmod/profiles/AirplaneModeSettings.java224
-rw-r--r--sdk/src/java/cyanogenmod/profiles/BrightnessSettings.java222
-rw-r--r--sdk/src/java/cyanogenmod/profiles/ConnectionSettings.java475
-rw-r--r--sdk/src/java/cyanogenmod/profiles/LockSettings.java179
-rw-r--r--sdk/src/java/cyanogenmod/profiles/RingModeSettings.java214
-rw-r--r--sdk/src/java/cyanogenmod/profiles/StreamSettings.java216
-rw-r--r--sdk/src/java/cyanogenmod/providers/CMSettings.java3258
-rw-r--r--sdk/src/java/cyanogenmod/providers/DataUsageContract.java164
-rw-r--r--sdk/src/java/cyanogenmod/providers/ThemesContract.java717
-rw-r--r--sdk/src/java/cyanogenmod/providers/WeatherContract.java240
-rw-r--r--sdk/src/java/cyanogenmod/themes/IThemeChangeListener.aidl23
-rw-r--r--sdk/src/java/cyanogenmod/themes/IThemeProcessingListener.aidl22
-rw-r--r--sdk/src/java/cyanogenmod/themes/IThemeService.aidl44
-rw-r--r--sdk/src/java/cyanogenmod/themes/ThemeChangeRequest.aidl19
-rw-r--r--sdk/src/java/cyanogenmod/themes/ThemeChangeRequest.java322
-rw-r--r--sdk/src/java/cyanogenmod/themes/ThemeManager.java391
-rw-r--r--sdk/src/java/cyanogenmod/util/ColorUtils.java526
-rw-r--r--sdk/src/java/cyanogenmod/weather/CMWeatherManager.java373
-rw-r--r--sdk/src/java/cyanogenmod/weather/ICMWeatherManager.aidl31
-rw-r--r--sdk/src/java/cyanogenmod/weather/IRequestInfoListener.aidl31
-rw-r--r--sdk/src/java/cyanogenmod/weather/IWeatherServiceProviderChangeListener.aidl22
-rw-r--r--sdk/src/java/cyanogenmod/weather/RequestInfo.aidl19
-rw-r--r--sdk/src/java/cyanogenmod/weather/RequestInfo.java356
-rw-r--r--sdk/src/java/cyanogenmod/weather/WeatherInfo.aidl19
-rwxr-xr-xsdk/src/java/cyanogenmod/weather/WeatherInfo.java524
-rw-r--r--sdk/src/java/cyanogenmod/weather/WeatherLocation.aidl19
-rw-r--r--sdk/src/java/cyanogenmod/weather/WeatherLocation.java173
-rw-r--r--sdk/src/java/cyanogenmod/weather/util/WeatherUtils.java84
-rw-r--r--sdk/src/java/cyanogenmod/weatherservice/IWeatherProviderService.aidl28
-rw-r--r--sdk/src/java/cyanogenmod/weatherservice/IWeatherProviderServiceClient.aidl26
-rw-r--r--sdk/src/java/cyanogenmod/weatherservice/ServiceRequest.java118
-rw-r--r--sdk/src/java/cyanogenmod/weatherservice/ServiceRequestResult.aidl19
-rw-r--r--sdk/src/java/cyanogenmod/weatherservice/ServiceRequestResult.java204
-rw-r--r--sdk/src/java/cyanogenmod/weatherservice/WeatherProviderService.java196
-rw-r--r--sdk/src/java/org/cyanogenmod/internal/logging/CMMetricsLogger.java70
-rw-r--r--sdk/src/java/org/cyanogenmod/internal/statusbar/ExternalQuickSettingsRecord.java53
-rw-r--r--sdk/src/java/org/cyanogenmod/internal/statusbar/IStatusBarCustomTileHolder.aidl25
-rw-r--r--sdk/src/java/org/cyanogenmod/internal/themes/IIconCacheManager.aidl24
-rw-r--r--sdk/src/java/org/cyanogenmod/internal/util/CmLockPatternUtils.java106
-rw-r--r--sdk/src/java/org/cyanogenmod/internal/util/ImageUtils.java332
-rw-r--r--sdk/src/java/org/cyanogenmod/internal/util/QSConstants.java111
-rw-r--r--sdk/src/java/org/cyanogenmod/internal/util/QSUtils.java312
-rw-r--r--sdk/src/java/org/cyanogenmod/internal/util/ScreenType.java66
-rw-r--r--sdk/src/java/org/cyanogenmod/internal/util/ThemeUtils.java687
101 files changed, 22133 insertions, 0 deletions
diff --git a/sdk/res/res/values/attrs.xml b/sdk/res/res/values/attrs.xml
new file mode 100644
index 0000000..b9ee43b
--- /dev/null
+++ b/sdk/res/res/values/attrs.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2016 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.
+-->
+<resources>
+ <declare-styleable name="LiveLockScreen">
+ <attr name="settingsActivity" format="string" />
+ <attr name="type">
+ <flag name="experience" value="1"/>
+ <flag name="event" value="2"/>
+ </attr>
+ </declare-styleable>
+</resources> \ No newline at end of file
diff --git a/sdk/src/java/cyanogenmod/alarmclock/ClockContract.java b/sdk/src/java/cyanogenmod/alarmclock/ClockContract.java
new file mode 100644
index 0000000..63a1d27
--- /dev/null
+++ b/sdk/src/java/cyanogenmod/alarmclock/ClockContract.java
@@ -0,0 +1,314 @@
+/*
+ * 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;
+
+/**
+ * <p>
+ * The contract between the clock provider and desk clock. Contains
+ * definitions for the supported URIs and data columns.
+ * </p>
+ * <h3>Overview</h3>
+ * <p>
+ * ClockContract defines the data model of clock related information.
+ * This data is stored in a number of tables:
+ * </p>
+ * <ul>
+ * <li>The {@link AlarmsColumns} table holds the user created alarms</li>
+ * <li>The {@link InstancesColumns} table holds the current state of each
+ * alarm in the AlarmsColumn table.
+ * </li>
+ * <li>The {@link CitiesColumns} table holds all user selectable cities</li>
+ * </ul>
+ *
+ * <p>
+ * 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.
+ * </p>
+ */
+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
+ * <p>Type: BOOLEAN</p>
+ */
+ public static final String VIBRATE = "vibrate";
+
+ /**
+ * Alarm label.
+ *
+ * <p>Type: STRING</p>
+ */
+ 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.
+ *
+ * <p>Type: STRING</p>
+ */
+ public static final String RINGTONE = "ringtone";
+
+ /**
+ * True if alarm should start off quiet and slowly increase volume
+ * <P>Type: BOOLEAN</P>
+ */
+ public static final String INCREASING_VOLUME = "incvol";
+
+ /**
+ * Profile to change to when alarm triggers
+ * <P>Type: STRING</P>
+ */
+ 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.
+ * <p>Type: INTEGER</p>
+ */
+ public static final String HOUR = "hour";
+
+ /**
+ * Minutes in localtime 0 - 59.
+ * <p>Type: INTEGER</p>
+ */
+ public static final String MINUTES = "minutes";
+
+ /**
+ * Days of the week encoded as a bit set.
+ * <p>Type: INTEGER</p>
+ *
+ */
+ public static final String DAYS_OF_WEEK = "daysofweek";
+
+ /**
+ * True if alarm is active.
+ * <p>Type: BOOLEAN</p>
+ */
+ public static final String ENABLED = "enabled";
+
+ /**
+ * Determine if alarm is deleted after it has been used.
+ * <p>Type: INTEGER</p>
+ */
+ 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 state when alarm has been dismissed before its intended firing time.
+ */
+ public static final int PREDISMISSED_STATE = 8;
+
+ /**
+ * Alarm year.
+ *
+ * <p>Type: INTEGER</p>
+ */
+ public static final String YEAR = "year";
+
+ /**
+ * Alarm month in year.
+ *
+ * <p>Type: INTEGER</p>
+ */
+ public static final String MONTH = "month";
+
+ /**
+ * Alarm day in month.
+ *
+ * <p>Type: INTEGER</p>
+ */
+ public static final String DAY = "day";
+
+ /**
+ * Alarm hour in 24-hour localtime 0 - 23.
+ * <p>Type: INTEGER</p>
+ */
+ public static final String HOUR = "hour";
+
+ /**
+ * Alarm minutes in localtime 0 - 59
+ * <p>Type: INTEGER</p>
+ */
+ public static final String MINUTES = "minutes";
+
+ /**
+ * Foreign key to Alarms table
+ * <p>Type: INTEGER (long)</p>
+ */
+ public static final String ALARM_ID = "alarm_id";
+
+ /**
+ * Alarm state
+ * <p>Type: INTEGER</p>
+ */
+ 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.
+ * <p>Type: STRING</p>
+ */
+ public static final String CITY_ID = "city_id";
+
+ /**
+ * City name.
+ * <p>Type: STRING</p>
+ */
+ public static final String CITY_NAME = "city_name";
+
+ /**
+ * Timezone name of city.
+ * <p>Type: STRING</p>
+ */
+ public static final String TIMEZONE_NAME = "timezone_name";
+
+ /**
+ * Timezone offset.
+ * <p>Type: INTEGER</p>
+ */
+ public static final String TIMEZONE_OFFSET = "timezone_offset";
+ }
+}
diff --git a/sdk/src/java/cyanogenmod/alarmclock/CyanogenModAlarmClock.java b/sdk/src/java/cyanogenmod/alarmclock/CyanogenModAlarmClock.java
new file mode 100644
index 0000000..76508a9
--- /dev/null
+++ b/sdk/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.
+ * <p>
+ * This action sets an alarm to be enabled or disabled.
+ * </p><p>
+ * 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}.
+ * </p>
+ *
+ * <p>Requires permission {@link #MODIFY_ALARMS_PERMISSION} to launch this
+ * intent.
+ * </p>
+ *
+ * <p>Always set the package name of the Intent that will launch this action
+ * to {@link #DESKCLOCK_PACKAGE} explicitly, for security.</p>
+ *
+ * <h3>Request parameters</h3>
+ * <ul>
+ * <li>{@link #EXTRA_ALARM_ID} <em>(required)</em>: The id of the alarm to modify,
+ * as stored in {@link cyanogenmod.alarmclock.ClockContract.AlarmsColumns#_ID}</li>
+ * <li>{@link #EXTRA_ENABLED} <em>(required)</em>: Whether to set this alarm to be enabled
+ * or disabled. </li>
+ * </ul>
+ */
+ public static final String ACTION_SET_ALARM_ENABLED
+ = "cyanogenmod.alarmclock.SET_ALARM_ENABLED";
+
+ /**
+ * Bundle extra: The id of the alarm.
+ * <p>
+ * Used by {@link #ACTION_SET_ALARM_ENABLED}.
+ * </p><p>
+ * This extra is required.
+ * </p><p>
+ * The value is an {@link Long} and is the ID stored in
+ * {@link cyanogenmod.alarmclock.ClockContract.AlarmsColumns#_ID} for this alarm.
+ * </p>
+ *
+ * @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.
+ * <p>
+ * Used by {@link #ACTION_SET_ALARM_ENABLED}.
+ * </p><p>
+ * This extra is required.
+ * </p><p>
+ * 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.
+ * </p>
+ *
+ * @see #ACTION_SET_ALARM_ENABLED
+ * @see #EXTRA_ALARM_ID
+ */
+ public static final String EXTRA_ENABLED = "cyanogenmod.intent.extra.alarmclock.ENABLED";
+
+ /**
+ * <p>
+ * Retrieves an Intent that is prepopulated with the proper action and ComponentName to
+ * create a new alarm in the CyanogenMod DeskClock application.
+ * </p>
+ * <p> 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.
+ * </p>
+ * <p>Requires permission {@link android.Manifest.permission#SET_ALARM} to launch this
+ * intent.
+ * </p>
+ *
+ * @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<ResolveInfo> 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/sdk/src/java/cyanogenmod/app/BaseLiveLockManagerService.java b/sdk/src/java/cyanogenmod/app/BaseLiveLockManagerService.java
new file mode 100644
index 0000000..feae94f
--- /dev/null
+++ b/sdk/src/java/cyanogenmod/app/BaseLiveLockManagerService.java
@@ -0,0 +1,226 @@
+/*
+ * Copyright (C) 2016 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.app;
+
+import android.annotation.NonNull;
+import android.app.AppGlobals;
+import android.app.Service;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.util.Log;
+
+import cyanogenmod.platform.Manifest;
+
+/**
+ * Base Live lock screen manager service to be extended by applications that implement the
+ * {@link LiveLockScreenManager#SERVICE_INTERFACE}
+ *
+ * @hide
+ */
+abstract public class BaseLiveLockManagerService extends Service
+ implements ILiveLockScreenManagerProvider {
+ private static final String TAG = BaseLiveLockManagerService.class.getSimpleName();
+
+ private final RemoteCallbackList<ILiveLockScreenChangeListener> mChangeListeners =
+ new RemoteCallbackList<>();
+
+ @Override
+ public final IBinder onBind(Intent intent) {
+ return mService;
+ }
+
+ @Override
+ public final IBinder asBinder() {
+ return mService;
+ }
+
+ @Override
+ abstract public void enqueueLiveLockScreen(String pkg, int id, LiveLockScreenInfo lls,
+ int[] idReceived, int userId) throws RemoteException;
+
+ @Override
+ abstract public void cancelLiveLockScreen(String pkg, int id, int userId)
+ throws RemoteException;
+
+ @Override
+ abstract public LiveLockScreenInfo getCurrentLiveLockScreen() throws RemoteException;
+
+ @Override
+ abstract public void updateDefaultLiveLockScreen(LiveLockScreenInfo llsInfo)
+ throws RemoteException;
+
+ @Override
+ public boolean getLiveLockScreenEnabled() throws RemoteException {
+ return false;
+ }
+
+ @Override
+ public final boolean registerChangeListener(
+ ILiveLockScreenChangeListener listener) throws RemoteException {
+ return mChangeListeners.register(listener);
+ }
+
+ @Override
+ public final boolean unregisterChangeListener(
+ ILiveLockScreenChangeListener listener) throws RemoteException {
+ return mChangeListeners.unregister(listener);
+ }
+
+ /**
+ * This method should be called whenever there is an update to the current Live lock screen
+ * to be displayed.
+ *
+ * @param llsInfo LiveLockScreenInfo for the current Live lock screen
+ */
+ protected final void notifyChangeListeners(LiveLockScreenInfo llsInfo) {
+ int N = mChangeListeners.beginBroadcast();
+ for (int i = 0; i < N; i++) {
+ ILiveLockScreenChangeListener listener = mChangeListeners.getBroadcastItem(i);
+ try {
+ listener.onLiveLockScreenChanged(llsInfo);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Unable to notifiy change listener", e);
+ }
+ }
+ mChangeListeners.finishBroadcast();
+ }
+
+ /**
+ * Returns true if the caller has been granted the
+ * {@link cyanogenmod.platform.Manifest.permission#LIVE_LOCK_SCREEN_MANAGER_ACCESS_PRIVATE}
+ * permission.
+ *
+ * @return
+ */
+ private final boolean hasPrivatePermissions() {
+ return checkCallingPermission(Manifest.permission
+ .LIVE_LOCK_SCREEN_MANAGER_ACCESS_PRIVATE) == PackageManager.PERMISSION_GRANTED;
+ }
+
+ /**
+ * Enforces the {@link cyanogenmod.platform.Manifest.permission#LIVE_LOCK_SCREEN_MANAGER_ACCESS}
+ * permission.
+ */
+ protected final void enforceAccessPermission() {
+ if (hasPrivatePermissions()) return;
+
+ enforceCallingPermission(Manifest.permission.LIVE_LOCK_SCREEN_MANAGER_ACCESS,
+ null);
+ }
+
+ /**
+ * Enforces the
+ * {@link cyanogenmod.platform.Manifest.permission#LIVE_LOCK_SCREEN_MANAGER_ACCESS_PRIVATE}
+ * permission.
+ */
+ protected final void enforcePrivateAccessPermission() {
+ enforceCallingPermission(
+ Manifest.permission.LIVE_LOCK_SCREEN_MANAGER_ACCESS_PRIVATE, null);
+ }
+
+ /**
+ * Enforces the LLS being shown/canceled is from the calling package or from a system app that
+ * has the
+ * {@link cyanogenmod.platform.Manifest.permission#LIVE_LOCK_SCREEN_MANAGER_ACCESS_PRIVATE}
+ * permission.
+ *
+ * @param pkg Package name of caller
+ * @param llsInfo Live lock screen info with component to check
+ */
+ protected final void enforceSamePackageOrSystem(String pkg,
+ @NonNull LiveLockScreenInfo llsInfo) {
+ // only apps with the private permission can show/cancel live lock screens from other
+ // packages
+ if (hasPrivatePermissions()) return;
+
+ if (llsInfo.component != null && !llsInfo.component.getPackageName().equals(pkg)) {
+ throw new SecurityException("Modifying Live lock screen from different packages not " +
+ "allowed. Calling package: " + pkg + " LLS package: " +
+ llsInfo.component.getPackageName());
+ }
+
+ final int uid = Binder.getCallingUid();
+ try {
+ ApplicationInfo ai = AppGlobals.getPackageManager().getApplicationInfo(
+ pkg, 0, UserHandle.getCallingUserId());
+ if (ai == null) {
+ throw new SecurityException("Unknown package " + pkg);
+ }
+ if (!UserHandle.isSameApp(ai.uid, uid)) {
+ throw new SecurityException("Calling uid " + uid + " gave package"
+ + pkg + " which is owned by uid " + ai.uid);
+ }
+ } catch (RemoteException re) {
+ throw new SecurityException("Unknown package " + pkg + "\n" + re);
+ }
+ }
+
+ private final IBinder mService = new ILiveLockScreenManagerProvider.Stub() {
+ @Override
+ public void enqueueLiveLockScreen(String pkg, int id, LiveLockScreenInfo llsInfo,
+ int[] idReceived, int userId) throws RemoteException {
+ enforceAccessPermission();
+ enforceSamePackageOrSystem(pkg, llsInfo);
+ BaseLiveLockManagerService.this.enqueueLiveLockScreen(pkg, id, llsInfo, idReceived,
+ userId);
+ }
+
+ @Override
+ public void cancelLiveLockScreen(String pkg, int id, int userId) throws RemoteException {
+ enforceAccessPermission();
+ BaseLiveLockManagerService.this.cancelLiveLockScreen(pkg, id, userId);
+ }
+
+ @Override
+ public LiveLockScreenInfo getCurrentLiveLockScreen() throws RemoteException {
+ enforceAccessPermission();
+ return BaseLiveLockManagerService.this.getCurrentLiveLockScreen();
+ }
+
+ @Override
+ public void updateDefaultLiveLockScreen(LiveLockScreenInfo llsInfo) throws RemoteException {
+ enforcePrivateAccessPermission();
+ BaseLiveLockManagerService.this.updateDefaultLiveLockScreen(llsInfo);
+ }
+
+ @Override
+ public boolean getLiveLockScreenEnabled() throws RemoteException {
+ enforceAccessPermission();
+ return BaseLiveLockManagerService.this.getLiveLockScreenEnabled();
+ }
+
+ @Override
+ public boolean registerChangeListener(
+ ILiveLockScreenChangeListener listener) throws RemoteException {
+ enforcePrivateAccessPermission();
+ return BaseLiveLockManagerService.this.registerChangeListener(listener);
+ }
+
+ @Override
+ public boolean unregisterChangeListener(
+ ILiveLockScreenChangeListener listener) throws RemoteException {
+ enforcePrivateAccessPermission();
+ return BaseLiveLockManagerService.this.unregisterChangeListener(listener);
+ }
+ };
+}
diff --git a/sdk/src/java/cyanogenmod/app/CMContextConstants.java b/sdk/src/java/cyanogenmod/app/CMContextConstants.java
new file mode 100644
index 0000000..98171b8
--- /dev/null
+++ b/sdk/src/java/cyanogenmod/app/CMContextConstants.java
@@ -0,0 +1,218 @@
+/**
+ * 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.app;
+
+import android.annotation.SdkConstant;
+
+/**
+ * @hide
+ * TODO: We need to somehow make these managers accessible via getSystemService
+ */
+public final class CMContextConstants {
+
+ /**
+ * @hide
+ */
+ private CMContextConstants() {
+ // Empty constructor
+ }
+
+ /**
+ * Use with {@link android.content.Context#getSystemService} to retrieve a
+ * {@link cyanogenmod.app.CMStatusBarManager} for informing the user of
+ * background events.
+ *
+ * @see android.content.Context#getSystemService
+ * @see cyanogenmod.app.CMStatusBarManager
+ */
+ public static final String CM_STATUS_BAR_SERVICE = "cmstatusbar";
+
+ /**
+ * Use with {@link android.content.Context#getSystemService} to retrieve a
+ * {@link cyanogenmod.app.ProfileManager} for informing the user of
+ * background events.
+ *
+ * @see android.content.Context#getSystemService
+ * @see cyanogenmod.app.ProfileManager
+ *
+ * @hide
+ */
+ public static final String CM_PROFILE_SERVICE = "profile";
+
+ /**
+ * Use with {@link android.content.Context#getSystemService} to retrieve a
+ * {@link cyanogenmod.app.PartnerInterface} interact with system settings.
+ *
+ * @see android.content.Context#getSystemService
+ * @see cyanogenmod.app.PartnerInterface
+ *
+ * @hide
+ */
+ public static final String CM_PARTNER_INTERFACE = "cmpartnerinterface";
+
+ /**
+ * Use with {@link android.content.Context#getSystemService} to retrieve a
+ * {@link cyanogenmod.app.CMTelephonyManager} to manage the phone and
+ * data connection.
+ *
+ * @see android.content.Context#getSystemService
+ * @see cyanogenmod.app.CMTelephonyManager
+ *
+ * @hide
+ */
+ public static final String CM_TELEPHONY_MANAGER_SERVICE = "cmtelephonymanager";
+
+ /**
+ * Use with {@link android.content.Context#getSystemService} to retrieve a
+ * {@link cyanogenmod.hardware.CMHardwareManager} to manage the extended
+ * hardware features of the device.
+ *
+ * @see android.content.Context#getSystemService
+ * @see cyanogenmod.hardware.CMHardwareManager
+ *
+ * @hide
+ */
+ public static final String CM_HARDWARE_SERVICE = "cmhardware";
+
+ /**
+ * @hide
+ */
+ public static final String CM_APP_SUGGEST_SERVICE = "cmappsuggest";
+
+ /**
+ * Control device power profile and characteristics.
+ *
+ * @hide
+ */
+ public static final String CM_PERFORMANCE_SERVICE = "cmperformance";
+
+ /**
+ * Controls changing and applying themes
+ *
+ * @hide
+ */
+ public static final String CM_THEME_SERVICE = "cmthemes";
+
+ /**
+ * Manages composed icons
+ *
+ * @hide
+ */
+ public static final String CM_ICON_CACHE_SERVICE = "cmiconcache";
+
+ /**
+ * @hide
+ */
+ public static final String CM_LIVE_LOCK_SCREEN_SERVICE = "cmlivelockscreen";
+
+ /**
+ * Use with {@link android.content.Context#getSystemService} to retrieve a
+ * {@link cyanogenmod.weather.CMWeatherManager} to manage the weather service
+ * settings and request weather updates
+ *
+ * @see android.content.Context#getSystemService
+ * @see cyanogenmod.weather.CMWeatherManager
+ *
+ * @hide
+ */
+ public static final String CM_WEATHER_SERVICE = "cmweather";
+
+ /**
+ * Features supported by the CMSDK.
+ */
+ public static class Features {
+ /**
+ * Feature for {@link PackageManager#getSystemAvailableFeatures} and
+ * {@link PackageManager#hasSystemFeature}: The device includes the hardware abstraction
+ * framework service utilized by the cmsdk.
+ */
+ @SdkConstant(SdkConstant.SdkConstantType.FEATURE)
+ public static final String HARDWARE_ABSTRACTION = "org.cyanogenmod.hardware";
+
+ /**
+ * Feature for {@link PackageManager#getSystemAvailableFeatures} and
+ * {@link PackageManager#hasSystemFeature}: The device includes the cm status bar service
+ * utilzed by the cmsdk.
+ */
+ @SdkConstant(SdkConstant.SdkConstantType.FEATURE)
+ public static final String STATUSBAR = "org.cyanogenmod.statusbar";
+
+ /**
+ * Feature for {@link PackageManager#getSystemAvailableFeatures} and
+ * {@link PackageManager#hasSystemFeature}: The device includes the cm profiles service
+ * utilized by the cmsdk.
+ */
+ @SdkConstant(SdkConstant.SdkConstantType.FEATURE)
+ public static final String PROFILES = "org.cyanogenmod.profiles";
+
+ /**
+ * Feature for {@link PackageManager#getSystemAvailableFeatures} and
+ * {@link PackageManager#hasSystemFeature}: The device includes the cm app suggest service
+ * utilized by the cmsdk.
+ */
+ @SdkConstant(SdkConstant.SdkConstantType.FEATURE)
+ public static final String APP_SUGGEST = "org.cyanogenmod.appsuggest";
+
+ /**
+ * Feature for {@link PackageManager#getSystemAvailableFeatures} and
+ * {@link PackageManager#hasSystemFeature}: The device includes the cm telephony service
+ * utilized by the cmsdk.
+ */
+ @SdkConstant(SdkConstant.SdkConstantType.FEATURE)
+ public static final String TELEPHONY = "org.cyanogenmod.telephony";
+
+ /**
+ * Feature for {@link PackageManager#getSystemAvailableFeatures} and
+ * {@link PackageManager#hasSystemFeature}: The device includes the cm theme service
+ * utilized by the cmsdk.
+ */
+ @SdkConstant(SdkConstant.SdkConstantType.FEATURE)
+ public static final String THEMES = "org.cyanogenmod.theme";
+
+ /**
+ * Feature for {@link PackageManager#getSystemAvailableFeatures} and
+ * {@link PackageManager#hasSystemFeature}: The device includes the cm performance service
+ * utilized by the cmsdk.
+ */
+ @SdkConstant(SdkConstant.SdkConstantType.FEATURE)
+ public static final String PERFORMANCE = "org.cyanogenmod.performance";
+
+ /**
+ * Feature for {@link PackageManager#getSystemAvailableFeatures} and
+ * {@link PackageManager#hasSystemFeature}: The device includes the cm partner service
+ * utilized by the cmsdk.
+ */
+ @SdkConstant(SdkConstant.SdkConstantType.FEATURE)
+ public static final String PARTNER = "org.cyanogenmod.partner";
+
+ /*
+ * Feature for {@link PackageManager#getSystemAvailableFeatures} and
+ * {@link PackageManager#hasSystemFeature}: The device includes the Live lock screen
+ * feature.
+ */
+ @SdkConstant(SdkConstant.SdkConstantType.FEATURE)
+ public static final String LIVE_LOCK_SCREEN = "org.cyanogenmod.livelockscreen";
+
+ /**
+ * Feature for {@link PackageManager#getSystemAvailableFeatures} and
+ * {@link PackageManager#hasSystemFeature}: The device includes the cm weather weather
+ * service utilized by the cmsdk.
+ */
+ @SdkConstant(SdkConstant.SdkConstantType.FEATURE)
+ public static final String WEATHER_SERVICES = "org.cyanogenmod.weather";
+ }
+}
diff --git a/sdk/src/java/cyanogenmod/app/CMStatusBarManager.java b/sdk/src/java/cyanogenmod/app/CMStatusBarManager.java
new file mode 100644
index 0000000..d696a82
--- /dev/null
+++ b/sdk/src/java/cyanogenmod/app/CMStatusBarManager.java
@@ -0,0 +1,247 @@
+/**
+ * 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.app;
+
+import android.content.Context;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.UserHandle;
+import android.util.Log;
+import android.util.Slog;
+
+import cyanogenmod.app.ICMStatusBarManager;
+
+/**
+ * The CMStatusBarManager allows you to publish and remove CustomTiles within the
+ * Quick Settings Panel.
+ *
+ * <p>
+ * Each of the publish methods takes an int id parameter and optionally a
+ * {@link String} tag parameter, which may be {@code null}. These parameters
+ * are used to form a pair (tag, id), or ({@code null}, id) if tag is
+ * unspecified. This pair identifies this custom tile from your app to the
+ * system, so that pair should be unique within your app. If you call one
+ * of the publish methods with a (tag, id) pair that is currently active and
+ * a new set of custom tile parameters, it will be updated. For example,
+ * if you pass a new custom tile icon, the old icon in the panel will
+ * be replaced with the new one. This is also the same tag and id you pass
+ * to the {@link #removeTile(int)} or {@link #removeTile(String, int)} method to clear
+ * this custom tile.
+ *
+ * <p>
+ * To get the instance of this class, utilize CMStatusBarManager#getInstance(Context context)
+ *
+ * @see cyanogenmod.app.CustomTile
+ */
+public class CMStatusBarManager {
+ private static final String TAG = "CMStatusBarManager";
+ private static boolean localLOGV = false;
+
+ private Context mContext;
+
+ private static ICMStatusBarManager sService;
+
+ private static CMStatusBarManager sCMStatusBarManagerInstance;
+ private CMStatusBarManager(Context context) {
+ Context appContext = context.getApplicationContext();
+ if (appContext != null) {
+ mContext = appContext;
+ } else {
+ mContext = context;
+ }
+ sService = getService();
+
+ if (context.getPackageManager().hasSystemFeature(
+ cyanogenmod.app.CMContextConstants.Features.STATUSBAR) && sService == null) {
+ throw new RuntimeException("Unable to get CMStatusBarService. The service either" +
+ " crashed, was not started, or the interface has been called to early in" +
+ " SystemServer init");
+ }
+ }
+
+ /**
+ * Get or create an instance of the {@link cyanogenmod.app.CMStatusBarManager}
+ * @param context
+ * @return {@link cyanogenmod.app.CMStatusBarManager}
+ */
+ public static CMStatusBarManager getInstance(Context context) {
+ if (sCMStatusBarManagerInstance == null) {
+ sCMStatusBarManagerInstance = new CMStatusBarManager(context);
+ }
+ return sCMStatusBarManagerInstance;
+ }
+
+ /**
+ * Post a custom tile to be shown in the status bar panel. If a custom tile with
+ * the same id has already been posted by your application and has not yet been removed, it
+ * will be replaced by the updated information.
+ *
+ * You will need the cyanogenmod.permission.PUBLISH_CUSTOM_TILE
+ * to utilize this functionality.
+ *
+ * @param id An identifier for this customTile unique within your
+ * application.
+ * @param customTile A {@link CustomTile} object describing what to show the user.
+ * Must not be null.
+ */
+ public void publishTile(int id, CustomTile customTile) {
+ publishTile(null, id, customTile);
+ }
+
+ /**
+ * Post a custom tile to be shown in the status bar panel. If a custom tile with
+ * the same tag and id has already been posted by your application and has not yet been
+ * removed, it will be replaced by the updated information.
+ *
+ * You will need the cyanogenmod.permission.PUBLISH_CUSTOM_TILE
+ * to utilize this functionality.
+ *
+ * @param tag A string identifier for this custom tile. May be {@code null}.
+ * @param id An identifier for this custom tile. The pair (tag, id) must be unique
+ * within your application.
+ * @param customTile A {@link cyanogenmod.app.CustomTile} object describing what to
+ * show the user. Must not be null.
+ */
+ public void publishTile(String tag, int id, CustomTile customTile) {
+ if (sService == null) {
+ Log.w(TAG, "not connected to CMStatusBarManagerService");
+ return;
+ }
+
+ int[] idOut = new int[1];
+ String pkg = mContext.getPackageName();
+ if (localLOGV) Log.v(TAG, pkg + ": create(" + id + ", " + customTile + ")");
+ try {
+ sService.createCustomTileWithTag(pkg, mContext.getOpPackageName(), tag, id,
+ customTile, idOut, UserHandle.myUserId());
+ if (id != idOut[0]) {
+ Log.w(TAG, "notify: id corrupted: sent " + id + ", got back " + idOut[0]);
+ }
+ } catch (RemoteException e) {
+ Slog.w("CMStatusBarManager", "warning: no cm status bar service");
+ }
+ }
+
+ /**
+ * Similar to {@link cyanogenmod.app.CMStatusBarManager#publishTile(int id, cyanogenmod.app.CustomTile)},
+ * however lets you specify a {@link android.os.UserHandle}
+ *
+ * You will need the cyanogenmod.permission.PUBLISH_CUSTOM_TILE
+ * to utilize this functionality.
+ *
+ * @param tag A string identifier for this custom tile. May be {@code null}.
+ * @param id An identifier for this custom tile. The pair (tag, id) must be unique
+ * within your application.
+ * @param customTile A {@link cyanogenmod.app.CustomTile} object describing what to
+ * show the user. Must not be null.
+ * @param user A user handle to publish the tile as.
+ */
+ public void publishTileAsUser(String tag, int id, CustomTile customTile, UserHandle user) {
+ if (sService == null) {
+ Log.w(TAG, "not connected to CMStatusBarManagerService");
+ return;
+ }
+
+ int[] idOut = new int[1];
+ String pkg = mContext.getPackageName();
+ if (localLOGV) Log.v(TAG, pkg + ": create(" + id + ", " + customTile + ")");
+ try {
+ sService.createCustomTileWithTag(pkg, mContext.getOpPackageName(), tag, id,
+ customTile, idOut, user.getIdentifier());
+ if (id != idOut[0]) {
+ Log.w(TAG, "notify: id corrupted: sent " + id + ", got back " + idOut[0]);
+ }
+ } catch (RemoteException e) {
+ Slog.w("CMStatusBarManager", "warning: no cm status bar service");
+ }
+ }
+
+ /**
+ * Remove a custom tile that's currently published to the StatusBarPanel.
+ *
+ * You will need the cyanogenmod.permission.PUBLISH_CUSTOM_TILE
+ * to utilize this functionality.
+ *
+ * @param id The identifier for the custom tile to be removed.
+ */
+ public void removeTile(int id) {
+ removeTile(null, id);
+ }
+
+ /**
+ * Remove a custom tile that's currently published to the StatusBarPanel.
+ *
+ * You will need the cyanogenmod.platform.PUBLISH_CUSTOM_TILE
+ * to utilize this functionality.
+ *
+ * @param tag The string identifier for the custom tile to be removed.
+ * @param id The identifier for the custom tile to be removed.
+ */
+ public void removeTile(String tag, int id) {
+ if (sService == null) {
+ Log.w(TAG, "not connected to CMStatusBarManagerService");
+ return;
+ }
+
+ String pkg = mContext.getPackageName();
+ if (localLOGV) Log.v(TAG, pkg + ": remove(" + id + ")");
+ try {
+ sService.removeCustomTileWithTag(pkg, tag, id, UserHandle.myUserId());
+ } catch (RemoteException e) {
+ Slog.w("CMStatusBarManager", "warning: no cm status bar service");
+ }
+ }
+
+ /**
+ * Similar to {@link cyanogenmod.app.CMStatusBarManager#removeTile(String tag, int id)}
+ * however lets you specific a {@link android.os.UserHandle}
+ *
+ * You will need the cyanogenmod.platform.PUBLISH_CUSTOM_TILE
+ * to utilize this functionality.
+ *
+ * @param tag The string identifier for the custom tile to be removed.
+ * @param id The identifier for the custom tile to be removed.
+ * @param user The user handle to remove the tile from.
+ */
+ public void removeTileAsUser(String tag, int id, UserHandle user) {
+ if (sService == null) {
+ Log.w(TAG, "not connected to CMStatusBarManagerService");
+ return;
+ }
+
+ String pkg = mContext.getPackageName();
+ if (localLOGV) Log.v(TAG, pkg + ": remove(" + id + ")");
+ try {
+ sService.removeCustomTileWithTag(pkg, tag, id, user.getIdentifier());
+ } catch (RemoteException e) {
+ }
+ }
+
+ /** @hide */
+ public ICMStatusBarManager getService() {
+ if (sService != null) {
+ return sService;
+ }
+ IBinder b = ServiceManager.getService(CMContextConstants.CM_STATUS_BAR_SERVICE);
+ if (b != null) {
+ sService = ICMStatusBarManager.Stub.asInterface(b);
+ return sService;
+ }
+ return null;
+ }
+}
diff --git a/sdk/src/java/cyanogenmod/app/CMTelephonyManager.java b/sdk/src/java/cyanogenmod/app/CMTelephonyManager.java
new file mode 100644
index 0000000..4285f44
--- /dev/null
+++ b/sdk/src/java/cyanogenmod/app/CMTelephonyManager.java
@@ -0,0 +1,358 @@
+/**
+ * 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.app;
+
+import android.content.Context;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.telephony.SubscriptionInfo;
+import android.util.Log;
+import android.util.Slog;
+
+import java.util.List;
+
+import cyanogenmod.app.CMContextConstants;
+
+/**
+ * The CMTelephonyManager allows you to view and manage the phone state and
+ * the data connection, with multiple SIMs support.
+ *
+ * <p>
+ * To get the instance of this class, utilize CMTelephonyManager#getInstance(Context context)
+ */
+public class CMTelephonyManager {
+
+ /**
+ * Subscription ID used to set the default Phone and SMS to "ask every time".
+ */
+ public static final int ASK_FOR_SUBSCRIPTION_ID = 0;
+
+ private static final String TAG = "CMTelephonyManager";
+ private static boolean localLOGD = Log.isLoggable(TAG, Log.DEBUG);
+
+ private static ICMTelephonyManager sService;
+ private static CMTelephonyManager sCMTelephonyManagerInstance;
+ private Context mContext;
+
+ private CMTelephonyManager(Context context) {
+ Context appContext = context.getApplicationContext();
+ if (appContext != null) {
+ mContext = appContext;
+ } else {
+ mContext = context;
+ }
+ sService = getService();
+
+ if (context.getPackageManager().hasSystemFeature(CMContextConstants.Features.TELEPHONY)
+ && sService == null) {
+ throw new RuntimeException("Unable to get CMTelephonyManagerService. " +
+ "The service either crashed, was not started, or the interface has been " +
+ "called to early in SystemServer init");
+ }
+ }
+
+ /**
+ * Get or create an instance of the {@link cyanogenmod.app.CMTelephonyManager}
+ *
+ * @return {@link cyanogenmod.app.CMTelephonyManager}
+ */
+ public static CMTelephonyManager getInstance(Context context) {
+ if (sCMTelephonyManagerInstance == null) {
+ sCMTelephonyManagerInstance = new CMTelephonyManager(context);
+ }
+ return sCMTelephonyManagerInstance;
+ }
+
+ /** @hide */
+ public ICMTelephonyManager getService() {
+ if (sService != null) {
+ return sService;
+ }
+ IBinder b = ServiceManager.getService(CMContextConstants.CM_TELEPHONY_MANAGER_SERVICE);
+ if (b != null) {
+ sService = ICMTelephonyManager.Stub.asInterface(b);
+ return sService;
+ }
+ return null;
+ }
+
+ /**
+ * Gets the list of {@link SubscriptionInfo} that are registered on the
+ * phone.
+ *
+ * @return The list of SIM subscriptions. The returning list can be null or empty.
+ * @see SubscriptionInfo
+ */
+ public List<SubscriptionInfo> getSubInformation() {
+ if (sService == null) {
+ Log.w(TAG, "not connected to CMTelephonyManager");
+ return null;
+ }
+
+ if (localLOGD) {
+ String pkg = mContext.getPackageName();
+ Log.v(TAG, pkg + " getting the SIMs information");
+ }
+ List<SubscriptionInfo> subInfoList = null;
+ try {
+ subInfoList = sService.getSubInformation();
+ if (subInfoList == null) {
+ Log.w(TAG, "no subscription list was returned from the service");
+ } else if (subInfoList.isEmpty()) {
+ Log.w(TAG, "the subscription list is empty");
+ }
+ } catch (RemoteException e) {
+ Slog.w(TAG, "warning: no cm telephony manager service");
+ }
+
+ return subInfoList;
+ }
+
+ /**
+ * Returns the state of the SIM by subscription ID.
+ *
+ * If the subscription ID is not valid the method will return {@code false}.
+ *
+ * @param subId The subscription ID to query.
+ * @return {@code true} if the SIM is activated (even without signal or requesting the
+ * PIN/PUK), {@code false} otherwise.
+ */
+ public boolean isSubActive(int subId) {
+ if (sService == null) {
+ Log.w(TAG, "not connected to CMTelephonyManager");
+ return false;
+ }
+
+ if (localLOGD) {
+ String pkg = mContext.getPackageName();
+ Log.v(TAG, pkg + " getting the state of the SIM with subscription: " + subId);
+ }
+ boolean simActive = false;
+ try {
+ simActive = sService.isSubActive(subId);
+ if (localLOGD) {
+ String pkg = mContext.getPackageName();
+ Log.v(TAG, pkg + " getting the SIM state with subscription " + subId + " as active: " + simActive);
+ }
+ } catch (RemoteException e) {
+ Slog.w(TAG, "warning: no cm telephony manager service");
+ }
+
+ return simActive;
+ }
+
+ /**
+ * Sets the state of one of the SIMs by subscription ID.
+ *
+ * If the subscription ID is not valid or the SIM already
+ * is in the desired state the method will do nothing.
+ *
+ * @param subId The subscription ID to change the state.
+ * @param state {@code true} to activate the SIM, {@code false} to disable.
+ */
+ public void setSubState(int subId, boolean state) {
+ if (sService == null) {
+ Log.w(TAG, "not connected to CMTelephonyManager");
+ return;
+ }
+
+ if (localLOGD) {
+ String pkg = mContext.getPackageName();
+ Log.v(TAG, pkg + " setting the state of the SIM with subscription " + subId + " as active: " + state);
+ }
+
+ try {
+ sService.setSubState(subId, state);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "warning: no cm telephony manager service");
+ }
+ }
+
+ /**
+ * Checks if the received subscription received has the data
+ * connection enabled.
+ *
+ * This method will return {@code true} (or {@code false} if inactive on the SIM)
+ * even when an internet connection is active through Wifi/BT.
+ *
+ * If the subscription ID is not valid the method will return false.
+ *
+ * @param subId The subscription ID to query.
+ * @return {@code true} if the data connection is enabled on the SIM, {@code false} otherwise.
+ */
+ public boolean isDataConnectionSelectedOnSub(int subId) {
+ if (sService == null) {
+ Log.w(TAG, "not connected to CMTelephonyManager");
+ return false;
+ }
+
+ if (localLOGD) {
+ String pkg = mContext.getPackageName();
+ Log.v(TAG, pkg + " getting if the data connection is enabled for SIM for subscription: " + subId);
+ }
+ boolean dataConnectionActiveOnSim = false;
+ try {
+ dataConnectionActiveOnSim = sService.isDataConnectionSelectedOnSub(subId);
+ if (localLOGD) {
+ String pkg = mContext.getPackageName();
+ Log.v(TAG, pkg + " getting if the data connection is enabled for SIM with subscription " +
+ subId + " as active: " + dataConnectionActiveOnSim);
+ }
+ } catch (RemoteException e) {
+ Slog.w(TAG, "warning: no cm telephony manager service");
+ }
+
+ return dataConnectionActiveOnSim;
+ }
+
+ /**
+ * Checks if the network data connection is enabled.
+ *
+ * This method will return {@code true} (or {@code false} if inactive)
+ * even when an internet connection is active through Wifi/BT.
+ *
+ * @return {@code true} if the network data connection is enabled, {@code false} otherwise.
+ */
+ public boolean isDataConnectionEnabled() {
+ if (sService == null) {
+ Log.w(TAG, "not connected to CMTelephonyManager");
+ return false;
+ }
+
+ if (localLOGD) {
+ String pkg = mContext.getPackageName();
+ Log.v(TAG, pkg + " getting if the network data connection is enabled");
+ }
+ boolean dataConnectionEnabled = false;
+ try {
+ dataConnectionEnabled = sService.isDataConnectionEnabled();
+ if (localLOGD) {
+ String pkg = mContext.getPackageName();
+ Log.v(TAG, pkg + " getting if the network data connection is enabled: " + dataConnectionEnabled);
+ }
+ } catch (RemoteException e) {
+ Slog.w(TAG, "warning: no cm telephony manager service");
+ }
+
+ return dataConnectionEnabled;
+ }
+
+ /**
+ * Sets the network data conection active or inactive.
+ *
+ * @param state If {@code true} enables the network data connection, if {@code false} disables it.
+ */
+ public void setDataConnectionState(boolean state) {
+ if (sService == null) {
+ Log.w(TAG, "not connected to CMTelephonyManager");
+ return;
+ }
+
+ if (localLOGD) {
+ String pkg = mContext.getPackageName();
+ Log.v(TAG, pkg + " setting the network data connection enabled: " + state);
+ }
+
+ try {
+ sService.setDataConnectionState(state);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "warning: no cm telephony manager service");
+ }
+ }
+
+ /**
+ * Sets the data connection state on one of the SIMs by subscription ID.
+ *
+ * If the subscription ID is not valid or the data connection is already
+ * enabled on the SIM the method will do nothing.
+ *
+ * @param subId The subscription ID to set the network data connection.
+ * @hide
+ */
+ public void setDataConnectionSelectedOnSub(int subId) {
+ if (sService == null) {
+ Log.w(TAG, "not connected to CMTelephonyManager");
+ return;
+ }
+
+ if (localLOGD) {
+ String pkg = mContext.getPackageName();
+ Log.v(TAG, pkg + " setting the network data connection for SIM with subscription: " + subId);
+ }
+
+ try {
+ sService.setDataConnectionSelectedOnSub(subId);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "warning: no cm telephony manager service");
+ }
+ }
+
+ /**
+ * Sets the default phone used to make phone calls as the one received on subId.
+ *
+ * If ASK_FOR_SUBSCRIPTION_ID is used as a parameter, then the option to choose
+ * what SIM to use is selected.
+ *
+ * @param subId The subscription to set as default for phone calls.
+ * To select SIM when calling use ASK_FOR_SUBSCRIPTION_ID.
+ */
+ public void setDefaultPhoneSub(int subId) {
+ if (sService == null) {
+ Log.w(TAG, "not connected to CMTelephonyManager");
+ return;
+ }
+
+ if (localLOGD) {
+ String pkg = mContext.getPackageName();
+ Log.v(TAG, pkg + " setting the subscription used for phone calls as: " + subId);
+ }
+
+ try {
+ sService.setDefaultPhoneSub(subId);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "warning: no cm telephony manager service");
+ }
+ }
+
+ /**
+ * Sets the default phone used to send SMS as the one received on subId.
+ *
+ * If ASK_FOR_SUBSCRIPTION_ID is used as a parameter, then the option to choose
+ * what SIM to use is selected.
+ *
+ * @param subId The subscription to set as default for sending SMS.
+ * To select SIM when sending SMS use ASK_FOR_SUBSCRIPTION_ID.
+ */
+ public void setDefaultSmsSub(int subId) {
+ if (sService == null) {
+ Log.w(TAG, "not connected to CMTelephonyManager");
+ return;
+ }
+
+ if (localLOGD) {
+ String pkg = mContext.getPackageName();
+ Log.v(TAG, pkg + " setting the subscription used for SMS as: " + subId);
+ }
+
+ try {
+ sService.setDefaultSmsSub(subId);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "warning: no cm telephony manager service");
+ }
+ }
+}
diff --git a/sdk/src/java/cyanogenmod/app/CustomTile.aidl b/sdk/src/java/cyanogenmod/app/CustomTile.aidl
new file mode 100644
index 0000000..1a35ad0
--- /dev/null
+++ b/sdk/src/java/cyanogenmod/app/CustomTile.aidl
@@ -0,0 +1,19 @@
+/**
+ * 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.app;
+
+parcelable CustomTile;
diff --git a/sdk/src/java/cyanogenmod/app/CustomTile.java b/sdk/src/java/cyanogenmod/app/CustomTile.java
new file mode 100644
index 0000000..dee8c37
--- /dev/null
+++ b/sdk/src/java/cyanogenmod/app/CustomTile.java
@@ -0,0 +1,1106 @@
+/**
+ * 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.app;
+
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+import android.util.Log;
+import android.widget.RemoteViews;
+import cyanogenmod.os.Build;
+
+import cyanogenmod.os.Concierge;
+import cyanogenmod.os.Concierge.ParcelInfo;
+
+import java.util.ArrayList;
+
+/**
+ * A class that represents a quick settings tile
+ *
+ * <p>The {@link cyanogenmod.app.CustomTile.Builder} has been added to make it
+ * easier to construct CustomTiles.</p>
+ */
+public class CustomTile implements Parcelable {
+
+ /** Max count allowed by PseudoGridView within SystemUi **/
+ public static final int PSEUDO_GRID_ITEM_MAX_COUNT = 9;
+
+ private String resourcesPackageName = "";
+
+ /**
+ * An optional intent to execute when the custom tile entry is clicked. If
+ * this is an activity, it must include the
+ * {@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK} flag, which requires
+ * that you take care of task management.
+ *
+ * This takes priority over the onClickUri.
+ **/
+ public PendingIntent onClick;
+
+ /**
+ * An optional intent to execute when the custom tile entry is long clicked. If
+ * this is an activity, it must include the
+ * {@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK} flag, which requires
+ * that you take care of task management. Activities will also automatically trigger
+ * the host panel to automatically collapse after executing the pending intent.
+ **/
+ public PendingIntent onLongClick;
+
+ /**
+ * An optional settings intent to execute when the custom tile's detail is shown
+ * If this is an activity, it must include the
+ * {@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK} flag, which requires
+ * that you take care of task management
+ */
+ public Intent onSettingsClick;
+
+ /**
+ * The intent to execute when the custom tile is explicitly removed by the user.
+ *
+ * This probably shouldn't be launching an activity since several of those will be sent
+ * at the same time.
+ */
+ public PendingIntent deleteIntent;
+
+ /**
+ * An optional Uri to be parsed and broadcast on tile click, if an onClick pending intent
+ * is specified, it will take priority over the uri to be broadcasted.
+ **/
+ public Uri onClickUri;
+
+ /**
+ * A label specific to the quick settings tile to be created
+ */
+ public String label;
+
+ /**
+ * A content description for the custom tile state
+ */
+ public String contentDescription;
+
+ /**
+ * An icon to represent the custom tile
+ */
+ public int icon;
+
+ /**
+ * A remote icon to represent the custom tile
+ */
+ public Bitmap remoteIcon;
+
+ /**
+ * An expanded style for when the CustomTile is clicked, can either be
+ * a {@link GridExpandedStyle} or a {@link ListExpandedStyle}
+ */
+ public ExpandedStyle expandedStyle;
+
+ /**
+ * Boolean that forces the status bar panel to collapse when a user clicks on the
+ * {@link CustomTile}
+ * By default {@link #collapsePanel} is true
+ */
+ public boolean collapsePanel = true;
+
+ /**
+ * Indicates whether this tile has sensitive data that have to be hidden on
+ * secure lockscreens.
+ * By default {@link #sensitiveData} is false
+ */
+ public boolean sensitiveData = false;
+
+ /**
+ * Unflatten the CustomTile from a parcel.
+ */
+ public CustomTile(Parcel parcel) {
+ // Read parcelable version via the Concierge
+ ParcelInfo parcelInfo = Concierge.receiveParcel(parcel);
+ int parcelableVersion = parcelInfo.getParcelVersion();
+
+ // Pattern here is that all new members should be added to the end of
+ // the writeToParcel method. Then we step through each version, until the latest
+ // API release to help unravel this parcel
+ if (parcelableVersion >= Build.CM_VERSION_CODES.APRICOT) {
+ if (parcel.readInt() != 0) {
+ this.onClick = PendingIntent.CREATOR.createFromParcel(parcel);
+ }
+ if (parcel.readInt() != 0) {
+ this.onSettingsClick = Intent.CREATOR.createFromParcel(parcel);
+ }
+ if (parcel.readInt() != 0) {
+ this.onClickUri = Uri.CREATOR.createFromParcel(parcel);
+ }
+ if (parcel.readInt() != 0) {
+ this.label = parcel.readString();
+ }
+ if (parcel.readInt() != 0) {
+ this.contentDescription = parcel.readString();
+ }
+ if (parcel.readInt() != 0) {
+ this.expandedStyle = ExpandedStyle.CREATOR.createFromParcel(parcel);
+ }
+ this.icon = parcel.readInt();
+ }
+
+ if (parcelableVersion >= Build.CM_VERSION_CODES.BOYSENBERRY) {
+ this.resourcesPackageName = parcel.readString();
+ this.collapsePanel = (parcel.readInt() == 1);
+ if (parcel.readInt() != 0) {
+ this.remoteIcon = Bitmap.CREATOR.createFromParcel(parcel);
+ }
+ if (parcel.readInt() != 0) {
+ this.deleteIntent = PendingIntent.CREATOR.createFromParcel(parcel);
+ }
+ this.sensitiveData = (parcel.readInt() == 1);
+ }
+
+ if (parcelableVersion >= Build.CM_VERSION_CODES.DRAGON_FRUIT) {
+ if (parcel.readInt() != 0) {
+ this.onLongClick = PendingIntent.CREATOR.createFromParcel(parcel);
+ }
+ }
+
+ // Complete parcel info for the concierge
+ parcelInfo.complete();
+ }
+
+ /**
+ * Constructs a CustomTile object with default values.
+ * You might want to consider using {@link cyanogenmod.app.CustomTile.Builder} instead.
+ */
+ public CustomTile()
+ {
+ // Empty constructor
+ }
+
+ /** @hide **/
+ public String getResourcesPackageName() {
+ return resourcesPackageName;
+ }
+
+ @Override
+ public CustomTile clone() {
+ CustomTile that = new CustomTile();
+ cloneInto(that);
+ return that;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder b = new StringBuilder();
+ String NEW_LINE = System.getProperty("line.separator");
+ if (onClickUri != null) {
+ b.append("onClickUri=" + onClickUri.toString() + NEW_LINE);
+ }
+ if (onClick != null) {
+ b.append("onClick=" + onClick.toString() + NEW_LINE);
+ }
+ if (onLongClick != null) {
+ b.append("onLongClick=" + onLongClick.toString() + NEW_LINE);
+ }
+ if (onSettingsClick != null) {
+ b.append("onSettingsClick=" + onSettingsClick.toString() + NEW_LINE);
+ }
+ if (!TextUtils.isEmpty(label)) {
+ b.append("label=" + label + NEW_LINE);
+ }
+ if (!TextUtils.isEmpty(contentDescription)) {
+ b.append("contentDescription=" + contentDescription + NEW_LINE);
+ }
+ if (expandedStyle != null) {
+ b.append("expandedStyle=" + expandedStyle + NEW_LINE);
+ }
+
+ b.append("icon=" + icon + NEW_LINE);
+ b.append("resourcesPackageName=" + resourcesPackageName + NEW_LINE);
+ b.append("collapsePanel=" + collapsePanel + NEW_LINE);
+ if (remoteIcon != null) {
+ b.append("remoteIcon=" + remoteIcon.getGenerationId() + NEW_LINE);
+ }
+ if (deleteIntent != null) {
+ b.append("deleteIntent=" + deleteIntent.toString() + NEW_LINE);
+ }
+ b.append("sensitiveData=" + sensitiveData + NEW_LINE);
+ return b.toString();
+ }
+
+ /**
+ * Copy all of this into that
+ * @hide
+ */
+ public void cloneInto(CustomTile that) {
+ that.resourcesPackageName = this.resourcesPackageName;
+ that.onClick = this.onClick;
+ that.onLongClick = this.onLongClick;
+ that.onSettingsClick = this.onSettingsClick;
+ that.onClickUri = this.onClickUri;
+ that.label = this.label;
+ that.contentDescription = this.contentDescription;
+ that.expandedStyle = this.expandedStyle;
+ that.icon = this.icon;
+ that.collapsePanel = this.collapsePanel;
+ that.remoteIcon = this.remoteIcon;
+ that.deleteIntent = this.deleteIntent;
+ that.sensitiveData = this.sensitiveData;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ // Tell the concierge to prepare the parcel
+ ParcelInfo parcelInfo = Concierge.prepareParcel(out);
+
+ // ==== APRICOT =====
+ if (onClick != null) {
+ out.writeInt(1);
+ onClick.writeToParcel(out, 0);
+ } else {
+ out.writeInt(0);
+ }
+ if (onSettingsClick != null) {
+ out.writeInt(1);
+ onSettingsClick.writeToParcel(out, 0);
+ } else {
+ out.writeInt(0);
+ }
+ if (onClickUri != null) {
+ out.writeInt(1);
+ onClickUri.writeToParcel(out, 0);
+ } else {
+ out.writeInt(0);
+ }
+ if (label != null) {
+ out.writeInt(1);
+ out.writeString(label);
+ } else {
+ out.writeInt(0);
+ }
+ if (contentDescription != null) {
+ out.writeInt(1);
+ out.writeString(contentDescription);
+ } else {
+ out.writeInt(0);
+ }
+ if (expandedStyle != null) {
+ out.writeInt(1);
+ expandedStyle.writeToParcel(out, 0);
+ } else {
+ out.writeInt(0);
+ }
+ out.writeInt(icon);
+
+ // ==== BOYSENBERRY =====
+ out.writeString(resourcesPackageName);
+ out.writeInt(collapsePanel ? 1 : 0);
+ if (remoteIcon != null) {
+ out.writeInt(1);
+ remoteIcon.writeToParcel(out, 0);
+ } else {
+ out.writeInt(0);
+ }
+ if (deleteIntent != null) {
+ out.writeInt(1);
+ deleteIntent.writeToParcel(out, 0);
+ } else {
+ out.writeInt(0);
+ }
+ out.writeInt(sensitiveData ? 1 : 0);
+
+ // ==== DRAGONFRUIT ====
+ if (onLongClick != null) {
+ out.writeInt(1);
+ onLongClick.writeToParcel(out, 0);
+ } else {
+ out.writeInt(0);
+ }
+
+ // Complete the parcel info for the concierge
+ parcelInfo.complete();
+ }
+
+ /**
+ * An object that can apply an expanded view style to a {@link CustomTile.Builder}
+ * object.
+ */
+ public static class ExpandedStyle implements Parcelable {
+ /**
+ * @hide
+ */
+ public static final int NO_STYLE = -1;
+
+ /**
+ * Identifier for a grid style expanded view
+ */
+ public static final int GRID_STYLE = 0;
+
+ /**
+ * Identifier for a list style expanded view
+ */
+ public static final int LIST_STYLE = 1;
+
+ /**
+ * Identifier for a remote view style expanded view
+ */
+ public static final int REMOTE_STYLE = 2;
+
+ private ExpandedStyle() {
+ styleId = NO_STYLE;
+ }
+
+ private RemoteViews contentViews;
+ private ExpandedItem[] expandedItems;
+ private int styleId;
+
+ private ExpandedStyle(Parcel parcel) {
+ // Read parcelable version via the Concierge
+ ParcelInfo parcelInfo = Concierge.receiveParcel(parcel);
+ int parcelableVersion = parcelInfo.getParcelVersion();
+
+ // Pattern here is that all new members should be added to the end of
+ // the writeToParcel method. Then we step through each version, until the latest
+ // API release to help unravel this parcel
+ if (parcelableVersion >= Build.CM_VERSION_CODES.APRICOT) {
+ if (parcel.readInt() != 0) {
+ expandedItems = parcel.createTypedArray(ExpandedItem.CREATOR);
+ }
+ styleId = parcel.readInt();
+ }
+
+ if (parcelableVersion >= Build.CM_VERSION_CODES.BOYSENBERRY) {
+ if (parcel.readInt() != 0) {
+ contentViews = RemoteViews.CREATOR.createFromParcel(parcel);
+ }
+ }
+
+ // Complete parcel info for the concierge
+ parcelInfo.complete();
+ }
+
+ /**
+ * @hide
+ */
+ public void setBuilder(Builder builder) {
+ if (builder != null) {
+ builder.setExpandedStyle(this);
+ }
+ }
+
+ /**
+ * @hide
+ */
+ protected void internalSetExpandedItems(ArrayList<? extends ExpandedItem> items) {
+ if (styleId == GRID_STYLE && items.size() > PSEUDO_GRID_ITEM_MAX_COUNT) {
+ Log.w(CustomTile.class.getName(),
+ "Attempted to publish greater than max grid item count");
+ }
+ expandedItems = new ExpandedItem[items.size()];
+ items.toArray(expandedItems);
+ }
+
+ /**
+ * @hide
+ */
+ protected void internalSetRemoteViews(RemoteViews remoteViews) {
+ contentViews = remoteViews;
+ }
+
+ /**
+ * @hide
+ */
+ protected void internalStyleId(int id) {
+ styleId = id;
+ }
+
+ /**
+ * Retrieve the {@link ExpandedItem}s that have been set on this expanded style
+ * @return array of {@link ExpandedItem}
+ */
+ public ExpandedItem[] getExpandedItems() {
+ return expandedItems;
+ }
+
+ /**
+ * Retrieve the RemoteViews that have been set on this expanded style
+ * @return RemoteViews
+ */
+ public RemoteViews getContentViews() {
+ return contentViews;
+ }
+
+ /**
+ * Retrieve the style id associated with the {@link ExpandedStyle}
+ * @return id for style
+ */
+ public int getStyle() {
+ return styleId;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel parcel, int i) {
+ // Tell the concierge to prepare the parcel
+ ParcelInfo parcelInfo = Concierge.prepareParcel(parcel);
+
+ // ==== APRICOT ====
+ if (expandedItems != null) {
+ parcel.writeInt(1);
+ parcel.writeTypedArray(expandedItems, 0);
+ } else {
+ parcel.writeInt(0);
+ }
+ parcel.writeInt(styleId);
+
+ // ==== BOYSENBERRY ====
+ if (contentViews != null) {
+ parcel.writeInt(1);
+ contentViews.writeToParcel(parcel, 0);
+ } else {
+ parcel.writeInt(0);
+ }
+
+ // Complete the parcel info for the concierge
+ parcelInfo.complete();
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder b = new StringBuilder();
+ String NEW_LINE = System.getProperty("line.separator");
+ if (expandedItems != null) {
+ b.append("expandedItems= "+ NEW_LINE);
+ for (ExpandedItem item : expandedItems) {
+ b.append(" item=" + item.toString() + NEW_LINE);
+ }
+ }
+ b.append("styleId=" + styleId + NEW_LINE);
+ return b.toString();
+ }
+
+ /**
+ * Parcelable.Creator that instantiates ExpandedStyle objects
+ */
+ public static final Creator<ExpandedStyle> CREATOR =
+ new Creator<ExpandedStyle>() {
+ public ExpandedStyle createFromParcel(Parcel in) {
+ return new ExpandedStyle(in);
+ }
+
+ @Override
+ public ExpandedStyle[] newArray(int size) {
+ return new ExpandedStyle[size];
+ }
+ };
+ }
+
+ /**
+ * An instance of {@link ExpandedStyle} that shows the {@link ExpandedGridItem}s in a
+ * non-scrollable grid.
+ */
+ public static class GridExpandedStyle extends ExpandedStyle {
+ /**
+ * Constructs a GridExpandedStyle object with default values.
+ */
+ public GridExpandedStyle() {
+ internalStyleId(GRID_STYLE);
+ }
+
+ /**
+ * Sets an {@link ArrayList} of {@link ExpandedGridItem}'s to be utilized by
+ * the PseudoGridView for presentation.
+ *
+ * Since the PseudoGridView is not a Grid with an adapter instance, there's a hard
+ * limit specified by {@link #PSEUDO_GRID_ITEM_MAX_COUNT}
+ * @param expandedGridItems an array list of {@link ExpandedGridItem}s
+ */
+ public void setGridItems(ArrayList<ExpandedGridItem> expandedGridItems) {
+ internalSetExpandedItems(expandedGridItems);
+ }
+ }
+
+ /**
+ * An instance of {@link ExpandedStyle} that shows the {@link ExpandedListItem}'s in a
+ * scrollable ListView.
+ */
+ public static class ListExpandedStyle extends ExpandedStyle {
+ /**
+ * Constructs a ListExpandedStyle object with default values.
+ */
+ public ListExpandedStyle() {
+ internalStyleId(LIST_STYLE);
+ }
+
+ /**
+ * Sets an {@link ArrayList} of {@link ExpandedListItem}s to be utilized by
+ * the ListView for presentation.
+ * @param expandedListItems an array list of {@link ExpandedListItem}s
+ */
+ public void setListItems(ArrayList<ExpandedListItem> expandedListItems) {
+ internalSetExpandedItems(expandedListItems);
+ }
+ }
+
+ /**
+ * An instance of {@link ExpandedStyle} that shows a remote view in the remote process
+ */
+ public static class RemoteExpandedStyle extends ExpandedStyle {
+ /**
+ * Constructs a RemoteExpandedStyle object with default values.
+ */
+ public RemoteExpandedStyle() {
+ internalStyleId(REMOTE_STYLE);
+ }
+
+ /**
+ * Sets the RemoteViews for the {@link RemoteExpandedStyle}
+ * @param remoteViews a remote view
+ */
+ public void setRemoteViews(RemoteViews remoteViews) {
+ internalSetRemoteViews(remoteViews);
+ }
+ }
+
+ /**
+ * A container object that is utilized by {@link ExpandedStyle} to show specific items in either
+ * a PseudoGridView or a ListView via {@link GridExpandedStyle} and {@link ListExpandedStyle}
+ */
+ public static class ExpandedItem implements Parcelable {
+
+ /**
+ * A {@link PendingIntent} associated with the item.
+ * Triggers a {@link PendingIntent#send()} when the item is clicked.
+ */
+ public PendingIntent onClickPendingIntent;
+
+ /**
+ * A drawable resource id associated with the {@link ExpandedItem}
+ */
+ public int itemDrawableResourceId;
+
+ /**
+ * A bitmap to be utilized instead of #itemDrawableResourceId
+ */
+ public Bitmap itemBitmapResource;
+
+ /**
+ * The title of the item
+ */
+ public String itemTitle;
+
+ /**
+ * The summary associated with the item, may be null
+ */
+ public String itemSummary = null;
+
+ private ExpandedItem() {
+ // Don't want to have this baseclass be instantiable
+ }
+
+ /**
+ * @hide
+ */
+ protected void internalSetItemDrawable(int resourceId) {
+ itemDrawableResourceId = resourceId;
+ }
+
+ /**
+ * @hide
+ */
+ protected void internalSetItemBitmap(Bitmap bitmap) {
+ itemBitmapResource = bitmap;
+ }
+
+ /**
+ * @hide
+ */
+ protected void internalSetItemSummary(String resourceId) {
+ itemSummary = resourceId;
+ }
+
+ /**
+ * @hide
+ */
+ protected void internalSetItemTitle(String title) {
+ itemTitle = title;
+ }
+
+ /**
+ * @hide
+ */
+ protected void internalSetOnClickPendingIntent(PendingIntent pendingIntent) {
+ onClickPendingIntent = pendingIntent;
+ }
+
+ /**
+ * Unflatten the ExpandedItem from a parcel.
+ */
+ protected ExpandedItem(Parcel parcel) {
+ // Read parcelable version via the Concierge
+ ParcelInfo parcelInfo = Concierge.receiveParcel(parcel);
+ int parcelableVersion = parcelInfo.getParcelVersion();
+
+ // Pattern here is that all new members should be added to the end of
+ // the writeToParcel method. Then we step through each version, until the latest
+ // API release to help unravel this parcel
+ if (parcelableVersion >= Build.CM_VERSION_CODES.APRICOT) {
+ if (parcel.readInt() != 0) {
+ onClickPendingIntent = PendingIntent.CREATOR.createFromParcel(parcel);
+ }
+ if (parcel.readInt() != 0) {
+ itemTitle = parcel.readString();
+ }
+ if (parcel.readInt() != 0) {
+ itemSummary = parcel.readString();
+ }
+ itemDrawableResourceId = parcel.readInt();
+ }
+
+ if (parcelableVersion >= Build.CM_VERSION_CODES.BOYSENBERRY) {
+ if (parcel.readInt() != 0) {
+ itemBitmapResource = Bitmap.CREATOR.createFromParcel(parcel);
+ }
+ }
+
+ // Complete parcel info for the concierge
+ parcelInfo.complete();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ // Tell the concierge to prepare the parcel
+ ParcelInfo parcelInfo = Concierge.prepareParcel(out);
+
+ // ==== APRICOT ====
+ if (onClickPendingIntent != null) {
+ out.writeInt(1);
+ onClickPendingIntent.writeToParcel(out, 0);
+ } else {
+ out.writeInt(0);
+ }
+ if (!TextUtils.isEmpty(itemTitle)) {
+ out.writeInt(1);
+ out.writeString(itemTitle);
+ } else {
+ out.writeInt(0);
+ }
+ if (!TextUtils.isEmpty(itemSummary)) {
+ out.writeInt(1);
+ out.writeString(itemSummary);
+ } else {
+ out.writeInt(0);
+ }
+ out.writeInt(itemDrawableResourceId);
+
+ // ==== BOYSENBERRY ====
+ if (itemBitmapResource != null) {
+ out.writeInt(1);
+ itemBitmapResource.writeToParcel(out, 0);
+ } else {
+ out.writeInt(0);
+ }
+
+ // Complete the parcel info for the concierge
+ parcelInfo.complete();
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder b = new StringBuilder();
+ String NEW_LINE = System.getProperty("line.separator");
+ if (onClickPendingIntent != null) {
+ b.append("onClickPendingIntent= " + onClickPendingIntent.toString() + NEW_LINE);
+ }
+ if (itemTitle != null) {
+ b.append("itemTitle= " + itemTitle.toString() + NEW_LINE);
+ }
+ if (itemSummary != null) {
+ b.append("itemSummary= " + itemSummary.toString() + NEW_LINE);
+ }
+ b.append("itemDrawableResourceId=" + itemDrawableResourceId + NEW_LINE);
+ if (itemBitmapResource != null) {
+ b.append("itemBitmapResource=" + itemBitmapResource.getGenerationId() + NEW_LINE);
+ }
+ return b.toString();
+ }
+
+ public static final Creator<ExpandedItem> CREATOR =
+ new Creator<ExpandedItem>() {
+ @Override
+ public ExpandedItem createFromParcel(Parcel in) {
+ return new ExpandedItem(in);
+ }
+
+ @Override
+ public ExpandedItem[] newArray(int size) {
+ return new ExpandedItem[size];
+ }
+ };
+ }
+
+ /**
+ * An instance of {@link ExpandedItem} to be utilized within a {@link GridExpandedStyle}
+ */
+ public static class ExpandedGridItem extends ExpandedItem {
+ /**
+ * Constructor for the ExpandedGridItem
+ */
+ public ExpandedGridItem() {
+ }
+
+ /**
+ * Sets the title for the {@link ExpandedGridItem}
+ * @param title a string title
+ */
+ public void setExpandedGridItemTitle(String title) {
+ internalSetItemTitle(title);
+ }
+
+ /**
+ * Sets the {@link PendingIntent} associated with the {@link ExpandedGridItem}
+ * @param intent a pending intent to be triggered on click
+ */
+ public void setExpandedGridItemOnClickIntent(PendingIntent intent) {
+ internalSetOnClickPendingIntent(intent);
+ }
+
+ /**
+ * Sets the drawable resource id associated with the {@link ExpandedGridItem}
+ * @param resourceId a resource id that maps to a drawable
+ */
+ public void setExpandedGridItemDrawable(int resourceId) {
+ internalSetItemDrawable(resourceId);
+ }
+
+ /**
+ * Sets the bitmap associated with the {@link ExpandedGridItem} to be utilized instead of
+ * the {@link ExpandedItem#itemDrawableResourceId}
+ *
+ * Note, sending many items with bitmaps over IPC may result in a
+ * TransactionTooLargeException.
+ * @param bitmap
+ */
+ public void setExpandedGridItemBitmap(Bitmap bitmap) {
+ internalSetItemBitmap(bitmap);
+ }
+ }
+
+ /**
+ * An instance of {@link ExpandedItem} to be utilized within a {@link ListExpandedStyle}
+ */
+ public static class ExpandedListItem extends ExpandedItem {
+ /**
+ * Constructor fot the ExpandedListItem
+ */
+ public ExpandedListItem() {
+ }
+
+ /**
+ * Sets the title for the {@link ExpandedListItem}
+ * @param title a string title
+ */
+ public void setExpandedListItemTitle(String title) {
+ internalSetItemTitle(title);
+ }
+
+ /**
+ * Sets the title for the {@link ExpandedListItem}
+ * @param summary a string summary
+ */
+ public void setExpandedListItemSummary(String summary) {
+ internalSetItemSummary(summary);
+ }
+
+ /**
+ * Sets the {@link PendingIntent} associated with the {@link ExpandedListItem}
+ * @param intent a pending intent to be triggered on click
+ */
+ public void setExpandedListItemOnClickIntent(PendingIntent intent) {
+ internalSetOnClickPendingIntent(intent);
+ }
+
+ /**
+ * Sets the drawable resource id associated with the {@link ExpandedListItem}
+ * @param resourceId a resource id that maps to a drawable
+ */
+ public void setExpandedListItemDrawable(int resourceId) {
+ internalSetItemDrawable(resourceId);
+ }
+
+ /**
+ * Sets the bitmap associated with the {@link ExpandedListItem} to be utilized instead of
+ * the {@link ExpandedItem#itemDrawableResourceId}
+ *
+ * Note, sending many items with bitmaps over IPC may result in a
+ * TransactionTooLargeException.
+ * @param bitmap
+ */
+ public void setExpandedListItemBitmap(Bitmap bitmap) {
+ internalSetItemBitmap(bitmap);
+ }
+ }
+
+ /**
+ * Parcelable.Creator that instantiates CustomTile objects
+ */
+ public static final Creator<CustomTile> CREATOR =
+ new Creator<CustomTile>() {
+ public CustomTile createFromParcel(Parcel in) {
+ return new CustomTile(in);
+ }
+
+ @Override
+ public CustomTile[] newArray(int size) {
+ return new CustomTile[size];
+ }
+ };
+
+ /**
+ * Builder class for {@link cyanogenmod.app.CustomTile} objects.
+ *
+ * Provides a convenient way to set the various fields of a {@link cyanogenmod.app.CustomTile}
+ *
+ * <p>Example:
+ *
+ * <pre class="prettyprint">
+ * CustomTile customTile = new CustomTile.Builder(mContext)
+ * .setLabel("custom label")
+ * .setContentDescription("custom description")
+ * .setOnClickIntent(pendingIntent)
+ * .setOnSettingsClickIntent(intent)
+ * .setOnClickUri(Uri.parse("custom uri"))
+ * .setIcon(R.drawable.ic_launcher)
+ * .build();
+ * </pre>
+ */
+ public static class Builder {
+ private PendingIntent mOnClick;
+ private PendingIntent mOnLongClick;
+ private Intent mOnSettingsClick;
+ private Uri mOnClickUri;
+ private String mLabel;
+ private String mContentDescription;
+ private int mIcon;
+ private Bitmap mRemoteIcon;
+ private Context mContext;
+ private ExpandedStyle mExpandedStyle;
+ private boolean mCollapsePanel = true;
+ private PendingIntent mDeleteIntent;
+ private boolean mSensitiveData = false;
+
+ /**
+ * Constructs a new Builder with the defaults:
+ */
+ public Builder(Context context) {
+ mContext = context;
+ }
+
+ /**
+ * Set the label for the custom tile
+ * @param label a string to be used for the custom tile label
+ * @return {@link cyanogenmod.app.CustomTile.Builder}
+ */
+ public Builder setLabel(String label) {
+ mLabel = label;
+ return this;
+ }
+
+ /**
+ * Set the label for the custom tile
+ * @param id a string resource id to be used for the custom tile label
+ * @return {@link cyanogenmod.app.CustomTile.Builder}
+ */
+ public Builder setLabel(int id) {
+ mLabel = mContext.getString(id);
+ return this;
+ }
+
+ /**
+ * Set the content description for the custom tile
+ * @param contentDescription a string to explain content
+ * @return {@link cyanogenmod.app.CustomTile.Builder}
+ */
+ public Builder setContentDescription(String contentDescription) {
+ mContentDescription = contentDescription;
+ return this;
+ }
+
+ /**
+ * Set the content description for the custom tile
+ * @param id a string resource id to explain content
+ * @return {@link cyanogenmod.app.CustomTile.Builder}
+ */
+ public Builder setContentDescription(int id) {
+ mContentDescription = mContext.getString(id);
+ return this;
+ }
+
+ /**
+ * Set a {@link android.app.PendingIntent} to be fired on custom tile click
+ * @param intent
+ * @return {@link cyanogenmod.app.CustomTile.Builder}
+ */
+ public Builder setOnClickIntent(PendingIntent intent) {
+ mOnClick = intent;
+ return this;
+ }
+
+ /**
+ * Set a {@link android.app.PendingIntent} to be fired on custom tile long press.
+ * Note: if this is an activity, the host panel will automatically collapse.
+ * @param intent
+ * @return {@link cyanogenmod.app.CustomTile.Builder}
+ */
+ public Builder setOnLongClickIntent(PendingIntent intent) {
+ mOnLongClick = intent;
+ return this;
+ }
+
+ /**
+ * Set a settings {@link android.content.Intent} to be fired on custom
+ * tile detail pane click
+ * @param intent
+ * @return {@link cyanogenmod.app.CustomTile.Builder}
+ */
+ public Builder setOnSettingsClickIntent(Intent intent) {
+ mOnSettingsClick = intent;
+ return this;
+ }
+
+ /**
+ * Set a {@link android.net.Uri} to be broadcasted in an intent on custom tile click
+ * @param uri
+ * @return {@link cyanogenmod.app.CustomTile.Builder}
+ */
+ public Builder setOnClickUri(Uri uri) {
+ mOnClickUri = uri;
+ return this;
+ }
+
+ /**
+ * Set an icon for the custom tile to be presented to the user
+ *
+ * @param drawableId
+ * @return {@link cyanogenmod.app.CustomTile.Builder}
+ */
+ public Builder setIcon(int drawableId) {
+ mIcon = drawableId;
+ return this;
+ }
+
+ /**
+ * Set a bitmap icon to the custom tile to be utilized instead of {@link CustomTile#icon}
+ *
+ * This will unset {@link #setIcon(int)} if utilized together.
+ * @see CustomTile#remoteIcon
+ * @param remoteIcon
+ * @return {@link cyanogenmod.app.CustomTile.Builder}
+ */
+ public Builder setIcon(Bitmap remoteIcon) {
+ mIcon = 0; // empty
+ mRemoteIcon = remoteIcon;
+ return this;
+ }
+
+ /**
+ * Set an {@link ExpandedStyle} to to be displayed when a user clicks the custom tile
+ * @param expandedStyle
+ * @return {@link cyanogenmod.app.CustomTile.Builder}
+ */
+ public Builder setExpandedStyle(ExpandedStyle expandedStyle) {
+ if (mExpandedStyle != expandedStyle) {
+ mExpandedStyle = expandedStyle;
+ if (mExpandedStyle != null) {
+ expandedStyle.setBuilder(this);
+ }
+ }
+ return this;
+ }
+
+ /**
+ * Set whether or not the Statusbar Panel should be collapsed when an
+ * {@link #onClick} or {@link #onClickUri} event is fired.
+ * @param bool
+ * @return {@link cyanogenmod.app.CustomTile.Builder}
+ */
+ public Builder shouldCollapsePanel(boolean bool) {
+ mCollapsePanel = bool;
+ return this;
+ }
+
+ /**
+ * Supply a {@link PendingIntent} to send when the custom tile is cleared explicitly
+ * by the user.
+ *
+ * @see CustomTile#deleteIntent
+ * @param intent
+ * @return {@link cyanogenmod.app.CustomTile.Builder}
+ */
+ public Builder setDeleteIntent(PendingIntent intent) {
+ mDeleteIntent = intent;
+ return this;
+ }
+
+ /**
+ * Indicates whether this tile has sensitive data that have to be hidden
+ * on secure lockscreens.
+ * @param bool
+ * @return {@link cyanogenmod.app.CustomTile.Builder}
+ */
+ public Builder hasSensitiveData(boolean bool) {
+ mSensitiveData = bool;
+ return this;
+ }
+
+ /**
+ * Create a {@link cyanogenmod.app.CustomTile} object
+ * @return {@link cyanogenmod.app.CustomTile}
+ */
+ public CustomTile build() {
+ CustomTile tile = new CustomTile();
+ tile.resourcesPackageName = mContext.getPackageName();
+ tile.onClick = mOnClick;
+ tile.onLongClick = mOnLongClick;
+ tile.onSettingsClick = mOnSettingsClick;
+ tile.onClickUri = mOnClickUri;
+ tile.label = mLabel;
+ tile.contentDescription = mContentDescription;
+ tile.expandedStyle = mExpandedStyle;
+ tile.icon = mIcon;
+ tile.collapsePanel = mCollapsePanel;
+ tile.remoteIcon = mRemoteIcon;
+ tile.deleteIntent = mDeleteIntent;
+ tile.sensitiveData = mSensitiveData;
+ return tile;
+ }
+ }
+}
diff --git a/sdk/src/java/cyanogenmod/app/CustomTileListenerService.java b/sdk/src/java/cyanogenmod/app/CustomTileListenerService.java
new file mode 100644
index 0000000..2c8036f
--- /dev/null
+++ b/sdk/src/java/cyanogenmod/app/CustomTileListenerService.java
@@ -0,0 +1,232 @@
+/**
+ * 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.app;
+
+import android.annotation.SdkConstant;
+import android.app.Service;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Log;
+
+import cyanogenmod.app.ICustomTileListener;
+import cyanogenmod.app.ICMStatusBarManager;
+
+import org.cyanogenmod.internal.statusbar.IStatusBarCustomTileHolder;
+
+/**
+ * A service that receives calls from the system when new custom tiles are
+ * posted or removed.
+ * <p>To extend this class, you must declare the service in your manifest file with
+ * the cyanogenmod.permission.BIND_CUSTOM_TILE_LISTENER_SERVICE
+ * and include an intent filter with the {@link #SERVICE_INTERFACE} action. For example:</p>
+ * <pre>
+ * &lt;service android:name=".CustomTileListener"
+ * android:label="&#64;string/service_name"
+ * android:permission="cyanogenmod.permission.BIND_CUSTOM_TILE_LISTENER_SERVICE">
+ * &lt;intent-filter>
+ * &lt;action android:name="cyanogenmod.app.CustomTileListenerService" />
+ * &lt;/intent-filter>
+ * &lt;/service></pre>
+ */
+public class CustomTileListenerService extends Service {
+ private final String TAG = CustomTileListenerService.class.getSimpleName()
+ + "[" + getClass().getSimpleName() + "]";
+ /**
+ * The {@link android.content.Intent} that must be declared as handled by the service.
+ */
+ @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
+ public static final String SERVICE_INTERFACE
+ = "cyanogenmod.app.CustomTileListenerService";
+
+ private ICustomTileListenerWrapper mWrapper = null;
+ private ICMStatusBarManager mStatusBarService;
+ /** Only valid after a successful call to (@link registerAsService}. */
+ private int mCurrentUser;
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ if (mWrapper == null) {
+ mWrapper = new ICustomTileListenerWrapper();
+ }
+ return mWrapper;
+ }
+
+ private final ICMStatusBarManager getStatusBarInterface() {
+ if (mStatusBarService == null) {
+ mStatusBarService = ICMStatusBarManager.Stub.asInterface(
+ ServiceManager.getService(CMContextConstants.CM_STATUS_BAR_SERVICE));
+ }
+ return mStatusBarService;
+ }
+
+ /**
+ * Directly register this service with the StatusBar Manager.
+ *
+ * <p>Only system services may use this call. It will fail for non-system callers.
+ * Apps should ask the user to add their listener in Settings.
+ *
+ * @param context Context required for accessing resources. Since this service isn't
+ * launched as a real Service when using this method, a context has to be passed in.
+ * @param componentName the component that will consume the custom tile information
+ * @param currentUser the user to use as the stream filter
+ * @hide
+ */
+ public void registerAsSystemService(Context context, ComponentName componentName,
+ int currentUser) throws RemoteException {
+ if (isBound()) {
+ return;
+ }
+ ICMStatusBarManager statusBarInterface = mStatusBarService;
+ if (mStatusBarService != null) {
+ mWrapper = new ICustomTileListenerWrapper();
+ statusBarInterface.registerListener(mWrapper, componentName, currentUser);
+ mCurrentUser = currentUser;
+ }
+ }
+
+ /**
+ * Directly unregister this service from the StatusBar Manager.
+ *
+ * <P>This method will fail for listeners that were not registered
+ * with (@link registerAsService).
+ * @hide
+ */
+ public void unregisterAsSystemService() throws RemoteException {
+ if (isBound()) {
+ ICMStatusBarManager statusBarInterface = mStatusBarService;
+ statusBarInterface.unregisterListener(mWrapper, mCurrentUser);
+ mWrapper = null;
+ mStatusBarService = null;
+ }
+ }
+
+
+ private class ICustomTileListenerWrapper extends ICustomTileListener.Stub {
+ @Override
+ public void onListenerConnected() {
+ synchronized (mWrapper) {
+ try {
+ CustomTileListenerService.this.onListenerConnected();
+ } catch (Throwable t) {
+ Log.w(TAG, "Error running onListenerConnected", t);
+ }
+ }
+ }
+ @Override
+ public void onCustomTilePosted(IStatusBarCustomTileHolder sbcHolder) {
+ StatusBarPanelCustomTile sbc;
+ try {
+ sbc = sbcHolder.get();
+ } catch (RemoteException e) {
+ Log.w(TAG, "onCustomTilePosted: Error receiving StatusBarPanelCustomTile", e);
+ return;
+ }
+ synchronized (mWrapper) {
+ try {
+ CustomTileListenerService.this.onCustomTilePosted(sbc);
+ } catch (Throwable t) {
+ Log.w(TAG, "Error running onCustomTilePosted", t);
+ }
+ }
+ }
+ @Override
+ public void onCustomTileRemoved(IStatusBarCustomTileHolder sbcHolder) {
+ StatusBarPanelCustomTile sbc;
+ try {
+ sbc = sbcHolder.get();
+ } catch (RemoteException e) {
+ Log.w(TAG, "onCustomTileRemoved: Error receiving StatusBarPanelCustomTile", e);
+ return;
+ }
+ synchronized (mWrapper) {
+ try {
+ CustomTileListenerService.this.onCustomTileRemoved(sbc);
+ } catch (Throwable t) {
+ Log.w(TAG, "Error running onCustomTileRemoved", t);
+ }
+ }
+ }
+ }
+
+ /**
+ * Implement this method to learn about new custom tiles as they are posted by apps.
+ *
+ * @param sbc A data structure encapsulating the original {@link cyanogenmod.app.CustomTile}
+ * object as well as its identifying information (tag and id) and source
+ * (package name).
+ */
+ public void onCustomTilePosted(StatusBarPanelCustomTile sbc) {
+ // optional
+ }
+
+ /**
+ * Implement this method to learn when custom tiles are removed.
+ *
+ * @param sbc A data structure encapsulating at least the original information (tag and id)
+ * and source (package name) used to post the {@link cyanogenmod.app.CustomTile} that
+ * was just removed.
+ */
+ public void onCustomTileRemoved(StatusBarPanelCustomTile sbc) {
+ // optional
+ }
+
+ /**
+ * Implement this method to learn about when the listener is enabled and connected to
+ * the status bar manager.
+ * at this time.
+ */
+ public void onListenerConnected() {
+ // optional
+ }
+
+ /**
+ * Inform the {@link cyanogenmod.app.CMStatusBarManager} about dismissal of a single custom tile.
+ * <p>
+ * Use this if your listener has a user interface that allows the user to dismiss individual
+ * custom tiles, similar to the behavior of Android's status bar and notification panel.
+ * It should be called after the user dismisses a single custom tile using your UI;
+ * upon being informed, the cmstatusbar manager will actually remove the custom tile
+ * and you will get an {@link #onCustomTileRemoved(StatusBarPanelCustomTile)} callback.
+ * <P>
+ *
+ * @param pkg Package of the notifying app.
+ * @param tag Tag of the custom tile as specified by the notifying app
+ * @param id ID of the custom tile as specified by the notifying app
+ * <p>
+ */
+ public final void removeCustomTile(String pkg, String tag, int id) {
+ if (!isBound()) return;
+ try {
+ mStatusBarService.removeCustomTileFromListener(
+ mWrapper, pkg, tag, id);
+ } catch (android.os.RemoteException ex) {
+ Log.v(TAG, "Unable to contact cmstautusbar manager", ex);
+ }
+ }
+
+ private boolean isBound() {
+ if (getStatusBarInterface() == null || mWrapper == null) {
+ Log.w(TAG, "CustomTile listener service not yet bound.");
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/sdk/src/java/cyanogenmod/app/ICMStatusBarManager.aidl b/sdk/src/java/cyanogenmod/app/ICMStatusBarManager.aidl
new file mode 100644
index 0000000..62283a5
--- /dev/null
+++ b/sdk/src/java/cyanogenmod/app/ICMStatusBarManager.aidl
@@ -0,0 +1,38 @@
+/**
+ * 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.app;
+
+import android.content.ComponentName;
+
+import cyanogenmod.app.CustomTile;
+import cyanogenmod.app.ICustomTileListener;
+
+/** @hide */
+interface ICMStatusBarManager {
+ // --- Methods below are for use by 3rd party applications to publish quick
+ // settings tiles to the status bar panel
+ // You need the PUBLISH_CUSTOM_TILE permission
+ void createCustomTileWithTag(String pkg, String opPkg, String tag, int id,
+ in CustomTile tile, inout int[] idReceived, int userId);
+ void removeCustomTileWithTag(String pkg, String tag, int id, int userId);
+
+ // --- Methods below are for use by 3rd party applications
+ // You need the BIND_QUICK_SETTINGS_TILE_LISTENER permission
+ void registerListener(in ICustomTileListener listener, in ComponentName component, int userid);
+ void unregisterListener(in ICustomTileListener listener, int userid);
+ void removeCustomTileFromListener(in ICustomTileListener listener, String pkg, String tag, int id);
+}
diff --git a/sdk/src/java/cyanogenmod/app/ICMTelephonyManager.aidl b/sdk/src/java/cyanogenmod/app/ICMTelephonyManager.aidl
new file mode 100644
index 0000000..743d61c
--- /dev/null
+++ b/sdk/src/java/cyanogenmod/app/ICMTelephonyManager.aidl
@@ -0,0 +1,38 @@
+/**
+ * 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.app;
+
+import android.telephony.SubscriptionInfo;
+
+import java.util.List;
+
+/** @hide */
+interface ICMTelephonyManager {
+ // --- Methods below are for use by 3rd party applications to manage phone and data connection
+ // You need the READ_MSIM_PHONE_STATE permission
+ List<SubscriptionInfo> getSubInformation();
+ boolean isSubActive(int subId);
+ boolean isDataConnectionSelectedOnSub(int subId);
+ boolean isDataConnectionEnabled();
+
+ // You need the MODIFY_MSIM_PHONE_STATE permission
+ void setSubState(int subId, boolean state);
+ void setDataConnectionSelectedOnSub(int subId);
+ void setDataConnectionState(boolean state);
+ void setDefaultPhoneSub(int subId);
+ void setDefaultSmsSub(int subId);
+}
diff --git a/sdk/src/java/cyanogenmod/app/ICustomTileListener.aidl b/sdk/src/java/cyanogenmod/app/ICustomTileListener.aidl
new file mode 100644
index 0000000..9f21f52
--- /dev/null
+++ b/sdk/src/java/cyanogenmod/app/ICustomTileListener.aidl
@@ -0,0 +1,29 @@
+/**
+ * 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.app;
+
+import cyanogenmod.app.StatusBarPanelCustomTile;
+
+import org.cyanogenmod.internal.statusbar.IStatusBarCustomTileHolder;
+
+/** @hide */
+oneway interface ICustomTileListener
+{
+ void onListenerConnected();
+ void onCustomTilePosted(in IStatusBarCustomTileHolder customTileHolder);
+ void onCustomTileRemoved(in IStatusBarCustomTileHolder customTileHolder);
+}
diff --git a/sdk/src/java/cyanogenmod/app/ILiveLockScreenChangeListener.aidl b/sdk/src/java/cyanogenmod/app/ILiveLockScreenChangeListener.aidl
new file mode 100644
index 0000000..48e7f36
--- /dev/null
+++ b/sdk/src/java/cyanogenmod/app/ILiveLockScreenChangeListener.aidl
@@ -0,0 +1,27 @@
+/*
+** Copyright (C) 2016 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.app;
+
+import cyanogenmod.app.LiveLockScreenInfo;
+
+/**
+ * Listener interface for notifying clients that the current Live lock screen has changed.
+ * @hide
+ */
+interface ILiveLockScreenChangeListener {
+ void onLiveLockScreenChanged(in LiveLockScreenInfo llsInfo);
+} \ No newline at end of file
diff --git a/sdk/src/java/cyanogenmod/app/ILiveLockScreenManager.aidl b/sdk/src/java/cyanogenmod/app/ILiveLockScreenManager.aidl
new file mode 100644
index 0000000..15142c1
--- /dev/null
+++ b/sdk/src/java/cyanogenmod/app/ILiveLockScreenManager.aidl
@@ -0,0 +1,73 @@
+/*
+** Copyright (C) 2016 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.app;
+
+import cyanogenmod.app.ILiveLockScreenChangeListener;
+import cyanogenmod.app.LiveLockScreenInfo;
+
+/** @hide */
+interface ILiveLockScreenManager {
+
+ /**
+ * Enqueue a Live lock screen to be displayed.
+ */
+ void enqueueLiveLockScreen(String pkg, int id, in LiveLockScreenInfo lls,
+ inout int[] idReceived, int userId);
+
+ /**
+ * Cancel displaying a Live lock screen.
+ */
+ void cancelLiveLockScreen(String pkg, int id, int userId);
+
+ /**
+ * Get the current Live lock screen that should be displayed.
+ */
+ LiveLockScreenInfo getCurrentLiveLockScreen();
+
+ /**
+ * Get the default Live lock screen. This is the Live lock screen that should be displayed
+ * when no other Live lock screens are queued.
+ */
+ LiveLockScreenInfo getDefaultLiveLockScreen();
+
+ /**
+ * Set the default Live lock screen. This is the Live lock screen that should be displayed
+ * when no other Live lock screens are queued.
+ */
+ void setDefaultLiveLockScreen(in LiveLockScreenInfo llsInfo);
+
+ /**
+ * Set whether Live lock screen feature is enabled.
+ */
+ oneway void setLiveLockScreenEnabled(boolean enabled);
+
+ /**
+ * Get the enabled state of the Live lock screen feature.
+ */
+ boolean getLiveLockScreenEnabled();
+
+ /**
+ * Registers an ILiveLockScreenChangeListener that will be called when the current Live lock
+ * screen changes.
+ */
+ boolean registerChangeListener(in ILiveLockScreenChangeListener listener);
+
+ /**
+ * Unregisters a previously registered ILiveLockScreenChangeListener.
+ */
+ boolean unregisterChangeListener(in ILiveLockScreenChangeListener listener);
+} \ No newline at end of file
diff --git a/sdk/src/java/cyanogenmod/app/ILiveLockScreenManagerProvider.aidl b/sdk/src/java/cyanogenmod/app/ILiveLockScreenManagerProvider.aidl
new file mode 100644
index 0000000..933eb97
--- /dev/null
+++ b/sdk/src/java/cyanogenmod/app/ILiveLockScreenManagerProvider.aidl
@@ -0,0 +1,65 @@
+/*
+** Copyright (C) 2016 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.app;
+
+import cyanogenmod.app.ILiveLockScreenChangeListener;
+import cyanogenmod.app.LiveLockScreenInfo;
+
+/**
+ * Interface to be implemented by services that support the
+ * {@link LiveLockScreenManager#SERVICE_INTERFACE}
+ * @hide
+ */
+interface ILiveLockScreenManagerProvider {
+
+ /**
+ * Enqueue a Live lock screen to be displayed.
+ */
+ void enqueueLiveLockScreen(String pkg, int id, in LiveLockScreenInfo lls,
+ inout int[] idReceived, int userId);
+
+ /**
+ * Cancel displaying a Live lock screen.
+ */
+ void cancelLiveLockScreen(String pkg, int id, int userId);
+
+ /**
+ * Get the current Live lock screen that should be displayed.
+ */
+ LiveLockScreenInfo getCurrentLiveLockScreen();
+
+ /**
+ * Called when the default Live lock screen has changed.
+ */
+ oneway void updateDefaultLiveLockScreen(in LiveLockScreenInfo llsInfo);
+
+ /**
+ * Get the enabled state of the Live lock screen feature.
+ */
+ boolean getLiveLockScreenEnabled();
+
+ /**
+ * Registers an ILiveLockScreenChangeListener that will be called when the current Live lock
+ * screen changes.
+ */
+ boolean registerChangeListener(in ILiveLockScreenChangeListener listener);
+
+ /**
+ * Unregisters a previously registered ILiveLockScreenChangeListener.
+ */
+ boolean unregisterChangeListener(in ILiveLockScreenChangeListener listener);
+} \ No newline at end of file
diff --git a/sdk/src/java/cyanogenmod/app/IPartnerInterface.aidl b/sdk/src/java/cyanogenmod/app/IPartnerInterface.aidl
new file mode 100644
index 0000000..1b02dd1
--- /dev/null
+++ b/sdk/src/java/cyanogenmod/app/IPartnerInterface.aidl
@@ -0,0 +1,30 @@
+/* //device/java/android/android/app/IProfileManager.aidl
+**
+** 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.app;
+
+/** {@hide} */
+interface IPartnerInterface
+{
+ void setAirplaneModeEnabled(boolean enabled);
+ void setMobileDataEnabled(boolean enabled);
+ boolean setZenMode(int mode);
+ void shutdown();
+ void reboot();
+ String getCurrentHotwordPackageName();
+ boolean setZenModeWithDuration(int mode, long durationMillis);
+}
diff --git a/sdk/src/java/cyanogenmod/app/IProfileManager.aidl b/sdk/src/java/cyanogenmod/app/IProfileManager.aidl
new file mode 100644
index 0000000..091ba55
--- /dev/null
+++ b/sdk/src/java/cyanogenmod/app/IProfileManager.aidl
@@ -0,0 +1,51 @@
+/* //device/java/android/android/app/IProfileManager.aidl
+**
+** 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.app;
+
+import cyanogenmod.app.Profile;
+import android.app.NotificationGroup;
+import android.os.ParcelUuid;
+
+/** {@hide} */
+interface IProfileManager
+{
+ boolean setActiveProfile(in ParcelUuid profileParcelUuid);
+ boolean setActiveProfileByName(String profileName);
+ Profile getActiveProfile();
+
+ boolean addProfile(in Profile profile);
+ boolean removeProfile(in Profile profile);
+ void updateProfile(in Profile profile);
+
+ Profile getProfile(in ParcelUuid profileParcelUuid);
+ Profile getProfileByName(String profileName);
+ Profile[] getProfiles();
+ boolean profileExists(in ParcelUuid profileUuid);
+ boolean profileExistsByName(String profileName);
+ boolean notificationGroupExistsByName(String notificationGroupName);
+
+ NotificationGroup[] getNotificationGroups();
+ void addNotificationGroup(in NotificationGroup group);
+ void removeNotificationGroup(in NotificationGroup group);
+ void updateNotificationGroup(in NotificationGroup group);
+ NotificationGroup getNotificationGroupForPackage(in String pkg);
+ NotificationGroup getNotificationGroup(in ParcelUuid groupParcelUuid);
+
+ void resetAll();
+ boolean isEnabled();
+}
diff --git a/sdk/src/java/cyanogenmod/app/LiveLockScreenInfo.aidl b/sdk/src/java/cyanogenmod/app/LiveLockScreenInfo.aidl
new file mode 100644
index 0000000..bffa3b0
--- /dev/null
+++ b/sdk/src/java/cyanogenmod/app/LiveLockScreenInfo.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2016 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.app;
+
+parcelable LiveLockScreenInfo;
diff --git a/sdk/src/java/cyanogenmod/app/LiveLockScreenInfo.java b/sdk/src/java/cyanogenmod/app/LiveLockScreenInfo.java
new file mode 100644
index 0000000..5ac2220
--- /dev/null
+++ b/sdk/src/java/cyanogenmod/app/LiveLockScreenInfo.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2016 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.app;
+
+import android.annotation.NonNull;
+import android.content.ComponentName;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import android.text.TextUtils;
+
+import cyanogenmod.os.Build;
+import cyanogenmod.os.Concierge;
+import cyanogenmod.os.Concierge.ParcelInfo;
+
+/**
+ * Data structure defining a Live lock screen.
+ */
+public class LiveLockScreenInfo implements Parcelable {
+
+ /**
+ * Default Live lock screen {@link #priority}.
+ */
+ public static final int PRIORITY_DEFAULT = 0;
+
+ /**
+ * Lower {@link #priority}, for items that are less important.
+ */
+ public static final int PRIORITY_LOW = -1;
+
+ /**
+ * Lowest {@link #priority}.
+ */
+ public static final int PRIORITY_MIN = -2;
+
+ /**
+ * Higher {@link #priority}, for items that are more important
+ */
+ public static final int PRIORITY_HIGH = 1;
+
+ /**
+ * Highest {@link #priority}.
+ */
+ public static final int PRIORITY_MAX = 2;
+
+ /**
+ * The component, which implements
+ * {@link cyanogenmod.externalviews.KeyguardExternalViewProviderService}, to display for this
+ * live lock screen.
+ */
+ public ComponentName component;
+
+ /**
+ * Relative priority for this Live lock screen.
+ */
+ public int priority;
+
+ /**
+ * Constructs a LiveLockScreenInfo object with the given values.
+ * You might want to consider using {@link Builder} instead.
+ */
+ public LiveLockScreenInfo(@NonNull ComponentName component, int priority) {
+ this.component = component;
+ this.priority = priority;
+ }
+
+ /**
+ * Constructs a LiveLockScreenInfo object with default values.
+ * You might want to consider using {@link Builder} instead.
+ */
+ public LiveLockScreenInfo()
+ {
+ this.component = null;
+ this.priority = PRIORITY_DEFAULT;
+ }
+
+ private LiveLockScreenInfo(Parcel source) {
+ // Read parcelable version via the Concierge
+ ParcelInfo parcelInfo = Concierge.receiveParcel(source);
+ int parcelableVersion = parcelInfo.getParcelVersion();
+
+ this.priority = source.readInt();
+ String component = source.readString();
+ this.component = !TextUtils.isEmpty(component)
+ ? ComponentName.unflattenFromString(component)
+ : null;
+
+ // Complete parcel info for the concierge
+ parcelInfo.complete();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ // Tell the concierge to prepare the parcel
+ ParcelInfo parcelInfo = Concierge.prepareParcel(dest);
+
+ dest.writeInt(priority);
+ dest.writeString(component != null ? component.flattenToString() : "");
+
+ // Complete the parcel info for the concierge
+ parcelInfo.complete();
+ }
+
+ @Override
+ public String toString() {
+ return "LiveLockScreenInfo: priority=" + priority +
+ ", component=" + component;
+ }
+
+ @Override
+ public LiveLockScreenInfo clone() {
+ LiveLockScreenInfo that = new LiveLockScreenInfo();
+ cloneInto(that);
+ return that;
+ }
+
+ /**
+ * Copy all (or if heavy is false, all except Bitmaps and RemoteViews) members
+ * of this into that.
+ * @hide
+ */
+ public void cloneInto(LiveLockScreenInfo that) {
+ that.component = this.component.clone();
+ that.priority = this.priority;
+ }
+
+ public static final Parcelable.Creator<LiveLockScreenInfo> CREATOR =
+ new Parcelable.Creator<LiveLockScreenInfo>() {
+ @Override
+ public LiveLockScreenInfo createFromParcel(Parcel source) {
+ return new LiveLockScreenInfo(source);
+ }
+
+ @Override
+ public LiveLockScreenInfo[] newArray(int size) {
+ return new LiveLockScreenInfo[0];
+ }
+ };
+
+ /**
+ * Builder class for {@link LiveLockScreenInfo} objects. Provides a convenient way to set
+ * various fields of a {@link LiveLockScreenInfo}.
+ */
+ public static class Builder {
+ private int mPriority;
+ private ComponentName mComponent;
+
+ public Builder setPriority(int priority) {
+ if (priority < PRIORITY_MIN || priority > PRIORITY_MAX) {
+ throw new IllegalArgumentException("Invalid priorty given (" + priority + "): " +
+ PRIORITY_MIN + " <= priority <= " + PRIORITY_MIN);
+ }
+ mPriority = priority;
+ return this;
+ }
+
+ public Builder setComponent(@NonNull ComponentName component) {
+ if (component == null) {
+ throw new IllegalArgumentException(
+ "Cannot call setComponent with a null component");
+ }
+ mComponent = component;
+ return this;
+ }
+
+ public LiveLockScreenInfo build() {
+ return new LiveLockScreenInfo(mComponent, mPriority);
+ }
+ }
+}
diff --git a/sdk/src/java/cyanogenmod/app/LiveLockScreenManager.java b/sdk/src/java/cyanogenmod/app/LiveLockScreenManager.java
new file mode 100644
index 0000000..c5fa4ce
--- /dev/null
+++ b/sdk/src/java/cyanogenmod/app/LiveLockScreenManager.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright (C) 2016 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.app;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SdkConstant;
+import android.content.Context;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.UserHandle;
+import android.util.Log;
+
+/**
+ * Manages enabling/disabling Live lock screens as well as what Live lock screen to display when
+ * enabled.
+ */
+public class LiveLockScreenManager {
+ private static final String TAG = LiveLockScreenManager.class.getSimpleName();
+ private static ILiveLockScreenManager sService;
+ private static LiveLockScreenManager sInstance;
+
+ private Context mContext;
+
+ /**
+ * The {@link android.content.Intent} that must be declared as handled by the service.
+ */
+ @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
+ public static final String SERVICE_INTERFACE
+ = "cyanogenmod.app.LiveLockScreenManagerService";
+
+ private LiveLockScreenManager(Context context) {
+ mContext = context;
+ sService = getService();
+ if (context.getPackageManager().hasSystemFeature(
+ CMContextConstants.Features.LIVE_LOCK_SCREEN) && sService == null) {
+ throw new RuntimeException("Unable to get LiveLockScreenManagerService. " +
+ "The service either crashed, was not started, or the interface has " +
+ "been called to early in SystemServer init");
+ }
+ }
+
+ private ILiveLockScreenManager getService() {
+ if (sService == null) {
+ IBinder b = ServiceManager.getService(CMContextConstants.CM_LIVE_LOCK_SCREEN_SERVICE);
+ if (b != null) {
+ sService = ILiveLockScreenManager.Stub.asInterface(b);
+ }
+ }
+
+ return sService;
+ }
+
+ private void logServiceException(Exception e) {
+ Log.w(TAG, "Unable to access LiveLockScreenServiceBroker", e);
+ }
+
+ public static LiveLockScreenManager getInstance(Context context) {
+ if (sInstance == null) {
+ sInstance = new LiveLockScreenManager(context);
+ }
+
+ return sInstance;
+ }
+
+ /**
+ * Requests a Live lock screen, defined in {@param lls}, to be displayed with the given id.
+ * @param id An identifier for this notification unique within your application.
+ * @param llsInfo A {@link LiveLockScreenInfo} object describing what Live lock screen to show
+ * the user.
+ * @return True if the Live lock screen was successfully received by the backing service
+ */
+ public boolean show(int id, @NonNull final LiveLockScreenInfo llsInfo) {
+ int[] idOut = new int[1];
+ String pkg = mContext.getPackageName();
+ boolean success = true;
+ try {
+ sService.enqueueLiveLockScreen(pkg, id, llsInfo, idOut, UserHandle.myUserId());
+ if (id != idOut[0]) {
+ Log.w(TAG, "show: id corrupted: sent " + id + ", got back " + idOut[0]);
+ success = false;
+ }
+ } catch (RemoteException e) {
+ logServiceException(e);
+ success = false;
+ }
+
+ return success;
+ }
+
+ /**
+ * Cancels a previously shown Live lock screen.
+ * @param id An identifier for this notification unique within your application.
+ */
+ public void cancel(int id) {
+ String pkg = mContext.getPackageName();
+ try {
+ sService.cancelLiveLockScreen(pkg, id, UserHandle.myUserId());
+ } catch (RemoteException e) {
+ logServiceException(e);
+ }
+ }
+
+ /**
+ * Sets the default Live lock screen to display when no other Live lock screens are queued
+ * up for display.
+ * <p>
+ * This is not available to third party applications.
+ * </p>
+ */
+ public void setDefaultLiveLockScreen(@Nullable LiveLockScreenInfo llsInfo) {
+ try {
+ sService.setDefaultLiveLockScreen(llsInfo);
+ } catch (RemoteException e) {
+ logServiceException(e);
+ }
+ }
+
+ /**
+ * Gets the default Live lock screen that is displayed when no other Live lock screens are
+ * queued up for display.
+ * <p>
+ * This is not available to third party applications.
+ * </p>
+ */
+ public LiveLockScreenInfo getDefaultLiveLockScreen() {
+ try {
+ return sService.getDefaultLiveLockScreen();
+ } catch (RemoteException e) {
+ logServiceException(e);
+ }
+
+ return null;
+ }
+
+ /** @hide */
+ public LiveLockScreenInfo getCurrentLiveLockScreen() {
+ LiveLockScreenInfo lls = null;
+ try {
+ lls = sService.getCurrentLiveLockScreen();
+ } catch (RemoteException e) {
+ logServiceException(e);
+ }
+
+ return lls;
+ }
+
+ /** @hide */
+ public boolean getLiveLockScreenEnabled() {
+ try {
+ return sService.getLiveLockScreenEnabled();
+ } catch (RemoteException e) {
+ logServiceException(e);
+ }
+
+ return false;
+ }
+
+ /** @hide */
+ public void setLiveLockScreenEnabled(boolean enabled) {
+ try {
+ sService.setLiveLockScreenEnabled(enabled);
+ } catch (RemoteException e) {
+ logServiceException(e);
+ }
+ }
+}
diff --git a/sdk/src/java/cyanogenmod/app/PartnerInterface.java b/sdk/src/java/cyanogenmod/app/PartnerInterface.java
new file mode 100644
index 0000000..a7661ff
--- /dev/null
+++ b/sdk/src/java/cyanogenmod/app/PartnerInterface.java
@@ -0,0 +1,262 @@
+/*
+ * 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.app;
+
+import android.content.Context;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Log;
+
+/**
+ * <p>
+ * The PartnerInterface allows applications to interact with a subset of system settings.
+ * </p>
+ */
+public class PartnerInterface {
+ /**
+ * 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 and #setZenModeWithDuration
+ */
+ 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 and #setZenModeWithDuration
+ */
+ public static final int ZEN_MODE_NO_INTERRUPTIONS = 2;
+
+
+ private static IPartnerInterface sService;
+
+ private Context mContext;
+
+ /**
+ * Allows an application to change network settings,
+ * such as enabling or disabling airplane mode.
+ */
+ 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 = "PartnerInterface";
+
+ private static PartnerInterface sPartnerInterfaceInstance;
+
+ private PartnerInterface(Context context) {
+ Context appContext = context.getApplicationContext();
+ if (appContext != null) {
+ mContext = appContext;
+ } else {
+ mContext = context;
+ }
+ sService = getService();
+ if (context.getPackageManager().hasSystemFeature(
+ CMContextConstants.Features.PARTNER) && sService == null) {
+ throw new RuntimeException("Unable to get PartnerInterfaceService. The service" +
+ " either crashed, was not started, or the interface has been called to early" +
+ " in SystemServer init");
+ }
+ }
+
+ /**
+ * Get or create an instance of the {@link cyanogenmod.app.PartnerInterface}
+ * @param context
+ * @return {@link PartnerInterface}
+ */
+ public static PartnerInterface getInstance(Context context) {
+ if (sPartnerInterfaceInstance == null) {
+ sPartnerInterfaceInstance = new PartnerInterface(context);
+ }
+ return sPartnerInterfaceInstance;
+ }
+
+ /** @hide */
+ static public IPartnerInterface getService() {
+ if (sService != null) {
+ return sService;
+ }
+ IBinder b = ServiceManager.getService(CMContextConstants.CM_PARTNER_INTERFACE);
+ if (b != null) {
+ sService = IPartnerInterface.Stub.asInterface(b);
+ return sService;
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Turns on or off airplane mode.
+ *
+ * You will need {@link #MODIFY_NETWORK_SETTINGS_PERMISSION}
+ * to utilize this functionality.
+ * @param enabled if true, sets airplane mode to enabled, otherwise disables airplane mode.
+ */
+ public void setAirplaneModeEnabled(boolean enabled) {
+ if (sService == null) {
+ return;
+ }
+ try {
+ sService.setAirplaneModeEnabled(enabled);
+ } catch (RemoteException e) {
+ Log.e(TAG, e.getLocalizedMessage(), e);
+ }
+ }
+
+ /**
+ * Turns on or off mobile network.
+ *
+ * You will need {@link #MODIFY_NETWORK_SETTINGS_PERMISSION}
+ * to utilize this functionality.
+ * @param enabled if true, sets mobile network to enabled, otherwise disables mobile network.
+ */
+ public void setMobileDataEnabled(boolean enabled) {
+ if (sService == null) {
+ return;
+ }
+ try {
+ sService.setMobileDataEnabled(enabled);
+ } catch (RemoteException e) {
+ Log.e(TAG, e.getLocalizedMessage(), e);
+ }
+ }
+
+ /**
+ * 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 sService.setZenMode(mode);
+ } catch (RemoteException e) {
+ Log.e(TAG, e.getLocalizedMessage(), e);
+ }
+ return false;
+ }
+
+ /**
+ * Set the zen mode for the device, allow a duration parameter
+ *
+ * You will need {@link #MODIFY_SOUND_SETTINGS_PERMISSION}
+ * to utilize this functionality.
+ *
+ * @see #ZEN_MODE_IMPORTANT_INTERRUPTIONS
+ * @see #ZEN_MODE_NO_INTERRUPTIONS
+ * @param mode The zen mode to set the device to.
+ * One of {@link #ZEN_MODE_IMPORTANT_INTERRUPTIONS},
+ * {@link #ZEN_MODE_NO_INTERRUPTIONS}.
+ * If using {@link #ZEN_MODE_OFF}, the duration will be ignored
+ * @param durationMillis The duration in milliseconds,
+ * if duration + System.currentTimeMillis() results in
+ * long overflow, duration will be "indefinitely".
+ * all durationMillis < 0 will mean "indefinitely".
+ *
+ */
+ public boolean setZenModeWithDuration(int mode, long durationMillis) {
+ if (sService == null) {
+ return false;
+ }
+ try {
+ return sService.setZenModeWithDuration(mode, durationMillis);
+ } catch (RemoteException e) {
+ Log.e(TAG, e.getLocalizedMessage(), e);
+ }
+ return false;
+ }
+
+ /**
+ * Shuts down the device, immediately.
+ *
+ * You will need {@link android.Manifest.permission.REBOOT}
+ * to utilize this functionality.
+ */
+ public void shutdownDevice() {
+ if (sService == null) {
+ return;
+ }
+ try {
+ sService.shutdown();
+ } catch (RemoteException e) {
+ Log.e(TAG, e.getLocalizedMessage(), e);
+ }
+ }
+
+ /**
+ * Reboots the device, immediately.
+ *
+ * You will need {@link android.Manifest.permission.REBOOT}
+ * to utilize this functionality.
+ */
+ public void rebootDevice() {
+ if (sService == null) {
+ return;
+ }
+ try {
+ sService.reboot();
+ } catch (RemoteException e) {
+ Log.e(TAG, e.getLocalizedMessage(), e);
+ }
+ }
+
+ /**
+ * Retrieves the package name of the application that currently holds the
+ * {@link cyanogenmod.media.MediaRecorder.AudioSource#HOTWORD} input.
+ * @return The package name or null if no application currently holds the HOTWORD input.
+ */
+ public String getCurrentHotwordPackageName() {
+ if (sService == null) {
+ return null;
+ }
+ try {
+ return sService.getCurrentHotwordPackageName();
+ } catch (RemoteException e) {
+ Log.e(TAG, e.getLocalizedMessage(), e);
+ }
+ return null;
+ }
+}
diff --git a/sdk/src/java/cyanogenmod/app/Profile.aidl b/sdk/src/java/cyanogenmod/app/Profile.aidl
new file mode 100644
index 0000000..ff6c54e
--- /dev/null
+++ b/sdk/src/java/cyanogenmod/app/Profile.aidl
@@ -0,0 +1,19 @@
+/**
+ * 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.app;
+
+parcelable Profile;
diff --git a/sdk/src/java/cyanogenmod/app/Profile.java b/sdk/src/java/cyanogenmod/app/Profile.java
new file mode 100755
index 0000000..9a4666d
--- /dev/null
+++ b/sdk/src/java/cyanogenmod/app/Profile.java
@@ -0,0 +1,1365 @@
+/*
+ * 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.app;
+
+import android.content.Context;
+import android.media.AudioManager;
+import android.os.Parcel;
+import android.os.ParcelUuid;
+import android.os.Parcelable;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.internal.policy.IKeyguardService;
+import cyanogenmod.os.Build;
+import cyanogenmod.profiles.AirplaneModeSettings;
+import cyanogenmod.profiles.BrightnessSettings;
+import cyanogenmod.profiles.ConnectionSettings;
+import cyanogenmod.profiles.LockSettings;
+import cyanogenmod.profiles.RingModeSettings;
+import cyanogenmod.profiles.StreamSettings;
+
+import cyanogenmod.os.Concierge;
+import cyanogenmod.os.Concierge.ParcelInfo;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.UUID;
+
+/**
+ * A class that represents a device profile.
+ *
+ * A {@link Profile} can serve a multitude of purposes, allowing the creator(user)
+ * to set overrides for streams, triggers, screen lock, brightness, various other
+ * settings.
+ */
+public final class Profile implements Parcelable, Comparable {
+
+ private String mName;
+
+ private int mNameResId;
+
+ private UUID mUuid;
+
+ private ArrayList<UUID> mSecondaryUuids = new ArrayList<UUID>();
+
+ private Map<UUID, ProfileGroup> profileGroups = new HashMap<UUID, ProfileGroup>();
+
+ private ProfileGroup mDefaultGroup;
+
+ private boolean mStatusBarIndicator = false;
+
+ private boolean mDirty;
+
+ private static final String TAG = "Profile";
+
+ private int mProfileType;
+
+ private Map<Integer, StreamSettings> streams = new HashMap<Integer, StreamSettings>();
+
+ private Map<String, ProfileTrigger> mTriggers = new HashMap<String, ProfileTrigger>();
+
+ private Map<Integer, ConnectionSettings> connections = new HashMap<Integer, ConnectionSettings>();
+
+ private Map<Integer, ConnectionSettings> networkConnectionSubIds = new HashMap<>();
+
+ private RingModeSettings mRingMode = new RingModeSettings();
+
+ private AirplaneModeSettings mAirplaneMode = new AirplaneModeSettings();
+
+ private BrightnessSettings mBrightness = new BrightnessSettings();
+
+ private LockSettings mScreenLockMode = new LockSettings();
+
+ private int mExpandedDesktopMode = ExpandedDesktopMode.DEFAULT;
+
+ private int mDozeMode = DozeMode.DEFAULT;
+
+ private int mNotificationLightMode = NotificationLightMode.DEFAULT;
+
+ /**
+ * Lock modes of a device
+ */
+ public static class LockMode {
+ /** Represents a default state lock mode (user choice) */
+ public static final int DEFAULT = 0;
+ /** Represents an insecure state lock mode, where the device has no security screen */
+ public static final int INSECURE = 1;
+ /** Represents a disabled state lock mode, where the devices lock screen can be removed */
+ public static final int DISABLE = 2;
+ }
+
+ /**
+ * Expanded desktop modes available on a device
+ */
+ public static class ExpandedDesktopMode {
+ /** Represents a default state expanded desktop mode (user choice) */
+ public static final int DEFAULT = 0;
+ /** Represents an enabled expanded desktop mode */
+ public static final int ENABLE = 1;
+ /** Represents a disabled expanded desktop mode */
+ public static final int DISABLE = 2;
+ }
+
+ /**
+ * Doze modes available on a device
+ */
+ public static class DozeMode {
+ /** Represents a default Doze mode (user choice) */
+ public static final int DEFAULT = 0;
+ /** Represents an enabled Doze mode */
+ public static final int ENABLE = 1;
+ /** Represents an disabled Doze mode */
+ public static final int DISABLE = 2;
+ }
+
+ /**
+ * Notification light modes available on a device
+ */
+ public static class NotificationLightMode {
+ /** Represents a default Notification light mode (user choice) */
+ public static final int DEFAULT = 0;
+ /** Represents an enabled Notification light mode */
+ public static final int ENABLE = 1;
+ /** Represents a disabled Notification light mode */
+ public static final int DISABLE = 2;
+ }
+
+ /**
+ * Available trigger types on the device, usually hardware
+ */
+ public static class TriggerType {
+ /** Represents a WiFi trigger type */
+ public static final int WIFI = 0;
+ /** Represents a Bluetooth trigger type */
+ public static final int BLUETOOTH = 1;
+ }
+
+ /**
+ * Various trigger states associated with a {@link TriggerType}
+ */
+ public static class TriggerState {
+ /** A {@link TriggerState) for when the {@link TriggerType} connects */
+ public static final int ON_CONNECT = 0;
+ /** A {@link TriggerState) for when the {@link TriggerType} disconnects */
+ public static final int ON_DISCONNECT = 1;
+ /** A {@link TriggerState) for when the {@link TriggerType} is disabled */
+ public static final int DISABLED = 2;
+ /**
+ * A {@link TriggerState) for when the {@link TriggerType#BLUETOOTH}
+ * connects for A2DP session
+ */
+ public static final int ON_A2DP_CONNECT = 3;
+ /**
+ * A {@link TriggerState) for when the {@link TriggerType#BLUETOOTH}
+ * disconnects from A2DP session
+ */
+ public static final int ON_A2DP_DISCONNECT = 4;
+ }
+
+ /**
+ * A {@link Profile} type
+ */
+ public static class Type {
+ /** Profile type which represents a toggle {@link Profile} */
+ public static final int TOGGLE = 0;
+ /** Profile type which represents a conditional {@link Profile} */
+ public static final int CONDITIONAL = 1;
+ }
+
+ /**
+ * A {@link ProfileTrigger} is a {@link TriggerType} which can be queried from the OS
+ */
+ public static class ProfileTrigger implements Parcelable {
+ private int mType;
+ private String mId;
+ private String mName;
+ private int mState;
+
+
+ /**
+ * Construct a {@link ProfileTrigger} based on its type {@link Profile.TriggerType} and if
+ * the trigger should fire on a {@link Profile.TriggerState} change.
+ *
+ * Example:
+ * <pre class="prettyprint">
+ * triggerId = trigger.getSSID(); // Use the AP's SSID as identifier
+ * triggerName = trigger.getTitle(); // Use the AP's name as the trigger name
+ * triggerType = Profile.TriggerType.WIFI; // This is a wifi trigger
+ * triggerState = Profile.TriggerState.ON_CONNECT; // On Connect of this, trigger
+ *
+ * Profile.ProfileTrigger profileTrigger =
+ * new Profile.ProfileTrigger(triggerType, triggerId, triggerState, triggerName);
+ * </pre>
+ *
+ * @param type a {@link Profile.TriggerType}
+ * @param id an identifier for the ProfileTrigger
+ * @param state {@link Profile.TriggerState} depending on the TriggerType
+ * @param name an identifying name for the ProfileTrigger
+ */
+ public ProfileTrigger(int type, String id, int state, String name) {
+ mType = type;
+ mId = id;
+ mState = state;
+ mName = name;
+ }
+
+ private ProfileTrigger(Parcel in) {
+ // Read parcelable version via the Concierge
+ ParcelInfo parcelInfo = Concierge.receiveParcel(in);
+ int parcelableVersion = parcelInfo.getParcelVersion();
+
+ // Pattern here is that all new members should be added to the end of
+ // the writeToParcel method. Then we step through each version, until the latest
+ // API release to help unravel this parcel
+ if (parcelableVersion >= Build.CM_VERSION_CODES.BOYSENBERRY) {
+ mType = in.readInt();
+ mId = in.readString();
+ mState = in.readInt();
+ mName = in.readString();
+ }
+
+ // Complete parcel info for the concierge
+ parcelInfo.complete();
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ // Tell the concierge to prepare the parcel
+ ParcelInfo parcelInfo = Concierge.prepareParcel(dest);
+
+ dest.writeInt(mType);
+ dest.writeString(mId);
+ dest.writeInt(mState);
+ dest.writeString(mName);
+
+ // Complete the parcel info for the concierge
+ parcelInfo.complete();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * Get the {@link ProfileTrigger} {@link TriggerType}
+ * @return {@link TriggerType}
+ */
+ public int getType() {
+ return mType;
+ }
+
+ /**
+ * Get the name associated with the {@link ProfileTrigger}
+ * @return a string name
+ */
+ public String getName() {
+ return mName;
+ }
+
+ /**
+ * Get the id associated with the {@link ProfileTrigger}
+ * @return an string identifier
+ */
+ public String getId() {
+ return mId;
+ }
+
+ /**
+ * Get the state associated with the {@link ProfileTrigger}
+ * @return an integer indicating the state
+ */
+ public int getState() {
+ return mState;
+ }
+
+ /**
+ * @hide
+ */
+ public void getXmlString(StringBuilder builder, Context context) {
+ final String itemType = mType == TriggerType.WIFI ? "wifiAP" : "btDevice";
+
+ builder.append("<");
+ builder.append(itemType);
+ builder.append(" ");
+ builder.append(getIdType(mType));
+ builder.append("=\"");
+ builder.append(mId);
+ builder.append("\" state=\"");
+ builder.append(mState);
+ builder.append("\" name=\"");
+ builder.append(mName);
+ builder.append("\"></");
+ builder.append(itemType);
+ builder.append(">\n");
+ }
+
+ /**
+ * @hide
+ */
+ public static ProfileTrigger fromXml(XmlPullParser xpp, Context context) {
+ final String name = xpp.getName();
+ final int type;
+
+ if (name.equals("wifiAP")) {
+ type = TriggerType.WIFI;
+ } else if (name.equals("btDevice")) {
+ type = TriggerType.BLUETOOTH;
+ } else {
+ return null;
+ }
+
+ String id = xpp.getAttributeValue(null, getIdType(type));
+ int state = Integer.valueOf(xpp.getAttributeValue(null, "state"));
+ String triggerName = xpp.getAttributeValue(null, "name");
+ if (triggerName == null) {
+ triggerName = id;
+ }
+
+ return new ProfileTrigger(type, id, state, triggerName);
+ }
+
+ private static String getIdType(int type) {
+ return type == TriggerType.WIFI ? "ssid" : "address";
+ }
+
+ /**
+ * @hide
+ */
+ public static final Parcelable.Creator<ProfileTrigger> CREATOR
+ = new Parcelable.Creator<ProfileTrigger>() {
+ public ProfileTrigger createFromParcel(Parcel in) {
+ return new ProfileTrigger(in);
+ }
+
+ @Override
+ public ProfileTrigger[] newArray(int size) {
+ return new ProfileTrigger[size];
+ }
+ };
+ }
+
+ /** @hide */
+ public static final Parcelable.Creator<Profile> CREATOR = new Parcelable.Creator<Profile>() {
+ public Profile createFromParcel(Parcel in) {
+ return new Profile(in);
+ }
+
+ @Override
+ public Profile[] newArray(int size) {
+ return new Profile[size];
+ }
+ };
+
+ public Profile(String name) {
+ this(name, -1, UUID.randomUUID());
+ }
+
+ /** @hide */
+ public Profile(String name, int nameResId, UUID uuid) {
+ mName = name;
+ mNameResId = nameResId;
+ mUuid = uuid;
+ mProfileType = Type.TOGGLE; //Default to toggle type
+ mDirty = false;
+ }
+
+ private Profile(Parcel in) {
+ readFromParcel(in);
+ }
+
+ /**
+ * Get the {@link TriggerState} for a {@link ProfileTrigger} with a given id
+ * @param type {@link TriggerType}
+ * @param id string id of {@link ProfileTrigger}
+ * @return {@link TriggerState}
+ */
+ public int getTriggerState(int type, String id) {
+ ProfileTrigger trigger = id != null ? mTriggers.get(id) : null;
+ if (trigger != null) {
+ return trigger.mState;
+ }
+ return TriggerState.DISABLED;
+ }
+
+ /**
+ * Get all the {@link ProfileTrigger}s for a given {@link TriggerType}
+ * @param type {@link TriggerType}
+ * @return an array list of {@link ProfileTrigger}s
+ */
+ public ArrayList<ProfileTrigger> getTriggersFromType(int type) {
+ ArrayList<ProfileTrigger> result = new ArrayList<ProfileTrigger>();
+ for (Entry<String, ProfileTrigger> profileTrigger: mTriggers.entrySet()) {
+ ProfileTrigger trigger = profileTrigger.getValue();
+ if (trigger.getType() == type) {
+ result.add(trigger);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Set a custom {@link ProfileTrigger}
+ * @hide
+ */
+ public void setTrigger(int type, String id, int state, String name) {
+ if (id == null
+ || type < TriggerType.WIFI || type > TriggerType.BLUETOOTH
+ || state < TriggerState.ON_CONNECT || state > TriggerState.ON_A2DP_DISCONNECT) {
+ return;
+ }
+
+ ProfileTrigger trigger = mTriggers.get(id);
+
+ if (state == TriggerState.DISABLED) {
+ if (trigger != null) {
+ mTriggers.remove(id);
+ }
+ } else if (trigger != null) {
+ trigger.mState = state;
+ } else {
+ mTriggers.put(id, new ProfileTrigger(type, id, state, name));
+ }
+
+ mDirty = true;
+ }
+
+ /**
+ * Set a {@link ProfileTrigger} on the {@link Profile}
+ * @param trigger a {@link ProfileTrigger}
+ */
+ public void setTrigger(ProfileTrigger trigger) {
+ setTrigger(trigger.getType(), trigger.getId(), trigger.getState(), trigger.getName());
+ }
+
+ public int compareTo(Object obj) {
+ Profile tmp = (Profile) obj;
+ if (mName.compareTo(tmp.mName) < 0) {
+ return -1;
+ } else if (mName.compareTo(tmp.mName) > 0) {
+ return 1;
+ }
+ return 0;
+ }
+
+ /**
+ * Add a {@link ProfileGroup} to the {@link Profile}
+ * @param profileGroup
+ * @hide
+ */
+ public void addProfileGroup(ProfileGroup profileGroup) {
+ if (profileGroup == null) {
+ return;
+ }
+
+ if (profileGroup.isDefaultGroup()) {
+ /* we must not have more than one default group */
+ if (mDefaultGroup != null) {
+ return;
+ }
+ mDefaultGroup = profileGroup;
+ }
+ profileGroups.put(profileGroup.getUuid(), profileGroup);
+ mDirty = true;
+ }
+
+ /**
+ * Remove a {@link ProfileGroup} with a given {@link UUID}
+ * @param uuid
+ * @hide
+ */
+ public void removeProfileGroup(UUID uuid) {
+ if (!profileGroups.get(uuid).isDefaultGroup()) {
+ profileGroups.remove(uuid);
+ } else {
+ Log.e(TAG, "Cannot remove default group: " + uuid);
+ }
+ }
+
+ /**
+ * Get {@link ProfileGroup}s associated with the {@link Profile}
+ * @return {@link ProfileGroup[]}
+ * @hide
+ */
+ public ProfileGroup[] getProfileGroups() {
+ return profileGroups.values().toArray(new ProfileGroup[profileGroups.size()]);
+ }
+
+ /**
+ * Get a {@link ProfileGroup} with a given {@link UUID}
+ * @param uuid
+ * @return a {@link ProfileGroup}
+ * @hide
+ */
+ public ProfileGroup getProfileGroup(UUID uuid) {
+ return profileGroups.get(uuid);
+ }
+
+ /**
+ * Get the default {@link ProfileGroup} associated with the {@link Profile}
+ * @return the default {@link ProfileGroup}
+ * @hide
+ */
+ public ProfileGroup getDefaultGroup() {
+ return mDefaultGroup;
+ }
+
+ /** @hide */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /** @hide */
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ // Tell the concierge to prepare the parcel
+ ParcelInfo parcelInfo = Concierge.prepareParcel(dest);
+
+ // === BOYSENBERRY ===
+ if (!TextUtils.isEmpty(mName)) {
+ dest.writeInt(1);
+ dest.writeString(mName);
+ } else {
+ dest.writeInt(0);
+ }
+ if (mNameResId != 0) {
+ dest.writeInt(1);
+ dest.writeInt(mNameResId);
+ } else {
+ dest.writeInt(0);
+ }
+ if (mUuid != null) {
+ dest.writeInt(1);
+ new ParcelUuid(mUuid).writeToParcel(dest, 0);
+ } else {
+ dest.writeInt(0);
+ }
+ if (mSecondaryUuids != null && !mSecondaryUuids.isEmpty()) {
+ ArrayList<ParcelUuid> uuids = new ArrayList<ParcelUuid>(mSecondaryUuids.size());
+ for (UUID u : mSecondaryUuids) {
+ uuids.add(new ParcelUuid(u));
+ }
+ dest.writeInt(1);
+ dest.writeParcelableArray(uuids.toArray(new Parcelable[uuids.size()]), flags);
+ } else {
+ dest.writeInt(0);
+ }
+ dest.writeInt(mStatusBarIndicator ? 1 : 0);
+ dest.writeInt(mProfileType);
+ dest.writeInt(mDirty ? 1 : 0);
+ if (profileGroups != null && !profileGroups.isEmpty()) {
+ dest.writeInt(1);
+ dest.writeTypedArray(profileGroups.values().toArray(
+ new ProfileGroup[0]), flags);
+ } else {
+ dest.writeInt(0);
+ }
+ if (streams != null && !streams.isEmpty()) {
+ dest.writeInt(1);
+ dest.writeTypedArray(streams.values().toArray(
+ new StreamSettings[0]), flags);
+ } else {
+ dest.writeInt(0);
+ }
+ if (connections != null && !connections.isEmpty()) {
+ dest.writeInt(1);
+ dest.writeTypedArray(connections.values().toArray(
+ new ConnectionSettings[0]), flags);
+ } else {
+ dest.writeInt(0);
+ }
+ if (mRingMode != null) {
+ dest.writeInt(1);
+ mRingMode.writeToParcel(dest, 0);
+ } else {
+ dest.writeInt(0);
+ }
+ if (mAirplaneMode != null) {
+ dest.writeInt(1);
+ mAirplaneMode.writeToParcel(dest, 0);
+ } else {
+ dest.writeInt(0);
+ }
+ if (mBrightness != null) {
+ dest.writeInt(1);
+ mBrightness.writeToParcel(dest, 0);
+ } else {
+ dest.writeInt(0);
+ }
+ if (mScreenLockMode != null) {
+ dest.writeInt(1);
+ mScreenLockMode.writeToParcel(dest, 0);
+ } else {
+ dest.writeInt(0);
+ }
+ dest.writeTypedArray(mTriggers.values().toArray(new ProfileTrigger[0]), flags);
+ dest.writeInt(mExpandedDesktopMode);
+ dest.writeInt(mDozeMode);
+
+ // === ELDERBERRY ===
+ dest.writeInt(mNotificationLightMode);
+
+ if (networkConnectionSubIds != null && !networkConnectionSubIds.isEmpty()) {
+ dest.writeInt(1);
+ dest.writeTypedArray(networkConnectionSubIds.values().toArray(
+ new ConnectionSettings[0]), flags);
+ } else {
+ dest.writeInt(0);
+ }
+
+ // Complete the parcel info for the concierge
+ parcelInfo.complete();
+ }
+
+ /** @hide */
+ public void readFromParcel(Parcel in) {
+ // Read parcelable version via the Concierge
+ ParcelInfo parcelInfo = Concierge.receiveParcel(in);
+ int parcelableVersion = parcelInfo.getParcelVersion();
+
+ // Pattern here is that all new members should be added to the end of
+ // the writeToParcel method. Then we step through each version, until the latest
+ // API release to help unravel this parcel
+ if (parcelableVersion >= Build.CM_VERSION_CODES.BOYSENBERRY) {
+ if (in.readInt() != 0) {
+ mName = in.readString();
+ }
+ if (in.readInt() != 0) {
+ mNameResId = in.readInt();
+ }
+ if (in.readInt() != 0) {
+ mUuid = ParcelUuid.CREATOR.createFromParcel(in).getUuid();
+ }
+ if (in.readInt() != 0) {
+ for (Parcelable parcel : in.readParcelableArray(null)) {
+ ParcelUuid u = (ParcelUuid) parcel;
+ mSecondaryUuids.add(u.getUuid());
+ }
+ }
+ mStatusBarIndicator = (in.readInt() == 1);
+ mProfileType = in.readInt();
+ mDirty = (in.readInt() == 1);
+ if (in.readInt() != 0) {
+ for (ProfileGroup group : in.createTypedArray(ProfileGroup.CREATOR)) {
+ profileGroups.put(group.getUuid(), group);
+ if (group.isDefaultGroup()) {
+ mDefaultGroup = group;
+ }
+ }
+ }
+ if (in.readInt() != 0) {
+ for (StreamSettings stream : in.createTypedArray(StreamSettings.CREATOR)) {
+ streams.put(stream.getStreamId(), stream);
+ }
+ }
+ if (in.readInt() != 0) {
+ for (ConnectionSettings connection :
+ in.createTypedArray(ConnectionSettings.CREATOR)) {
+ connections.put(connection.getConnectionId(), connection);
+ }
+ }
+ if (in.readInt() != 0) {
+ mRingMode = RingModeSettings.CREATOR.createFromParcel(in);
+ }
+ if (in.readInt() != 0) {
+ mAirplaneMode = AirplaneModeSettings.CREATOR.createFromParcel(in);
+ }
+ if (in.readInt() != 0) {
+ mBrightness = BrightnessSettings.CREATOR.createFromParcel(in);
+ }
+ if (in.readInt() != 0) {
+ mScreenLockMode = LockSettings.CREATOR.createFromParcel(in);
+ }
+ for (ProfileTrigger trigger : in.createTypedArray(ProfileTrigger.CREATOR)) {
+ mTriggers.put(trigger.mId, trigger);
+ }
+ mExpandedDesktopMode = in.readInt();
+ mDozeMode = in.readInt();
+ }
+ if (parcelableVersion >= Build.CM_VERSION_CODES.ELDERBERRY) {
+ mNotificationLightMode = in.readInt();
+ if (in.readInt() != 0) {
+ for (ConnectionSettings connection :
+ in.createTypedArray(ConnectionSettings.CREATOR)) {
+ // elderberry can do msim connections
+ networkConnectionSubIds.put(connection.getSubId(), connection);
+ }
+ }
+ }
+
+ // Complete the parcel info for the concierge
+ parcelInfo.complete();
+ }
+
+ /**
+ * Get the name associated with the {@link Profile}
+ * @return a string name of the profile
+ */
+ public String getName() {
+ return mName;
+ }
+
+ /**
+ * Set a name for the {@link Profile}
+ * @param name a string for the {@link Profile}
+ */
+ public void setName(String name) {
+ mName = name;
+ mNameResId = -1;
+ mDirty = true;
+ }
+
+ /**
+ * Get the {@link Type} of the {@link Profile}
+ * @return
+ */
+ public int getProfileType() {
+ return mProfileType;
+ }
+
+ /**
+ * Set the {@link Type} for the {@link Profile}
+ * @param type a type of profile
+ */
+ public void setProfileType(int type) {
+ mProfileType = type;
+ mDirty = true;
+ }
+
+ /**
+ * Get the {@link UUID} associated with the {@link Profile}
+ * @return the uuid for the profile
+ */
+ public UUID getUuid() {
+ if (this.mUuid == null) this.mUuid = UUID.randomUUID();
+ return this.mUuid;
+ }
+
+ /**
+ * Get the secondary {@link UUID}s for the {@link Profile}
+ * @return the secondary uuids for the Profile
+ */
+ public UUID[] getSecondaryUuids() {
+ return mSecondaryUuids.toArray(new UUID[mSecondaryUuids.size()]);
+ }
+
+ /**
+ * Set a list of secondary {@link UUID}s for the {@link Profile}
+ * @param uuids
+ */
+ public void setSecondaryUuids(List<UUID> uuids) {
+ mSecondaryUuids.clear();
+ if (uuids != null) {
+ mSecondaryUuids.addAll(uuids);
+ mDirty = true;
+ }
+ }
+
+ /**
+ * Add a secondary {@link UUID} to the {@link Profile}
+ * @param uuid
+ */
+ public void addSecondaryUuid(UUID uuid) {
+ if (uuid != null) {
+ mSecondaryUuids.add(uuid);
+ mDirty = true;
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public boolean getStatusBarIndicator() {
+ return mStatusBarIndicator;
+ }
+
+ /**
+ * @hide
+ */
+ public void setStatusBarIndicator(boolean newStatusBarIndicator) {
+ mStatusBarIndicator = newStatusBarIndicator;
+ mDirty = true;
+ }
+
+ /**
+ * Check if the given {@link Profile} is a {@link Type#CONDITIONAL}
+ * @return true if conditional
+ */
+ public boolean isConditionalType() {
+ return(mProfileType == Type.CONDITIONAL ? true : false);
+ }
+
+ /**
+ * @hide
+ */
+ public void setConditionalType() {
+ mProfileType = Type.CONDITIONAL;
+ mDirty = true;
+ }
+
+ /**
+ * Get the {@link RingModeSettings} for the {@link Profile}
+ * @return
+ */
+ public RingModeSettings getRingMode() {
+ return mRingMode;
+ }
+
+ /**
+ * Set the {@link RingModeSettings} for the {@link Profile}
+ * @param descriptor
+ */
+ public void setRingMode(RingModeSettings descriptor) {
+ mRingMode = descriptor;
+ mDirty = true;
+ }
+
+ /**
+ * Get the {@link LockSettings} for the {@link Profile}
+ * @return
+ */
+ public LockSettings getScreenLockMode() {
+ return mScreenLockMode;
+ }
+
+ /**
+ * Set the {@link LockSettings} for the {@link Profile}
+ * @param screenLockMode
+ */
+ public void setScreenLockMode(LockSettings screenLockMode) {
+ mScreenLockMode = screenLockMode;
+ mDirty = true;
+ }
+
+ /**
+ * Get the {@link ExpandedDesktopMode} for the {@link Profile}
+ * @return
+ */
+ public int getExpandedDesktopMode() {
+ return mExpandedDesktopMode;
+ }
+
+ /**
+ * Set the {@link ExpandedDesktopMode} for the {@link Profile}
+ * @return
+ */
+ public void setExpandedDesktopMode(int expandedDesktopMode) {
+ if (expandedDesktopMode < ExpandedDesktopMode.DEFAULT
+ || expandedDesktopMode > ExpandedDesktopMode.DISABLE) {
+ mExpandedDesktopMode = ExpandedDesktopMode.DEFAULT;
+ } else {
+ mExpandedDesktopMode = expandedDesktopMode;
+ }
+ mDirty = true;
+ }
+
+ /**
+ * Get the {@link DozeMode} associated with the {@link Profile}
+ * @return
+ */
+ public int getDozeMode() {
+ return mDozeMode;
+ }
+
+ /**
+ * Set the {@link DozeMode} associated with the {@link Profile}
+ * @return
+ */
+ public void setDozeMode(int dozeMode) {
+ if (dozeMode < DozeMode.DEFAULT
+ || dozeMode > DozeMode.DISABLE) {
+ mDozeMode = DozeMode.DEFAULT;
+ } else {
+ mDozeMode = dozeMode;
+ }
+ mDirty = true;
+ }
+
+ /**
+ * Get the {@link NotificationLightMode} associated with the {@link Profile}
+ * @return
+ */
+ public int getNotificationLightMode() {
+ return mNotificationLightMode;
+ }
+
+ /**
+ * Set the {@link NotificationLightMode} associated with the {@link Profile}
+ * @return
+ */
+ public void setNotificationLightMode(int notificationLightMode) {
+ if (notificationLightMode < NotificationLightMode.DEFAULT
+ || notificationLightMode > NotificationLightMode.DISABLE) {
+ mNotificationLightMode = NotificationLightMode.DEFAULT;
+ } else {
+ mNotificationLightMode = notificationLightMode;
+ }
+ mDirty = true;
+ }
+
+ /**
+ * Get the {@link AirplaneModeSettings} associated with the {@link Profile}
+ * @return
+ */
+ public AirplaneModeSettings getAirplaneMode() {
+ return mAirplaneMode;
+ }
+
+ /**
+ * Set the {@link AirplaneModeSettings} associated with the {@link Profile}
+ * @param descriptor
+ */
+ public void setAirplaneMode(AirplaneModeSettings descriptor) {
+ mAirplaneMode = descriptor;
+ mDirty = true;
+ }
+
+ /**
+ * Get the {@link BrightnessSettings} associated with the {@link Profile}
+ * @return
+ */
+ public BrightnessSettings getBrightness() {
+ return mBrightness;
+ }
+
+ /**
+ * Set the {@link BrightnessSettings} associated with the {@link Profile}
+ * @return
+ */
+ public void setBrightness(BrightnessSettings descriptor) {
+ mBrightness = descriptor;
+ mDirty = true;
+ }
+
+ /** @hide */
+ public boolean isDirty() {
+ if (mDirty) {
+ return true;
+ }
+ for (ProfileGroup group : profileGroups.values()) {
+ if (group.isDirty()) {
+ return true;
+ }
+ }
+ for (StreamSettings stream : streams.values()) {
+ if (stream.isDirty()) {
+ return true;
+ }
+ }
+ for (ConnectionSettings conn : connections.values()) {
+ if (conn.isDirty()) {
+ return true;
+ }
+ }
+ for (ConnectionSettings conn : networkConnectionSubIds.values()) {
+ if (conn.isDirty()) {
+ return true;
+ }
+ }
+ if (mRingMode.isDirty()) {
+ return true;
+ }
+ if (mAirplaneMode.isDirty()) {
+ return true;
+ }
+ if (mBrightness.isDirty()) {
+ return true;
+ }
+ return false;
+ }
+
+ /** @hide */
+ public void getXmlString(StringBuilder builder, Context context) {
+ builder.append("<profile ");
+ if (mNameResId > 0) {
+ builder.append("nameres=\"");
+ builder.append(context.getResources().getResourceEntryName(mNameResId));
+ } else {
+ builder.append("name=\"");
+ builder.append(TextUtils.htmlEncode(getName()));
+ }
+ builder.append("\" uuid=\"");
+ builder.append(TextUtils.htmlEncode(getUuid().toString()));
+ builder.append("\">\n");
+
+ builder.append("<uuids>");
+ for (UUID u : mSecondaryUuids) {
+ builder.append("<uuid>");
+ builder.append(TextUtils.htmlEncode(u.toString()));
+ builder.append("</uuid>");
+ }
+ builder.append("</uuids>\n");
+
+ builder.append("<profiletype>");
+ builder.append(getProfileType() == Type.TOGGLE ? "toggle" : "conditional");
+ builder.append("</profiletype>\n");
+
+ builder.append("<statusbar>");
+ builder.append(getStatusBarIndicator() ? "yes" : "no");
+ builder.append("</statusbar>\n");
+
+ if (mScreenLockMode != null) {
+ builder.append("<screen-lock-mode>");
+ mScreenLockMode.writeXmlString(builder, context);
+ builder.append("</screen-lock-mode>\n");
+ }
+
+ builder.append("<expanded-desktop-mode>");
+ builder.append(mExpandedDesktopMode);
+ builder.append("</expanded-desktop-mode>\n");
+
+ builder.append("<doze-mode>");
+ builder.append(mDozeMode);
+ builder.append("</doze-mode>\n");
+
+ builder.append("<notification-light-mode>");
+ builder.append(mNotificationLightMode);
+ builder.append("</notification-light-mode>\n");
+
+ mAirplaneMode.getXmlString(builder, context);
+
+ mBrightness.getXmlString(builder, context);
+
+ mRingMode.getXmlString(builder, context);
+
+ for (ProfileGroup pGroup : profileGroups.values()) {
+ pGroup.getXmlString(builder, context);
+ }
+ for (StreamSettings sd : streams.values()) {
+ sd.getXmlString(builder, context);
+ }
+ for (ConnectionSettings cs : connections.values()) {
+ cs.getXmlString(builder, context);
+ }
+ for (ConnectionSettings cs : networkConnectionSubIds.values()) {
+ cs.getXmlString(builder, context);
+ }
+ if (!mTriggers.isEmpty()) {
+ builder.append("<triggers>\n");
+ for (ProfileTrigger trigger : mTriggers.values()) {
+ trigger.getXmlString(builder, context);
+ }
+ builder.append("</triggers>\n");
+ }
+
+ builder.append("</profile>\n");
+ mDirty = false;
+ }
+
+ private static List<UUID> readSecondaryUuidsFromXml(XmlPullParser xpp, Context context)
+ throws XmlPullParserException,
+ IOException {
+ ArrayList<UUID> uuids = new ArrayList<UUID>();
+ int event = xpp.next();
+ while (event != XmlPullParser.END_TAG || !xpp.getName().equals("uuids")) {
+ if (event == XmlPullParser.START_TAG) {
+ String name = xpp.getName();
+ if (name.equals("uuid")) {
+ try {
+ uuids.add(UUID.fromString(xpp.nextText()));
+ } catch (NullPointerException e) {
+ Log.w(TAG, "Null Pointer - invalid UUID");
+ } catch (IllegalArgumentException e) {
+ Log.w(TAG, "UUID not recognized");
+ }
+ }
+ }
+ event = xpp.next();
+ }
+ return uuids;
+ }
+
+ private static void readTriggersFromXml(XmlPullParser xpp, Context context, Profile profile)
+ throws XmlPullParserException, IOException {
+ int event = xpp.next();
+ while (event != XmlPullParser.END_TAG || !xpp.getName().equals("triggers")) {
+ if (event == XmlPullParser.START_TAG) {
+ ProfileTrigger trigger = ProfileTrigger.fromXml(xpp, context);
+ if (trigger != null) {
+ profile.mTriggers.put(trigger.mId, trigger);
+ }
+ } else if (event == XmlPullParser.END_DOCUMENT) {
+ throw new IOException("Premature end of file while parsing triggers");
+ }
+ event = xpp.next();
+ }
+ }
+
+ /** @hide */
+ public void validateRingtones(Context context) {
+ for (ProfileGroup pg : profileGroups.values()) {
+ pg.validateOverrideUris(context);
+ }
+ }
+
+ /** @hide */
+ public static Profile fromXml(XmlPullParser xpp, Context context)
+ throws XmlPullParserException, IOException {
+ String value = xpp.getAttributeValue(null, "nameres");
+ int profileNameResId = -1;
+ String profileName = null;
+
+ if (value != null) {
+ profileNameResId = context.getResources().getIdentifier(value, "string",
+ "cyanogenmod.platform");
+ if (profileNameResId > 0) {
+ profileName = context.getResources().getString(profileNameResId);
+ }
+ }
+
+ if (profileName == null) {
+ profileName = xpp.getAttributeValue(null, "name");
+ }
+
+ UUID profileUuid = UUID.randomUUID();
+ try {
+ profileUuid = UUID.fromString(xpp.getAttributeValue(null, "uuid"));
+ } catch (NullPointerException e) {
+ Log.w(TAG,
+ "Null Pointer - UUID not found for "
+ + profileName
+ + ". New UUID generated: "
+ + profileUuid.toString()
+ );
+ } catch (IllegalArgumentException e) {
+ Log.w(TAG,
+ "UUID not recognized for "
+ + profileName
+ + ". New UUID generated: "
+ + profileUuid.toString()
+ );
+ }
+
+ Profile profile = new Profile(profileName, profileNameResId, profileUuid);
+ int event = xpp.next();
+ while (event != XmlPullParser.END_TAG) {
+ if (event == XmlPullParser.START_TAG) {
+ String name = xpp.getName();
+ if (name.equals("uuids")) {
+ profile.setSecondaryUuids(readSecondaryUuidsFromXml(xpp, context));
+ }
+ if (name.equals("statusbar")) {
+ profile.setStatusBarIndicator(xpp.nextText().equals("yes"));
+ }
+ if (name.equals("profiletype")) {
+ profile.setProfileType(xpp.nextText().equals("toggle")
+ ? Type.TOGGLE : Type.CONDITIONAL);
+ }
+ if (name.equals("ringModeDescriptor")) {
+ RingModeSettings smd = RingModeSettings.fromXml(xpp, context);
+ profile.setRingMode(smd);
+ }
+ if (name.equals("airplaneModeDescriptor")) {
+ AirplaneModeSettings amd = AirplaneModeSettings.fromXml(xpp, context);
+ profile.setAirplaneMode(amd);
+ }
+ if (name.equals("brightnessDescriptor")) {
+ BrightnessSettings bd = BrightnessSettings.fromXml(xpp, context);
+ profile.setBrightness(bd);
+ }
+ if (name.equals("screen-lock-mode")) {
+ LockSettings lockMode = new LockSettings(Integer.valueOf(xpp.nextText()));
+ profile.setScreenLockMode(lockMode);
+ }
+ if (name.equals("expanded-desktop-mode")) {
+ profile.setExpandedDesktopMode(Integer.valueOf(xpp.nextText()));
+ }
+ if (name.equals("doze-mode")) {
+ profile.setDozeMode(Integer.valueOf(xpp.nextText()));
+ }
+ if (name.equals("notification-light-mode")) {
+ profile.setNotificationLightMode(Integer.valueOf(xpp.nextText()));
+ }
+ if (name.equals("profileGroup")) {
+ ProfileGroup pg = ProfileGroup.fromXml(xpp, context);
+ profile.addProfileGroup(pg);
+ }
+ if (name.equals("streamDescriptor")) {
+ StreamSettings sd = StreamSettings.fromXml(xpp, context);
+ profile.setStreamSettings(sd);
+ }
+ if (name.equals("connectionDescriptor")) {
+ ConnectionSettings cs = ConnectionSettings.fromXml(xpp, context);
+ if (Build.CM_VERSION.SDK_INT >= Build.CM_VERSION_CODES.ELDERBERRY
+ && cs.getConnectionId() == ConnectionSettings.PROFILE_CONNECTION_2G3G4G) {
+ profile.networkConnectionSubIds.put(cs.getSubId(), cs);
+ } else {
+ profile.connections.put(cs.getConnectionId(), cs);
+ }
+ }
+ if (name.equals("triggers")) {
+ readTriggersFromXml(xpp, context, profile);
+ }
+ } else if (event == XmlPullParser.END_DOCUMENT) {
+ throw new IOException("Premature end of file while parsing profle:" + profileName);
+ }
+ event = xpp.next();
+ }
+
+ /* we just loaded from XML, so nothing needs saving */
+ profile.mDirty = false;
+
+ return profile;
+ }
+
+ /** @hide */
+ public void doSelect(Context context, IKeyguardService keyguardService) {
+ // Set stream volumes
+ AudioManager am = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+ for (StreamSettings sd : streams.values()) {
+ if (sd.isOverride()) {
+ am.setStreamVolume(sd.getStreamId(), sd.getValue(), 0);
+ }
+ }
+ // Set connections
+ for (ConnectionSettings cs : connections.values()) {
+ if (cs.isOverride()) {
+ cs.processOverride(context);
+ }
+ }
+ for (ConnectionSettings cs : networkConnectionSubIds.values()) {
+ if (cs.isOverride()) {
+ cs.processOverride(context);
+ }
+ }
+
+ // Set ring mode
+ mRingMode.processOverride(context);
+ // Set airplane mode
+ mAirplaneMode.processOverride(context);
+
+ // Set brightness
+ mBrightness.processOverride(context);
+
+ if (keyguardService != null) {
+ // Set lock screen mode
+ mScreenLockMode.processOverride(context, keyguardService);
+ } else {
+ Log.e(TAG, "cannot process screen lock override without a keyguard service.");
+ }
+
+ // Set expanded desktop
+ // if (mExpandedDesktopMode != ExpandedDesktopMode.DEFAULT) {
+ // Settings.System.putIntForUser(context.getContentResolver(),
+ // Settings.System.EXPANDED_DESKTOP_STATE,
+ // mExpandedDesktopMode == ExpandedDesktopMode.ENABLE ? 1 : 0,
+ // UserHandle.USER_CURRENT);
+ // }
+
+ // Set doze mode
+ if (mDozeMode != DozeMode.DEFAULT) {
+ Settings.Secure.putIntForUser(context.getContentResolver(),
+ Settings.Secure.DOZE_ENABLED,
+ mDozeMode == DozeMode.ENABLE ? 1 : 0,
+ UserHandle.USER_CURRENT);
+ }
+
+ // Set notification light mode
+ if (mNotificationLightMode != NotificationLightMode.DEFAULT) {
+ Settings.System.putIntForUser(context.getContentResolver(),
+ Settings.System.NOTIFICATION_LIGHT_PULSE,
+ mNotificationLightMode == NotificationLightMode.ENABLE ? 1 : 0,
+ UserHandle.USER_CURRENT);
+ }
+ }
+
+ /**
+ * Get the settings for a stream id in the {@link Profile}
+ * @return {@link StreamSettings}
+ */
+ public StreamSettings getSettingsForStream(int streamId){
+ return streams.get(streamId);
+ }
+
+ /**
+ * Set the {@link StreamSettings} for the {@link Profile}
+ * @param descriptor
+ */
+ public void setStreamSettings(StreamSettings descriptor){
+ streams.put(descriptor.getStreamId(), descriptor);
+ mDirty = true;
+ }
+
+ /**
+ * Get the {@link StreamSettings} for the {@link Profile}
+ * @return {@link Collection<StreamSettings>}
+ */
+ public Collection<StreamSettings> getStreamSettings(){
+ return streams.values();
+ }
+
+ /**
+ * Get the settings for a connection id in the {@link Profile}
+ * @return {@link ConnectionSettings}
+ */
+ public ConnectionSettings getSettingsForConnection(int connectionId){
+ if (connectionId == ConnectionSettings.PROFILE_CONNECTION_2G3G4G) {
+ if (networkConnectionSubIds.size() > 1) {
+ throw new UnsupportedOperationException("Use getConnectionSettingsWithSubId for MSIM devices!");
+ } else {
+ return networkConnectionSubIds.values().iterator().next();
+ }
+ }
+ return connections.get(connectionId);
+ }
+
+ /**
+ * Get the settings for a {@link ConnectionSettings#PROFILE_CONNECTION_2G3G4G} by sub id.
+ *
+ * @param subId the sub id to lookup. Can be {@link android.telephony.SubscriptionManager#INVALID_SUBSCRIPTION_ID}
+ * @return {@link ConnectionSettings}
+ */
+ public ConnectionSettings getConnectionSettingWithSubId(int subId) {
+ return networkConnectionSubIds.get(subId);
+ }
+
+ /**
+ * Set the {@link ConnectionSettings} for the {@link Profile}
+ * @param descriptor
+ */
+ public void setConnectionSettings(ConnectionSettings descriptor) {
+ if (descriptor.getConnectionId() == ConnectionSettings.PROFILE_CONNECTION_2G3G4G) {
+ networkConnectionSubIds.put(descriptor.getSubId(), descriptor);
+ } else {
+ connections.put(descriptor.getConnectionId(), descriptor);
+ }
+ mDirty = true;
+ }
+
+ /**
+ * Get the {@link ConnectionSettings} for the {@link Profile}
+ * @return {@link Collection<ConnectionSettings>}
+ */
+ public Collection<ConnectionSettings> getConnectionSettings(){
+ List<ConnectionSettings> combinedList = new ArrayList<>();
+ combinedList.addAll(connections.values());
+ combinedList.addAll(networkConnectionSubIds.values());
+ return combinedList;
+ }
+}
diff --git a/sdk/src/java/cyanogenmod/app/ProfileGroup.java b/sdk/src/java/cyanogenmod/app/ProfileGroup.java
new file mode 100644
index 0000000..56ec507
--- /dev/null
+++ b/sdk/src/java/cyanogenmod/app/ProfileGroup.java
@@ -0,0 +1,395 @@
+/*
+ * 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.app;
+
+import android.app.Notification;
+import android.app.NotificationGroup;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.media.RingtoneManager;
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.ParcelUuid;
+import android.text.TextUtils;
+import android.util.Log;
+
+import cyanogenmod.os.Build;
+
+import cyanogenmod.os.Concierge;
+import cyanogenmod.os.Concierge.ParcelInfo;
+
+import java.io.IOException;
+import java.util.UUID;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+/**
+ * @hide
+ * TODO: This isn't ready for public use
+ */
+public final class ProfileGroup implements Parcelable {
+ private static final String TAG = "ProfileGroup";
+
+ private String mName;
+ private int mNameResId;
+
+ private UUID mUuid;
+
+ private Uri mSoundOverride = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
+ private Uri mRingerOverride = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE);
+
+ private Mode mSoundMode = Mode.DEFAULT;
+ private Mode mRingerMode = Mode.DEFAULT;
+ private Mode mVibrateMode = Mode.DEFAULT;
+ private Mode mLightsMode = Mode.DEFAULT;
+
+ private boolean mDefaultGroup = false;
+ private boolean mDirty;
+
+ /** @hide */
+ public static final Parcelable.Creator<ProfileGroup> CREATOR
+ = new Parcelable.Creator<ProfileGroup>() {
+ public ProfileGroup createFromParcel(Parcel in) {
+ return new ProfileGroup(in);
+ }
+
+ @Override
+ public ProfileGroup[] newArray(int size) {
+ return new ProfileGroup[size];
+ }
+ };
+
+ /** @hide */
+ public ProfileGroup(UUID uuid, boolean defaultGroup) {
+ this(null, uuid, defaultGroup);
+ }
+
+ private ProfileGroup(String name, UUID uuid, boolean defaultGroup) {
+ mName = name;
+ mUuid = (uuid != null) ? uuid : UUID.randomUUID();
+ mDefaultGroup = defaultGroup;
+ mDirty = uuid == null;
+ }
+
+ /** @hide */
+ private ProfileGroup(Parcel in) {
+ readFromParcel(in);
+ }
+
+ /** @hide */
+ public boolean matches(NotificationGroup group, boolean defaultGroup) {
+ if (mUuid.equals(group.getUuid())) {
+ return true;
+ }
+
+ /* fallback matches for backwards compatibility */
+ boolean matches = false;
+
+ /* fallback attempt 1: match name */
+ if (mName != null && mName.equals(group.getName())) {
+ matches = true;
+ /* fallback attempt 2: match for the 'defaultGroup' flag to match the wildcard group */
+ } else if (mDefaultGroup && defaultGroup) {
+ matches = true;
+ }
+
+ if (!matches) {
+ return false;
+ }
+
+ mName = null;
+ mUuid = group.getUuid();
+ mDirty = true;
+
+ return true;
+ }
+
+ public UUID getUuid() {
+ return mUuid;
+ }
+
+ public boolean isDefaultGroup() {
+ return mDefaultGroup;
+ }
+
+ /** @hide */
+ public boolean isDirty() {
+ return mDirty;
+ }
+
+ /** @hide */
+ public void setSoundOverride(Uri sound) {
+ mSoundOverride = sound;
+ mDirty = true;
+ }
+
+ public Uri getSoundOverride() {
+ return mSoundOverride;
+ }
+
+ /** @hide */
+ public void setRingerOverride(Uri ringer) {
+ mRingerOverride = ringer;
+ mDirty = true;
+ }
+
+ public Uri getRingerOverride() {
+ return mRingerOverride;
+ }
+
+ /** @hide */
+ public void setSoundMode(Mode soundMode) {
+ mSoundMode = soundMode;
+ mDirty = true;
+ }
+
+ public Mode getSoundMode() {
+ return mSoundMode;
+ }
+
+ /** @hide */
+ public void setRingerMode(Mode ringerMode) {
+ mRingerMode = ringerMode;
+ mDirty = true;
+ }
+
+ public Mode getRingerMode() {
+ return mRingerMode;
+ }
+
+ /** @hide */
+ public void setVibrateMode(Mode vibrateMode) {
+ mVibrateMode = vibrateMode;
+ mDirty = true;
+ }
+
+ public Mode getVibrateMode() {
+ return mVibrateMode;
+ }
+
+ /** @hide */
+ public void setLightsMode(Mode lightsMode) {
+ mLightsMode = lightsMode;
+ mDirty = true;
+ }
+
+ public Mode getLightsMode() {
+ return mLightsMode;
+ }
+
+ // TODO : add support for LEDs / screen etc.
+
+ /** @hide */
+ public void applyOverridesToNotification(Notification notification) {
+ switch (mSoundMode) {
+ case OVERRIDE:
+ notification.sound = mSoundOverride;
+ break;
+ case SUPPRESS:
+ notification.defaults &= ~Notification.DEFAULT_SOUND;
+ notification.sound = null;
+ break;
+ case DEFAULT:
+ break;
+ }
+ switch (mVibrateMode) {
+ case OVERRIDE:
+ notification.defaults |= Notification.DEFAULT_VIBRATE;
+ break;
+ case SUPPRESS:
+ notification.defaults &= ~Notification.DEFAULT_VIBRATE;
+ notification.vibrate = null;
+ break;
+ case DEFAULT:
+ break;
+ }
+ switch (mLightsMode) {
+ case OVERRIDE:
+ notification.defaults |= Notification.DEFAULT_LIGHTS;
+ break;
+ case SUPPRESS:
+ notification.defaults &= ~Notification.DEFAULT_LIGHTS;
+ notification.flags &= ~Notification.FLAG_SHOW_LIGHTS;
+ break;
+ case DEFAULT:
+ break;
+ }
+ }
+
+ private boolean validateOverrideUri(Context context, Uri uri) {
+ if (RingtoneManager.isDefault(uri)) {
+ return true;
+ }
+ Cursor cursor = context.getContentResolver().query(uri, null, null, null, null);
+ boolean valid = false;
+
+ if (cursor != null) {
+ valid = cursor.moveToFirst();
+ cursor.close();
+ }
+ return valid;
+ }
+
+ void validateOverrideUris(Context context) {
+ if (!validateOverrideUri(context, mSoundOverride)) {
+ mSoundOverride = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
+ mSoundMode = Mode.DEFAULT;
+ mDirty = true;
+ }
+ if (!validateOverrideUri(context, mRingerOverride)) {
+ mRingerOverride = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE);
+ mRingerMode = Mode.DEFAULT;
+ mDirty = true;
+ }
+ }
+
+ /** @hide */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /** @hide */
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ // Tell the concierge to prepare the parcel
+ ParcelInfo parcelInfo = Concierge.prepareParcel(dest);
+
+ // === BOYSENBERRY ===
+ dest.writeString(mName);
+ new ParcelUuid(mUuid).writeToParcel(dest, 0);
+ dest.writeInt(mDefaultGroup ? 1 : 0);
+ dest.writeInt(mDirty ? 1 : 0);
+ dest.writeParcelable(mSoundOverride, flags);
+ dest.writeParcelable(mRingerOverride, flags);
+ dest.writeString(mSoundMode.name());
+ dest.writeString(mRingerMode.name());
+ dest.writeString(mVibrateMode.name());
+ dest.writeString(mLightsMode.name());
+
+ // Complete the parcel info for the concierge
+ parcelInfo.complete();
+ }
+
+ /** @hide */
+ public void readFromParcel(Parcel in) {
+ // Read parcelable version via the Concierge
+ ParcelInfo parcelInfo = Concierge.receiveParcel(in);
+ int parcelableVersion = parcelInfo.getParcelVersion();
+
+ // Pattern here is that all new members should be added to the end of
+ // the writeToParcel method. Then we step through each version, until the latest
+ // API release to help unravel this parcel
+ if (parcelableVersion >= Build.CM_VERSION_CODES.BOYSENBERRY) {
+ mName = in.readString();
+ mUuid = ParcelUuid.CREATOR.createFromParcel(in).getUuid();
+ mDefaultGroup = in.readInt() != 0;
+ mDirty = in.readInt() != 0;
+ mSoundOverride = in.readParcelable(null);
+ mRingerOverride = in.readParcelable(null);
+
+ mSoundMode = Mode.valueOf(Mode.class, in.readString());
+ mRingerMode = Mode.valueOf(Mode.class, in.readString());
+ mVibrateMode = Mode.valueOf(Mode.class, in.readString());
+ mLightsMode = Mode.valueOf(Mode.class, in.readString());
+ }
+
+ // Complete parcel info for the concierge
+ parcelInfo.complete();
+ }
+
+ public enum Mode {
+ SUPPRESS, DEFAULT, OVERRIDE;
+ }
+
+ /** @hide */
+ public void getXmlString(StringBuilder builder, Context context) {
+ builder.append("<profileGroup uuid=\"");
+ builder.append(TextUtils.htmlEncode(mUuid.toString()));
+ if (mName != null) {
+ builder.append("\" name=\"");
+ builder.append(mName);
+ }
+ builder.append("\" default=\"");
+ builder.append(isDefaultGroup());
+ builder.append("\">\n<sound>");
+ builder.append(TextUtils.htmlEncode(mSoundOverride.toString()));
+ builder.append("</sound>\n<ringer>");
+ builder.append(TextUtils.htmlEncode(mRingerOverride.toString()));
+ builder.append("</ringer>\n<soundMode>");
+ builder.append(mSoundMode);
+ builder.append("</soundMode>\n<ringerMode>");
+ builder.append(mRingerMode);
+ builder.append("</ringerMode>\n<vibrateMode>");
+ builder.append(mVibrateMode);
+ builder.append("</vibrateMode>\n<lightsMode>");
+ builder.append(mLightsMode);
+ builder.append("</lightsMode>\n</profileGroup>\n");
+ mDirty = false;
+ }
+
+ /** @hide */
+ public static ProfileGroup fromXml(XmlPullParser xpp, Context context)
+ throws XmlPullParserException, IOException {
+ String name = xpp.getAttributeValue(null, "name");
+ UUID uuid = null;
+ String value = xpp.getAttributeValue(null, "uuid");
+
+ if (value != null) {
+ try {
+ uuid = UUID.fromString(value);
+ } catch (IllegalArgumentException e) {
+ Log.w(TAG, "UUID not recognized for " + name + ", using new one.");
+ }
+ }
+
+ value = xpp.getAttributeValue(null, "default");
+ boolean defaultGroup = TextUtils.equals(value, "true");
+
+ ProfileGroup profileGroup = new ProfileGroup(name, uuid, defaultGroup);
+ int event = xpp.next();
+ while (event != XmlPullParser.END_TAG || !xpp.getName().equals("profileGroup")) {
+ if (event == XmlPullParser.START_TAG) {
+ name = xpp.getName();
+ if (name.equals("sound")) {
+ profileGroup.setSoundOverride(Uri.parse(xpp.nextText()));
+ } else if (name.equals("ringer")) {
+ profileGroup.setRingerOverride(Uri.parse(xpp.nextText()));
+ } else if (name.equals("soundMode")) {
+ profileGroup.setSoundMode(Mode.valueOf(xpp.nextText()));
+ } else if (name.equals("ringerMode")) {
+ profileGroup.setRingerMode(Mode.valueOf(xpp.nextText()));
+ } else if (name.equals("vibrateMode")) {
+ profileGroup.setVibrateMode(Mode.valueOf(xpp.nextText()));
+ } else if (name.equals("lightsMode")) {
+ profileGroup.setLightsMode(Mode.valueOf(xpp.nextText()));
+ }
+ } else if (event == XmlPullParser.END_DOCUMENT) {
+ throw new IOException("Premature end of file while parsing profleGroup:" + name);
+ }
+ event = xpp.next();
+ }
+
+ /* we just loaded from XML, no need to save */
+ profileGroup.mDirty = false;
+
+ return profileGroup;
+ }
+}
diff --git a/sdk/src/java/cyanogenmod/app/ProfileManager.java b/sdk/src/java/cyanogenmod/app/ProfileManager.java
new file mode 100644
index 0000000..c2470cb
--- /dev/null
+++ b/sdk/src/java/cyanogenmod/app/ProfileManager.java
@@ -0,0 +1,555 @@
+/*
+ * 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.app;
+
+import java.util.UUID;
+
+import android.annotation.SdkConstant;
+import android.annotation.SdkConstant.SdkConstantType;
+import android.app.NotificationGroup;
+import android.content.Context;
+import android.os.IBinder;
+import android.os.ParcelUuid;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Log;
+
+import cyanogenmod.app.IProfileManager;
+
+import com.android.internal.R;
+import cyanogenmod.providers.CMSettings;
+
+
+/**
+ * <p>
+ * The ProfileManager allows you to create {@link Profile}s and {@link ProfileGroup}s to create
+ * specific behavior states depending on triggers from hardware devices changing states, such as:
+ *
+ * <pre class="prettyprint">
+ * WiFi being enabled
+ * WiFi connecting to a certain AP
+ * Bluetooth connecting to a certain device
+ * Bluetooth disconnecting to a certain device
+ * NFC tag being scanned
+ * </pre>
+ *
+ * <p>
+ * Depending on these triggers, you can override connection settings, lockscreen modes, media
+ * stream volumes and various other settings.
+ *
+ * <p>
+ * To get the instance of this class, utilize ProfileManager#getInstance(Context context)
+ *
+ * <p>
+ * This manager requires the MODIFY_PROFILES permission.
+ *
+ * @see cyanogenmod.app.Profile
+ * @see cyanogenmod.app.ProfileGroup
+ */
+public class ProfileManager {
+
+ private static IProfileManager sService;
+
+ private Context mContext;
+
+ private static final String TAG = "ProfileManager";
+
+ /**
+ * <p>Broadcast Action: A new profile has been selected. This can be triggered by the user
+ * or by calls to the ProfileManagerService / Profile.</p>
+ */
+ public static final String INTENT_ACTION_PROFILE_SELECTED =
+ "cyanogenmod.platform.intent.action.PROFILE_SELECTED";
+
+ /**
+ * <p>Broadcast Action: Current profile has been updated. This is triggered every time the
+ * currently active profile is updated, instead of selected.</p>
+ * <p> For instance, this includes profile updates caused by a locale change, which doesn't
+ * trigger a profile selection, but causes its name to change.</p>
+ */
+ public static final String INTENT_ACTION_PROFILE_UPDATED =
+ "cyanogenmod.platform.intent.action.PROFILE_UPDATED";
+
+ /**
+ * Extra for {@link #INTENT_ACTION_PROFILE_SELECTED} and {@link #INTENT_ACTION_PROFILE_UPDATED}:
+ * The name of the newly activated or updated profile
+ */
+ public static final String EXTRA_PROFILE_NAME = "name";
+
+ /**
+ * Extra for {@link #INTENT_ACTION_PROFILE_SELECTED} and {@link #INTENT_ACTION_PROFILE_UPDATED}:
+ * The string representation of the UUID of the newly activated or updated profile
+ */
+ public static final String EXTRA_PROFILE_UUID = "uuid";
+
+ /**
+ * Extra for {@link #INTENT_ACTION_PROFILE_SELECTED}:
+ * The name of the previously active profile
+ */
+ public static final String EXTRA_LAST_PROFILE_NAME = "lastName";
+
+ /**
+ * Extra for {@link #INTENT_ACTION_PROFILE_SELECTED}:
+ * The string representation of the UUID of the previously active profile
+ */
+ public static final String EXTRA_LAST_PROFILE_UUID = "lastUuid";
+
+ /**
+ * Activity Action: Shows a profile picker.
+ * <p>
+ * Input: {@link #EXTRA_PROFILE_EXISTING_UUID}, {@link #EXTRA_PROFILE_SHOW_NONE},
+ * {@link #EXTRA_PROFILE_TITLE}.
+ * <p>
+ * Output: {@link #EXTRA_PROFILE_PICKED_UUID}.
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_PROFILE_PICKER =
+ "cyanogenmod.platform.intent.action.PROFILE_PICKER";
+
+ /**
+ * Constant for NO_PROFILE
+ */
+ public static final UUID NO_PROFILE =
+ UUID.fromString("00000000-0000-0000-0000-000000000000");
+
+ /**
+ * Given to the profile picker as a boolean. Whether to show an item for
+ * deselect the profile. If the "None" item is picked,
+ * {@link #EXTRA_PROFILE_PICKED_UUID} will be {@link #NO_PROFILE}.
+ *
+ * @see #ACTION_PROFILE_PICKER
+ */
+ public static final String EXTRA_PROFILE_SHOW_NONE =
+ "cyanogenmod.platform.intent.extra.profile.SHOW_NONE";
+
+ /**
+ * Given to the profile picker as a {@link UUID} string representation. The {@link UUID}
+ * representation of the current profile, which will be used to show a checkmark next to
+ * the item for this {@link UUID}. If the item is {@link #NO_PROFILE} then "None" item
+ * is selected if {@link #EXTRA_PROFILE_SHOW_NONE} is enabled. Otherwise, the current
+ * profile is selected.
+ *
+ * @see #ACTION_PROFILE_PICKER
+ */
+ public static final String EXTRA_PROFILE_EXISTING_UUID =
+ "cyanogenmod.platform.extra.profile.EXISTING_UUID";
+
+ /**
+ * Given to the profile picker as a {@link CharSequence}. The title to
+ * show for the profile picker. This has a default value that is suitable
+ * in most cases.
+ *
+ * @see #ACTION_PROFILE_PICKER
+ */
+ public static final String EXTRA_PROFILE_TITLE =
+ "cyanogenmod.platform.intent.extra.profile.TITLE";
+
+ /**
+ * Returned from the profile picker as a {@link UUID} string representation.
+ * <p>
+ * It will be one of:
+ * <li> the picked profile,
+ * <li> null if the "None" item was picked.
+ *
+ * @see #ACTION_PROFILE_PICKER
+ */
+ public static final String EXTRA_PROFILE_PICKED_UUID =
+ "cyanogenmod.platform.intent.extra.profile.PICKED_UUID";
+
+ /**
+ * Broadcast intent action indicating that Profiles has been enabled or disabled.
+ * One extra provides this state as an int.
+ *
+ * @see #EXTRA_PROFILES_STATE
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String PROFILES_STATE_CHANGED_ACTION =
+ "cyanogenmod.platform.app.profiles.PROFILES_STATE_CHANGED";
+
+ /**
+ * The lookup key for an int that indicates whether Profiles are enabled or
+ * disabled. Retrieve it with {@link android.content.Intent#getIntExtra(String,int)}.
+ *
+ * @see #PROFILES_STATE_DISABLED
+ * @see #PROFILES_STATE_ENABLED
+ */
+ public static final String EXTRA_PROFILES_STATE = "profile_state";
+
+ /**
+ * Set the resource id theme to use for the dialog picker activity.<br/>
+ * The default theme is <code>com.android.internal.R.Theme_Holo_Dialog_Alert</code>.
+ *
+ * @see #ACTION_PROFILE_PICKER
+ */
+ public static final String EXTRA_PROFILE_DIALOG_THEME =
+ "cyanogenmod.platform.intent.extra.profile.DIALOG_THEME";
+
+ /**
+ * Profiles are disabled.
+ *
+ * @see #PROFILES_STATE_CHANGED_ACTION
+ */
+ public static final int PROFILES_STATE_DISABLED = 0;
+
+ /**
+ * Profiles are enabled.
+ *
+ * @see #PROFILES_STATE_CHANGED_ACTION
+ */
+ public static final int PROFILES_STATE_ENABLED = 1;
+
+ private static ProfileManager sProfileManagerInstance;
+ private ProfileManager(Context context) {
+ Context appContext = context.getApplicationContext();
+ if (appContext != null) {
+ mContext = appContext;
+ } else {
+ mContext = context;
+ }
+ sService = getService();
+
+ if (context.getPackageManager().hasSystemFeature(
+ cyanogenmod.app.CMContextConstants.Features.PROFILES) && sService == null) {
+ throw new RuntimeException("Unable to get ProfileManagerService. The service either" +
+ " crashed, was not started, or the interface has been called to early in" +
+ " SystemServer init");
+ }
+ }
+
+ /**
+ * Get or create an instance of the {@link cyanogenmod.app.ProfileManager}
+ * @param context
+ * @return {@link ProfileManager}
+ */
+ public static ProfileManager getInstance(Context context) {
+ if (sProfileManagerInstance == null) {
+ sProfileManagerInstance = new ProfileManager(context);
+ }
+ return sProfileManagerInstance;
+ }
+
+ /** @hide */
+ static public IProfileManager getService() {
+ if (sService != null) {
+ return sService;
+ }
+ IBinder b = ServiceManager.getService(CMContextConstants.CM_PROFILE_SERVICE);
+ sService = IProfileManager.Stub.asInterface(b);
+ return sService;
+ }
+
+ @Deprecated
+ public void setActiveProfile(String profileName) {
+ try {
+ getService().setActiveProfileByName(profileName);
+ } catch (RemoteException e) {
+ Log.e(TAG, e.getLocalizedMessage(), e);
+ }
+ }
+
+ /**
+ * Set the active {@link Profile} by {@link UUID}
+ * @param profileUuid the {@link UUID} associated with the profile
+ */
+ public void setActiveProfile(UUID profileUuid) {
+ try {
+ getService().setActiveProfile(new ParcelUuid(profileUuid));
+ } catch (RemoteException e) {
+ Log.e(TAG, e.getLocalizedMessage(), e);
+ }
+ }
+
+ /**
+ * Get the active {@link Profile}
+ * @return active {@link Profile}
+ */
+ public Profile getActiveProfile() {
+ try {
+ return getService().getActiveProfile();
+ } catch (RemoteException e) {
+ Log.e(TAG, e.getLocalizedMessage(), e);
+ }
+ return null;
+ }
+
+ /**
+ * Add a {@link Profile} that can be selected by the user
+ * @param profile a {@link Profile} object
+ */
+ public void addProfile(Profile profile) {
+ try {
+ getService().addProfile(profile);
+ } catch (RemoteException e) {
+ Log.e(TAG, e.getLocalizedMessage(), e);
+ }
+ }
+
+ /**
+ * Remove a {@link Profile} from user selection
+ * @param profile a {@link Profile} object
+ */
+ public void removeProfile(Profile profile) {
+ try {
+ getService().removeProfile(profile);
+ } catch (RemoteException e) {
+ Log.e(TAG, e.getLocalizedMessage(), e);
+ }
+ }
+
+ /**
+ * Update a {@link Profile} object
+ * @param profile a {@link Profile} object
+ */
+ public void updateProfile(Profile profile) {
+ try {
+ getService().updateProfile(profile);
+ } catch (RemoteException e) {
+ Log.e(TAG, e.getLocalizedMessage(), e);
+ }
+ }
+
+ /**
+ * Get the {@link Profile} object by its literal name
+ * @param profileName name associated with the profile
+ * @return profile a {@link Profile} object
+ */
+ @Deprecated
+ public Profile getProfile(String profileName) {
+ try {
+ return getService().getProfileByName(profileName);
+ } catch (RemoteException e) {
+ Log.e(TAG, e.getLocalizedMessage(), e);
+ }
+ return null;
+ }
+
+ /**
+ * Get a {@link Profile} via {@link UUID}
+ * @param profileUuid {@link UUID} associated with the profile
+ * @return {@link Profile}
+ */
+ public Profile getProfile(UUID profileUuid) {
+ try {
+ return getService().getProfile(new ParcelUuid(profileUuid));
+ } catch (RemoteException e) {
+ Log.e(TAG, e.getLocalizedMessage(), e);
+ }
+ return null;
+ }
+
+ /**
+ * Get the profile names currently available to the user
+ * @return {@link String[]} of profile names
+ */
+ public String[] getProfileNames() {
+ try {
+ Profile[] profiles = getService().getProfiles();
+ String[] names = new String[profiles.length];
+ for (int i = 0; i < profiles.length; i++) {
+ names[i] = profiles[i].getName();
+ }
+ return names;
+ } catch (RemoteException e) {
+ Log.e(TAG, e.getLocalizedMessage(), e);
+ }
+ return null;
+ }
+
+ /**
+ * Get the {@link Profile}s currently available to the user
+ * @return {@link Profile[]}
+ */
+ public Profile[] getProfiles() {
+ try {
+ return getService().getProfiles();
+ } catch (RemoteException e) {
+ Log.e(TAG, e.getLocalizedMessage(), e);
+ }
+ return null;
+ }
+
+ /**
+ * Check if a {@link Profile} exists via its literal name
+ * @param profileName a profile name
+ * @return whether or not the profile exists
+ */
+ public boolean profileExists(String profileName) {
+ try {
+ return getService().profileExistsByName(profileName);
+ } catch (RemoteException e) {
+ Log.e(TAG, e.getLocalizedMessage(), e);
+ // To be on the safe side, we'll return "true", to prevent duplicate profiles
+ // from being created.
+ return true;
+ }
+ }
+
+ /**
+ * Check if a {@link Profile} exists via its {@link UUID}
+ * @param profileUuid the profiles {@link UUID}
+ * @return whether or not the profile exists
+ */
+ public boolean profileExists(UUID profileUuid) {
+ try {
+ return getService().profileExists(new ParcelUuid(profileUuid));
+ } catch (RemoteException e) {
+ Log.e(TAG, e.getLocalizedMessage(), e);
+ // To be on the safe side, we'll return "true", to prevent duplicate profiles
+ // from being created.
+ return true;
+ }
+ }
+
+ /**
+ * Check if a NotificationGroup exists
+ * @param notificationGroupName the name of the notification group
+ * @return whether or not the notification group exists
+ * @hide
+ */
+ public boolean notificationGroupExists(String notificationGroupName) {
+ try {
+ return getService().notificationGroupExistsByName(notificationGroupName);
+ } catch (RemoteException e) {
+ Log.e(TAG, e.getLocalizedMessage(), e);
+ // To be on the safe side, we'll return "true", to prevent duplicate notification
+ // groups from being created.
+ return true;
+ }
+ }
+
+ /**
+ * Get the currently available NotificationGroups
+ * @return NotificationGroup
+ * @hide
+ */
+ public NotificationGroup[] getNotificationGroups() {
+ try {
+ return getService().getNotificationGroups();
+ } catch (RemoteException e) {
+ Log.e(TAG, e.getLocalizedMessage(), e);
+ }
+ return null;
+ }
+
+ /**
+ * Add a NotificationGroup to the available list
+ * @param group NotificationGroup
+ * @hide
+ */
+ public void addNotificationGroup(NotificationGroup group) {
+ try {
+ getService().addNotificationGroup(group);
+ } catch (RemoteException e) {
+ Log.e(TAG, e.getLocalizedMessage(), e);
+ }
+ }
+
+ /**
+ * Remove a NotificationGroup from the available list
+ * @param group NotificationGroup
+ * @hide
+ */
+ public void removeNotificationGroup(NotificationGroup group) {
+ try {
+ getService().removeNotificationGroup(group);
+ } catch (RemoteException e) {
+ Log.e(TAG, e.getLocalizedMessage(), e);
+ }
+ }
+
+ /**
+ * Update a NotificationGroup from the available list
+ * @param group NotificationGroup
+ * @hide
+ */
+ public void updateNotificationGroup(NotificationGroup group) {
+ try {
+ getService().updateNotificationGroup(group);
+ } catch (RemoteException e) {
+ Log.e(TAG, e.getLocalizedMessage(), e);
+ }
+ }
+
+ /**
+ * Get a NotificationGroup for a specific package
+ * @param pkg name of the package
+ * @hide
+ */
+ public NotificationGroup getNotificationGroupForPackage(String pkg) {
+ try {
+ return getService().getNotificationGroupForPackage(pkg);
+ } catch (RemoteException e) {
+ Log.e(TAG, e.getLocalizedMessage(), e);
+ }
+ return null;
+ }
+
+ /**
+ * Get a NotificationGroup from the available list via {@link UUID}
+ * @param uuid {@link UUID} of the notification group
+ * @hide
+ */
+ public NotificationGroup getNotificationGroup(UUID uuid) {
+ try {
+ return getService().getNotificationGroup(new ParcelUuid(uuid));
+ } catch (RemoteException e) {
+ Log.e(TAG, e.getLocalizedMessage(), e);
+ }
+ return null;
+ }
+
+ /**
+ * Get an active {@link ProfileGroup} via its package name
+ * @param packageName the package name associated to the profile group
+ * @return {@link ProfileGroup}
+ * @hide
+ */
+ public ProfileGroup getActiveProfileGroup(String packageName) {
+ NotificationGroup notificationGroup = getNotificationGroupForPackage(packageName);
+ if (notificationGroup == null) {
+ ProfileGroup defaultGroup = getActiveProfile().getDefaultGroup();
+ return defaultGroup;
+ }
+ return getActiveProfile().getProfileGroup(notificationGroup.getUuid());
+ }
+
+ /**
+ * Reset all profiles, groups, and notification groups to default state
+ */
+ public void resetAll() {
+ try {
+ getService().resetAll();
+ } catch (RemoteException e) {
+ Log.e(TAG, e.getLocalizedMessage(), e);
+ } catch (SecurityException e) {
+ Log.e(TAG, e.getLocalizedMessage(), e);
+ }
+ }
+
+ /**
+ * Check if profiles are currently activated in the system
+ * @return whether profiles are enabled
+ */
+ public boolean isProfilesEnabled() {
+ try {
+ return getService().isEnabled();
+ } catch (RemoteException e) {
+ Log.e(TAG, e.getLocalizedMessage(), e);
+ }
+ return false;
+ }
+}
diff --git a/sdk/src/java/cyanogenmod/app/StatusBarPanelCustomTile.aidl b/sdk/src/java/cyanogenmod/app/StatusBarPanelCustomTile.aidl
new file mode 100644
index 0000000..96cfb6a
--- /dev/null
+++ b/sdk/src/java/cyanogenmod/app/StatusBarPanelCustomTile.aidl
@@ -0,0 +1,20 @@
+/**
+ * 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.app;
+
+parcelable StatusBarPanelCustomTile;
+
diff --git a/sdk/src/java/cyanogenmod/app/StatusBarPanelCustomTile.java b/sdk/src/java/cyanogenmod/app/StatusBarPanelCustomTile.java
new file mode 100644
index 0000000..7710e5a
--- /dev/null
+++ b/sdk/src/java/cyanogenmod/app/StatusBarPanelCustomTile.java
@@ -0,0 +1,256 @@
+/*
+ * 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.app;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.UserHandle;
+
+import cyanogenmod.os.Build;
+
+import cyanogenmod.os.Concierge;
+import cyanogenmod.os.Concierge.ParcelInfo;
+
+/**
+ * Class encapsulating a Custom Tile. Sent by the StatusBarManagerService to clients including
+ * the status bar panel and any {@link cyanogenmod.app.CustomTileListenerService} clients.
+ */
+public class StatusBarPanelCustomTile implements Parcelable {
+
+ private final String pkg;
+ private final int id;
+ private final String tag;
+ private final String key;
+
+ private final int uid;
+ private final String resPkg;
+ private final String opPkg;
+ private final int initialPid;
+ private final CustomTile customTile;
+ private final UserHandle user;
+ private final long postTime;
+
+ public StatusBarPanelCustomTile(String pkg, String resPkg, String opPkg, int id, String tag,
+ int uid, int initialPid, CustomTile customTile, UserHandle user) {
+ this(pkg, resPkg, opPkg, id, tag, uid, initialPid, customTile, user,
+ System.currentTimeMillis());
+ }
+
+ public StatusBarPanelCustomTile(String pkg, String resPkg, String opPkg, int id, String tag,
+ int uid, int initialPid, CustomTile customTile, UserHandle user,
+ long postTime) {
+ if (pkg == null) throw new NullPointerException();
+ if (customTile == null) throw new NullPointerException();
+
+ this.pkg = pkg;
+ this.resPkg = resPkg;
+ this.opPkg = opPkg;
+ this.id = id;
+ this.tag = tag;
+ this.uid = uid;
+ this.initialPid = initialPid;
+ this.customTile = customTile;
+ this.user = user;
+ this.postTime = postTime;
+ this.key = key();
+ }
+
+
+ public StatusBarPanelCustomTile(Parcel in) {
+ // Read parcelable version via the Concierge
+ ParcelInfo parcelInfo = Concierge.receiveParcel(in);
+ int parcelableVersion = parcelInfo.getParcelVersion();
+
+ // tmp variables for final
+ String tmpResPkg = null;
+ String tmpPkg = null;
+ String tmpOpPkg = null;
+ int tmpId = -1;
+ String tmpTag = null;
+ int tmpUid = -1;
+ int tmpPid = -1;
+ CustomTile tmpCustomTile = null;
+ UserHandle tmpUser = null;
+ long tmpPostTime = -1;
+
+ // Pattern here is that all new members should be added to the end of
+ // the writeToParcel method. Then we step through each version, until the latest
+ // API release to help unravel this parcel
+ if (parcelableVersion >= Build.CM_VERSION_CODES.APRICOT) {
+ // default
+ tmpPkg = in.readString();
+ tmpOpPkg = in.readString();
+ tmpId = in.readInt();
+ if (in.readInt() != 0) {
+ tmpTag = in.readString();
+ } else {
+ tmpTag = null;
+ }
+ tmpUid = in.readInt();
+ tmpPid = in.readInt();
+ tmpCustomTile = new CustomTile(in);
+ tmpUser = UserHandle.readFromParcel(in);
+ tmpPostTime = in.readLong();
+ }
+
+ if (parcelableVersion >= Build.CM_VERSION_CODES.BOYSENBERRY) {
+ tmpResPkg = in.readString();
+ }
+
+ // Assign finals
+ this.resPkg = tmpResPkg;
+ this.pkg = tmpPkg;
+ this.opPkg = tmpOpPkg;
+ this.id = tmpId;
+ this.tag = tmpTag;
+ this.uid = tmpUid;
+ this.initialPid = tmpPid;
+ this.customTile = tmpCustomTile;
+ this.user = tmpUser;
+ this.postTime = tmpPostTime;
+ this.key = key();
+
+ // Complete parcel info for the concierge
+ parcelInfo.complete();
+ }
+
+ private String key() {
+ return user.getIdentifier() + "|" + pkg + "|" + id + "|" + tag + "|" + uid;
+ }
+
+ public static final Creator<StatusBarPanelCustomTile> CREATOR
+ = new Creator<StatusBarPanelCustomTile>()
+ {
+ public StatusBarPanelCustomTile createFromParcel(Parcel parcel)
+ {
+ return new StatusBarPanelCustomTile(parcel);
+ }
+
+ public StatusBarPanelCustomTile[] newArray(int size)
+ {
+ return new StatusBarPanelCustomTile[size];
+ }
+ };
+
+ /** The {@link cyanogenmod.app.CustomTile} supplied to
+ * {@link cyanogenmod.app.CMStatusBarManager#publishTile(int, cyanogenmod.app.CustomTile)}.
+ */
+ public CustomTile getCustomTile() {
+ return customTile;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ // Tell the concierge to prepare the parcel
+ ParcelInfo parcelInfo = Concierge.prepareParcel(out);
+
+ // ==== APRICOT ===
+ out.writeString(this.pkg);
+ out.writeString(this.opPkg);
+ out.writeInt(this.id);
+ if (this.tag != null) {
+ out.writeInt(1);
+ out.writeString(this.tag);
+ } else {
+ out.writeInt(0);
+ }
+ out.writeInt(this.uid);
+ out.writeInt(this.initialPid);
+ this.customTile.writeToParcel(out, flags);
+ user.writeToParcel(out, flags);
+ out.writeLong(this.postTime);
+
+ // ==== BOYSENBERRY =====
+ out.writeString(this.resPkg);
+
+ // Complete the parcel info for the concierge
+ parcelInfo.complete();
+ }
+
+ @Override
+ public StatusBarPanelCustomTile clone() {
+ return new StatusBarPanelCustomTile(this.pkg, this.resPkg, this.opPkg,
+ this.id, this.tag, this.uid, this.initialPid,
+ this.customTile.clone(), this.user, this.postTime);
+ }
+
+ /**
+ * Returns a userHandle for the instance of the app that posted this tile.
+ */
+ public int getUserId() {
+ return this.user.getIdentifier();
+ }
+
+ /** The package of the app that posted the tile */
+ public String getPackage() {
+ return pkg;
+ }
+
+ /** The id supplied to CMStatusBarManager */
+ public int getId() {
+ return id;
+ }
+
+ /** The tag supplied to CMStatusBarManager or null if no tag was specified. */
+ public String getTag() {
+ return tag;
+ }
+
+ /**
+ * A unique instance key for this tile record.
+ */
+ public String getKey() {
+ return key;
+ }
+
+ /** The notifying app's calling uid. @hide */
+ public int getUid() {
+ return uid;
+ }
+
+ /** The package used for load resources from. @hide */
+ public String getResPkg() {
+ return resPkg;
+ }
+
+ /** The package used for AppOps tracking. @hide */
+ public String getOpPkg() {
+ return opPkg;
+ }
+
+ /** @hide */
+ public int getInitialPid() {
+ return initialPid;
+ }
+
+ /**
+ * The {@link android.os.UserHandle} for whom this CustomTile is intended.
+ */
+ public UserHandle getUser() {
+ return user;
+ }
+
+ /** The time (in {@link System#currentTimeMillis} time) the CustomTile was published, */
+ public long getPostTime() {
+ return postTime;
+ }
+}
diff --git a/sdk/src/java/cyanogenmod/app/ThemeComponent.java b/sdk/src/java/cyanogenmod/app/ThemeComponent.java
new file mode 100644
index 0000000..d3f6625
--- /dev/null
+++ b/sdk/src/java/cyanogenmod/app/ThemeComponent.java
@@ -0,0 +1,38 @@
+/*
+ * 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.app;
+
+/**
+ * The id value here matches the framework. Unknown is given a -1 value since future
+ * framework components will always be positive.
+ * @hide
+ */
+public enum ThemeComponent {
+ UNKNOWN(-1),
+ OVERLAY(0),
+ BOOT_ANIM(1),
+ WALLPAPER(2),
+ LOCKSCREEN(3),
+ FONT(4),
+ ICON(5),
+ SOUND(6);
+
+ public int id;
+ ThemeComponent(int id) {
+ this.id = id;
+ }
+
+}
diff --git a/sdk/src/java/cyanogenmod/app/ThemeVersion.java b/sdk/src/java/cyanogenmod/app/ThemeVersion.java
new file mode 100644
index 0000000..b9846c6
--- /dev/null
+++ b/sdk/src/java/cyanogenmod/app/ThemeVersion.java
@@ -0,0 +1,206 @@
+/*
+ * 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.app;
+
+import android.os.Build;
+
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @hide
+ */
+public class ThemeVersion {
+ private static final String THEME_VERSION_CLASS_NAME = "android.content.ThemeVersion";
+ private static final String THEME_VERSION_FIELD_NAME = "THEME_VERSION";
+ private static final String MIN_SUPPORTED_THEME_VERSION_FIELD_NAME =
+ "MIN_SUPPORTED_THEME_VERSION";
+ private static final int CM11 = 1;
+ private static final int CM12_PRE_VERSIONING = 2;
+
+ public static int getVersion() {
+ int version;
+ try {
+ Class<?> themeVersionClass = Class.forName(THEME_VERSION_CLASS_NAME);
+ Field themeVersionField = themeVersionClass.getField(THEME_VERSION_FIELD_NAME);
+ version = (Integer) themeVersionField.get(null);
+ } catch(Exception e) {
+ // Field doesn't exist. Fallback to SDK level
+ version = Build.VERSION.SDK_INT < 21 ? CM11 :
+ CM12_PRE_VERSIONING;
+ }
+ return version;
+ }
+
+ public static int getMinSupportedVersion() {
+ int getMinSupportedVersion;
+ try {
+ Class<?> themeVersionClass = Class.forName(THEME_VERSION_CLASS_NAME);
+ Field themeVersionField =
+ themeVersionClass.getField(MIN_SUPPORTED_THEME_VERSION_FIELD_NAME);
+ getMinSupportedVersion = (Integer) themeVersionField.get(null);
+ } catch(Exception e) {
+ // Field doesn't exist. Fallback to SDK level
+ getMinSupportedVersion = Build.VERSION.SDK_INT < 21 ? CM11 :
+ CM12_PRE_VERSIONING;
+ }
+ return getMinSupportedVersion;
+ }
+
+ public static ComponentVersion getComponentVersion(ThemeComponent component) {
+ int version = getVersion();
+ if (version == 1) {
+ throw new UnsupportedOperationException();
+ } else if (version == 2) {
+ return ThemeVersionImpl2.getDeviceComponentVersion(component);
+ } else {
+ return ThemeVersionImpl3.getDeviceComponentVersion(component);
+ }
+ }
+
+ public static List<ComponentVersion> getComponentVersions() {
+ int version = getVersion();
+ if (version == 1) {
+ throw new UnsupportedOperationException();
+ } else if (version == 2) {
+ return ThemeVersionImpl2.getDeviceComponentVersions();
+ } else {
+ return ThemeVersionImpl3.getDeviceComponentVersions();
+ }
+ }
+
+ public static class ComponentVersion {
+ protected int id;
+ protected String name;
+ protected ThemeComponent component;
+ protected int minVersion;
+ protected int currentVersion;
+
+ protected ComponentVersion(int id, ThemeComponent component, int targetVersion) {
+ this(id, component, component.name(), targetVersion, targetVersion);
+ }
+
+ protected ComponentVersion(int id,
+ ThemeComponent component,
+ String name,
+ int minVersion,
+ int targetVersion) {
+ this.id = id;
+ this.component = component;
+ this.name = name;
+ this.minVersion = minVersion;
+ this.currentVersion = targetVersion;
+ }
+
+ public ComponentVersion(ComponentVersion copy) {
+ this(copy.id, copy.component, copy.name, copy.minVersion, copy.currentVersion);
+ }
+
+ public int getId() {
+ return id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public ThemeComponent getComponent() {
+ return component;
+ }
+
+ public int getMinVersion() {
+ return minVersion;
+ }
+
+ public int getCurrentVersion() {
+ return currentVersion;
+ }
+ }
+
+ private static class ThemeVersionImpl2 {
+ private static ArrayList<ComponentVersion> cVersions = new ArrayList<ComponentVersion>() {
+ {
+ add(new ComponentVersion(0, ThemeComponent.OVERLAY, 2));
+ add(new ComponentVersion(1, ThemeComponent.BOOT_ANIM, 1));
+ add(new ComponentVersion(2, ThemeComponent.WALLPAPER, 1));
+ add(new ComponentVersion(3, ThemeComponent.LOCKSCREEN, 1));
+ add(new ComponentVersion(4, ThemeComponent.ICON, 1));
+ add(new ComponentVersion(5, ThemeComponent.FONT, 1));
+ add(new ComponentVersion(6, ThemeComponent.SOUND, 1));
+ }
+ };
+
+ public static ComponentVersion getDeviceComponentVersion(ThemeComponent component) {
+ for(ComponentVersion compVersion : cVersions) {
+ if (compVersion.component.equals(component)) {
+ return new ComponentVersion(compVersion);
+ }
+ }
+ return null;
+ }
+
+ public static List<ComponentVersion> getDeviceComponentVersions() {
+ ArrayList<ComponentVersion> versions = new ArrayList<ComponentVersion>();
+ versions.addAll(cVersions);
+ return versions;
+ }
+ }
+
+ private static class ThemeVersionImpl3 {
+ public static ComponentVersion getDeviceComponentVersion(ThemeComponent component) {
+ for(android.content.ThemeVersion.ComponentVersion version :
+ android.content.ThemeVersion.ComponentVersion.values()) {
+ ComponentVersion sdkVersionInfo = fwCompVersionToSdkVersion(version);
+ if (sdkVersionInfo.component.equals(component)) {
+ return sdkVersionInfo;
+ }
+ }
+ return null;
+ }
+
+ public static List<ComponentVersion> getDeviceComponentVersions() {
+ List<ComponentVersion> versions = new ArrayList<ComponentVersion>();
+
+ for(android.content.ThemeVersion.ComponentVersion version :
+ android.content.ThemeVersion.ComponentVersion.values()) {
+ versions.add(fwCompVersionToSdkVersion(version));
+ }
+
+ return versions;
+ }
+
+ public static ComponentVersion fwCompVersionToSdkVersion(
+ android.content.ThemeVersion.ComponentVersion version) {
+ // Find the SDK component with the matching id
+ // If no ID matches then the FW must have a newer component that we don't
+ // know anything about. We can still return the id and name
+ ThemeComponent component = ThemeComponent.UNKNOWN;
+ for(ThemeComponent aComponent : ThemeComponent.values()) {
+ if (aComponent.id == version.id) {
+ component = aComponent;
+ }
+ }
+
+ int id = version.id;
+ String name = version.name();
+ int minVersion = version.minSupportedVersion;
+ int targetVersion = version.currentVersion;
+
+ return new ComponentVersion(id, component, name, minVersion, targetVersion);
+ }
+ }
+}
diff --git a/sdk/src/java/cyanogenmod/app/suggest/AppSuggestManager.java b/sdk/src/java/cyanogenmod/app/suggest/AppSuggestManager.java
new file mode 100644
index 0000000..667eaa7
--- /dev/null
+++ b/sdk/src/java/cyanogenmod/app/suggest/AppSuggestManager.java
@@ -0,0 +1,150 @@
+/**
+ * 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.app.suggest;
+
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.BitmapFactory;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Log;
+
+import java.io.FileNotFoundException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+import cyanogenmod.app.CMContextConstants;
+import cyanogenmod.app.suggest.ApplicationSuggestion;
+
+/**
+ * Provides an interface to get information about suggested apps for an intent which may include
+ * applications not installed on the device. This is used by the CMResolver in order to provide
+ * suggestions when an intent is fired but no application exists for the given intent.
+ *
+ * @hide
+ */
+public class AppSuggestManager {
+ private static final String TAG = AppSuggestManager.class.getSimpleName();
+ private static final boolean DEBUG = true;
+
+ private static IAppSuggestManager sImpl;
+
+ private static AppSuggestManager sInstance;
+
+ private Context mContext;
+
+ /**
+ * Gets an instance of the AppSuggestManager.
+ *
+ * @param context
+ *
+ * @return An instance of the AppSuggestManager
+ */
+ public static synchronized AppSuggestManager getInstance(Context context) {
+ if (sInstance != null) {
+ return sInstance;
+ }
+
+ context = context.getApplicationContext() != null ?
+ context.getApplicationContext() : context;
+
+ sInstance = new AppSuggestManager(context);
+
+ if (context.getPackageManager().hasSystemFeature(CMContextConstants.Features.APP_SUGGEST)
+ && sImpl == null) {
+ throw new RuntimeException("Unable to get AppSuggestManagerService. " +
+ "The service either crashed, was not started, or the interface has been" +
+ " called to early in SystemServer init");
+ }
+
+ return sInstance;
+ }
+
+ private AppSuggestManager(Context context) {
+ mContext = context.getApplicationContext();
+ sImpl = getService();
+ }
+
+ /** @hide */
+ public static synchronized IAppSuggestManager getService() {
+ if (sImpl == null) {
+ IBinder b = ServiceManager.getService(CMContextConstants.CM_APP_SUGGEST_SERVICE);
+ if (b != null) {
+ sImpl = IAppSuggestManager.Stub.asInterface(b);
+ } else {
+ Log.e(TAG, "Unable to find implementation for app suggest service");
+ }
+ }
+
+ return sImpl;
+ }
+
+ /**
+ * Checks to see if an intent is handled by the App Suggestions Service. This should be
+ * implemented in such a way that it is safe to call inline on the UI Thread.
+ *
+ * @param intent The intent
+ * @return true if the App Suggestions Service has suggestions for this intent, false otherwise
+ */
+ public boolean handles(Intent intent) {
+ IAppSuggestManager mgr = getService();
+ if (mgr == null) return false;
+ try {
+ return mgr.handles(intent);
+ } catch (RemoteException e) {
+ return false;
+ }
+ }
+
+ /**
+ *
+ * Gets a list of the suggestions for the given intent.
+ *
+ * @param intent The intent
+ * @return A list of application suggestions or an empty list if none.
+ */
+ public List<ApplicationSuggestion> getSuggestions(Intent intent) {
+ IAppSuggestManager mgr = getService();
+ if (mgr == null) return new ArrayList<>(0);
+ try {
+ return mgr.getSuggestions(intent);
+ } catch (RemoteException e) {
+ return new ArrayList<>(0);
+ }
+ }
+
+ /**
+ * Loads the icon for the given suggestion.
+ *
+ * @param suggestion The suggestion to load the icon for
+ *
+ * @return A {@link Drawable} or null if one cannot be found
+ */
+ public Drawable loadIcon(ApplicationSuggestion suggestion) {
+ try {
+ InputStream is = mContext.getContentResolver()
+ .openInputStream(suggestion.getThumbailUri());
+ return Drawable.createFromStream(is, null);
+ } catch (FileNotFoundException e) {
+ return null;
+ }
+ }
+}
diff --git a/sdk/src/java/cyanogenmod/app/suggest/ApplicationSuggestion.aidl b/sdk/src/java/cyanogenmod/app/suggest/ApplicationSuggestion.aidl
new file mode 100644
index 0000000..7ab8584
--- /dev/null
+++ b/sdk/src/java/cyanogenmod/app/suggest/ApplicationSuggestion.aidl
@@ -0,0 +1,22 @@
+/**
+ * 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.app.suggest;
+
+/**
+ * @hide
+ */
+parcelable ApplicationSuggestion;
diff --git a/sdk/src/java/cyanogenmod/app/suggest/ApplicationSuggestion.java b/sdk/src/java/cyanogenmod/app/suggest/ApplicationSuggestion.java
new file mode 100644
index 0000000..17e40b9
--- /dev/null
+++ b/sdk/src/java/cyanogenmod/app/suggest/ApplicationSuggestion.java
@@ -0,0 +1,110 @@
+/**
+ * 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.app.suggest;
+
+import android.annotation.NonNull;
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import cyanogenmod.os.Build;
+import cyanogenmod.os.Concierge;
+import cyanogenmod.os.Concierge.ParcelInfo;
+
+/**
+ * @hide
+ */
+public class ApplicationSuggestion implements Parcelable {
+
+ public static final Creator<ApplicationSuggestion> CREATOR =
+ new Creator<ApplicationSuggestion>() {
+ public ApplicationSuggestion createFromParcel(Parcel in) {
+ return new ApplicationSuggestion(in);
+ }
+
+ public ApplicationSuggestion[] newArray(int size) {
+ return new ApplicationSuggestion[size];
+ }
+ };
+
+ private String mName;
+
+ private String mPackage;
+
+ private Uri mDownloadUri;
+
+ private Uri mThumbnailUri;
+
+ public ApplicationSuggestion(@NonNull String name, @NonNull String pkg,
+ @NonNull Uri downloadUri, @NonNull Uri thumbnailUri) {
+ mName = name;
+ mPackage = pkg;
+ mDownloadUri = downloadUri;
+ mThumbnailUri = thumbnailUri;
+ }
+
+ private ApplicationSuggestion(Parcel in) {
+ // Read parcelable version via the Concierge
+ ParcelInfo parcelInfo = Concierge.receiveParcel(in);
+ int parcelableVersion = parcelInfo.getParcelVersion();
+
+ if (parcelableVersion >= Build.CM_VERSION_CODES.APRICOT) {
+ mName = in.readString();
+ mPackage = in.readString();
+ mDownloadUri = in.readParcelable(Uri.class.getClassLoader());
+ mThumbnailUri = in.readParcelable(Uri.class.getClassLoader());
+ }
+
+ // Complete parcel info for the concierge
+ parcelInfo.complete();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ // Tell the concierge to prepare the parcel
+ ParcelInfo parcelInfo = Concierge.prepareParcel(out);
+
+ out.writeString(mName);
+ out.writeString(mPackage);
+ out.writeParcelable(mDownloadUri, flags);
+ out.writeParcelable(mThumbnailUri, flags);
+
+ // Complete the parcel info for the concierge
+ parcelInfo.complete();
+ }
+
+ public String getName() {
+ return mName;
+ }
+
+ public String getPackageName() {
+ return mPackage;
+ }
+
+ public Uri getDownloadUri() {
+ return mDownloadUri;
+ }
+
+ public Uri getThumbailUri() {
+ return mThumbnailUri;
+ }
+}
diff --git a/sdk/src/java/cyanogenmod/app/suggest/IAppSuggestManager.aidl b/sdk/src/java/cyanogenmod/app/suggest/IAppSuggestManager.aidl
new file mode 100644
index 0000000..68ab87f
--- /dev/null
+++ b/sdk/src/java/cyanogenmod/app/suggest/IAppSuggestManager.aidl
@@ -0,0 +1,30 @@
+/**
+ * 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.app.suggest;
+
+import android.content.Intent;
+
+import cyanogenmod.app.suggest.ApplicationSuggestion;
+
+/**
+ * @hide
+ */
+interface IAppSuggestManager {
+ boolean handles(in Intent intent);
+
+ List<ApplicationSuggestion> getSuggestions(in Intent intent);
+} \ No newline at end of file
diff --git a/sdk/src/java/cyanogenmod/app/suggest/IAppSuggestProvider.aidl b/sdk/src/java/cyanogenmod/app/suggest/IAppSuggestProvider.aidl
new file mode 100644
index 0000000..759880d
--- /dev/null
+++ b/sdk/src/java/cyanogenmod/app/suggest/IAppSuggestProvider.aidl
@@ -0,0 +1,30 @@
+/**
+ * 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.app.suggest;
+
+import android.content.Intent;
+
+import cyanogenmod.app.suggest.ApplicationSuggestion;
+
+/**
+ * @hide
+ */
+interface IAppSuggestProvider {
+ boolean handles(in Intent intent);
+
+ List<ApplicationSuggestion> getSuggestions(in Intent intent);
+} \ No newline at end of file
diff --git a/sdk/src/java/cyanogenmod/content/Intent.java b/sdk/src/java/cyanogenmod/content/Intent.java
new file mode 100644
index 0000000..6369b7c
--- /dev/null
+++ b/sdk/src/java/cyanogenmod/content/Intent.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2015 The CyanogenMod Project
+ * This code has been modified. Portions copyright (C) 2010, T-Mobile USA, Inc.
+ *
+ * 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.content;
+
+import android.Manifest;
+
+/**
+ * CyanogenMod specific intent definition class.
+ */
+public class Intent {
+
+ /**
+ * Activity Action: Start action associated with long press on the recents key.
+ * <p>Input: {@link #EXTRA_LONG_PRESS_RELEASE} is set to true if the long press
+ * is released
+ * <p>Output: Nothing
+ * @hide
+ */
+ public static final String ACTION_RECENTS_LONG_PRESS =
+ "cyanogenmod.intent.action.RECENTS_LONG_PRESS";
+
+ /**
+ * This field is part of the intent {@link #ACTION_RECENTS_LONG_PRESS}.
+ * The type of the extra is a boolean that indicates if the long press
+ * is released.
+ * @hide
+ */
+ public static final String EXTRA_RECENTS_LONG_PRESS_RELEASE =
+ "cyanogenmod.intent.extra.RECENTS_LONG_PRESS_RELEASE";
+
+ /**
+ * Intent filter to update protected app component's settings
+ */
+ public static final String ACTION_PROTECTED = "cyanogenmod.intent.action.PACKAGE_PROTECTED";
+
+ /**
+ * Intent filter to notify change in state of protected application.
+ */
+ public static final String ACTION_PROTECTED_CHANGED =
+ "cyanogenmod.intent.action.PROTECTED_COMPONENT_UPDATE";
+
+ /**
+ * This field is part of the intent {@link #ACTION_PROTECTED_CHANGED}.
+ * Intent extra field for the state of protected application
+ */
+ public static final String EXTRA_PROTECTED_STATE =
+ "cyanogenmod.intent.extra.PACKAGE_PROTECTED_STATE";
+
+ /**
+ * This field is part of the intent {@link #ACTION_PROTECTED_CHANGED}.
+ * Intent extra field to indicate protected component value
+ */
+ public static final String EXTRA_PROTECTED_COMPONENTS =
+ "cyanogenmod.intent.extra.PACKAGE_PROTECTED_COMPONENTS";
+
+ /**
+ * Broadcast action: notify the system that the user has performed a gesture on the screen
+ * to launch the camera. Broadcast should be protected to receivers holding the
+ * {@link Manifest.permission#STATUS_BAR_SERVICE} permission.
+ * @hide
+ */
+ public static final String ACTION_SCREEN_CAMERA_GESTURE =
+ "cyanogenmod.intent.action.SCREEN_CAMERA_GESTURE";
+
+ /**
+ * Broadcast action: perform any initialization required for CMHW services.
+ * Runs when the service receives the signal the device has booted, but
+ * should happen before {@link android.content.Intent#ACTION_BOOT_COMPLETED}.
+ *
+ * Requires {@link cyanogenmod.platform.Manifest.permission#HARDWARE_ABSTRACTION_ACCESS}.
+ * @hide
+ */
+ public static final String ACTION_INITIALIZE_CM_HARDWARE =
+ "cyanogenmod.intent.action.INITIALIZE_CM_HARDWARE";
+
+ /**
+ * Broadcast Action: Indicate that an unrecoverable error happened during app launch.
+ * Could indicate that curently applied theme is malicious.
+ * @hide
+ */
+ public static final String ACTION_APP_FAILURE = "cyanogenmod.intent.action.APP_FAILURE";
+
+ /**
+ * Used to indicate that a theme package has been installed or un-installed.
+ */
+ public static final String CATEGORY_THEME_PACKAGE_INSTALLED_STATE_CHANGE =
+ "cyanogenmod.intent.category.THEME_PACKAGE_INSTALL_STATE_CHANGE";
+
+ /**
+ * Action sent from the provider when a theme has been fully installed. Fully installed
+ * means that the apk was installed by PackageManager and the theme resources were
+ * processed and cached by {@link org.cyanogenmod.platform.internal.ThemeManagerService}
+ * Requires the {@link cyanogenmod.platform.Manifest.permission#READ_THEMES} permission to
+ * receive this broadcast.
+ */
+ public static final String ACTION_THEME_INSTALLED =
+ "cyanogenmod.intent.action.THEME_INSTALLED";
+
+ /**
+ * Action sent from the provider when a theme has been updated.
+ * Requires the {@link cyanogenmod.platform.Manifest.permission#READ_THEMES} permission to
+ * receive this broadcast.
+ */
+ public static final String ACTION_THEME_UPDATED =
+ "cyanogenmod.intent.action.THEME_UPDATED";
+
+ /**
+ * Action sent from the provider when a theme has been removed.
+ * Requires the {@link cyanogenmod.platform.Manifest.permission#READ_THEMES} permission to
+ * receive this broadcast.
+ */
+ public static final String ACTION_THEME_REMOVED =
+ "cyanogenmod.intent.action.THEME_REMOVED";
+
+ /**
+ * Uri scheme used to broadcast the theme's package name when broadcasting
+ * {@link Intent#ACTION_THEME_INSTALLED} or
+ * {@link Intent#ACTION_THEME_REMOVED}
+ */
+ public static final String URI_SCHEME_PACKAGE = "package";
+
+ /**
+ * Implicit action to open live lock screen settings.
+ * @hide
+ */
+ public static final String ACTION_OPEN_LIVE_LOCKSCREEN_SETTINGS =
+ "cyanogenmod.intent.action.OPEN_LIVE_LOCKSCREEN_SETTINGS";
+
+}
diff --git a/sdk/src/java/cyanogenmod/externalviews/ExternalView.java b/sdk/src/java/cyanogenmod/externalviews/ExternalView.java
new file mode 100644
index 0000000..3c3adb4
--- /dev/null
+++ b/sdk/src/java/cyanogenmod/externalviews/ExternalView.java
@@ -0,0 +1,247 @@
+/*
+ * 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.externalviews;
+
+import android.app.Activity;
+import android.app.Application;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewTreeObserver;
+
+import java.util.LinkedList;
+
+/**
+ * TODO: unhide once documented and finalized
+ * @hide
+ */
+public class ExternalView extends View implements Application.ActivityLifecycleCallbacks,
+ ViewTreeObserver.OnPreDrawListener {
+
+ private LinkedList<Runnable> mQueue = new LinkedList<Runnable>();
+
+ protected Context mContext;
+ protected final ExternalViewProperties mExternalViewProperties;
+ protected volatile IExternalViewProvider mExternalViewProvider;
+
+ public ExternalView(Context context, AttributeSet attrs) {
+ this(context, attrs, null);
+ }
+
+ public ExternalView(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs);
+ }
+
+ public ExternalView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ this(context, attrs);
+ }
+
+ public ExternalView(Context context, AttributeSet attributeSet, ComponentName componentName) {
+ super(context, attributeSet);
+ mContext = getContext();
+ mExternalViewProperties = new ExternalViewProperties(this, mContext);
+ Application app = (mContext instanceof Activity) ? ((Activity) mContext).getApplication()
+ : (Application) mContext;
+ app.registerActivityLifecycleCallbacks(this);
+ if (componentName != null) {
+ mContext.bindService(new Intent().setComponent(componentName),
+ mServiceConnection, Context.BIND_AUTO_CREATE);
+ }
+ }
+
+ private ServiceConnection mServiceConnection = new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ try {
+ mExternalViewProvider = IExternalViewProvider.Stub.asInterface(
+ IExternalViewProviderFactory.Stub.asInterface(service).createExternalView(null));
+ executeQueue();
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ mExternalViewProvider = null;
+ }
+ };
+
+ private void executeQueue() {
+ while (!mQueue.isEmpty()) {
+ Runnable r = mQueue.pop();
+ r.run();
+ }
+ }
+
+ protected void performAction(Runnable r) {
+ if (mExternalViewProvider != null) {
+ r.run();
+ } else {
+ mQueue.add(r);
+ }
+ }
+
+ // view overrides, for positioning
+
+ @Override
+ public boolean onPreDraw() {
+ long cur = System.currentTimeMillis();
+ if (!mExternalViewProperties.hasChanged()) {
+ return true;
+ }
+ final int x = mExternalViewProperties.getX();
+ final int y = mExternalViewProperties.getY();
+ final int width = mExternalViewProperties.getWidth();
+ final int height = mExternalViewProperties.getHeight();
+ final boolean visible = mExternalViewProperties.isVisible();
+ final Rect clipRect = mExternalViewProperties.getHitRect();
+ performAction(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ mExternalViewProvider.alterWindow(x, y, width, height, visible,
+ clipRect);
+ } catch (RemoteException e) {
+ }
+ }
+ });
+ return true;
+ }
+
+ // Activity lifecycle callbacks
+
+ @Override
+ public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
+ }
+
+ @Override
+ public void onActivityStarted(Activity activity) {
+ performAction(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ mExternalViewProvider.onStart();
+ } catch (RemoteException e) {
+ }
+ }
+ });
+ }
+
+ @Override
+ public void onActivityResumed(Activity activity) {
+ performAction(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ mExternalViewProvider.onResume();
+ } catch (RemoteException e) {
+ }
+ getViewTreeObserver().addOnPreDrawListener(ExternalView.this);
+ }
+ });
+ }
+
+ @Override
+ public void onActivityPaused(Activity activity) {
+ performAction(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ mExternalViewProvider.onPause();
+ } catch (RemoteException e) {
+ }
+ getViewTreeObserver().removeOnPreDrawListener(ExternalView.this);
+ }
+ });
+ }
+
+ @Override
+ public void onActivityStopped(Activity activity) {
+ performAction(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ mExternalViewProvider.onStop();
+ } catch (RemoteException e) {
+ }
+ }
+ });
+ }
+
+ @Override
+ public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
+ }
+
+ @Override
+ public void onActivityDestroyed(Activity activity) {
+ mExternalViewProvider = null;
+ mContext.unbindService(mServiceConnection);
+ }
+
+ // Placeholder callbacks
+
+ @Override
+ public void onDetachedFromWindow() {
+ performAction(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ mExternalViewProvider.onDetach();
+ } catch (RemoteException e) {
+ }
+ }
+ });
+ }
+
+ @Override
+ public void onAttachedToWindow() {
+ performAction(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ mExternalViewProvider.onAttach(null);
+ } catch (RemoteException e) {
+ }
+ }
+ });
+ }
+
+ /**
+ * Sets the component of the ExternalViewProviderService to be used for this ExternalView.
+ * If a provider is already connected to this view, it is first unbound before binding to the
+ * new provider.
+ * @param componentName
+ */
+ public void setProviderComponent(ComponentName componentName) {
+ // unbind any existing external view provider
+ if (mExternalViewProvider != null) {
+ mContext.unbindService(mServiceConnection);
+ }
+ if (componentName != null) {
+ mContext.bindService(new Intent().setComponent(componentName),
+ mServiceConnection, Context.BIND_AUTO_CREATE);
+ }
+ }
+}
diff --git a/sdk/src/java/cyanogenmod/externalviews/ExternalViewProperties.java b/sdk/src/java/cyanogenmod/externalviews/ExternalViewProperties.java
new file mode 100644
index 0000000..2b4404d
--- /dev/null
+++ b/sdk/src/java/cyanogenmod/externalviews/ExternalViewProperties.java
@@ -0,0 +1,95 @@
+/*
+ * 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.externalviews;
+
+import android.app.Activity;
+import android.app.Application;
+import android.content.Context;
+import android.graphics.Rect;
+import android.view.View;
+
+/**
+ * TODO: unhide once documented and finalized
+ * @hide
+ */
+public class ExternalViewProperties {
+
+ private final int[] mScreenCoords = new int[2];
+ private final View mView;
+ private final View mDecorView;
+ private int mWidth, mHeight;
+ private boolean mVisible;
+ private Rect mHitRect = new Rect();
+
+ ExternalViewProperties(View view, Context context) {
+ mView = view;
+ if (context instanceof Activity) {
+ mDecorView = ((Activity) context).getWindow().getDecorView();
+ } else {
+ mDecorView = null;
+ }
+ }
+
+ public Rect getHitRect() {
+ return mHitRect;
+ }
+
+ public int getX() {
+ return mScreenCoords[0];
+ }
+
+ public int getY() {
+ return mScreenCoords[1];
+ }
+
+ public int getWidth() {
+ return mWidth;
+ }
+
+ public int getHeight() {
+ return mHeight;
+ }
+
+ public boolean isVisible() {
+ return mVisible;
+ }
+
+ public boolean hasChanged() {
+ int previousWidth = mWidth;
+ int previousHeight = mHeight;
+ mWidth = mView.getWidth();
+ mHeight = mView.getHeight();
+
+ int previousX = mScreenCoords[0];
+ int previousY = mScreenCoords[1];
+ mView.getLocationOnScreen(mScreenCoords);
+ int newX = mScreenCoords[0];
+ int newY = mScreenCoords[1];
+
+ mHitRect.setEmpty();
+ if (mDecorView != null) {
+ mDecorView.getHitRect(mHitRect);
+ }
+ boolean visible = mView.getLocalVisibleRect(mHitRect);
+ mVisible = visible;
+
+ // Check if anything actually changed
+ return previousX != newX || previousY != newY
+ || previousWidth != mWidth || previousHeight != mHeight
+ || mVisible != visible;
+ }
+}
diff --git a/sdk/src/java/cyanogenmod/externalviews/ExternalViewProviderService.java b/sdk/src/java/cyanogenmod/externalviews/ExternalViewProviderService.java
new file mode 100644
index 0000000..b15e01a
--- /dev/null
+++ b/sdk/src/java/cyanogenmod/externalviews/ExternalViewProviderService.java
@@ -0,0 +1,236 @@
+/*
+ * 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.externalviews;
+
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.Window;
+import android.view.WindowManager;
+
+import com.android.internal.policy.PhoneWindow;
+
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.FutureTask;
+
+/**
+ * TODO: unhide once documented and finalized
+ * @hide
+ */
+public abstract class ExternalViewProviderService extends Service {
+
+ private static final String TAG = "ExternalViewProvider";
+ private static final boolean DEBUG = false;
+
+ private WindowManager mWindowManager;
+ private final Handler mHandler = new Handler();
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+
+ mWindowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
+ }
+
+ @Override
+ public final IBinder onBind(Intent intent) {
+ return new IExternalViewProviderFactory.Stub() {
+ @Override public IBinder createExternalView(final Bundle options) {
+ FutureTask<IBinder> c = new FutureTask<IBinder>(new Callable<IBinder>() {
+ @Override
+ public IBinder call() throws Exception {
+ return ExternalViewProviderService.this.createExternalView(options).mImpl;
+ }
+ });
+ mHandler.post(c);
+ try {
+ return c.get();
+ } catch (InterruptedException | ExecutionException e) {
+ Log.e(TAG, "error: ", e);
+ return null;
+ }
+ }
+ };
+ }
+
+ protected abstract Provider createExternalView(Bundle options);
+
+ protected abstract class Provider {
+ public static final int DEFAULT_WINDOW_TYPE = WindowManager.LayoutParams.TYPE_PHONE;
+ public static final int DEFAULT_WINDOW_FLAGS =
+ WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL |
+ WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS |
+ WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;;
+
+ private final class ProviderImpl extends IExternalViewProvider.Stub {
+ private final Window mWindow;
+ private final WindowManager.LayoutParams mParams;
+
+ private boolean mShouldShow = true;
+ private boolean mAskedShow = false;
+
+ public ProviderImpl(Provider provider) {
+ mWindow = new PhoneWindow(ExternalViewProviderService.this);
+ ((ViewGroup) mWindow.getDecorView()).addView(onCreateView());
+
+ mParams = new WindowManager.LayoutParams();
+ mParams.type = provider.getWindowType();
+ mParams.flags = provider.getWindowFlags();
+ mParams.gravity = Gravity.LEFT | Gravity.TOP;
+ mParams.format = PixelFormat.TRANSPARENT;
+ }
+
+ @Override
+ public void onAttach(IBinder windowToken) throws RemoteException {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mWindowManager.addView(mWindow.getDecorView(), mParams);
+ Provider.this.onAttach();
+ }
+ });
+ }
+
+ @Override
+ public void onStart() throws RemoteException {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ Provider.this.onStart();
+ }
+ });
+ }
+
+ @Override
+ public void onResume() throws RemoteException {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mShouldShow = true;
+ updateVisibility();
+ Provider.this.onResume();
+ }
+ });
+ }
+
+ @Override
+ public void onPause() throws RemoteException {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mShouldShow = false;
+ updateVisibility();
+ Provider.this.onPause();
+ }
+ });
+ }
+
+ @Override
+ public void onStop() throws RemoteException {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ Provider.this.onStop();
+ }
+ });
+ }
+
+ @Override
+ public void onDetach() throws RemoteException {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mWindowManager.removeView(mWindow.getDecorView());
+ Provider.this.onDetach();
+ }
+ });
+ }
+
+ @Override
+ public void alterWindow(final int x, final int y, final int width, final int height,
+ final boolean visible, final Rect clipRect) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mParams.x = x;
+ mParams.y = y;
+ mParams.width = width;
+ mParams.height = height;
+
+ if (DEBUG) Log.d(TAG, mParams.toString());
+
+ mAskedShow = visible;
+
+ updateVisibility();
+
+ View decorView = mWindow.getDecorView();
+ if (decorView.getVisibility() == View.VISIBLE) {
+ decorView.setClipBounds(clipRect);
+ }
+
+ if (mWindow.getDecorView().getVisibility() != View.GONE)
+ mWindowManager.updateViewLayout(mWindow.getDecorView(), mParams);
+ }
+ });
+ }
+
+ private void updateVisibility() {
+ if (DEBUG) Log.d(TAG, "shouldShow = " + mShouldShow + " askedShow = " + mAskedShow);
+ mWindow.getDecorView().setVisibility(mShouldShow && mAskedShow ?
+ View.VISIBLE : View.GONE);
+ }
+ }
+
+ private final ProviderImpl mImpl = new ProviderImpl(this);
+ private final Bundle mOptions;
+
+ protected Provider(Bundle options) {
+ mOptions = options;
+ }
+
+ protected Bundle getOptions() {
+ return mOptions;
+ }
+
+ protected void onAttach() {}
+ protected abstract View onCreateView();
+ protected void onStart() {}
+ protected void onResume() {}
+ protected void onPause() {}
+ protected void onStop() {}
+ protected void onDetach() {}
+
+ /*package*/ int getWindowType() {
+ return DEFAULT_WINDOW_TYPE;
+ }
+
+ /*package*/ int getWindowFlags() {
+ return DEFAULT_WINDOW_FLAGS;
+ }
+ }
+} \ No newline at end of file
diff --git a/sdk/src/java/cyanogenmod/externalviews/IExternalViewProvider.aidl b/sdk/src/java/cyanogenmod/externalviews/IExternalViewProvider.aidl
new file mode 100644
index 0000000..e05215f
--- /dev/null
+++ b/sdk/src/java/cyanogenmod/externalviews/IExternalViewProvider.aidl
@@ -0,0 +1,32 @@
+/**
+ * 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.externalviews;
+
+import android.graphics.Rect;
+
+/** @hide */
+interface IExternalViewProvider
+{
+ oneway void onAttach(in IBinder windowToken);
+ oneway void onStart();
+ oneway void onResume();
+ oneway void onPause();
+ oneway void onStop();
+ oneway void onDetach();
+
+ void alterWindow(in int x, in int y, in int width, in int height, in boolean visible, in Rect clipRect);
+}
diff --git a/sdk/src/java/cyanogenmod/externalviews/IExternalViewProviderFactory.aidl b/sdk/src/java/cyanogenmod/externalviews/IExternalViewProviderFactory.aidl
new file mode 100644
index 0000000..75a8b35
--- /dev/null
+++ b/sdk/src/java/cyanogenmod/externalviews/IExternalViewProviderFactory.aidl
@@ -0,0 +1,25 @@
+/**
+ * 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.externalviews;
+
+import android.os.Bundle;
+
+/** @hide */
+interface IExternalViewProviderFactory
+{
+ IBinder createExternalView(in Bundle options);
+}
diff --git a/sdk/src/java/cyanogenmod/externalviews/IKeyguardExternalViewCallbacks.aidl b/sdk/src/java/cyanogenmod/externalviews/IKeyguardExternalViewCallbacks.aidl
new file mode 100644
index 0000000..c9d75e2
--- /dev/null
+++ b/sdk/src/java/cyanogenmod/externalviews/IKeyguardExternalViewCallbacks.aidl
@@ -0,0 +1,30 @@
+/*
+ * 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.externalviews;
+
+import android.content.Intent;
+
+/** @hide */
+interface IKeyguardExternalViewCallbacks {
+ boolean requestDismiss();
+ boolean requestDismissAndStartActivity(in Intent intent);
+ oneway void collapseNotificationPanel();
+ oneway void setInteractivity(boolean isInteractive);
+ oneway void onAttachedToWindow();
+ oneway void onDetachedFromWindow();
+ oneway void slideLockscreenIn();
+}
diff --git a/sdk/src/java/cyanogenmod/externalviews/IKeyguardExternalViewProvider.aidl b/sdk/src/java/cyanogenmod/externalviews/IKeyguardExternalViewProvider.aidl
new file mode 100644
index 0000000..a708984
--- /dev/null
+++ b/sdk/src/java/cyanogenmod/externalviews/IKeyguardExternalViewProvider.aidl
@@ -0,0 +1,42 @@
+/**
+ * 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.externalviews;
+
+import android.graphics.Rect;
+
+import cyanogenmod.externalviews.IKeyguardExternalViewCallbacks;
+
+/** @hide */
+interface IKeyguardExternalViewProvider
+{
+ oneway void onAttach(in IBinder windowToken);
+ oneway void onDetach();
+
+ // Keyguard specific interface
+ oneway void onKeyguardShowing(boolean screenOn);
+ oneway void onKeyguardDismissed();
+ oneway void onBouncerShowing(boolean showing);
+ oneway void onScreenTurnedOn();
+ oneway void onScreenTurnedOff();
+
+ oneway void registerCallback(in IKeyguardExternalViewCallbacks callback);
+ oneway void unregisterCallback(in IKeyguardExternalViewCallbacks callback);
+
+ void alterWindow(in int x, in int y, in int width, in int height, in boolean visible,
+ in Rect clipRect);
+ oneway void onLockscreenSlideOffsetChanged(float swipeProgress);
+}
diff --git a/sdk/src/java/cyanogenmod/externalviews/KeyguardExternalView.java b/sdk/src/java/cyanogenmod/externalviews/KeyguardExternalView.java
new file mode 100644
index 0000000..9f4059a
--- /dev/null
+++ b/sdk/src/java/cyanogenmod/externalviews/KeyguardExternalView.java
@@ -0,0 +1,493 @@
+/*
+ * 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.externalviews;
+
+import android.app.Activity;
+import android.app.Application;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewTreeObserver;
+import android.view.WindowManager;
+
+import java.util.LinkedList;
+
+/**
+ * This class provides a placeholder view for hosting an external view, provided by a
+ * {@link cyanogenmod.externalviews.KeyguardExternalViewProviderService}, within the lock screen.
+ *
+ * <p>This class is intended to only be used within the SystemUi process.</p>
+ * @hide
+ */
+public class KeyguardExternalView extends View implements ViewTreeObserver.OnPreDrawListener,
+ IBinder.DeathRecipient {
+ private static final String TAG = KeyguardExternalView.class.getSimpleName();
+
+ /**
+ * An extra passed via an intent that provides a list of permissions that should be requested
+ * from the user.
+ */
+ public static final String EXTRA_PERMISSION_LIST = "permissions_list";
+
+ /**
+ * Category defining an activity to call to request permissions that a
+ * {@link cyanogenmod.externalviews.KeyguardExternalViewProviderService} will need. Apps that
+ * provide a {@link cyanogenmod.externalviews.KeyguardExternalViewProviderService} should
+ * check that they have the required permission before making any method calls that would
+ * require a dangerous permission to be granted.
+ */
+ public static final String CATEGORY_KEYGUARD_GRANT_PERMISSION
+ = "org.cyanogenmod.intent.category.KEYGUARD_GRANT_PERMISSION";
+
+ private LinkedList<Runnable> mQueue = new LinkedList<Runnable>();
+
+ private Context mContext;
+ private final ExternalViewProperties mExternalViewProperties;
+ private volatile IKeyguardExternalViewProvider mExternalViewProvider;
+ private IBinder mService;
+ private final Point mDisplaySize;
+ private boolean mIsInteractive;
+
+ private KeyguardExternalViewCallbacks mCallback;
+
+ private OnWindowAttachmentChangedListener mWindowAttachmentListener;
+
+ public KeyguardExternalView(Context context, AttributeSet attrs) {
+ this(context, attrs, null);
+ }
+
+ public KeyguardExternalView(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs);
+ }
+
+ public KeyguardExternalView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ this(context, attrs);
+ }
+
+ /**
+ * @param context
+ * @param attributeSet
+ * @param componentName The component name for the
+ * {@link cyanogenmod.externalviews.KeyguardExternalViewProviderService}
+ * that will be bound to create the external view.
+ */
+ public KeyguardExternalView(Context context, AttributeSet attributeSet, ComponentName componentName) {
+ super(context, attributeSet);
+ mContext = getContext();
+ mExternalViewProperties = new ExternalViewProperties(this, mContext);
+ if (componentName != null) {
+ mContext.bindService(new Intent().setComponent(componentName),
+ mServiceConnection, Context.BIND_AUTO_CREATE);
+ }
+ mDisplaySize = new Point();
+ WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+ wm.getDefaultDisplay().getRealSize(mDisplaySize);
+ }
+
+ private ServiceConnection mServiceConnection = new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ try {
+ IExternalViewProviderFactory factory = IExternalViewProviderFactory.Stub.asInterface(service);
+ if (factory != null) {
+ mExternalViewProvider = IKeyguardExternalViewProvider.Stub.asInterface(
+ factory.createExternalView(null));
+ if (mExternalViewProvider != null) {
+ mExternalViewProvider.registerCallback(
+ KeyguardExternalView.this.mKeyguardExternalViewCallbacks);
+ mService = service;
+ mService.linkToDeath(KeyguardExternalView.this, 0);
+ executeQueue();
+ } else {
+ Log.e(TAG, "Unable to get external view provider");
+ }
+ } else {
+ Log.e(TAG, "Unable to get external view provider factory");
+ }
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ // We should unbind the service if we failed to connect to the provider
+ if (mService != service && service != null) {
+ mContext.unbindService(mServiceConnection);
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ if (mExternalViewProvider != null) {
+ try {
+ mExternalViewProvider.unregisterCallback(
+ KeyguardExternalView.this.mKeyguardExternalViewCallbacks);
+ } catch (RemoteException e) {
+ }
+ mExternalViewProvider = null;
+ }
+ if (mService != null) {
+ mService.unlinkToDeath(KeyguardExternalView.this, 0);
+ mService = null;
+ }
+ }
+ };
+
+ private final IKeyguardExternalViewCallbacks mKeyguardExternalViewCallbacks =
+ new IKeyguardExternalViewCallbacks.Stub() {
+ @Override
+ public boolean requestDismiss() throws RemoteException {
+ if (mCallback != null) {
+ return mCallback.requestDismiss();
+ }
+
+ return false;
+ }
+
+ @Override
+ public boolean requestDismissAndStartActivity(Intent intent) throws RemoteException {
+ if (mCallback != null) {
+ return mCallback.requestDismissAndStartActivity(intent);
+ }
+
+ return false;
+ }
+
+ @Override
+ public void collapseNotificationPanel() throws RemoteException {
+ if (mCallback != null) {
+ mCallback.collapseNotificationPanel();
+ }
+ }
+
+ @Override
+ public void setInteractivity(boolean isInteractive) {
+ mIsInteractive = isInteractive;
+ }
+
+ @Override
+ public void onAttachedToWindow() {
+ if (mWindowAttachmentListener != null) {
+ mWindowAttachmentListener.onAttachedToWindow();
+ }
+ }
+
+ @Override
+ public void onDetachedFromWindow() {
+ if (mWindowAttachmentListener != null) {
+ mWindowAttachmentListener.onDetachedFromWindow();
+ }
+ }
+
+ @Override
+ public void slideLockscreenIn() {
+ if (mCallback != null) {
+ mCallback.slideLockscreenIn();
+ }
+ }
+ };
+
+ private void executeQueue() {
+ while (!mQueue.isEmpty()) {
+ Runnable r = mQueue.pop();
+ r.run();
+ }
+ }
+
+ protected void performAction(Runnable r) {
+ if (mExternalViewProvider != null) {
+ r.run();
+ } else {
+ mQueue.add(r);
+ }
+ }
+
+ // view overrides, for positioning
+
+ @Override
+ public boolean onPreDraw() {
+ if (!mExternalViewProperties.hasChanged()) {
+ return true;
+ }
+ // keyguard views always take up the full screen when visible
+ final int x = mExternalViewProperties.getX();
+ final int y = mExternalViewProperties.getY();
+ final int width = mDisplaySize.x - x;
+ final int height = mDisplaySize.y - y;
+ final boolean visible = mExternalViewProperties.isVisible();
+ final Rect clipRect = new Rect(x, y, width + x, height + y);
+ performAction(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ mExternalViewProvider.alterWindow(x, y, width, height, visible,
+ clipRect);
+ } catch (RemoteException e) {
+ }
+ }
+ });
+ return true;
+ }
+
+ // Placeholder callbacks
+
+ @Override
+ public void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ performAction(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ mExternalViewProvider.onDetach();
+ } catch (RemoteException e) {
+ }
+ }
+ });
+ }
+
+ @Override
+ public void onAttachedToWindow() {
+ performAction(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ mExternalViewProvider.onAttach(null);
+ } catch (RemoteException e) {
+ }
+ }
+ });
+ }
+
+ @Override
+ public void binderDied() {
+ if (mCallback != null) {
+ mCallback.providerDied();
+ }
+ }
+
+ /**
+ * Sets the component of the {@link cyanogenmod.externalviews.KeyguardExternalViewProviderService}
+ * to be used for this ExternalView. If a provider is already connected to this view, it is
+ * first unbound before binding to the new provider.
+ * @param componentName The {@link cyanogenmod.externalviews.KeyguardExternalViewProviderService}
+ * to bind to.
+ */
+ public void setProviderComponent(ComponentName componentName) {
+ // unbind any existing external view provider
+ if (mExternalViewProvider != null) {
+ mContext.unbindService(mServiceConnection);
+ }
+ if (componentName != null) {
+ mContext.bindService(new Intent().setComponent(componentName),
+ mServiceConnection, Context.BIND_AUTO_CREATE);
+ }
+ }
+
+ /**
+ * Called from the host when the keyguard is being shown to the user.
+ * @param screenOn True if the screen is currently on.
+ */
+ public void onKeyguardShowing(final boolean screenOn) {
+ performAction(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ mExternalViewProvider.onKeyguardShowing(screenOn);
+ } catch (RemoteException e) {
+ }
+ }
+ });
+ }
+
+ /**
+ * Called from the host when the user has unlocked the device. Once this is called the lock
+ * lock screen should no longer displayed.
+ */
+ public void onKeyguardDismissed() {
+ performAction(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ mExternalViewProvider.onKeyguardDismissed();
+ } catch (RemoteException e) {
+ }
+ }
+ });
+ }
+
+ /**
+ * Called from the host when the keyguard is displaying the security screen for the user to
+ * enter their pin, password, or pattern.
+ * @param showing True if the bouncer is being show or false when it is dismissed without the
+ * device being unlocked.
+ */
+ public void onBouncerShowing(final boolean showing) {
+ performAction(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ mExternalViewProvider.onBouncerShowing(showing);
+ } catch (RemoteException e) {
+ }
+ }
+ });
+ }
+
+ /**
+ * Called from the host when the screen is turned on.
+ */
+ public void onScreenTurnedOn() {
+ performAction(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ mExternalViewProvider.onScreenTurnedOn();
+ } catch (RemoteException e) {
+ }
+ }
+ });
+ }
+
+ /**
+ * Called from the host when the screen is turned off.
+ */
+ public void onScreenTurnedOff() {
+ performAction(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ mExternalViewProvider.onScreenTurnedOff();
+ } catch (RemoteException e) {
+ }
+ }
+ });
+ }
+
+ /**
+ * Called from the host when the user is swiping the lockscreen
+ * to transition into the live lock screen
+ *
+ * @param swipeProgress [0-1] represents the progress of the swipe
+ */
+ public void onLockscreenSlideOffsetChanged(final float swipeProgress) {
+ performAction(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ mExternalViewProvider.onLockscreenSlideOffsetChanged(swipeProgress);
+ } catch (RemoteException e) {
+ }
+ }
+ });
+ }
+
+ /**
+ * External views provided by a
+ * {@link cyanogenmod.externalviews.KeyguardExternalViewProviderService} can be either
+ * interactive or non-interactive.
+ *
+ * <p>A non-interactive component does not receive any input events and functions similar to a
+ * live wallpaper.</p>
+ *
+ * <p>An interactive component can receive input events and allows the user to interact with it
+ * when the notification panel is not being displayed on top of the external view.</p>
+ *
+ * @return True if the current external view is interactive.
+ */
+ public boolean isInteractive() {
+ return mIsInteractive;
+ }
+
+ /**
+ * Registers a {@link cyanogenmod.externalviews.KeyguardExternalView.KeyguardExternalViewCallbacks}
+ * for receiving events from the
+ * {@link cyanogenmod.externalviews.KeyguardExternalViewProviderService}
+ * @param callback The callback to register
+ */
+ public void registerKeyguardExternalViewCallback(KeyguardExternalViewCallbacks callback) {
+ mCallback = callback;
+ }
+
+ /**
+ * Unregister a previously registered
+ * {@link cyanogenmod.externalviews.KeyguardExternalView.KeyguardExternalViewCallbacks}
+ * @param callback The callback to unregister
+ */
+ public void unregisterKeyguardExternalViewCallback(KeyguardExternalViewCallbacks callback) {
+ if (mCallback != callback) {
+ throw new IllegalArgumentException("Callback not registered");
+ }
+ mCallback = null;
+ }
+
+ /**
+ * Registers a {@link cyanogenmod.externalviews.KeyguardExternalView.OnWindowAttachmentChangedListener}
+ * for receiving events from the
+ * {@link cyanogenmod.externalviews.KeyguardExternalViewProviderService}
+ * @param listener The callback to register
+ *
+ * @hide
+ */
+ public void registerOnWindowAttachmentChangedListener(
+ OnWindowAttachmentChangedListener listener) {
+ mWindowAttachmentListener = listener;
+ }
+
+ /**
+ * Unregister a previously registered
+ * {@link cyanogenmod.externalviews.KeyguardExternalView.OnWindowAttachmentChangedListener}
+ * @param listener The callback to unregister
+ *
+ * @hide
+ */
+ public void unregisterOnWindowAttachmentChangedListener(
+ OnWindowAttachmentChangedListener listener) {
+ if (mWindowAttachmentListener != listener) {
+ throw new IllegalArgumentException("Callback not registered");
+ }
+ mWindowAttachmentListener = null;
+ }
+
+ /**
+ * Callback interface for a {@link cyanogenmod.externalviews.KeyguardExternalViewProviderService}
+ * to send events to the host's registered
+ * {@link cyanogenmod.externalviews.KeyguardExternalView.KeyguardExternalViewCallbacks}
+ */
+ public interface KeyguardExternalViewCallbacks {
+ boolean requestDismiss();
+ boolean requestDismissAndStartActivity(Intent intent);
+ void collapseNotificationPanel();
+ void providerDied();
+ void slideLockscreenIn();
+ }
+
+ /**
+ * Callback interface for changes to the containing window being attached and detached from the
+ * window manager.
+ * @hide
+ */
+ public interface OnWindowAttachmentChangedListener {
+ void onAttachedToWindow();
+ void onDetachedFromWindow();
+ }
+}
diff --git a/sdk/src/java/cyanogenmod/externalviews/KeyguardExternalViewProviderService.java b/sdk/src/java/cyanogenmod/externalviews/KeyguardExternalViewProviderService.java
new file mode 100644
index 0000000..4c69a0a
--- /dev/null
+++ b/sdk/src/java/cyanogenmod/externalviews/KeyguardExternalViewProviderService.java
@@ -0,0 +1,636 @@
+/*
+ * 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.externalviews;
+
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.ActionMode;
+import android.view.Gravity;
+import android.view.KeyEvent;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.MotionEvent;
+import android.view.SearchEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.Window;
+import android.view.WindowManager;
+import android.view.accessibility.AccessibilityEvent;
+import com.android.internal.policy.PhoneWindow;
+
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.FutureTask;
+
+/**
+ * A class for providing a view that can be displayed within the lock screen. Applications that
+ * wish to provide a view to be displayed within the lock screen should extend this service.
+ *
+ * <p>Applications extending this class should include the
+ * {@link cyanogenmod.platform.Manifest.permission#THIRD_PARTY_KEYGUARD} permission in their
+ * manifest</p>
+
+ * <p>Applications extending this class should also extend
+ * {@link KeyguardExternalViewProviderService.Provider} and return a new instance of
+ * {@link KeyguardExternalViewProviderService.Provider} in
+ * {@link KeyguardExternalViewProviderService#createExternalView(Bundle)}.</p>
+ */
+public abstract class KeyguardExternalViewProviderService extends Service {
+
+ private static final String TAG = KeyguardExternalViewProviderService.class.getSimpleName();
+ private static final boolean DEBUG = false;
+
+ /**
+ * The action that must be declared as handled by this service.
+ *
+ * <p>{@code
+ * <intent-filter>
+ * <action android:name="cyanogenmod.externalviews.KeyguardExternalViewProviderService"/>
+ * </intent-filter>
+ *}</p>
+ */
+ public static final String SERVICE_INTERFACE =
+ "cyanogenmod.externalviews.KeyguardExternalViewProviderService";
+
+ /**
+ * Name under which an external keyguard view publishes information about itself.
+ * This meta-data must reference an XML resource containing
+ * a <code>&lt;lockscreen&gt;</code>
+ * tag.
+ */
+ public static final String META_DATA = "cyanogenmod.externalviews.keyguard";
+
+ private WindowManager mWindowManager;
+ private final Handler mHandler = new Handler();
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+
+ mWindowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ super.onStartCommand(intent, flags, startId);
+ return START_NOT_STICKY;
+ }
+
+ @Override
+ public final IBinder onBind(Intent intent) {
+ return new IExternalViewProviderFactory.Stub() {
+ @Override public IBinder createExternalView(final Bundle options) {
+ FutureTask<IBinder> c = new FutureTask<IBinder>(new Callable<IBinder>() {
+ @Override
+ public IBinder call() throws Exception {
+ return KeyguardExternalViewProviderService.this
+ .createExternalView(options).mImpl;
+ }
+ });
+ mHandler.post(c);
+ try {
+ return c.get();
+ } catch (InterruptedException | ExecutionException e) {
+ Log.e(TAG, "error: ", e);
+ return null;
+ }
+ }
+ };
+ }
+
+ /**
+ * Called when the host has bound to this service.
+ * @param options Optional bundle. This param is currently not used.
+ * @return The newly created provider.
+ */
+ protected abstract Provider createExternalView(Bundle options);
+
+ /**
+ * This class provides an interface for the host and service to communicate to each other.
+ */
+ protected abstract class Provider {
+ private final class ProviderImpl extends IKeyguardExternalViewProvider.Stub
+ implements Window.Callback {
+ private final Window mWindow;
+ private final WindowManager.LayoutParams mParams;
+
+ private boolean mShouldShow = true;
+ private boolean mAskedShow = false;
+
+ private final RemoteCallbackList<IKeyguardExternalViewCallbacks> mCallbacks =
+ new RemoteCallbackList<IKeyguardExternalViewCallbacks>();
+
+ public ProviderImpl(Provider provider) {
+ mWindow = new PhoneWindow(KeyguardExternalViewProviderService.this);
+ mWindow.setCallback(this);
+ ((ViewGroup) mWindow.getDecorView()).addView(onCreateView());
+
+ mParams = new WindowManager.LayoutParams();
+ mParams.type = provider.getWindowType();
+ mParams.flags = provider.getWindowFlags();
+ mParams.gravity = Gravity.LEFT | Gravity.TOP;
+ mParams.format = PixelFormat.TRANSPARENT;
+ }
+
+ @Override
+ public void onAttach(IBinder windowToken) throws RemoteException {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mWindowManager.addView(mWindow.getDecorView(), mParams);
+ Provider.this.onAttach();
+ }
+ });
+ }
+
+ @Override
+ public void onDetach() throws RemoteException {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mWindowManager.removeView(mWindow.getDecorView());
+ Provider.this.onDetach();
+ }
+ });
+ }
+
+ @Override
+ public void onKeyguardShowing(final boolean screenOn) throws RemoteException {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ Provider.this.onKeyguardShowing(screenOn);
+ }
+ });
+ }
+
+ @Override
+ public void onKeyguardDismissed() throws RemoteException {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ Provider.this.onKeyguardDismissed();
+ }
+ });
+ }
+
+ @Override
+ public void onBouncerShowing(final boolean showing) throws RemoteException {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ Provider.this.onBouncerShowing(showing);
+ }
+ });
+ }
+
+ @Override
+ public void onScreenTurnedOn() throws RemoteException {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ Provider.this.onScreenTurnedOn();
+ }
+ });
+ }
+
+ @Override
+ public void onScreenTurnedOff() throws RemoteException {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ Provider.this.onScreenTurnedOff();
+ }
+ });
+ }
+
+ @Override
+ public void onLockscreenSlideOffsetChanged(final float swipeProgress)
+ throws RemoteException {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ Provider.this.onLockscreenSlideOffsetChanged(swipeProgress);
+ }
+ });
+ }
+
+ @Override
+ public void alterWindow(final int x, final int y, final int width, final int height,
+ final boolean visible, final Rect clipRect) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mParams.x = x;
+ mParams.y = y;
+ mParams.width = width;
+ mParams.height = height;
+
+ if (DEBUG) Log.d(TAG, mParams.toString());
+
+ mAskedShow = visible;
+
+ updateVisibility();
+
+ View decorView = mWindow.getDecorView();
+ if (decorView.getVisibility() == View.VISIBLE) {
+ decorView.setClipBounds(clipRect);
+ }
+
+ if (mWindow.getDecorView().getVisibility() != View.GONE)
+ mWindowManager.updateViewLayout(mWindow.getDecorView(), mParams);
+ }
+ });
+ }
+
+ @Override
+ public void registerCallback(IKeyguardExternalViewCallbacks callback) {
+ mCallbacks.register(callback);
+ }
+
+ @Override
+ public void unregisterCallback(IKeyguardExternalViewCallbacks callback) {
+ mCallbacks.unregister(callback);
+ }
+
+ private void updateVisibility() {
+ if (DEBUG) Log.d(TAG, "shouldShow = " + mShouldShow + " askedShow = " + mAskedShow);
+ mWindow.getDecorView().setVisibility(mShouldShow && mAskedShow ?
+ View.VISIBLE : View.GONE);
+ }
+
+ // callbacks from provider to host
+ protected final boolean requestDismiss() {
+ boolean ret = true;
+ int N = mCallbacks.beginBroadcast();
+ for(int i=0; i < N; i++) {
+ IKeyguardExternalViewCallbacks callback = mCallbacks.getBroadcastItem(0);
+ try {
+ ret &= callback.requestDismiss();
+ } catch(RemoteException e) {
+ }
+ }
+ mCallbacks.finishBroadcast();
+ return ret;
+ }
+
+ protected final boolean requestDismissAndStartActivity(final Intent intent) {
+ boolean ret = true;
+ int N = mCallbacks.beginBroadcast();
+ for(int i=0; i < N; i++) {
+ IKeyguardExternalViewCallbacks callback = mCallbacks.getBroadcastItem(0);
+ try {
+ ret &= callback.requestDismissAndStartActivity(intent);
+ } catch(RemoteException e) {
+ }
+ }
+ mCallbacks.finishBroadcast();
+ return ret;
+ }
+
+ protected final void collapseNotificationPanel() {
+ int N = mCallbacks.beginBroadcast();
+ for(int i=0; i < N; i++) {
+ IKeyguardExternalViewCallbacks callback = mCallbacks.getBroadcastItem(0);
+ try {
+ callback.collapseNotificationPanel();
+ } catch(RemoteException e) {
+ }
+ }
+ mCallbacks.finishBroadcast();
+ }
+
+ protected final void setInteractivity(final boolean isInteractive) {
+ int N = mCallbacks.beginBroadcast();
+ for(int i=0; i < N; i++) {
+ IKeyguardExternalViewCallbacks callback = mCallbacks.getBroadcastItem(0);
+ try {
+ callback.setInteractivity(isInteractive);
+ } catch(RemoteException e) {
+ }
+ }
+ mCallbacks.finishBroadcast();
+ }
+
+ public void slideLockscreenIn() {
+ int N = mCallbacks.beginBroadcast();
+ for(int i=0; i < N; i++) {
+ IKeyguardExternalViewCallbacks callback = mCallbacks.getBroadcastItem(0);
+ try {
+ callback.slideLockscreenIn();
+ } catch(RemoteException e) {
+ }
+ }
+ mCallbacks.finishBroadcast();
+ }
+
+ // region Window callbacks
+ @Override
+ public boolean dispatchKeyEvent(KeyEvent event) {
+ return false;
+ }
+
+ @Override
+ public boolean dispatchKeyShortcutEvent(KeyEvent event) {
+ return false;
+ }
+
+ @Override
+ public boolean dispatchTouchEvent(MotionEvent event) {
+ return false;
+ }
+
+ @Override
+ public boolean dispatchTrackballEvent(MotionEvent event) {
+ return false;
+ }
+
+ @Override
+ public boolean dispatchGenericMotionEvent(MotionEvent event) {
+ return false;
+ }
+
+ @Override
+ public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
+ return false;
+ }
+
+ @Override
+ public View onCreatePanelView(int featureId) {
+ return null;
+ }
+
+ @Override
+ public boolean onCreatePanelMenu(int featureId, Menu menu) {
+ return false;
+ }
+
+ @Override
+ public boolean onPreparePanel(int featureId, View view, Menu menu) {
+ return false;
+ }
+
+ @Override
+ public boolean onMenuOpened(int featureId, Menu menu) {
+ return false;
+ }
+
+ @Override
+ public boolean onMenuItemSelected(int featureId, MenuItem item) {
+ return false;
+ }
+
+ @Override
+ public void onWindowAttributesChanged(WindowManager.LayoutParams attrs) {}
+
+ @Override
+ public void onContentChanged() {}
+
+ @Override
+ public void onWindowFocusChanged(boolean hasFocus) {}
+
+ @Override
+ public void onAttachedToWindow() {
+ int N = mCallbacks.beginBroadcast();
+ for(int i=0; i < N; i++) {
+ IKeyguardExternalViewCallbacks callback = mCallbacks.getBroadcastItem(0);
+ try {
+ callback.onAttachedToWindow();
+ } catch(RemoteException e) {
+ }
+ }
+ mCallbacks.finishBroadcast();
+ }
+
+ @Override
+ public void onDetachedFromWindow() {
+ int N = mCallbacks.beginBroadcast();
+ for(int i=0; i < N; i++) {
+ IKeyguardExternalViewCallbacks callback = mCallbacks.getBroadcastItem(0);
+ try {
+ callback.onDetachedFromWindow();
+ } catch(RemoteException e) {
+ }
+ }
+ mCallbacks.finishBroadcast();
+ }
+
+ @Override
+ public void onPanelClosed(int featureId, Menu menu) {}
+
+ @Override
+ public boolean onSearchRequested() {
+ return false;
+ }
+
+ @Override
+ public boolean onSearchRequested(SearchEvent searchEvent) {
+ return false;
+ }
+
+ @Override
+ public ActionMode onWindowStartingActionMode(ActionMode.Callback callback) {
+ return null;
+ }
+
+ @Override
+ public ActionMode onWindowStartingActionMode(ActionMode.Callback callback, int type) {
+ return null;
+ }
+
+ @Override
+ public void onActionModeStarted(ActionMode mode) {}
+
+ @Override
+ public void onActionModeFinished(ActionMode mode) {}
+ }
+
+ private final ProviderImpl mImpl = new ProviderImpl(this);
+ private final Bundle mOptions;
+
+ protected Provider(Bundle options) {
+ mOptions = options;
+ }
+
+ protected Bundle getOptions() {
+ return mOptions;
+ }
+
+ /**
+ * Called when the host view is attached to a window.
+ */
+ protected void onAttach() {}
+
+ /**
+ * Called when the host view is detached from a window.
+ */
+ protected void onDetach() {}
+
+ /**
+ * Callback used for getting the view to be displayed within the host's content.
+ * @return The view to be displayed within the host's content. If null is returned no
+ * content will be displayed.
+ */
+ protected abstract View onCreateView();
+
+ // keyguard events
+
+ /**
+ * Called from the host when the keyguard is being shown to the user.
+ * @param screenOn True if the screen is currently on.
+ */
+ protected abstract void onKeyguardShowing(boolean screenOn);
+
+ /**
+ * Called from the host when the user has unlocked the device. Once this is called the lock
+ * lock screen is no longer being displayed.
+ *
+ * <p>The view component should enter a paused state when this is called, and save any state
+ * information that may be needed once the lock screen is displayed again. For example, a
+ * non-interactive component that provides animated visuals should pause playback of those
+ * animations and save the state, if necessary, of that animation.</p>
+ */
+ protected abstract void onKeyguardDismissed();
+
+ /**
+ * Called from the host when the keyguard is displaying the security screen for the user to
+ * enter their pin, password, or pattern.
+ *
+ * <p>Interactive components will no longer have focus when the bouncer is displayed and
+ * should enter a paused or idle state while the bouncer is being shown.</p>
+ * @param showing True if the bouncer is being show or false when it is dismissed without the
+ * device being unlocked.
+ */
+ protected abstract void onBouncerShowing(boolean showing);
+
+ /**
+ * Called from the host when the screen is turned on.
+ *
+ * <p>The provided view should return to a running state when this is called. For example,
+ * a non-interactive component that provides animated visuals should resume playback of
+ * those animations.</p>
+ */
+ protected abstract void onScreenTurnedOn();
+
+ /**
+ * Called from the host when the screen is turned off.
+ *
+ * <p>The provided view should provided view should pause its activity, if not currently
+ * in a paused state, and do any work necessary to be ready when the screen is turned
+ * back on. This will allow for a seamless user experience once the screen is turned on.
+ * </p>
+ */
+ protected abstract void onScreenTurnedOff();
+
+ /**
+ * Called from the host when the user is swiping the lockscreen
+ * to transition into the live lock screen
+ *
+ * @param swipeProgress [0-1] represents the progress of the swipe
+ */
+ protected void onLockscreenSlideOffsetChanged(float swipeProgress) {}
+
+ // callbacks from provider to host
+
+ /**
+ * Request that the keyguard be dismissed. Calling this method will dismiss the lock
+ * screen, if it is a not secure, or present the user with the security screen for the user
+ * to enter their security code to finish dismissing the lock screen.
+ *
+ * <p>If the user has a secure lock screen and dismisses the bouncer without entering their
+ * secure code, the lock screen will not be dismissed and
+ * {@link KeyguardExternalViewProviderService.Provider#onBouncerShowing(boolean)} will be
+ * called with {@code onShowing} being set to false, indicating that the lock screen was not
+ * dismissed as requested.</p>
+ * @return True if the call succeeded.
+ */
+ protected final boolean requestDismiss() {
+ return mImpl.requestDismiss();
+ }
+
+ /**
+ * Request that the keyguard be dismissed and the activity provided by the given intent be
+ * started once the keyguard is dismissed. If a secure lock screen is being used the user
+ * will need to enter their correct security code to finish dismissing the lock screen.
+ *
+ * <p>If the user has a secure lock screen and dismisses the bouncer without entering their
+ * secure code, the lock screen will not be dismissed and
+ * {@link KeyguardExternalViewProviderService.Provider#onBouncerShowing(boolean)} will be
+ * called with onShowing being set to false, indicating that the lock screen was not
+ * dismissed as requested.</p>
+ * @param intent An intent specifying an activity to launch.
+ * @return True if the call succeeded.
+ */
+ protected final boolean requestDismissAndStartActivity(final Intent intent) {
+ return mImpl.requestDismissAndStartActivity(intent);
+ }
+
+ /**
+ * Call this method when you would like to take focus and hide the notification panel.
+ *
+ * <p>You should call this method if your component requires focus and the users's
+ * attention. The user will still be able to bring the notifications back into view by
+ * sliding down from the status bar.
+ * Calling this method has no effect for non-interactive components.</p>
+ */
+ protected final void collapseNotificationPanel() {
+ mImpl.collapseNotificationPanel();
+ }
+
+ /**
+ * This method should be called when the provided view needs to change from interactive to
+ * non-interactive and vice versa.
+ *
+ * <p>Interactive components can receive input focus and receive user interaction while
+ * non-interactive components never receive focus and are purely visual.</p>
+ * @param isInteractive
+ */
+ protected final void setInteractivity(final boolean isInteractive) {
+ mImpl.setInteractivity(isInteractive);
+ }
+
+ /**
+ * Call this method when you like to slide in the lockscreen on top of
+ * your live lockscreen. Only relevant if you use
+ * {@link KeyguardExternalViewProviderService.Provider#setInteractivity(boolean)}
+ */
+ protected final void slideLockscreenIn() {
+ mImpl.slideLockscreenIn();
+ }
+
+ /*package*/ final int getWindowType() {
+ return WindowManager.LayoutParams.TYPE_KEYGUARD_PANEL;
+ }
+
+ /*package*/ final int getWindowFlags() {
+ return WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL |
+ WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS |
+ WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN |
+ WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED |
+ WindowManager.LayoutParams.FLAG_FULLSCREEN;
+ }
+ }
+}
diff --git a/sdk/src/java/cyanogenmod/hardware/CMHardwareManager.java b/sdk/src/java/cyanogenmod/hardware/CMHardwareManager.java
new file mode 100644
index 0000000..5bfb858
--- /dev/null
+++ b/sdk/src/java/cyanogenmod/hardware/CMHardwareManager.java
@@ -0,0 +1,857 @@
+/*
+ * Copyright (C) 2015-2016 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.hardware;
+
+import android.content.Context;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Log;
+
+import cyanogenmod.app.CMContextConstants;
+
+import java.io.UnsupportedEncodingException;
+import java.lang.IllegalArgumentException;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Manages access to CyanogenMod hardware extensions
+ *
+ * <p>
+ * This manager requires the HARDWARE_ABSTRACTION_ACCESS permission.
+ * <p>
+ * To get the instance of this class, utilize CMHardwareManager#getInstance(Context context)
+ */
+public final class CMHardwareManager {
+ private static final String TAG = "CMHardwareManager";
+
+ private static ICMHardwareService sService;
+
+ private Context mContext;
+ /**
+ * Adaptive backlight support (this refers to technologies like NVIDIA SmartDimmer,
+ * QCOM CABL or Samsung CABC)
+ */
+ public static final int FEATURE_ADAPTIVE_BACKLIGHT = 0x1;
+
+ /**
+ * Color enhancement support
+ */
+ public static final int FEATURE_COLOR_ENHANCEMENT = 0x2;
+
+ /**
+ * Display RGB color calibration
+ */
+ public static final int FEATURE_DISPLAY_COLOR_CALIBRATION = 0x4;
+
+ /**
+ * Display gamma calibration
+ */
+ public static final int FEATURE_DISPLAY_GAMMA_CALIBRATION = 0x8;
+
+ /**
+ * High touch sensitivity for touch panels
+ */
+ public static final int FEATURE_HIGH_TOUCH_SENSITIVITY = 0x10;
+
+ /**
+ * Hardware navigation key disablement
+ */
+ public static final int FEATURE_KEY_DISABLE = 0x20;
+
+ /**
+ * Long term orbits (LTO)
+ */
+ public static final int FEATURE_LONG_TERM_ORBITS = 0x40;
+
+ /**
+ * Serial number other than ro.serialno
+ */
+ public static final int FEATURE_SERIAL_NUMBER = 0x80;
+
+ /**
+ * Increased display readability in bright light
+ */
+ public static final int FEATURE_SUNLIGHT_ENHANCEMENT = 0x100;
+
+ /**
+ * Double-tap the touch panel to wake up the device
+ */
+ public static final int FEATURE_TAP_TO_WAKE = 0x200;
+
+ /**
+ * Variable vibrator intensity
+ */
+ public static final int FEATURE_VIBRATOR = 0x400;
+
+ /**
+ * Touchscreen hovering
+ */
+ public static final int FEATURE_TOUCH_HOVERING = 0x800;
+
+ /**
+ * Auto contrast
+ */
+ public static final int FEATURE_AUTO_CONTRAST = 0x1000;
+
+ /**
+ * Display modes
+ */
+ public static final int FEATURE_DISPLAY_MODES = 0x2000;
+
+ /**
+ * Persistent storage
+ */
+ public static final int FEATURE_PERSISTENT_STORAGE = 0x4000;
+
+ /**
+ * Thermal change monitor
+ */
+ public static final int FEATURE_THERMAL_MONITOR = 0x8000;
+
+ /**
+ * Unique device ID
+ */
+ public static final int FEATURE_UNIQUE_DEVICE_ID = 0x10000;
+
+ private static final List<Integer> BOOLEAN_FEATURES = Arrays.asList(
+ FEATURE_ADAPTIVE_BACKLIGHT,
+ FEATURE_COLOR_ENHANCEMENT,
+ FEATURE_HIGH_TOUCH_SENSITIVITY,
+ FEATURE_KEY_DISABLE,
+ FEATURE_SUNLIGHT_ENHANCEMENT,
+ FEATURE_TAP_TO_WAKE,
+ FEATURE_TOUCH_HOVERING,
+ FEATURE_AUTO_CONTRAST,
+ FEATURE_THERMAL_MONITOR
+ );
+
+ private static CMHardwareManager sCMHardwareManagerInstance;
+
+ /**
+ * @hide to prevent subclassing from outside of the framework
+ */
+ private CMHardwareManager(Context context) {
+ Context appContext = context.getApplicationContext();
+ if (appContext != null) {
+ mContext = appContext;
+ } else {
+ mContext = context;
+ }
+ sService = getService();
+
+ if (context.getPackageManager().hasSystemFeature(
+ CMContextConstants.Features.HARDWARE_ABSTRACTION) && !checkService()) {
+ throw new RuntimeException("Unable to get CMHardwareService. The service either" +
+ " crashed, was not started, or the interface has been called to early in" +
+ " SystemServer init");
+ }
+ }
+
+ /**
+ * Get or create an instance of the {@link cyanogenmod.hardware.CMHardwareManager}
+ * @param context
+ * @return {@link CMHardwareManager}
+ */
+ public static CMHardwareManager getInstance(Context context) {
+ if (sCMHardwareManagerInstance == null) {
+ sCMHardwareManagerInstance = new CMHardwareManager(context);
+ }
+ return sCMHardwareManagerInstance;
+ }
+
+ /** @hide */
+ public static ICMHardwareService getService() {
+ if (sService != null) {
+ return sService;
+ }
+ IBinder b = ServiceManager.getService(CMContextConstants.CM_HARDWARE_SERVICE);
+ if (b != null) {
+ sService = ICMHardwareService.Stub.asInterface(b);
+ return sService;
+ }
+ return null;
+ }
+
+ /**
+ * @return the supported features bitmask
+ */
+ public int getSupportedFeatures() {
+ try {
+ if (checkService()) {
+ return sService.getSupportedFeatures();
+ }
+ } catch (RemoteException e) {
+ }
+ return 0;
+ }
+
+ /**
+ * Determine if a CM Hardware feature is supported on this device
+ *
+ * @param feature The CM Hardware feature to query
+ *
+ * @return true if the feature is supported, false otherwise.
+ */
+ public boolean isSupported(int feature) {
+ return feature == (getSupportedFeatures() & feature);
+ }
+
+ /**
+ * Determine if the given feature is enabled or disabled.
+ *
+ * Only used for features which have simple enable/disable controls.
+ *
+ * @param feature the CM Hardware feature to query
+ *
+ * @return true if the feature is enabled, false otherwise.
+ */
+ public boolean get(int feature) {
+ if (!BOOLEAN_FEATURES.contains(feature)) {
+ throw new IllegalArgumentException(feature + " is not a boolean");
+ }
+
+ try {
+ if (checkService()) {
+ return sService.get(feature);
+ }
+ } catch (RemoteException e) {
+ }
+ return false;
+ }
+
+ /**
+ * Enable or disable the given feature
+ *
+ * Only used for features which have simple enable/disable controls.
+ *
+ * @param feature the CM Hardware feature to set
+ * @param enable true to enable, false to disale
+ *
+ * @return true if the feature is enabled, false otherwise.
+ */
+ public boolean set(int feature, boolean enable) {
+ if (!BOOLEAN_FEATURES.contains(feature)) {
+ throw new IllegalArgumentException(feature + " is not a boolean");
+ }
+
+ try {
+ if (checkService()) {
+ return sService.set(feature, enable);
+ }
+ } catch (RemoteException e) {
+ }
+ return false;
+ }
+
+ private int getArrayValue(int[] arr, int idx, int defaultValue) {
+ if (arr == null || arr.length <= idx) {
+ return defaultValue;
+ }
+
+ return arr[idx];
+ }
+
+ /**
+ * {@hide}
+ */
+ public static final int VIBRATOR_INTENSITY_INDEX = 0;
+ /**
+ * {@hide}
+ */
+ public static final int VIBRATOR_DEFAULT_INDEX = 1;
+ /**
+ * {@hide}
+ */
+ public static final int VIBRATOR_MIN_INDEX = 2;
+ /**
+ * {@hide}
+ */
+ public static final int VIBRATOR_MAX_INDEX = 3;
+ /**
+ * {@hide}
+ */
+ public static final int VIBRATOR_WARNING_INDEX = 4;
+
+ private int[] getVibratorIntensityArray() {
+ try {
+ if (checkService()) {
+ return sService.getVibratorIntensity();
+ }
+ } catch (RemoteException e) {
+ }
+ return null;
+ }
+
+ /**
+ * @return The current vibrator intensity.
+ */
+ public int getVibratorIntensity() {
+ return getArrayValue(getVibratorIntensityArray(), VIBRATOR_INTENSITY_INDEX, 0);
+ }
+
+ /**
+ * @return The default vibrator intensity.
+ */
+ public int getVibratorDefaultIntensity() {
+ return getArrayValue(getVibratorIntensityArray(), VIBRATOR_DEFAULT_INDEX, 0);
+ }
+
+ /**
+ * @return The minimum vibrator intensity.
+ */
+ public int getVibratorMinIntensity() {
+ return getArrayValue(getVibratorIntensityArray(), VIBRATOR_MIN_INDEX, 0);
+ }
+
+ /**
+ * @return The maximum vibrator intensity.
+ */
+ public int getVibratorMaxIntensity() {
+ return getArrayValue(getVibratorIntensityArray(), VIBRATOR_MAX_INDEX, 0);
+ }
+
+ /**
+ * @return The warning threshold vibrator intensity.
+ */
+ public int getVibratorWarningIntensity() {
+ return getArrayValue(getVibratorIntensityArray(), VIBRATOR_WARNING_INDEX, 0);
+ }
+
+ /**
+ * Set the current vibrator intensity
+ *
+ * @param intensity the intensity to set, between {@link #getVibratorMinIntensity()} and
+ * {@link #getVibratorMaxIntensity()} inclusive.
+ *
+ * @return true on success, false otherwise.
+ */
+ public boolean setVibratorIntensity(int intensity) {
+ try {
+ if (checkService()) {
+ return sService.setVibratorIntensity(intensity);
+ }
+ } catch (RemoteException e) {
+ }
+ return false;
+ }
+
+ /**
+ * {@hide}
+ */
+ public static final int COLOR_CALIBRATION_RED_INDEX = 0;
+ /**
+ * {@hide}
+ */
+ public static final int COLOR_CALIBRATION_GREEN_INDEX = 1;
+ /**
+ * {@hide}
+ */
+ public static final int COLOR_CALIBRATION_BLUE_INDEX = 2;
+ /**
+ * {@hide}
+ */
+ public static final int COLOR_CALIBRATION_DEFAULT_INDEX = 3;
+ /**
+ * {@hide}
+ */
+ public static final int COLOR_CALIBRATION_MIN_INDEX = 4;
+ /**
+ * {@hide}
+ */
+ public static final int COLOR_CALIBRATION_MAX_INDEX = 5;
+
+ private int[] getDisplayColorCalibrationArray() {
+ try {
+ if (checkService()) {
+ return sService.getDisplayColorCalibration();
+ }
+ } catch (RemoteException e) {
+ }
+ return null;
+ }
+
+ /**
+ * @return the current RGB calibration, where int[0] = R, int[1] = G, int[2] = B.
+ */
+ public int[] getDisplayColorCalibration() {
+ int[] arr = getDisplayColorCalibrationArray();
+ if (arr == null || arr.length < 3) {
+ return null;
+ }
+ return Arrays.copyOf(arr, 3);
+ }
+
+ /**
+ * @return the default value for all colors
+ */
+ public int getDisplayColorCalibrationDefault() {
+ return getArrayValue(getDisplayColorCalibrationArray(), COLOR_CALIBRATION_DEFAULT_INDEX, 0);
+ }
+
+ /**
+ * @return The minimum value for all colors
+ */
+ public int getDisplayColorCalibrationMin() {
+ return getArrayValue(getDisplayColorCalibrationArray(), COLOR_CALIBRATION_MIN_INDEX, 0);
+ }
+
+ /**
+ * @return The minimum value for all colors
+ */
+ public int getDisplayColorCalibrationMax() {
+ return getArrayValue(getDisplayColorCalibrationArray(), COLOR_CALIBRATION_MAX_INDEX, 0);
+ }
+
+ /**
+ * Set the display color calibration to the given rgb triplet
+ *
+ * @param rgb RGB color calibration. Each value must be between
+ * {@link getDisplayColorCalibrationMin()} and {@link getDisplayColorCalibrationMax()},
+ * inclusive.
+ *
+ * @return true on success, false otherwise.
+ */
+ public boolean setDisplayColorCalibration(int[] rgb) {
+ try {
+ if (checkService()) {
+ return sService.setDisplayColorCalibration(rgb);
+ }
+ } catch (RemoteException e) {
+ }
+ return false;
+ }
+
+ /**
+ * Write a string to persistent storage, which persists thru factory reset
+ *
+ * @param key String identifier for this item. Must not exceed 64 characters.
+ * @param value The UTF-8 encoded string to store of at least 1 character. null deletes the key/value pair.
+ * @return true on success
+ */
+ public boolean writePersistentString(String key, String value) {
+ try {
+ if (checkService()) {
+ return sService.writePersistentBytes(key,
+ value == null ? null : value.getBytes("UTF-8"));
+ }
+ } catch (RemoteException e) {
+ } catch (UnsupportedEncodingException e) {
+ Log.e(TAG, e.getMessage(), e);
+ }
+ return false;
+ }
+
+ /**
+ * Write an integer to persistent storage, which persists thru factory reset
+ *
+ * @param key String identifier for this item. Must not exceed 64 characters.
+ * @param value The integer to store
+ * @return true on success
+ */
+ public boolean writePersistentInt(String key, int value) {
+ try {
+ if (checkService()) {
+ return sService.writePersistentBytes(key,
+ ByteBuffer.allocate(4).putInt(value).array());
+ }
+ } catch (RemoteException e) {
+ }
+ return false;
+ }
+
+ /**
+ * Write a byte array to persistent storage, which persists thru factory reset
+ *
+ * @param key String identifier for this item. Must not exceed 64 characters.
+ * @param value The byte array to store, must be 1-4096 bytes. null deletes the key/value pair.
+ * @return true on success
+ */
+ public boolean writePersistentBytes(String key, byte[] value) {
+ try {
+ if (checkService()) {
+ return sService.writePersistentBytes(key, value);
+ }
+ } catch (RemoteException e) {
+ }
+ return false;
+ }
+
+ /**
+ * Read a string from persistent storage
+ *
+ * @param key String identifier for this item. Must not exceed 64 characters.
+ * @return the stored UTF-8 encoded string, null if not found
+ */
+ public String readPersistentString(String key) {
+ try {
+ if (checkService()) {
+ byte[] bytes = sService.readPersistentBytes(key);
+ if (bytes != null) {
+ return new String(bytes, "UTF-8");
+ }
+ }
+ } catch (RemoteException e) {
+ } catch (UnsupportedEncodingException e) {
+ Log.e(TAG, e.getMessage(), e);
+ }
+ return null;
+ }
+
+ /**
+ * Read an integer from persistent storage
+ *
+ * @param key String identifier for this item. Must not exceed 64 characters.
+ * @return the stored integer, zero if not found
+ */
+ public int readPersistentInt(String key) {
+ try {
+ if (checkService()) {
+ byte[] bytes = sService.readPersistentBytes(key);
+ if (bytes != null) {
+ return ByteBuffer.wrap(bytes).getInt();
+ }
+ }
+ } catch (RemoteException e) {
+ }
+ return 0;
+ }
+
+ /**
+ * Read a byte array from persistent storage
+ *
+ * @param key String identifier for this item. Must not exceed 64 characters.
+ * @return the stored byte array, null if not found
+ */
+ public byte[] readPersistentBytes(String key) {
+ try {
+ if (checkService()) {
+ return sService.readPersistentBytes(key);
+ }
+ } catch (RemoteException e) {
+ }
+ return null;
+ }
+
+ /** Delete an object from persistent storage
+ *
+ * @param key String identifier for this item
+ * @return true if an item was deleted
+ */
+ public boolean deletePersistentObject(String key) {
+ try {
+ if (checkService()) {
+ return sService.writePersistentBytes(key, null);
+ }
+ } catch (RemoteException e) {
+ }
+ return false;
+ }
+
+ /**
+ * {@hide}
+ */
+ public static final int GAMMA_CALIBRATION_RED_INDEX = 0;
+ /**
+ * {@hide}
+ */
+ public static final int GAMMA_CALIBRATION_GREEN_INDEX = 1;
+ /**
+ * {@hide}
+ */
+ public static final int GAMMA_CALIBRATION_BLUE_INDEX = 2;
+ /**
+ * {@hide}
+ */
+ public static final int GAMMA_CALIBRATION_MIN_INDEX = 3;
+ /**
+ * {@hide}
+ */
+ public static final int GAMMA_CALIBRATION_MAX_INDEX = 4;
+
+ private int[] getDisplayGammaCalibrationArray(int idx) {
+ try {
+ if (checkService()) {
+ return sService.getDisplayGammaCalibration(idx);
+ }
+ } catch (RemoteException e) {
+ }
+ return null;
+ }
+
+ /**
+ * @return the number of RGB controls the device supports
+ */
+ @Deprecated
+ public int getNumGammaControls() {
+ try {
+ if (checkService()) {
+ return sService.getNumGammaControls();
+ }
+ } catch (RemoteException e) {
+ }
+ return 0;
+ }
+
+ /**
+ * @param the control to query
+ *
+ * @return the current RGB gamma calibration for the given control
+ */
+ @Deprecated
+ public int[] getDisplayGammaCalibration(int idx) {
+ int[] arr = getDisplayGammaCalibrationArray(idx);
+ if (arr == null || arr.length < 3) {
+ return null;
+ }
+ return Arrays.copyOf(arr, 3);
+ }
+
+ /**
+ * @return the minimum value for all colors
+ */
+ @Deprecated
+ public int getDisplayGammaCalibrationMin() {
+ return getArrayValue(getDisplayGammaCalibrationArray(0), GAMMA_CALIBRATION_MIN_INDEX, 0);
+ }
+
+ /**
+ * @return the maximum value for all colors
+ */
+ @Deprecated
+ public int getDisplayGammaCalibrationMax() {
+ return getArrayValue(getDisplayGammaCalibrationArray(0), GAMMA_CALIBRATION_MAX_INDEX, 0);
+ }
+
+ /**
+ * Set the display gamma calibration for a specific control
+ *
+ * @param idx the control to set
+ * @param rgb RGB color calibration. Each value must be between
+ * {@link getDisplayGammaCalibrationMin()} and {@link getDisplayGammaCalibrationMax()},
+ * inclusive.
+ *
+ * @return true on success, false otherwise.
+ */
+ @Deprecated
+ public boolean setDisplayGammaCalibration(int idx, int[] rgb) {
+ try {
+ if (checkService()) {
+ return sService.setDisplayGammaCalibration(idx, rgb);
+ }
+ } catch (RemoteException e) {
+ }
+ return false;
+ }
+
+ /**
+ * @return the source location of LTO data, or null on failure
+ */
+ public String getLtoSource() {
+ try {
+ if (checkService()) {
+ return sService.getLtoSource();
+ }
+ } catch (RemoteException e) {
+ }
+ return null;
+ }
+
+ /**
+ * @return the destination location of LTO data, or null on failure
+ */
+ public String getLtoDestination() {
+ try {
+ if (checkService()) {
+ return sService.getLtoDestination();
+ }
+ } catch (RemoteException e) {
+ }
+ return null;
+ }
+
+ /**
+ * @return the interval, in milliseconds, to trigger LTO data download
+ */
+ public long getLtoDownloadInterval() {
+ try {
+ if (checkService()) {
+ return sService.getLtoDownloadInterval();
+ }
+ } catch (RemoteException e) {
+ }
+ return 0;
+ }
+
+ /**
+ * @return the serial number to display instead of ro.serialno, or null on failure
+ */
+ public String getSerialNumber() {
+ try {
+ if (checkService()) {
+ return sService.getSerialNumber();
+ }
+ } catch (RemoteException e) {
+ }
+ return null;
+ }
+
+ /**
+ * @return an id that's both unique and deterministic for the device
+ */
+ public String getUniqueDeviceId() {
+ try {
+ if (checkService()) {
+ return sService.getUniqueDeviceId();
+ }
+ } catch (RemoteException e) {
+ }
+ return null;
+ }
+
+ /**
+ * @return true if adaptive backlight should be enabled when sunlight enhancement
+ * is enabled.
+ */
+ public boolean requireAdaptiveBacklightForSunlightEnhancement() {
+ try {
+ if (checkService()) {
+ return sService.requireAdaptiveBacklightForSunlightEnhancement();
+ }
+ } catch (RemoteException e) {
+ }
+ return false;
+ }
+
+ /**
+ * @return true if this implementation does it's own lux metering
+ */
+ public boolean isSunlightEnhancementSelfManaged() {
+ try {
+ if (checkService()) {
+ return sService.isSunlightEnhancementSelfManaged();
+ }
+ } catch (RemoteException e) {
+ }
+ return false;
+ }
+
+ /**
+ * @return a list of available display modes on the devices
+ */
+ public DisplayMode[] getDisplayModes() {
+ try {
+ if (checkService()) {
+ return sService.getDisplayModes();
+ }
+ } catch (RemoteException e) {
+ }
+ return null;
+ }
+
+ /**
+ * @return the currently active display mode
+ */
+ public DisplayMode getCurrentDisplayMode() {
+ try {
+ if (checkService()) {
+ return sService.getCurrentDisplayMode();
+ }
+ } catch (RemoteException e) {
+ }
+ return null;
+ }
+
+ /**
+ * @return the default display mode to be set on boot
+ */
+ public DisplayMode getDefaultDisplayMode() {
+ try {
+ if (checkService()) {
+ return sService.getDefaultDisplayMode();
+ }
+ } catch (RemoteException e) {
+ }
+ return null;
+ }
+
+ /**
+ * @return true if setting the mode was successful
+ */
+ public boolean setDisplayMode(DisplayMode mode, boolean makeDefault) {
+ try {
+ if (checkService()) {
+ return sService.setDisplayMode(mode, makeDefault);
+ }
+ } catch (RemoteException e) {
+ }
+ return false;
+ }
+
+ /**
+ * @return true if service is valid
+ */
+ private boolean checkService() {
+ if (sService == null) {
+ Log.w(TAG, "not connected to CMHardwareManagerService");
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * @return current thermal {@link cyanogenmod.hardware.ThermalListenerCallback.State}
+ */
+ public int getThermalState() {
+ try {
+ if (checkService()) {
+ return sService.getThermalState();
+ }
+ } catch (RemoteException e) {
+ }
+ return ThermalListenerCallback.State.STATE_UNKNOWN;
+ }
+
+ /**
+ * Register a callback to be notified of thermal state changes
+ * @return boolean indicating whether register succeeded or failed
+ */
+ public boolean registerThermalListener(ThermalListenerCallback thermalCallback) {
+ try {
+ if (checkService()) {
+ return sService.registerThermalListener(thermalCallback);
+ }
+ } catch (RemoteException e) {
+ }
+ return false;
+ }
+
+ /**
+ * Unregister a callback previously registered to be notified of thermal state changes
+ * @return boolean indicating whether un-registering succeeded or failed
+ */
+ public boolean unRegisterThermalListener(ThermalListenerCallback thermalCallback) {
+ try {
+ if (checkService()) {
+ return sService.unRegisterThermalListener(thermalCallback);
+ }
+ } catch (RemoteException e) {
+ }
+ return false;
+ }
+}
diff --git a/sdk/src/java/cyanogenmod/hardware/DisplayMode.aidl b/sdk/src/java/cyanogenmod/hardware/DisplayMode.aidl
new file mode 100644
index 0000000..a4f9163
--- /dev/null
+++ b/sdk/src/java/cyanogenmod/hardware/DisplayMode.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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.hardware;
+
+parcelable DisplayMode;
diff --git a/sdk/src/java/cyanogenmod/hardware/DisplayMode.java b/sdk/src/java/cyanogenmod/hardware/DisplayMode.java
new file mode 100644
index 0000000..77172b8
--- /dev/null
+++ b/sdk/src/java/cyanogenmod/hardware/DisplayMode.java
@@ -0,0 +1,109 @@
+/*
+ * 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.hardware;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import cyanogenmod.os.Build;
+import cyanogenmod.os.Concierge;
+import cyanogenmod.os.Concierge.ParcelInfo;
+
+/**
+ * Display Modes API
+ *
+ * A device may implement a list of preset display modes for different
+ * viewing intents, such as movies, photos, or extra vibrance. These
+ * modes may have multiple components such as gamma correction, white
+ * point adjustment, etc, but are activated by a single control point.
+ *
+ * This API provides support for enumerating and selecting the
+ * modes supported by the hardware.
+ *
+ * A DisplayMode is referenced by it's identifier and carries an
+ * associated name (up to the user to translate this value).
+ */
+public class DisplayMode implements Parcelable {
+ public final int id;
+ public final String name;
+
+ public DisplayMode(int id, String name) {
+ this.id = id;
+ this.name = name;
+ }
+
+ private DisplayMode(Parcel parcel) {
+ // Read parcelable version via the Concierge
+ ParcelInfo parcelInfo = Concierge.receiveParcel(parcel);
+ int parcelableVersion = parcelInfo.getParcelVersion();
+
+ // temp vars
+ int tmpId = -1;
+ String tmpName = null;
+
+ if (parcelableVersion >= Build.CM_VERSION_CODES.BOYSENBERRY) {
+ tmpId = parcel.readInt();
+ if (parcel.readInt() != 0) {
+ tmpName = parcel.readString();
+ }
+ }
+
+ // set temps
+ this.id = tmpId;
+ this.name = tmpName;
+
+ // Complete parcel info for the concierge
+ parcelInfo.complete();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ // Tell the concierge to prepare the parcel
+ ParcelInfo parcelInfo = Concierge.prepareParcel(out);
+
+ // ==== BOYSENBERRY =====
+ out.writeInt(id);
+ if (name != null) {
+ out.writeInt(1);
+ out.writeString(name);
+ } else {
+ out.writeInt(0);
+ }
+
+ // Complete the parcel info for the concierge
+ parcelInfo.complete();
+ }
+
+ /** @hide */
+ public static final Parcelable.Creator<DisplayMode> CREATOR =
+ new Parcelable.Creator<DisplayMode>() {
+ public DisplayMode createFromParcel(Parcel in) {
+ return new DisplayMode(in);
+ }
+
+ @Override
+ public DisplayMode[] newArray(int size) {
+ return new DisplayMode[size];
+ }
+ };
+
+}
diff --git a/sdk/src/java/cyanogenmod/hardware/ICMHardwareService.aidl b/sdk/src/java/cyanogenmod/hardware/ICMHardwareService.aidl
new file mode 100644
index 0000000..a1ae65b
--- /dev/null
+++ b/sdk/src/java/cyanogenmod/hardware/ICMHardwareService.aidl
@@ -0,0 +1,61 @@
+/**
+ * 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.hardware;
+
+import cyanogenmod.hardware.DisplayMode;
+import cyanogenmod.hardware.IThermalListenerCallback;
+
+/** @hide */
+interface ICMHardwareService {
+
+ int getSupportedFeatures();
+ boolean get(int feature);
+ boolean set(int feature, boolean enable);
+
+ int[] getDisplayColorCalibration();
+ boolean setDisplayColorCalibration(in int[] rgb);
+
+ int getNumGammaControls();
+ int[] getDisplayGammaCalibration(int idx);
+ boolean setDisplayGammaCalibration(int idx, in int[] rgb);
+
+ int[] getVibratorIntensity();
+ boolean setVibratorIntensity(int intensity);
+
+ String getLtoSource();
+ String getLtoDestination();
+ long getLtoDownloadInterval();
+
+ String getSerialNumber();
+
+ boolean requireAdaptiveBacklightForSunlightEnhancement();
+
+ DisplayMode[] getDisplayModes();
+ DisplayMode getCurrentDisplayMode();
+ DisplayMode getDefaultDisplayMode();
+ boolean setDisplayMode(in DisplayMode mode, boolean makeDefault);
+
+ boolean writePersistentBytes(String key, in byte[] bytes);
+ byte[] readPersistentBytes(String key);
+
+ int getThermalState();
+ boolean registerThermalListener(IThermalListenerCallback callback);
+ boolean unRegisterThermalListener(IThermalListenerCallback callback);
+ boolean isSunlightEnhancementSelfManaged();
+
+ String getUniqueDeviceId();
+}
diff --git a/sdk/src/java/cyanogenmod/hardware/IThermalListenerCallback.aidl b/sdk/src/java/cyanogenmod/hardware/IThermalListenerCallback.aidl
new file mode 100644
index 0000000..1a53af2
--- /dev/null
+++ b/sdk/src/java/cyanogenmod/hardware/IThermalListenerCallback.aidl
@@ -0,0 +1,21 @@
+/**
+ * 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.hardware;
+
+interface IThermalListenerCallback {
+ void onThermalChanged(int state);
+}
diff --git a/sdk/src/java/cyanogenmod/hardware/ThermalListenerCallback.java b/sdk/src/java/cyanogenmod/hardware/ThermalListenerCallback.java
new file mode 100644
index 0000000..3ef312d
--- /dev/null
+++ b/sdk/src/java/cyanogenmod/hardware/ThermalListenerCallback.java
@@ -0,0 +1,44 @@
+/**
+ * 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.hardware;
+
+/**
+ * Callback class to register for thermal state changes
+ */
+public abstract class ThermalListenerCallback extends IThermalListenerCallback.Stub {
+ public static final class State {
+ public static final int STATE_UNKNOWN = -1;
+ public static final int STATE_COOL = 0;
+ public static final int STATE_WARM_FALLING = 1;
+ public static final int STATE_WARM_RISING = 2;
+ public static final int STATE_CRITICAL = 3;
+ public static final String toString(int state) {
+ switch (state) {
+ case STATE_COOL:
+ return "STATE_COOL";
+ case STATE_WARM_FALLING:
+ return "STATE_WARM_FALLING";
+ case STATE_WARM_RISING:
+ return "STATE_WARM_RISING";
+ case STATE_CRITICAL:
+ return "STATE_CRITICAL";
+ default:
+ return "STATE_UNKNOWN";
+ }
+ }
+ }
+}
diff --git a/sdk/src/java/cyanogenmod/media/MediaRecorder.java b/sdk/src/java/cyanogenmod/media/MediaRecorder.java
new file mode 100644
index 0000000..c28cc93
--- /dev/null
+++ b/sdk/src/java/cyanogenmod/media/MediaRecorder.java
@@ -0,0 +1,84 @@
+/*
+ * 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.media;
+
+public class MediaRecorder {
+ /**
+ * Allows an application to listen passively to the device microphone in the background
+ * to detect speech of a hotword or phrase.
+ *
+ * This is a system|signature permission.
+ */
+ public static final String CAPTURE_AUDIO_HOTWORD_PERMISSION
+ = "android.permission.CAPTURE_AUDIO_HOTWORD";
+
+ /**
+ * <p>Broadcast Action: The state of the HOTWORD audio input has changed.:</p>
+ * <ul>
+ * <li><em>state</em> - A String value indicating the state of the input.
+ * {@link #EXTRA_HOTWORD_INPUT_STATE}. The value will be one of:
+ * {@link android.media.AudioRecord#RECORDSTATE_RECORDING} or
+ * {@link android.media.AudioRecord#RECORDSTATE_STOPPED}.
+ * </li>
+ * <li><em>package</em> - A String value indicating the package name of the application
+ * that currently holds the HOTWORD input.
+ * {@link #EXTRA_CURRENT_PACKAGE_NAME}
+ * </li>
+
+ * </ul>
+ *
+ * <p class="note">This is a protected intent that can only be sent
+ * by the system. It can only be received by packages that hold
+ * {@link android.Manifest.permission#CAPTURE_AUDIO_HOTWORD}.
+ */
+ //@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_HOTWORD_INPUT_CHANGED
+ = "com.cyanogenmod.intent.action.HOTWORD_INPUT_CHANGED";
+
+ /**
+ * Extra for {@link #ACTION_HOTWORD_INPUT_CHANGED} that provides the package name of the
+ * app in that controlled the HOTWORD input when the state changed. Can be reused for other
+ * purposes.
+ */
+ public static final String EXTRA_CURRENT_PACKAGE_NAME =
+ "com.cyanogenmod.intent.extra.CURRENT_PACKAGE_NAME";
+
+ /**
+ * Extra for {@link #ACTION_HOTWORD_INPUT_CHANGED} that provides the state of
+ * the input when the broadcast action was sent.
+ * @hide
+ */
+ public static final String EXTRA_HOTWORD_INPUT_STATE =
+ "com.cyanogenmod.intent.extra.HOTWORD_INPUT_STATE";
+
+
+ public static class AudioSource {
+ /**
+ * Audio source for preemptible, low-priority software hotword detection
+ * It presents the same gain and pre processing tuning as
+ * {@link android.media.MediaRecorder.AudioSource#VOICE_RECOGNITION}.
+ * <p>
+ * An application should use this audio source when it wishes to do
+ * always-on software hotword detection, while gracefully giving in to any other application
+ * that might want to read from the microphone.
+ * </p>
+ * You must hold {@link cyanogenmod.media.MediaRecorder#CAPTURE_AUDIO_HOTWORD_PERMISSION}
+ * to use this audio source.
+ */
+ public static final int HOTWORD = 1999;
+ }
+}
diff --git a/sdk/src/java/cyanogenmod/os/Build.java b/sdk/src/java/cyanogenmod/os/Build.java
new file mode 100644
index 0000000..ce7b8fb
--- /dev/null
+++ b/sdk/src/java/cyanogenmod/os/Build.java
@@ -0,0 +1,148 @@
+/*
+ * 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.os;
+
+import android.os.SystemProperties;
+import android.text.TextUtils;
+import android.util.SparseArray;
+
+/**
+ * Information about the current CyanogenMod build, extracted from system properties.
+ */
+public class Build {
+ /** Value used for when a build property is unknown. */
+ public static final String UNKNOWN = "unknown";
+
+ /** A build ID utilized to distinguish cyanogenmod versions */
+ public static final String CYANOGENMOD_VERSION = "ro.cm.version";
+
+ /** A build ID string meant for displaying to the user */
+ public static final String CYANOGENMOD_DISPLAY_VERSION = "ro.cm.display.version";
+
+ private static final SparseArray<String> sdkMap;
+ static
+ {
+ sdkMap = new SparseArray<String>();
+ sdkMap.put(CM_VERSION_CODES.APRICOT, "Apricot");
+ sdkMap.put(CM_VERSION_CODES.BOYSENBERRY, "Boysenberry");
+ sdkMap.put(CM_VERSION_CODES.CANTALOUPE, "Cantaloupe");
+ sdkMap.put(CM_VERSION_CODES.DRAGON_FRUIT, "Dragon Fruit");
+ sdkMap.put(CM_VERSION_CODES.ELDERBERRY, "Elderberry");
+ }
+
+ /** Various version strings. */
+ public static class CM_VERSION {
+ /**
+ * The user-visible SDK version of the framework; its possible
+ * values are defined in {@link Build.CM_VERSION_CODES}.
+ *
+ * Will return 0 if the device does not support the CM SDK.
+ */
+ public static final int SDK_INT = SystemProperties.getInt(
+ "ro.cm.build.version.plat.sdk", 0);
+ }
+
+ /**
+ * Enumeration of the currently known SDK version codes. These are the
+ * values that can be found in {@link CM_VERSION#SDK_INT}. Version numbers
+ * increment monotonically with each official platform release.
+ *
+ * To programmatically validate that a given API is available for use on the device,
+ * you can quickly check if the SDK_INT from the OS is provided and is greater or equal
+ * to the API level that your application is targeting.
+ *
+ * <p>Example for validating that Profiles API is available
+ * <pre class="prettyprint">
+ * private void removeActiveProfile() {
+ * Make sure we're running on BoysenBerry or higher to use Profiles API
+ * if (Build.CM_VERSION.SDK_INT >= Build.CM_VERSION_CODES.BOYSENBERRY) {
+ * ProfileManager profileManager = ProfileManager.getInstance(this);
+ * Profile activeProfile = profileManager.getActiveProfile();
+ * if (activeProfile != null) {
+ * profileManager.removeProfile(activeProfile);
+ * }
+ * }
+ * }
+ * </pre>
+ */
+ public static class CM_VERSION_CODES {
+ /**
+ * June 2015: The first version of the platform sdk for CyanogenMod
+ */
+ public static final int APRICOT = 1;
+
+ /**
+ * September 2015: The second version of the platform sdk for CyanogenMod
+ *
+ * <p>Applications targeting this or a later release will get these
+ * new features:</p>
+ * <ul>
+ * <li>Profiles API via {@link cyanogenmod.app.ProfileManager}
+ * <li>New Expanded Styles for Custom Tiles via
+ * {@link cyanogenmod.app.CustomTile.RemoteExpandedStyle}
+ * <li>Hardware Abstraction Framework Access via
+ * {@link cyanogenmod.hardware.CMHardwareManager} (Not for use by 3rd parties)
+ * <li>MSIM API via {@link cyanogenmod.app.CMTelephonyManager}
+ * <li>Interface for partners via {@link cyanogenmod.app.PartnerInterface}
+ * <li>Introductory Settings Provider {@link cyanogenmod.providers.CMSettings}
+ * <li>AlarmClock API via {@link cyanogenmod.alarmclock.CyanogenModAlarmClock}
+ * </ul>
+ */
+ public static final int BOYSENBERRY = 2;
+
+ /**
+ * November - December 2015: The third iteration of the platform sdk for CyanogenMod
+ * Transition api level that is mostly 1:1 to {@link #BOYSENBERRY}
+ */
+ public static final int CANTALOUPE = 3;
+
+ /**
+ * January 2016: The 4th iteration of the platform sdk for CyanogenMod
+ *
+ * <p>Applications targeting this or a later version will get access to these
+ * new features:</p>
+ * <ul>
+ * <li>External views api, and specifically Keyguard interfaces for making
+ * live lockscreens via {@link cyanogenmod.externalviews.KeyguardExternalView}</li>
+ * <li>Inclusion of the PerformanceManager interfaces, allowing an application to specify
+ * the type of mode to have the device be placed in via
+ * {@link cyanogenmod.power.PerformanceManager}</li>
+ * <li>Numerous new "System" settings exposed via the
+ * {@link cyanogenmod.providers.CMSettings.System} interface</li>
+ * </ul>
+ */
+ public static final int DRAGON_FRUIT = 4;
+
+ /**
+ * Future M Release - 2nd quarter 2016
+ */
+ public static final int ELDERBERRY = 5;
+ }
+
+ /**
+ * Retrieve the name for the SDK int
+ * @param sdkInt
+ * @return name of the SDK int, {@link #UNKNOWN) if not known
+ */
+ public static String getNameForSDKInt(int sdkInt) {
+ final String name = sdkMap.get(sdkInt);
+ if (TextUtils.isEmpty(name)) {
+ return UNKNOWN;
+ }
+ return name;
+ }
+}
diff --git a/sdk/src/java/cyanogenmod/os/Concierge.java b/sdk/src/java/cyanogenmod/os/Concierge.java
new file mode 100644
index 0000000..2463dec
--- /dev/null
+++ b/sdk/src/java/cyanogenmod/os/Concierge.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2016 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.os;
+
+import android.os.Parcel;
+
+import cyanogenmod.os.Build.CM_VERSION_CODES;
+
+/**
+ * Simply, Concierge handles your parcels and makes sure they get marshalled and unmarshalled
+ * correctly when cross IPC boundaries even when there is a version mismatch between the client
+ * sdk level and the framework implementation.
+ *
+ * <p>On incoming parcel (to be unmarshalled):
+ *
+ * <pre class="prettyprint">
+ * ParcelInfo incomingParcelInfo = Concierge.receiveParcel(incomingParcel);
+ * int parcelableVersion = incomingParcelInfo.getParcelVersion();
+ *
+ * // Do unmarshalling steps here iterating over every plausible version
+ *
+ * // Complete the process
+ * incomingParcelInfo.complete();
+ * </pre>
+ *
+ * <p>On outgoing parcel (to be marshalled):
+ *
+ * <pre class="prettyprint">
+ * ParcelInfo outgoingParcelInfo = Concierge.prepareParcel(incomingParcel);
+ *
+ * // Do marshalling steps here iterating over every plausible version
+ *
+ * // Complete the process
+ * outgoingParcelInfo.complete();
+ * </pre>
+ */
+public final class Concierge {
+
+ /** Not instantiable */
+ private Concierge() {
+ // Don't instantiate
+ }
+
+ /**
+ * Since there might be a case where new versions of the cm framework use applications running
+ * old versions of the protocol (and thus old versions of this class), we need a versioning
+ * system for the parcels sent between the core framework and its sdk users.
+ *
+ * This parcelable version should be the latest version API version listed in
+ * {@link CM_VERSION_CODES}
+ * @hide
+ */
+ public static final int PARCELABLE_VERSION = CM_VERSION_CODES.ELDERBERRY;
+
+ /**
+ * Tell the concierge to receive our parcel, so we can get information from it.
+ *
+ * MUST CALL {@link ParcelInfo#complete()} AFTER UNMARSHALLING.
+ *
+ * @param parcel Incoming parcel to be unmarshalled
+ * @return {@link ParcelInfo} containing parcel information, specifically the version.
+ */
+ public static ParcelInfo receiveParcel(Parcel parcel) {
+ return new ParcelInfo(parcel);
+ }
+
+ /**
+ * Prepare a parcel for the Concierge.
+ *
+ * MUST CALL {@link ParcelInfo#complete()} AFTER MARSHALLING.
+ *
+ * @param parcel Outgoing parcel to be marshalled
+ * @return {@link ParcelInfo} containing parcel information, specifically the version.
+ */
+ public static ParcelInfo prepareParcel(Parcel parcel) {
+ return new ParcelInfo(parcel, PARCELABLE_VERSION);
+ }
+
+ /**
+ * Parcel header info specific to the Parcel object that is passed in via
+ * {@link #prepareParcel(Parcel)} or {@link #receiveParcel(Parcel)}. The exposed method
+ * of {@link #getParcelVersion()} gets the api level of the parcel object.
+ */
+ public final static class ParcelInfo {
+ private Parcel mParcel;
+ private int mParcelableVersion;
+ private int mParcelableSize;
+ private int mStartPosition;
+ private int mSizePosition;
+ private boolean mCreation = false;
+
+ ParcelInfo(Parcel parcel) {
+ mCreation = false;
+ mParcel = parcel;
+ mParcelableVersion = parcel.readInt();
+ mParcelableSize = parcel.readInt();
+ mStartPosition = parcel.dataPosition();
+ }
+
+ ParcelInfo(Parcel parcel, int parcelableVersion) {
+ mCreation = true;
+ mParcel = parcel;
+ mParcelableVersion = parcelableVersion;
+
+ // Write parcelable version, make sure to define explicit changes
+ // within {@link #PARCELABLE_VERSION);
+ mParcel.writeInt(mParcelableVersion);
+
+ // Inject a placeholder that will store the parcel size from this point on
+ // (not including the size itself).
+ mSizePosition = parcel.dataPosition();
+ mParcel.writeInt(0);
+ mStartPosition = parcel.dataPosition();
+ }
+
+ /**
+ * Get the parcel version from the {@link Parcel} received by the Concierge.
+ * @return {@link #PARCELABLE_VERSION} of the {@link Parcel}
+ */
+ public int getParcelVersion() {
+ return mParcelableVersion;
+ }
+
+ /**
+ * Complete the {@link ParcelInfo} for the Concierge.
+ */
+ public void complete() {
+ if (mCreation) {
+ // Go back and write size
+ mParcelableSize = mParcel.dataPosition() - mStartPosition;
+ mParcel.setDataPosition(mSizePosition);
+ mParcel.writeInt(mParcelableSize);
+ mParcel.setDataPosition(mStartPosition + mParcelableSize);
+ } else {
+ mParcel.setDataPosition(mStartPosition + mParcelableSize);
+ }
+ }
+ }
+}
diff --git a/sdk/src/java/cyanogenmod/power/IPerformanceManager.aidl b/sdk/src/java/cyanogenmod/power/IPerformanceManager.aidl
new file mode 100644
index 0000000..bf44ac9
--- /dev/null
+++ b/sdk/src/java/cyanogenmod/power/IPerformanceManager.aidl
@@ -0,0 +1,31 @@
+/*
+ * Copyright 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.power;
+
+/** @hide */
+interface IPerformanceManager {
+
+ oneway void cpuBoost(int duration);
+
+ boolean setPowerProfile(int profile);
+
+ int getPowerProfile();
+
+ int getNumberOfProfiles();
+
+ boolean getProfileHasAppProfiles(int profile);
+}
diff --git a/sdk/src/java/cyanogenmod/power/PerformanceManager.java b/sdk/src/java/cyanogenmod/power/PerformanceManager.java
new file mode 100644
index 0000000..9b44399
--- /dev/null
+++ b/sdk/src/java/cyanogenmod/power/PerformanceManager.java
@@ -0,0 +1,206 @@
+/*
+ * 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.power;
+
+import android.content.Context;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Log;
+
+import cyanogenmod.app.CMContextConstants;
+
+/**
+ *
+ */
+public class PerformanceManager {
+
+ public static final String TAG = "PerformanceManager";
+
+ /**
+ * Power save profile
+ *
+ * This mode sacrifices performance for maximum power saving.
+ */
+ public static final int PROFILE_POWER_SAVE = 0;
+
+ /**
+ * Balanced power profile
+ *
+ * The default mode for balanced power savings and performance
+ */
+ public static final int PROFILE_BALANCED = 1;
+
+ /**
+ * High-performance profile
+ *
+ * This mode sacrifices power for maximum performance
+ */
+ public static final int PROFILE_HIGH_PERFORMANCE = 2;
+
+ /**
+ * Power save bias profile
+ *
+ * This mode decreases performance slightly to improve
+ * power savings.
+ */
+ public static final int PROFILE_BIAS_POWER_SAVE = 3;
+
+ /**
+ * Performance bias profile
+ *
+ * This mode improves performance at the cost of some power.
+ */
+ public static final int PROFILE_BIAS_PERFORMANCE = 4;
+
+ private int mNumberOfProfiles = 0;
+
+ /**
+ * Broadcast sent when profile is changed
+ */
+ public static final String POWER_PROFILE_CHANGED = "cyanogenmod.power.PROFILE_CHANGED";
+
+ private static IPerformanceManager sService;
+ private static PerformanceManager sInstance;
+
+ private PerformanceManager(Context context) {
+ sService = getService();
+ if (context.getPackageManager().hasSystemFeature(
+ CMContextConstants.Features.PERFORMANCE) && sService == null) {
+ throw new RuntimeException("Unable to get PerformanceManagerService. The service" +
+ " either crashed, was not started, or the interface has been called to early" +
+ " in SystemServer init");
+ }
+ try {
+ if (sService != null) {
+ mNumberOfProfiles = sService.getNumberOfProfiles();
+ }
+ } catch (RemoteException e) {
+ }
+ }
+
+ public static PerformanceManager getInstance(Context context) {
+ if (sInstance == null) {
+ sInstance = new PerformanceManager(context);
+ }
+ return sInstance;
+ }
+
+ /** @hide */
+ public static IPerformanceManager getService() {
+ if (sService != null) {
+ return sService;
+ }
+ IBinder b = ServiceManager.getService(CMContextConstants.CM_PERFORMANCE_SERVICE);
+ if (b != null) {
+ sService = IPerformanceManager.Stub.asInterface(b);
+ return sService;
+ }
+ return null;
+ }
+
+ private boolean checkService() {
+ if (sService == null) {
+ Log.w(TAG, "not connected to PerformanceManagerService");
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Boost the CPU. Boosts the cpu for the given duration in microseconds.
+ * Requires the {@link android.Manifest.permission#CPU_BOOST} permission.
+ *
+ * @param duration in microseconds to boost the CPU
+ * @hide
+ */
+ public void cpuBoost(int duration)
+ {
+ try {
+ if (checkService()) {
+ sService.cpuBoost(duration);
+ }
+ } catch (RemoteException e) {
+ }
+ }
+
+ /**
+ * Returns the number of supported profiles, -1 if unsupported
+ * This is queried via the PowerHAL.
+ */
+ public int getNumberOfProfiles() {
+ return mNumberOfProfiles;
+ }
+
+ /**
+ * Set the system power profile
+ *
+ * @throws IllegalArgumentException if invalid
+ */
+ public boolean setPowerProfile(int profile) {
+ if (mNumberOfProfiles < 1) {
+ throw new IllegalArgumentException("Power profiles not enabled on this system!");
+ }
+
+ boolean changed = false;
+ try {
+ if (checkService()) {
+ changed = sService.setPowerProfile(profile);
+ }
+ } catch (RemoteException e) {
+ throw new IllegalArgumentException(e);
+ }
+ return changed;
+ }
+
+ /**
+ * Gets the current power profile
+ *
+ * Returns null if power profiles are not enabled
+ */
+ public int getPowerProfile() {
+ int ret = -1;
+ if (mNumberOfProfiles > 0) {
+ try {
+ if (checkService()) {
+ ret = sService.getPowerProfile();
+ }
+ } catch (RemoteException e) {
+ // nothing
+ }
+ }
+ return ret;
+ }
+
+ /**
+ * Check if profile has app-specific profiles
+ *
+ * Returns true if profile has app-specific profiles.
+ */
+ public boolean getProfileHasAppProfiles(int profile) {
+ boolean ret = false;
+ if (mNumberOfProfiles > 0) {
+ try {
+ if (checkService()) {
+ ret = sService.getProfileHasAppProfiles(profile);
+ }
+ } catch (RemoteException e) {
+ // nothing
+ }
+ }
+ return ret;
+ }
+}
diff --git a/sdk/src/java/cyanogenmod/power/PerformanceManagerInternal.java b/sdk/src/java/cyanogenmod/power/PerformanceManagerInternal.java
new file mode 100644
index 0000000..67f158a
--- /dev/null
+++ b/sdk/src/java/cyanogenmod/power/PerformanceManagerInternal.java
@@ -0,0 +1,29 @@
+/*
+ * 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.power;
+
+import android.content.Intent;
+
+/** {@hide} */
+public interface PerformanceManagerInternal {
+
+ void activityResumed(Intent intent);
+
+ void cpuBoost(int duration);
+
+ void launchBoost();
+}
diff --git a/sdk/src/java/cyanogenmod/profiles/AirplaneModeSettings.java b/sdk/src/java/cyanogenmod/profiles/AirplaneModeSettings.java
new file mode 100644
index 0000000..a9b828f
--- /dev/null
+++ b/sdk/src/java/cyanogenmod/profiles/AirplaneModeSettings.java
@@ -0,0 +1,224 @@
+/*
+ * 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.profiles;
+
+import android.content.Context;
+import android.content.Intent;
+import android.provider.Settings;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import cyanogenmod.os.Build;
+import cyanogenmod.os.Concierge;
+import cyanogenmod.os.Concierge.ParcelInfo;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+
+import java.io.IOException;
+
+/**
+ * The {@link AirplaneModeSettings} class allows for overriding and setting the airplane mode.
+ *
+ * <p>Example for setting the airplane mode to enabled:
+ * <pre class="prettyprint">
+ * AirplaneModeSettings airplaneMode = new AirplaneModeSettings(BooleanState.STATE_ENABLED, true)
+ * profile.setAirplaneMode(airplaneMode);
+ * </pre>
+ */
+public final class AirplaneModeSettings implements Parcelable {
+
+ private int mValue;
+ private boolean mOverride;
+ private boolean mDirty;
+
+ /** @hide */
+ public static final Parcelable.Creator<AirplaneModeSettings> CREATOR =
+ new Parcelable.Creator<AirplaneModeSettings>() {
+ public AirplaneModeSettings createFromParcel(Parcel in) {
+ return new AirplaneModeSettings(in);
+ }
+
+ @Override
+ public AirplaneModeSettings[] newArray(int size) {
+ return new AirplaneModeSettings[size];
+ }
+ };
+
+ /**
+ * BooleanStates for specific {@link AirplaneModeSettings}
+ */
+ public static class BooleanState {
+ /** Disabled state */
+ public static final int STATE_DISALED = 0;
+ /** Enabled state */
+ public static final int STATE_ENABLED = 1;
+ }
+
+ /**
+ * Unwrap {@link AirplaneModeSettings} from a parcel.
+ * @param parcel
+ */
+ public AirplaneModeSettings(Parcel parcel) {
+ readFromParcel(parcel);
+ }
+
+ /**
+ * Construct a {@link AirplaneModeSettings} with a default value of
+ * {@link BooleanState#STATE_DISALED}.
+ */
+ public AirplaneModeSettings() {
+ this(BooleanState.STATE_DISALED, false);
+ }
+
+ /**
+ * Construct a {@link AirplaneModeSettings} with a default value and whether or not it should
+ * override user settings.
+ * @param value ex: {@link BooleanState#STATE_DISALED}
+ * @param override whether or not the setting should override user settings
+ */
+ public AirplaneModeSettings(int value, boolean override) {
+ mValue = value;
+ mOverride = override;
+ mDirty = false;
+ }
+
+ /**
+ * Get the default value for the {@link AirplaneModeSettings}
+ * @return integer value corresponding with its brightness value
+ */
+ public int getValue() {
+ return mValue;
+ }
+
+ /**
+ * Set the default value for the {@link AirplaneModeSettings}
+ * @param value {@link BooleanState#STATE_DISALED}
+ */
+ public void setValue(int value) {
+ mValue = value;
+ mDirty = true;
+ }
+
+ /**
+ * Set whether or not the {@link AirplaneModeSettings} should override default user values
+ * @param override boolean override
+ */
+ public void setOverride(boolean override) {
+ mOverride = override;
+ mDirty = true;
+ }
+
+ /**
+ * Check whether or not the {@link AirplaneModeSettings} overrides user settings.
+ * @return true if override
+ */
+ public boolean isOverride() {
+ return mOverride;
+ }
+
+ /** @hide */
+ public boolean isDirty() {
+ return mDirty;
+ }
+
+ /** @hide */
+ public void processOverride(Context context) {
+ if (isOverride()) {
+ int current = Settings.Global.getInt(context.getContentResolver(),
+ Settings.Global.AIRPLANE_MODE_ON, 0);
+ if (current != mValue) {
+ Settings.Global.putInt(context.getContentResolver(),
+ Settings.Global.AIRPLANE_MODE_ON, mValue);
+ Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+ intent.putExtra("state", mValue == 1);
+ context.sendBroadcast(intent);
+ }
+ }
+ }
+
+ /** @hide */
+ public static AirplaneModeSettings fromXml(XmlPullParser xpp, Context context)
+ throws XmlPullParserException, IOException {
+ int event = xpp.next();
+ AirplaneModeSettings airplaneModeDescriptor = new AirplaneModeSettings();
+ while ((event != XmlPullParser.END_TAG && event != XmlPullParser.END_DOCUMENT) ||
+ !xpp.getName().equals("airplaneModeDescriptor")) {
+ if (event == XmlPullParser.START_TAG) {
+ String name = xpp.getName();
+ if (name.equals("value")) {
+ airplaneModeDescriptor.mValue = Integer.parseInt(xpp.nextText());
+ } else if (name.equals("override")) {
+ airplaneModeDescriptor.mOverride = Boolean.parseBoolean(xpp.nextText());
+ }
+ } else if (event == XmlPullParser.END_DOCUMENT) {
+ throw new IOException("Premature end of file while parsing airplane mode settings");
+ }
+ event = xpp.next();
+ }
+ return airplaneModeDescriptor;
+ }
+
+ /** @hide */
+ public void getXmlString(StringBuilder builder, Context context) {
+ builder.append("<airplaneModeDescriptor>\n<value>");
+ builder.append(mValue);
+ builder.append("</value>\n<override>");
+ builder.append(mOverride);
+ builder.append("</override>\n</airplaneModeDescriptor>\n");
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /** @hide */
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ // Tell the concierge to prepare the parcel
+ ParcelInfo parcelInfo = Concierge.prepareParcel(dest);
+
+ // === BOYSENBERRY ===
+ dest.writeInt(mOverride ? 1 : 0);
+ dest.writeInt(mValue);
+ dest.writeInt(mDirty ? 1 : 0);
+
+ // Complete the parcel info for the concierge
+ parcelInfo.complete();
+ }
+
+ /** @hide */
+ public void readFromParcel(Parcel in) {
+ // Read parcelable version via the Concierge
+ ParcelInfo parcelInfo = Concierge.receiveParcel(in);
+ int parcelableVersion = parcelInfo.getParcelVersion();
+
+ // Pattern here is that all new members should be added to the end of
+ // the writeToParcel method. Then we step through each version, until the latest
+ // API release to help unravel this parcel
+ if (parcelableVersion >= Build.CM_VERSION_CODES.BOYSENBERRY) {
+ mOverride = in.readInt() != 0;
+ mValue = in.readInt();
+ mDirty = in.readInt() != 0;
+ }
+
+ // Complete parcel info for the concierge
+ parcelInfo.complete();
+ }
+}
diff --git a/sdk/src/java/cyanogenmod/profiles/BrightnessSettings.java b/sdk/src/java/cyanogenmod/profiles/BrightnessSettings.java
new file mode 100644
index 0000000..0786a72
--- /dev/null
+++ b/sdk/src/java/cyanogenmod/profiles/BrightnessSettings.java
@@ -0,0 +1,222 @@
+/*
+ * 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.profiles;
+
+import android.content.Context;
+import android.provider.Settings;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import cyanogenmod.os.Build;
+import cyanogenmod.os.Concierge;
+import cyanogenmod.os.Concierge.ParcelInfo;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+
+import java.io.IOException;
+
+/**
+ * The {@link BrightnessSettings} class allows for overriding and setting the brightness level
+ * of the display. The range for brightness is between 0 -> 255.
+ *
+ * <p>Example for setting the brightness to ~25% (255 * .25):
+ * <pre class="prettyprint">
+ * BrightnessSettings twentyFivePercent = new BrightnessSettings(63, true)
+ * profile.setBrightness(twentyFivePercent);
+ * </pre>
+ */
+public final class BrightnessSettings implements Parcelable {
+
+ private int mValue;
+ private boolean mOverride;
+ private boolean mDirty;
+
+ /** @hide */
+ public static final Parcelable.Creator<BrightnessSettings> CREATOR
+ = new Parcelable.Creator<BrightnessSettings>() {
+ public BrightnessSettings createFromParcel(Parcel in) {
+ return new BrightnessSettings(in);
+ }
+
+ @Override
+ public BrightnessSettings[] newArray(int size) {
+ return new BrightnessSettings[size];
+ }
+ };
+
+ /**
+ * Unwrap {@link BrightnessSettings} from a parcel.
+ * @param parcel
+ */
+ public BrightnessSettings(Parcel parcel) {
+ readFromParcel(parcel);
+ }
+
+ /**
+ * Construct a {@link BrightnessSettings} with a default value of 0.
+ */
+ public BrightnessSettings() {
+ this(0, false);
+ }
+
+ /**
+ * Construct a {@link BrightnessSettings} with a default value and whether or not it should
+ * override user settings.
+ * @param value ex: 255 (MAX)
+ * @param override whether or not the setting should override user settings
+ */
+ public BrightnessSettings(int value, boolean override) {
+ mValue = value;
+ mOverride = override;
+ mDirty = false;
+ }
+
+ /**
+ * Get the default value for the {@link BrightnessSettings}
+ * @return integer value corresponding with its brightness value
+ */
+ public int getValue() {
+ return mValue;
+ }
+
+ /**
+ * Set the default value for the {@link BrightnessSettings}
+ * @param value ex: 255 (MAX)
+ */
+ public void setValue(int value) {
+ mValue = value;
+ mDirty = true;
+ }
+
+ /**
+ * Set whether or not the {@link BrightnessSettings} should override default user values
+ * @param override boolean override
+ */
+ public void setOverride(boolean override) {
+ mOverride = override;
+ mDirty = true;
+ }
+
+ /**
+ * Check whether or not the {@link BrightnessSettings} overrides user settings.
+ * @return true if override
+ */
+ public boolean isOverride() {
+ return mOverride;
+ }
+
+ /** @hide */
+ public boolean isDirty() {
+ return mDirty;
+ }
+
+ /** @hide */
+ public void processOverride(Context context) {
+ if (isOverride()) {
+ final boolean automatic = Settings.System.getInt(context.getContentResolver(),
+ Settings.System.SCREEN_BRIGHTNESS_MODE,
+ Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL)
+ == Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC;
+ if (automatic) {
+ final float current = Settings.System.getFloat(context.getContentResolver(),
+ Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, -2f);
+ // Convert from [0, 255] to [-1, 1] for SCREEN_AUTO_BRIGHTNESS_ADJ
+ final float adj = mValue / (255 / 2f) - 1;
+ if (current != adj) {
+ Settings.System.putFloat(context.getContentResolver(),
+ Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, adj);
+ }
+ } else {
+ final int current = Settings.System.getInt(context.getContentResolver(),
+ Settings.System.SCREEN_BRIGHTNESS, -1);
+ if (current != mValue) {
+ Settings.System.putInt(context.getContentResolver(),
+ Settings.System.SCREEN_BRIGHTNESS, mValue);
+ }
+ }
+ }
+ }
+
+ /** @hide */
+ public static BrightnessSettings fromXml(XmlPullParser xpp, Context context)
+ throws XmlPullParserException, IOException {
+ int event = xpp.next();
+ BrightnessSettings brightnessDescriptor = new BrightnessSettings();
+ while (event != XmlPullParser.END_TAG || !xpp.getName().equals("brightnessDescriptor")) {
+ if (event == XmlPullParser.START_TAG) {
+ String name = xpp.getName();
+ if (name.equals("value")) {
+ brightnessDescriptor.mValue = Integer.parseInt(xpp.nextText());
+ } else if (name.equals("override")) {
+ brightnessDescriptor.mOverride = Boolean.parseBoolean(xpp.nextText());
+ }
+ }
+ event = xpp.next();
+ }
+ return brightnessDescriptor;
+ }
+
+ /** @hide */
+ public void getXmlString(StringBuilder builder, Context context) {
+ builder.append("<brightnessDescriptor>\n<value>");
+ builder.append(mValue);
+ builder.append("</value>\n<override>");
+ builder.append(mOverride);
+ builder.append("</override>\n</brightnessDescriptor>\n");
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /** @hide */
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ // Tell the concierge to prepare the parcel
+ ParcelInfo parcelInfo = Concierge.prepareParcel(dest);
+
+ // === BOYSENBERRY ===
+ dest.writeInt(mOverride ? 1 : 0);
+ dest.writeInt(mValue);
+ dest.writeInt(mDirty ? 1 : 0);
+
+ // Complete the parcel info for the concierge
+ parcelInfo.complete();
+ }
+
+ /** @hide */
+ public void readFromParcel(Parcel in) {
+ // Read parcelable version via the Concierge
+ ParcelInfo parcelInfo = Concierge.receiveParcel(in);
+ int parcelableVersion = parcelInfo.getParcelVersion();
+
+ // Pattern here is that all new members should be added to the end of
+ // the writeToParcel method. Then we step through each version, until the latest
+ // API release to help unravel this parcel
+ if (parcelableVersion >= Build.CM_VERSION_CODES.BOYSENBERRY) {
+ mOverride = in.readInt() != 0;
+ mValue = in.readInt();
+ mDirty = in.readInt() != 0;
+ }
+
+ // Complete parcel info for the concierge
+ parcelInfo.complete();
+ }
+}
diff --git a/sdk/src/java/cyanogenmod/profiles/ConnectionSettings.java b/sdk/src/java/cyanogenmod/profiles/ConnectionSettings.java
new file mode 100644
index 0000000..19954dd
--- /dev/null
+++ b/sdk/src/java/cyanogenmod/profiles/ConnectionSettings.java
@@ -0,0 +1,475 @@
+/*
+ * 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.profiles;
+
+import android.bluetooth.BluetoothAdapter;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.location.LocationManager;
+import android.net.wifi.WifiManager;
+import android.net.wimax.WimaxHelper;
+import android.nfc.NfcAdapter;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.provider.Settings;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import com.android.internal.telephony.RILConstants;
+
+import cyanogenmod.os.Build;
+import cyanogenmod.os.Concierge;
+import cyanogenmod.os.Concierge.ParcelInfo;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+
+/**
+ * The {@link ConnectionSettings} class allows for creating Network/Hardware overrides
+ * depending on their capabilities.
+ *
+ * <p>Example for enabling/disabling sync settings:
+ * <pre class="prettyprint">
+ * ConnectionSettings connectionSettings =
+ * new ConnectionSettings(ConnectionSettings.PROFILE_CONNECTION_SYNC,
+ * shouldBeEnabled() ?
+ * {@link BooleanState#STATE_ENABLED} : {@link BooleanState#STATE_DISALED},
+ * true)
+ * profile.setConnectionSettings(connectionSettings);
+ * </pre>
+ */
+public final class ConnectionSettings implements Parcelable {
+
+ private int mConnectionId;
+ private int mValue;
+ private boolean mOverride;
+ private boolean mDirty;
+
+ /**
+ * For use with {@link #PROFILE_CONNECTION_2G3G4G} to determine what subscription to control.
+ */
+ private int mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+
+ /**
+ * The {@link #PROFILE_CONNECTION_MOBILEDATA} allows for enabling and disabling the mobile
+ * data connection. Boolean connection settings {@link BooleanState}
+ */
+ public static final int PROFILE_CONNECTION_MOBILEDATA = 0;
+
+ /**
+ * The {@link #PROFILE_CONNECTION_WIFI} allows for enabling and disabling the WiFi connection
+ * on the device. Boolean connection settings {@link BooleanState}
+ */
+ public static final int PROFILE_CONNECTION_WIFI = 1;
+
+ /**
+ * The {@link #PROFILE_CONNECTION_WIFIAP} allows for enabling and disabling the WiFi hotspot
+ * on the device. Boolean connection settings {@link BooleanState}
+ */
+ public static final int PROFILE_CONNECTION_WIFIAP = 2;
+
+ /**
+ * The {@link #PROFILE_CONNECTION_WIMAX} allows for enabling and disabling the WIMAX radio (if exists)
+ * on the device. Boolean connection settings {@link BooleanState}
+ */
+ public static final int PROFILE_CONNECTION_WIMAX = 3;
+
+ /**
+ * The {@link #PROFILE_CONNECTION_GPS} allows for enabling and disabling the GPS radio (if exists)
+ * on the device. Boolean connection settings {@link BooleanState}
+ */
+ public static final int PROFILE_CONNECTION_GPS = 4;
+
+ /**
+ * The {@link #PROFILE_CONNECTION_SYNC} allows for enabling and disabling the global sync state
+ * on the device. Boolean connection settings {@link BooleanState}
+ */
+ public static final int PROFILE_CONNECTION_SYNC = 5;
+
+ /**
+ * The {@link #PROFILE_CONNECTION_BLUETOOTH} allows for enabling and disabling the Bluetooth device
+ * (if exists) on the device. Boolean connection settings {@link BooleanState}
+ */
+ public static final int PROFILE_CONNECTION_BLUETOOTH = 7;
+
+ /**
+ * The {@link #PROFILE_CONNECTION_NFC} allows for enabling and disabling the NFC device
+ * (if exists) on the device. Boolean connection settings {@link BooleanState}
+ */
+ public static final int PROFILE_CONNECTION_NFC = 8;
+
+ /**
+ * The {@link #PROFILE_CONNECTION_2G3G4G} allows for flipping between 2G/3G/4G (if exists)
+ * on the device.
+ */
+ public static final int PROFILE_CONNECTION_2G3G4G = 9;
+
+ // retrieved from Phone.apk
+ private static final String ACTION_MODIFY_NETWORK_MODE =
+ "com.android.internal.telephony.MODIFY_NETWORK_MODE";
+ private static final String EXTRA_NETWORK_MODE = "networkMode";
+ private static final String EXTRA_SUB_ID = "subId";
+
+ /**
+ * BooleanStates for specific {@link ConnectionSettings}
+ */
+ public static class BooleanState {
+ /** Disabled state */
+ public static final int STATE_DISALED = 0;
+ /** Enabled state */
+ public static final int STATE_ENABLED = 1;
+ }
+
+ private static final int CM_MODE_2G = 0;
+ private static final int CM_MODE_3G = 1;
+ private static final int CM_MODE_4G = 2;
+ private static final int CM_MODE_2G3G = 3;
+ private static final int CM_MODE_ALL = 4;
+
+ /** @hide */
+ public static final Parcelable.Creator<ConnectionSettings> CREATOR =
+ new Parcelable.Creator<ConnectionSettings>() {
+ public ConnectionSettings createFromParcel(Parcel in) {
+ return new ConnectionSettings(in);
+ }
+
+ @Override
+ public ConnectionSettings[] newArray(int size) {
+ return new ConnectionSettings[size];
+ }
+ };
+
+ /**
+ * Unwrap {@link ConnectionSettings} from a parcel.
+ * @param parcel
+ */
+ public ConnectionSettings(Parcel parcel) {
+ readFromParcel(parcel);
+ }
+
+ /**
+ * Construct a {@link ConnectionSettings} with a connection id and default states.
+ * @param connectionId ex: #PROFILE_CONNECTION_NFC
+ */
+ public ConnectionSettings(int connectionId) {
+ this(connectionId, 0, false);
+ }
+
+ /**
+ * Construct a {@link ConnectionSettings} with a connection id, default value
+ * {@see BooleanState}, and if the setting should override the user defaults.
+ * @param connectionId an identifier for the ConnectionSettings (ex:#PROFILE_CONNECTION_WIFI)
+ * @param value default value for the ConnectionSettings (ex:{@link BooleanState#STATE_ENABLED})
+ * @param override whether or not the {@link ConnectionSettings} should override user defaults
+ */
+ public ConnectionSettings(int connectionId, int value, boolean override) {
+ mConnectionId = connectionId;
+ mValue = value;
+ mOverride = override;
+ mDirty = false;
+ }
+
+ /**
+ * Retrieve the connection id associated with the {@link ConnectionSettings}
+ * @return an integer identifier
+ */
+ public int getConnectionId() {
+ return mConnectionId;
+ }
+
+ /**
+ * Get the default value for the {@link ConnectionSettings}
+ * @return integer value corresponding with its state
+ */
+ public int getValue() {
+ return mValue;
+ }
+
+ /**
+ * Set the default value for the {@link ConnectionSettings}
+ * @param value {@link BooleanState}
+ */
+ public void setValue(int value) {
+ mValue = value;
+ mDirty = true;
+ }
+
+ /**
+ * Set whether or not the {@link ConnectionSettings} should override default user values
+ * @param override boolean override
+ */
+ public void setOverride(boolean override) {
+ mOverride = override;
+ mDirty = true;
+ }
+
+ public void setSubId(int subId) {
+ mSubId = subId;
+ mDirty = true;
+ }
+
+ /**
+ * Check whether or not the {@link ConnectionSettings} overrides user settings.
+ * @return true if override
+ */
+ public boolean isOverride() {
+ return mOverride;
+ }
+
+ /**
+ * Get the subscription id which this {@link ConnectionSettings} should apply to.
+ * @return
+ */
+ public int getSubId() {
+ return mSubId;
+ }
+
+ /** @hide */
+ public boolean isDirty() {
+ return mDirty;
+ }
+
+ /** @hide */
+ public void processOverride(Context context) {
+ BluetoothAdapter bta = BluetoothAdapter.getDefaultAdapter();
+ LocationManager lm = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
+ WifiManager wm = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
+ TelephonyManager tm = (TelephonyManager)
+ context.getSystemService(Context.TELEPHONY_SERVICE);
+ NfcAdapter nfcAdapter = null;
+ try {
+ nfcAdapter = NfcAdapter.getNfcAdapter(context);
+ } catch (UnsupportedOperationException e) {
+ //Nfc not available
+ }
+
+ boolean forcedState = getValue() == 1;
+ boolean currentState;
+
+ switch (getConnectionId()) {
+ case PROFILE_CONNECTION_MOBILEDATA:
+ currentState = tm.getDataEnabled();
+ if (forcedState != currentState) {
+ int phoneCount = tm.getPhoneCount();
+ for (int i = 0; i < phoneCount; i++) {
+ Settings.Global.putInt(context.getContentResolver(),
+ Settings.Global.MOBILE_DATA + i, (forcedState) ? 1 : 0);
+ int[] subId = SubscriptionManager.getSubId(i);
+ tm.setDataEnabled(subId[0], forcedState);
+ }
+ }
+ break;
+ case PROFILE_CONNECTION_2G3G4G:
+ if (Build.CM_VERSION.SDK_INT >= Build.CM_VERSION_CODES.ELDERBERRY) {
+ Intent intent = new Intent(ACTION_MODIFY_NETWORK_MODE);
+ intent.putExtra(EXTRA_NETWORK_MODE, getValue());
+ intent.putExtra(EXTRA_SUB_ID, getSubId());
+ context.sendBroadcast(intent, "com.android.phone.CHANGE_NETWORK_MODE");
+ } else {
+ Intent intent = new Intent(ACTION_MODIFY_NETWORK_MODE);
+ switch(getValue()) {
+ case CM_MODE_2G:
+ intent.putExtra(EXTRA_NETWORK_MODE, RILConstants.NETWORK_MODE_GSM_ONLY);
+ break;
+ case CM_MODE_3G:
+ intent.putExtra(EXTRA_NETWORK_MODE, RILConstants.NETWORK_MODE_WCDMA_ONLY);
+ break;
+ case CM_MODE_4G:
+ intent.putExtra(EXTRA_NETWORK_MODE, RILConstants.NETWORK_MODE_LTE_ONLY);
+ break;
+ case CM_MODE_2G3G:
+ intent.putExtra(EXTRA_NETWORK_MODE, RILConstants.NETWORK_MODE_WCDMA_PREF);
+ break;
+ case CM_MODE_ALL:
+ intent.putExtra(EXTRA_NETWORK_MODE,
+ RILConstants.NETWORK_MODE_LTE_GSM_WCDMA);
+ break;
+ default:
+ return;
+ }
+ context.sendBroadcast(intent);
+ }
+ break;
+ case PROFILE_CONNECTION_BLUETOOTH:
+ int btstate = bta.getState();
+ if (forcedState && (btstate == BluetoothAdapter.STATE_OFF
+ || btstate == BluetoothAdapter.STATE_TURNING_OFF)) {
+ bta.enable();
+ } else if (!forcedState && (btstate == BluetoothAdapter.STATE_ON
+ || btstate == BluetoothAdapter.STATE_TURNING_ON)) {
+ bta.disable();
+ }
+ break;
+ case PROFILE_CONNECTION_GPS:
+ currentState = lm.isProviderEnabled(LocationManager.GPS_PROVIDER);
+ if (currentState != forcedState) {
+ Settings.Secure.setLocationProviderEnabled(context.getContentResolver(),
+ LocationManager.GPS_PROVIDER, forcedState);
+ }
+ break;
+ case PROFILE_CONNECTION_SYNC:
+ currentState = ContentResolver.getMasterSyncAutomatically();
+ if (forcedState != currentState) {
+ ContentResolver.setMasterSyncAutomatically(forcedState);
+ }
+ break;
+ case PROFILE_CONNECTION_WIFI:
+ int wifiApState = wm.getWifiApState();
+ currentState = wm.isWifiEnabled();
+ if (currentState != forcedState) {
+ // Disable wifi tether
+ if (forcedState && (wifiApState == WifiManager.WIFI_AP_STATE_ENABLING) ||
+ (wifiApState == WifiManager.WIFI_AP_STATE_ENABLED)) {
+ wm.setWifiApEnabled(null, false);
+ }
+ wm.setWifiEnabled(forcedState);
+ }
+ break;
+ case PROFILE_CONNECTION_WIFIAP:
+ int wifiState = wm.getWifiState();
+ currentState = wm.isWifiApEnabled();
+ if (currentState != forcedState) {
+ // Disable wifi
+ if (forcedState && (wifiState == WifiManager.WIFI_STATE_ENABLING) ||
+ (wifiState == WifiManager.WIFI_STATE_ENABLED)) {
+ wm.setWifiEnabled(false);
+ }
+ wm.setWifiApEnabled(null, forcedState);
+ }
+ break;
+ case PROFILE_CONNECTION_WIMAX:
+ if (WimaxHelper.isWimaxSupported(context)) {
+ currentState = WimaxHelper.isWimaxEnabled(context);
+ if (currentState != forcedState) {
+ WimaxHelper.setWimaxEnabled(context, forcedState);
+ }
+ }
+ break;
+ case PROFILE_CONNECTION_NFC:
+ if (nfcAdapter != null) {
+ int adapterState = nfcAdapter.getAdapterState();
+ currentState = (adapterState == NfcAdapter.STATE_ON ||
+ adapterState == NfcAdapter.STATE_TURNING_ON);
+ if (currentState != forcedState) {
+ if (forcedState) {
+ nfcAdapter.enable();
+ } else if (!forcedState && adapterState != NfcAdapter.STATE_TURNING_OFF) {
+ nfcAdapter.disable();
+ }
+ }
+ }
+ break;
+ }
+ }
+
+ /** @hide */
+ public static ConnectionSettings fromXml(XmlPullParser xpp, Context context)
+ throws XmlPullParserException, IOException {
+ int event = xpp.next();
+ ConnectionSettings connectionDescriptor = new ConnectionSettings(0);
+ while (event != XmlPullParser.END_TAG || !xpp.getName().equals("connectionDescriptor")) {
+ if (event == XmlPullParser.START_TAG) {
+ String name = xpp.getName();
+ if (name.equals("connectionId")) {
+ connectionDescriptor.mConnectionId = Integer.parseInt(xpp.nextText());
+ } else if (name.equals("value")) {
+ connectionDescriptor.mValue = Integer.parseInt(xpp.nextText());
+ } else if (name.equals("override")) {
+ connectionDescriptor.mOverride = Boolean.parseBoolean(xpp.nextText());
+ } else if (name.equals("subId")) {
+ connectionDescriptor.mSubId = Integer.parseInt(xpp.nextText());
+ }
+ } else if (event == XmlPullParser.END_DOCUMENT) {
+ throw new IOException("Premature end of file while parsing connection settings");
+ }
+ event = xpp.next();
+ }
+ return connectionDescriptor;
+ }
+
+ /** @hide */
+ public void getXmlString(StringBuilder builder, Context context) {
+ builder.append("<connectionDescriptor>\n<connectionId>");
+ builder.append(mConnectionId);
+ builder.append("</connectionId>\n<value>");
+ builder.append(mValue);
+ builder.append("</value>\n<override>");
+ builder.append(mOverride);
+ builder.append("</override>\n");
+ if (Build.CM_VERSION.SDK_INT >= Build.CM_VERSION_CODES.ELDERBERRY) {
+ if (mConnectionId == PROFILE_CONNECTION_2G3G4G
+ && mSubId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+ builder.append("<subId>").append(mSubId).append("</subId>\n");
+ }
+ }
+ builder.append("</connectionDescriptor>\n");
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /** @hide */
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ // Tell the concierge to prepare the parcel
+ ParcelInfo parcelInfo = Concierge.prepareParcel(dest);
+
+ // === BOYSENBERRY ===
+ dest.writeInt(mConnectionId);
+ dest.writeInt(mOverride ? 1 : 0);
+ dest.writeInt(mValue);
+ dest.writeInt(mDirty ? 1 : 0);
+
+ // === ELDERBERRY ===
+ if (mConnectionId == PROFILE_CONNECTION_2G3G4G) {
+ dest.writeInt(mSubId);
+ }
+
+ // Complete the parcel info for the concierge
+ parcelInfo.complete();
+ }
+
+ /** @hide */
+ public void readFromParcel(Parcel in) {
+ // Read parcelable version via the Concierge
+ ParcelInfo parcelInfo = Concierge.receiveParcel(in);
+ int parcelableVersion = parcelInfo.getParcelVersion();
+
+ // Pattern here is that all new members should be added to the end of
+ // the writeToParcel method. Then we step through each version, until the latest
+ // API release to help unravel this parcel
+ if (parcelableVersion >= Build.CM_VERSION_CODES.BOYSENBERRY) {
+ mConnectionId = in.readInt();
+ mOverride = in.readInt() != 0;
+ mValue = in.readInt();
+ mDirty = in.readInt() != 0;
+ }
+
+ if (parcelableVersion >= Build.CM_VERSION_CODES.ELDERBERRY) {
+ if (mConnectionId == PROFILE_CONNECTION_2G3G4G) {
+ mSubId = in.readInt();
+ }
+ }
+
+ // Complete parcel info for the concierge
+ parcelInfo.complete();
+ }
+}
diff --git a/sdk/src/java/cyanogenmod/profiles/LockSettings.java b/sdk/src/java/cyanogenmod/profiles/LockSettings.java
new file mode 100644
index 0000000..2be3725
--- /dev/null
+++ b/sdk/src/java/cyanogenmod/profiles/LockSettings.java
@@ -0,0 +1,179 @@
+/*
+ * 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.profiles;
+
+import android.app.admin.DevicePolicyManager;
+import android.content.Context;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.RemoteException;
+import android.util.Log;
+/* import android.view.WindowManagerPolicyControl; */
+import com.android.internal.policy.IKeyguardService;
+/* import com.android.internal.policy.PolicyManager; */
+
+import cyanogenmod.app.Profile;
+import cyanogenmod.os.Build;
+import cyanogenmod.os.Concierge;
+import cyanogenmod.os.Concierge.ParcelInfo;
+
+/**
+ * The {@link LockSettings} class allows for overriding and setting the
+ * current Lock screen state/security level. Value should be one a constant from
+ * of {@link Profile.LockMode}
+ *
+ * <p>Example for disabling lockscreen security:
+ * <pre class="prettyprint">
+ * LockSettings lock = new LockSettings(Profile.LockMode.INSECURE);
+ * profile.setScreenLockMode(lock);
+ * </pre>
+ */
+public final class LockSettings implements Parcelable {
+
+ private static final String TAG = LockSettings.class.getSimpleName();
+
+ private int mValue;
+ private boolean mDirty;
+
+ /** @hide */
+ public static final Creator<LockSettings> CREATOR
+ = new Creator<LockSettings>() {
+ public LockSettings createFromParcel(Parcel in) {
+ return new LockSettings(in);
+ }
+
+ @Override
+ public LockSettings[] newArray(int size) {
+ return new LockSettings[size];
+ }
+ };
+
+ /**
+ * Unwrap {@link LockSettings} from a parcel.
+ * @param parcel
+ */
+ public LockSettings(Parcel parcel) {
+ readFromParcel(parcel);
+ }
+
+ /**
+ * Construct a {@link LockSettings} with a default value of {@link Profile.LockMode.DEFAULT}.
+ */
+ public LockSettings() {
+ this(Profile.LockMode.DEFAULT);
+ }
+
+ /**
+ * Construct a {@link LockSettings} with a default value.
+ */
+ public LockSettings(int value) {
+ mValue = value;
+ mDirty = false;
+ }
+
+ /**
+ * Get the value for the {@link LockSettings}
+ * @return a constant from {@link Profile.LockMode}
+ */
+ public int getValue() {
+ return mValue;
+ }
+
+ /**
+ * Set the value for the {@link LockSettings}
+ *
+ * see {@link Profile.LockMode}
+ */
+ public void setValue(int value) {
+ mValue = value;
+ mDirty = true;
+ }
+
+ /** @hide */
+ public boolean isDirty() {
+ return mDirty;
+ }
+
+ /** @hide */
+ public void processOverride(Context context, IKeyguardService keyguard) {
+ boolean enable;
+ final DevicePolicyManager devicePolicyManager =
+ (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE);
+ if (devicePolicyManager != null && devicePolicyManager.requireSecureKeyguard()) {
+ enable = true;
+ } else {
+ switch (mValue) {
+ default:
+ case Profile.LockMode.DEFAULT:
+ case Profile.LockMode.INSECURE:
+ enable = true;
+ break;
+ case Profile.LockMode.DISABLE:
+ enable = false;
+ break;
+ }
+ }
+
+ try {
+ keyguard.setKeyguardEnabled(enable);
+ } catch (RemoteException e) {
+ Log.w(TAG, "unable to set keyguard enabled state to: " + enable, e);
+ }
+ }
+
+ /** @hide */
+ public void writeXmlString(StringBuilder builder, Context context) {
+ builder.append(mValue);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /** @hide */
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ // Tell the concierge to prepare the parcel
+ ParcelInfo parcelInfo = Concierge.prepareParcel(dest);
+
+ // === BOYSENBERRY ===
+ dest.writeInt(mValue);
+ dest.writeInt(mDirty ? 1 : 0);
+
+ // Complete the parcel info for the concierge
+ parcelInfo.complete();
+ }
+
+ /** @hide */
+ public void readFromParcel(Parcel in) {
+ // Read parcelable version via the Concierge
+ ParcelInfo parcelInfo = Concierge.receiveParcel(in);
+ int parcelableVersion = parcelInfo.getParcelVersion();
+
+ // Pattern here is that all new members should be added to the end of
+ // the writeToParcel method. Then we step through each version, until the latest
+ // API release to help unravel this parcel
+ if (parcelableVersion >= Build.CM_VERSION_CODES.BOYSENBERRY) {
+ mValue = in.readInt();
+ mDirty = in.readInt() != 0;
+ }
+
+ // Complete parcel info for the concierge
+ parcelInfo.complete();
+ }
+}
diff --git a/sdk/src/java/cyanogenmod/profiles/RingModeSettings.java b/sdk/src/java/cyanogenmod/profiles/RingModeSettings.java
new file mode 100644
index 0000000..bab74ac
--- /dev/null
+++ b/sdk/src/java/cyanogenmod/profiles/RingModeSettings.java
@@ -0,0 +1,214 @@
+/*
+ * 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.profiles;
+
+import android.content.Context;
+import android.media.AudioManager;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import cyanogenmod.os.Build;
+import cyanogenmod.os.Concierge;
+import cyanogenmod.os.Concierge.ParcelInfo;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+
+/**
+ * The {@link StreamSettings} class allows for creating various {@link android.media.AudioManager}
+ * overrides on the device depending on their capabilities.
+ *
+ * <p>Example for setting the default ring mode to muted:
+ * <pre class="prettyprint">
+ * RingModeSettings ringSettings = new RingModeSettings(RING_MODE_MUTE, true));
+ * profile.setRingMode(ringSettings);
+ * </pre>
+ */
+public final class RingModeSettings implements Parcelable {
+ public static final String RING_MODE_NORMAL = "normal";
+ public static final String RING_MODE_VIBRATE = "vibrate";
+ public static final String RING_MODE_MUTE = "mute";
+
+ private String mValue;
+ private boolean mOverride;
+ private boolean mDirty;
+
+ /** @hide */
+ public static final Parcelable.Creator<RingModeSettings> CREATOR
+ = new Parcelable.Creator<RingModeSettings>() {
+ public RingModeSettings createFromParcel(Parcel in) {
+ return new RingModeSettings(in);
+ }
+
+ @Override
+ public RingModeSettings[] newArray(int size) {
+ return new RingModeSettings[size];
+ }
+ };
+
+ /**
+ * Unwrap {@link RingModeSettings} from a parcel.
+ * @param parcel
+ */
+ public RingModeSettings(Parcel parcel) {
+ readFromParcel(parcel);
+ }
+
+ /**
+ * Construct a {@link RingModeSettings} with a default state of #RING_MODE_NORMAL.
+ */
+ public RingModeSettings() {
+ this(RING_MODE_NORMAL, false);
+ }
+
+ /**
+ * Construct a {@link RingModeSettings} with a default value and whether or not it should
+ * override user settings.
+ * @param value ex: {@link #RING_MODE_VIBRATE}
+ * @param override whether or not the setting should override user settings
+ */
+ public RingModeSettings(String value, boolean override) {
+ mValue = value;
+ mOverride = override;
+ mDirty = false;
+ }
+
+ /**
+ * Get the default value for the {@link RingModeSettings}
+ * @return integer value corresponding with its type
+ */
+ public String getValue() {
+ return mValue;
+ }
+
+ /**
+ * Set the default value for the {@link RingModeSettings}
+ * @param value ex: {@link #RING_MODE_VIBRATE}
+ */
+ public void setValue(String value) {
+ mValue = value;
+ mDirty = true;
+ }
+
+ /**
+ * Set whether or not the {@link RingModeSettings} should override default user values
+ * @param override boolean override
+ */
+ public void setOverride(boolean override) {
+ mOverride = override;
+ mDirty = true;
+ }
+
+ /**
+ * Check whether or not the {@link RingModeSettings} overrides user settings.
+ * @return true if override
+ */
+ public boolean isOverride() {
+ return mOverride;
+ }
+
+ /** @hide */
+ public boolean isDirty() {
+ return mDirty;
+ }
+
+ /** @hide */
+ public void processOverride(Context context) {
+ if (isOverride()) {
+ int ringerMode = AudioManager.RINGER_MODE_NORMAL;
+ if (mValue.equals(RING_MODE_MUTE)) {
+ ringerMode = AudioManager.RINGER_MODE_SILENT;
+ } else if (mValue.equals(RING_MODE_VIBRATE)) {
+ ringerMode = AudioManager.RINGER_MODE_VIBRATE;
+ }
+ AudioManager amgr = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+ amgr.setRingerModeInternal(ringerMode);
+ }
+ }
+
+ /** @hide */
+ public static RingModeSettings fromXml(XmlPullParser xpp, Context context)
+ throws XmlPullParserException, IOException {
+ int event = xpp.next();
+ RingModeSettings ringModeDescriptor = new RingModeSettings();
+ while ((event != XmlPullParser.END_TAG && event != XmlPullParser.END_DOCUMENT) ||
+ !xpp.getName().equals("ringModeDescriptor")) {
+ if (event == XmlPullParser.START_TAG) {
+ String name = xpp.getName();
+ if (name.equals("value")) {
+ ringModeDescriptor.mValue = xpp.nextText();
+ } else if (name.equals("override")) {
+ ringModeDescriptor.mOverride = Boolean.parseBoolean(xpp.nextText());
+ }
+ } else if (event == XmlPullParser.END_DOCUMENT) {
+ throw new IOException("Premature end of file while parsing ring mode settings");
+ }
+ event = xpp.next();
+ }
+ return ringModeDescriptor;
+ }
+
+ /** @hide */
+ public void getXmlString(StringBuilder builder, Context context) {
+ builder.append("<ringModeDescriptor>\n<value>");
+ builder.append(mValue);
+ builder.append("</value>\n<override>");
+ builder.append(mOverride);
+ builder.append("</override>\n</ringModeDescriptor>\n");
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /** @hide */
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ // Tell the concierge to prepare the parcel
+ ParcelInfo parcelInfo = Concierge.prepareParcel(dest);
+
+ // === BOYSENBERRY ===
+ dest.writeInt(mOverride ? 1 : 0);
+ dest.writeString(mValue);
+ dest.writeInt(mDirty ? 1 : 0);
+
+ // Complete the parcel info for the concierge
+ parcelInfo.complete();
+ }
+
+ /** @hide */
+ public void readFromParcel(Parcel in) {
+ // Read parcelable version via the Concierge
+ ParcelInfo parcelInfo = Concierge.receiveParcel(in);
+ int parcelableVersion = parcelInfo.getParcelVersion();
+
+ // Pattern here is that all new members should be added to the end of
+ // the writeToParcel method. Then we step through each version, until the latest
+ // API release to help unravel this parcel
+ if (parcelableVersion >= Build.CM_VERSION_CODES.BOYSENBERRY) {
+ mOverride = in.readInt() != 0;
+ mValue = in.readString();
+ mDirty = in.readInt() != 0;
+ }
+
+ // Complete parcel info for the concierge
+ parcelInfo.complete();
+ }
+}
diff --git a/sdk/src/java/cyanogenmod/profiles/StreamSettings.java b/sdk/src/java/cyanogenmod/profiles/StreamSettings.java
new file mode 100644
index 0000000..9ce9415
--- /dev/null
+++ b/sdk/src/java/cyanogenmod/profiles/StreamSettings.java
@@ -0,0 +1,216 @@
+/*
+ * 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.profiles;
+
+import android.content.Context;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+
+import cyanogenmod.os.Build;
+import cyanogenmod.os.Concierge;
+import cyanogenmod.os.Concierge.ParcelInfo;
+
+import java.io.IOException;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+/**
+ * The {@link StreamSettings} class allows for creating various {@link android.media.AudioManager}
+ * overrides on the device depending on their capabilities.
+ *
+ * <p>Example for setting the alarm stream defaults and override:
+ * <pre class="prettyprint">
+ * StreamSettings alarmStreamSettings = new StreamSettings(AudioManager.STREAM_ALARM,
+ * am.getStreamVolume(AudioManager.STREAM_ALARM), true));
+ * profile.setStreamSettings(alarmStreamSettings);
+ * </pre>
+ */
+public final class StreamSettings implements Parcelable{
+
+ private int mStreamId;
+ private int mValue;
+ private boolean mOverride;
+ private boolean mDirty;
+
+ /** @hide */
+ public static final Parcelable.Creator<StreamSettings> CREATOR =
+ new Parcelable.Creator<StreamSettings>() {
+ public StreamSettings createFromParcel(Parcel in) {
+ return new StreamSettings(in);
+ }
+
+ @Override
+ public StreamSettings[] newArray(int size) {
+ return new StreamSettings[size];
+ }
+ };
+
+ /**
+ * Unwrap {@link StreamSettings} from a parcel.
+ * @param parcel
+ */
+ public StreamSettings(Parcel parcel) {
+ readFromParcel(parcel);
+ }
+
+ /**
+ * Construct a {@link StreamSettings} with a stream id and default states.
+ * @param streamId ex: {@link android.media.AudioManager#STREAM_ALARM}
+ */
+ public StreamSettings(int streamId) {
+ this(streamId, 0, false);
+ }
+
+ /**
+ * Construct a {@link StreamSettings} with a stream id, default value,
+ * and if the setting should override the user defaults.
+ * @param streamId ex: {@link android.media.AudioManager#STREAM_ALARM}
+ * @param value default value for the {@link StreamSettings}
+ * @param override whether or not the {@link StreamSettings} should override user defaults
+ */
+ public StreamSettings(int streamId, int value, boolean override) {
+ mStreamId = streamId;
+ mValue = value;
+ mOverride = override;
+ mDirty = false;
+ }
+
+ /**
+ * Retrieve the stream id id associated with the {@link StreamSettings}
+ * @return an integer identifier
+ */
+ public int getStreamId() {
+ return mStreamId;
+ }
+
+ /**
+ * Get the default value for the {@link StreamSettings}
+ * @return integer value corresponding with its state
+ */
+ public int getValue() {
+ return mValue;
+ }
+
+ /**
+ * Set the default value for the {@link StreamSettings}
+ * @param value see {@link android.media.AudioManager} for viable values
+ */
+ public void setValue(int value) {
+ mValue = value;
+ mDirty = true;
+ }
+
+ /**
+ * Set whether or not the {@link StreamSettings} should override default user values
+ * @param override boolean override
+ */
+ public void setOverride(boolean override) {
+ mOverride = override;
+ mDirty = true;
+ }
+
+ /**
+ * Check whether or not the {@link StreamSettings} overrides user settings.
+ * @return true if override
+ */
+ public boolean isOverride() {
+ return mOverride;
+ }
+
+ /** @hide */
+ public boolean isDirty() {
+ return mDirty;
+ }
+
+ /** @hide */
+ public static StreamSettings fromXml(XmlPullParser xpp, Context context)
+ throws XmlPullParserException, IOException {
+ int event = xpp.next();
+ StreamSettings streamDescriptor = new StreamSettings(0);
+ while (event != XmlPullParser.END_TAG || !xpp.getName().equals("streamDescriptor")) {
+ if (event == XmlPullParser.START_TAG) {
+ String name = xpp.getName();
+ if (name.equals("streamId")) {
+ streamDescriptor.mStreamId = Integer.parseInt(xpp.nextText());
+ } else if (name.equals("value")) {
+ streamDescriptor.mValue = Integer.parseInt(xpp.nextText());
+ } else if (name.equals("override")) {
+ streamDescriptor.mOverride = Boolean.parseBoolean(xpp.nextText());
+ }
+ } else if (event == XmlPullParser.END_DOCUMENT) {
+ throw new IOException("Premature end of file while parsing stream settings");
+ }
+ event = xpp.next();
+ }
+ return streamDescriptor;
+ }
+
+ /** @hide */
+ public void getXmlString(StringBuilder builder, Context context) {
+ builder.append("<streamDescriptor>\n<streamId>");
+ builder.append(mStreamId);
+ builder.append("</streamId>\n<value>");
+ builder.append(mValue);
+ builder.append("</value>\n<override>");
+ builder.append(mOverride);
+ builder.append("</override>\n</streamDescriptor>\n");
+ mDirty = false;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /** @hide */
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ // Tell the concierge to prepare the parcel
+ ParcelInfo parcelInfo = Concierge.prepareParcel(dest);
+
+ // === BOYSENBERRY ===
+ dest.writeInt(mStreamId);
+ dest.writeInt(mOverride ? 1 : 0);
+ dest.writeInt(mValue);
+ dest.writeInt(mDirty ? 1 : 0);
+
+ // Complete the parcel info for the concierge
+ parcelInfo.complete();
+ }
+
+ /** @hide */
+ public void readFromParcel(Parcel in) {
+ // Read parcelable version via the Concierge
+ ParcelInfo parcelInfo = Concierge.receiveParcel(in);
+ int parcelableVersion = parcelInfo.getParcelVersion();
+
+ // Pattern here is that all new members should be added to the end of
+ // the writeToParcel method. Then we step through each version, until the latest
+ // API release to help unravel this parcel
+ if (parcelableVersion >= Build.CM_VERSION_CODES.BOYSENBERRY) {
+ mStreamId = in.readInt();
+ mOverride = in.readInt() != 0;
+ mValue = in.readInt();
+ mDirty = in.readInt() != 0;
+ }
+
+ // Complete parcel info for the concierge
+ parcelInfo.complete();
+ }
+}
diff --git a/sdk/src/java/cyanogenmod/providers/CMSettings.java b/sdk/src/java/cyanogenmod/providers/CMSettings.java
new file mode 100644
index 0000000..47cc524
--- /dev/null
+++ b/sdk/src/java/cyanogenmod/providers/CMSettings.java
@@ -0,0 +1,3258 @@
+/**
+ * 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.providers;
+
+import com.android.internal.util.ArrayUtils;
+
+import android.content.ContentResolver;
+import android.content.IContentProvider;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.SystemProperties;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.AndroidException;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Log;
+
+import com.android.internal.util.ArrayUtils;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.regex.Pattern;
+
+/**
+ * CMSettings contains CM specific preferences in System, Secure, and Global.
+ */
+public final class CMSettings {
+ private static final String TAG = "CMSettings";
+ private static final boolean LOCAL_LOGV = false;
+
+ public static final String AUTHORITY = "cmsettings";
+
+ public static class CMSettingNotFoundException extends AndroidException {
+ public CMSettingNotFoundException(String msg) {
+ super(msg);
+ }
+ }
+
+ // Intent actions for Settings
+ /**
+ * Activity Action: Show Data Usage Summary
+ * <p>
+ * Input: Nothing.
+ * <p>
+ * Output: Nothing.
+ */
+ public static final String ACTION_DATA_USAGE = "cyanogenmod.settings.ACTION_DATA_USAGE";
+
+ // region Call Methods
+
+ /**
+ * @hide - User handle argument extra to the fast-path call()-based requests
+ */
+ public static final String CALL_METHOD_USER_KEY = "_user";
+
+ /**
+ * @hide - Private call() method on SettingsProvider to read from 'system' table.
+ */
+ public static final String CALL_METHOD_GET_SYSTEM = "GET_system";
+
+ /**
+ * @hide - Private call() method on SettingsProvider to read from 'secure' table.
+ */
+ public static final String CALL_METHOD_GET_SECURE = "GET_secure";
+
+ /**
+ * @hide - Private call() method on SettingsProvider to read from 'global' table.
+ */
+ public static final String CALL_METHOD_GET_GLOBAL = "GET_global";
+
+ /**
+ * @hide - Private call() method to write to 'system' table
+ */
+ public static final String CALL_METHOD_PUT_SYSTEM = "PUT_system";
+
+ /**
+ * @hide - Private call() method to write to 'secure' table
+ */
+ public static final String CALL_METHOD_PUT_SECURE = "PUT_secure";
+
+ /**
+ * @hide - Private call() method to write to 'global' table
+ */
+ public static final String CALL_METHOD_PUT_GLOBAL= "PUT_global";
+
+ /**
+ * @hide - Private call() method on CMSettingsProvider to migrate CM settings
+ */
+ public static final String CALL_METHOD_MIGRATE_SETTINGS = "migrate_settings";
+
+ /**
+ * @hide - Private call() method on CMSettingsProvider to migrate CM settings for a user
+ */
+ public static final String CALL_METHOD_MIGRATE_SETTINGS_FOR_USER = "migrate_settings_for_user";
+
+ // endregion
+
+ // Thread-safe.
+ private static class NameValueCache {
+ private final String mVersionSystemProperty;
+ private final Uri mUri;
+
+ private static final String[] SELECT_VALUE =
+ new String[] { Settings.NameValueTable.VALUE };
+ private static final String NAME_EQ_PLACEHOLDER = "name=?";
+
+ // Must synchronize on 'this' to access mValues and mValuesVersion.
+ private final HashMap<String, String> mValues = new HashMap<String, String>();
+ private long mValuesVersion = 0;
+
+ // Initially null; set lazily and held forever. Synchronized on 'this'.
+ private IContentProvider mContentProvider = null;
+
+ // The method we'll call (or null, to not use) on the provider
+ // for the fast path of retrieving settings.
+ private final String mCallGetCommand;
+ private final String mCallSetCommand;
+
+ public NameValueCache(String versionSystemProperty, Uri uri,
+ String getCommand, String setCommand) {
+ mVersionSystemProperty = versionSystemProperty;
+ mUri = uri;
+ mCallGetCommand = getCommand;
+ mCallSetCommand = setCommand;
+ }
+
+ private IContentProvider lazyGetProvider(ContentResolver cr) {
+ IContentProvider cp;
+ synchronized (this) {
+ cp = mContentProvider;
+ if (cp == null) {
+ cp = mContentProvider = cr.acquireProvider(mUri.getAuthority());
+ }
+ }
+ return cp;
+ }
+
+ /**
+ * Puts a string name/value pair into the content provider for the specified user.
+ * @param cr The content resolver to use.
+ * @param name The name of the key to put into the content provider.
+ * @param value The value to put into the content provider.
+ * @param userId The user id to use for the content provider.
+ * @return Whether the put was successful.
+ */
+ public boolean putStringForUser(ContentResolver cr, String name, String value,
+ final int userId) {
+ try {
+ Bundle arg = new Bundle();
+ arg.putString(Settings.NameValueTable.VALUE, value);
+ arg.putInt(CALL_METHOD_USER_KEY, userId);
+ IContentProvider cp = lazyGetProvider(cr);
+ cp.call(cr.getPackageName(), mCallSetCommand, name, arg);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Can't set key " + name + " in " + mUri, e);
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Gets a string value with the specified name from the name/value cache if possible. If
+ * not, it will use the content resolver and perform a query.
+ * @param cr Content resolver to use if name/value cache does not contain the name or if
+ * the cache version is older than the current version.
+ * @param name The name of the key to search for.
+ * @param userId The user id of the cache to look in.
+ * @return The string value of the specified key.
+ */
+ public String getStringForUser(ContentResolver cr, String name, final int userId) {
+ final boolean isSelf = (userId == UserHandle.myUserId());
+ if (isSelf) {
+ if (LOCAL_LOGV) Log.d(TAG, "get setting for self");
+ long newValuesVersion = SystemProperties.getLong(mVersionSystemProperty, 0);
+
+ // Our own user's settings data uses a client-side cache
+ synchronized (this) {
+ if (mValuesVersion != newValuesVersion) {
+ if (LOCAL_LOGV || false) {
+ Log.v(TAG, "invalidate [" + mUri.getLastPathSegment() + "]: current "
+ + newValuesVersion + " != cached " + mValuesVersion);
+ }
+
+ mValues.clear();
+ mValuesVersion = newValuesVersion;
+ }
+
+ if (mValues.containsKey(name)) {
+ return mValues.get(name); // Could be null, that's OK -- negative caching
+ }
+ }
+ } else {
+ if (LOCAL_LOGV) Log.v(TAG, "get setting for user " + userId
+ + " by user " + UserHandle.myUserId() + " so skipping cache");
+ }
+
+ IContentProvider cp = lazyGetProvider(cr);
+
+ // Try the fast path first, not using query(). If this
+ // fails (alternate Settings provider that doesn't support
+ // this interface?) then we fall back to the query/table
+ // interface.
+ if (mCallGetCommand != null) {
+ try {
+ Bundle args = null;
+ if (!isSelf) {
+ args = new Bundle();
+ args.putInt(CALL_METHOD_USER_KEY, userId);
+ }
+ Bundle b = cp.call(cr.getPackageName(), mCallGetCommand, name, args);
+ if (b != null) {
+ String value = b.getPairValue();
+ // Don't update our cache for reads of other users' data
+ if (isSelf) {
+ synchronized (this) {
+ mValues.put(name, value);
+ }
+ } else {
+ if (LOCAL_LOGV) Log.i(TAG, "call-query of user " + userId
+ + " by " + UserHandle.myUserId()
+ + " so not updating cache");
+ }
+ return value;
+ }
+ // If the response Bundle is null, we fall through
+ // to the query interface below.
+ } catch (RemoteException e) {
+ // Not supported by the remote side? Fall through
+ // to query().
+ }
+ }
+
+ Cursor c = null;
+ try {
+ c = cp.query(cr.getPackageName(), mUri, SELECT_VALUE, NAME_EQ_PLACEHOLDER,
+ new String[]{name}, null, null);
+ if (c == null) {
+ Log.w(TAG, "Can't get key " + name + " from " + mUri);
+ return null;
+ }
+
+ String value = c.moveToNext() ? c.getString(0) : null;
+ synchronized (this) {
+ mValues.put(name, value);
+ }
+ if (LOCAL_LOGV) {
+ Log.v(TAG, "cache miss [" + mUri.getLastPathSegment() + "]: " +
+ name + " = " + (value == null ? "(null)" : value));
+ }
+ return value;
+ } catch (RemoteException e) {
+ Log.w(TAG, "Can't get key " + name + " from " + mUri, e);
+ return null; // Return null, but don't cache it.
+ } finally {
+ if (c != null) c.close();
+ }
+ }
+ }
+
+ // region Validators
+
+ /** @hide */
+ public static interface Validator {
+ public boolean validate(String value);
+ }
+
+ private static final Validator sBooleanValidator =
+ new DiscreteValueValidator(new String[] {"0", "1"});
+
+ private static final Validator sNonNegativeIntegerValidator = new Validator() {
+ @Override
+ public boolean validate(String value) {
+ try {
+ return Integer.parseInt(value) >= 0;
+ } catch (NumberFormatException e) {
+ return false;
+ }
+ }
+ };
+
+ private static final Validator sUriValidator = new Validator() {
+ @Override
+ public boolean validate(String value) {
+ try {
+ Uri.decode(value);
+ return true;
+ } catch (IllegalArgumentException e) {
+ return false;
+ }
+ }
+ };
+
+ private static final Validator sColorValidator =
+ new InclusiveIntegerRangeValidator(Integer.MIN_VALUE, Integer.MAX_VALUE);
+
+ private static final Validator sAlwaysTrueValidator = new Validator() {
+ @Override
+ public boolean validate(String value) {
+ return true;
+ }
+ };
+
+ private static final class DiscreteValueValidator implements Validator {
+ private final String[] mValues;
+
+ public DiscreteValueValidator(String[] values) {
+ mValues = values;
+ }
+
+ @Override
+ public boolean validate(String value) {
+ return ArrayUtils.contains(mValues, value);
+ }
+ }
+
+ private static final class InclusiveIntegerRangeValidator implements Validator {
+ private final int mMin;
+ private final int mMax;
+
+ public InclusiveIntegerRangeValidator(int min, int max) {
+ mMin = min;
+ mMax = max;
+ }
+
+ @Override
+ public boolean validate(String value) {
+ try {
+ final int intValue = Integer.parseInt(value);
+ return intValue >= mMin && intValue <= mMax;
+ } catch (NumberFormatException e) {
+ return false;
+ }
+ }
+ }
+
+ private static final class InclusiveFloatRangeValidator implements Validator {
+ private final float mMin;
+ private final float mMax;
+
+ public InclusiveFloatRangeValidator(float min, float max) {
+ mMin = min;
+ mMax = max;
+ }
+
+ @Override
+ public boolean validate(String value) {
+ try {
+ final float floatValue = Float.parseFloat(value);
+ return floatValue >= mMin && floatValue <= mMax;
+ } catch (NumberFormatException e) {
+ return false;
+ }
+ }
+ }
+
+ private static final class DelimitedListValidator implements Validator {
+ private final ArraySet<String> mValidValueSet;
+ private final String mDelimiter;
+ private final boolean mAllowEmptyList;
+
+ public DelimitedListValidator(String[] validValues, String delimiter,
+ boolean allowEmptyList) {
+ mValidValueSet = new ArraySet<String>(Arrays.asList(validValues));
+ mDelimiter = delimiter;
+ mAllowEmptyList = allowEmptyList;
+ }
+
+ @Override
+ public boolean validate(String value) {
+ ArraySet<String> values = new ArraySet<String>();
+ if (!TextUtils.isEmpty(value)) {
+ final String[] array = TextUtils.split(value, Pattern.quote(mDelimiter));
+ for (String item : array) {
+ if (TextUtils.isEmpty(item)) {
+ continue;
+ }
+ values.add(item);
+ }
+ }
+ if (values.size() > 0) {
+ values.removeAll(mValidValueSet);
+ // values.size() will be non-zero if it contains any values not in
+ // mValidValueSet
+ return values.size() == 0;
+ } else if (mAllowEmptyList) {
+ return true;
+ }
+
+ return false;
+ }
+ }
+ // endregion Validators
+
+ /**
+ * System settings, containing miscellaneous CM system preferences. This table holds simple
+ * name/value pairs. There are convenience functions for accessing individual settings entries.
+ */
+ public static final class System extends Settings.NameValueTable {
+ public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/system");
+
+ public static final String SYS_PROP_CM_SETTING_VERSION = "sys.cm_settings_system_version";
+
+ private static final NameValueCache sNameValueCache = new NameValueCache(
+ SYS_PROP_CM_SETTING_VERSION,
+ CONTENT_URI,
+ CALL_METHOD_GET_SYSTEM,
+ CALL_METHOD_PUT_SYSTEM);
+
+ // region Methods
+
+ /**
+ * Put a delimited list as a string
+ * @param resolver to access the database with
+ * @param name to store
+ * @param delimiter to split
+ * @param list to join and store
+ * @hide
+ */
+ public static void putListAsDelimitedString(ContentResolver resolver, String name,
+ String delimiter, List<String> list) {
+ String store = TextUtils.join(delimiter, list);
+ putString(resolver, name, store);
+ }
+
+ /**
+ * Get a delimited string returned as a list
+ * @param resolver to access the database with
+ * @param name to store
+ * @param delimiter to split the list with
+ * @return list of strings for a specific Settings.Secure item
+ * @hide
+ */
+ public static List<String> getDelimitedStringAsList(ContentResolver resolver, String name,
+ String delimiter) {
+ String baseString = getString(resolver, name);
+ List<String> list = new ArrayList<String>();
+ if (!TextUtils.isEmpty(baseString)) {
+ final String[] array = TextUtils.split(baseString, Pattern.quote(delimiter));
+ for (String item : array) {
+ if (TextUtils.isEmpty(item)) {
+ continue;
+ }
+ list.add(item);
+ }
+ }
+ return list;
+ }
+
+ /**
+ * Construct the content URI for a particular name/value pair, useful for monitoring changes
+ * with a ContentObserver.
+ * @param name to look up in the table
+ * @return the corresponding content URI
+ */
+ public static Uri getUriFor(String name) {
+ return Settings.NameValueTable.getUriFor(CONTENT_URI, name);
+ }
+
+ /**
+ * Look up a name in the database.
+ * @param resolver to access the database with
+ * @param name to look up in the table
+ * @return the corresponding value, or null if not present
+ */
+ public static String getString(ContentResolver resolver, String name) {
+ return getStringForUser(resolver, name, UserHandle.myUserId());
+ }
+
+ /** @hide */
+ public static String getStringForUser(ContentResolver resolver, String name,
+ int userId) {
+ return sNameValueCache.getStringForUser(resolver, name, userId);
+ }
+
+ /**
+ * Store a name/value pair into the database.
+ * @param resolver to access the database with
+ * @param name to store
+ * @param value to associate with the name
+ * @return true if the value was set, false on database errors
+ */
+ public static boolean putString(ContentResolver resolver, String name, String value) {
+ return putStringForUser(resolver, name, value, UserHandle.myUserId());
+ }
+
+ /** @hide */
+ public static boolean putStringForUser(ContentResolver resolver, String name, String value,
+ int userId) {
+ return sNameValueCache.putStringForUser(resolver, name, value, userId);
+ }
+
+ /**
+ * Convenience function for retrieving a single settings value
+ * as an integer. Note that internally setting values are always
+ * stored as strings; this function converts the string to an integer
+ * for you. The default value will be returned if the setting is
+ * not defined or not an integer.
+ *
+ * @param cr The ContentResolver to access.
+ * @param name The name of the setting to retrieve.
+ * @param def Value to return if the setting is not defined.
+ *
+ * @return The setting's current value, or 'def' if it is not defined
+ * or not a valid integer.
+ */
+ public static int getInt(ContentResolver cr, String name, int def) {
+ return getIntForUser(cr, name, def, UserHandle.myUserId());
+ }
+
+ /** @hide */
+ public static int getIntForUser(ContentResolver cr, String name, int def, int userId) {
+ String v = getStringForUser(cr, name, userId);
+ try {
+ return v != null ? Integer.parseInt(v) : def;
+ } catch (NumberFormatException e) {
+ return def;
+ }
+ }
+
+ /**
+ * Convenience function for retrieving a single settings value
+ * as an integer. Note that internally setting values are always
+ * stored as strings; this function converts the string to an integer
+ * for you.
+ * <p>
+ * This version does not take a default value. If the setting has not
+ * been set, or the string value is not a number,
+ * it throws {@link CMSettingNotFoundException}.
+ *
+ * @param cr The ContentResolver to access.
+ * @param name The name of the setting to retrieve.
+ *
+ * @throws CMSettingNotFoundException Thrown if a setting by the given
+ * name can't be found or the setting value is not an integer.
+ *
+ * @return The setting's current value.
+ */
+ public static int getInt(ContentResolver cr, String name)
+ throws CMSettingNotFoundException {
+ return getIntForUser(cr, name, UserHandle.myUserId());
+ }
+
+ /** @hide */
+ public static int getIntForUser(ContentResolver cr, String name, int userId)
+ throws CMSettingNotFoundException {
+ String v = getStringForUser(cr, name, userId);
+ try {
+ return Integer.parseInt(v);
+ } catch (NumberFormatException e) {
+ throw new CMSettingNotFoundException(name);
+ }
+ }
+
+ /**
+ * Convenience function for updating a single settings value as an
+ * integer. This will either create a new entry in the table if the
+ * given name does not exist, or modify the value of the existing row
+ * with that name. Note that internally setting values are always
+ * stored as strings, so this function converts the given value to a
+ * string before storing it.
+ *
+ * @param cr The ContentResolver to access.
+ * @param name The name of the setting to modify.
+ * @param value The new value for the setting.
+ * @return true if the value was set, false on database errors
+ */
+ public static boolean putInt(ContentResolver cr, String name, int value) {
+ return putIntForUser(cr, name, value, UserHandle.myUserId());
+ }
+
+ /** @hide */
+ public static boolean putIntForUser(ContentResolver cr, String name, int value,
+ int userId) {
+ return putStringForUser(cr, name, Integer.toString(value), userId);
+ }
+
+ /**
+ * Convenience function for retrieving a single settings value
+ * as a {@code long}. Note that internally setting values are always
+ * stored as strings; this function converts the string to a {@code long}
+ * for you. The default value will be returned if the setting is
+ * not defined or not a {@code long}.
+ *
+ * @param cr The ContentResolver to access.
+ * @param name The name of the setting to retrieve.
+ * @param def Value to return if the setting is not defined.
+ *
+ * @return The setting's current value, or 'def' if it is not defined
+ * or not a valid {@code long}.
+ */
+ public static long getLong(ContentResolver cr, String name, long def) {
+ return getLongForUser(cr, name, def, UserHandle.myUserId());
+ }
+
+ /** @hide */
+ public static long getLongForUser(ContentResolver cr, String name, long def,
+ int userId) {
+ String valString = getStringForUser(cr, name, userId);
+ long value;
+ try {
+ value = valString != null ? Long.parseLong(valString) : def;
+ } catch (NumberFormatException e) {
+ value = def;
+ }
+ return value;
+ }
+
+ /**
+ * Convenience function for retrieving a single settings value
+ * as a {@code long}. Note that internally setting values are always
+ * stored as strings; this function converts the string to a {@code long}
+ * for you.
+ * <p>
+ * This version does not take a default value. If the setting has not
+ * been set, or the string value is not a number,
+ * it throws {@link CMSettingNotFoundException}.
+ *
+ * @param cr The ContentResolver to access.
+ * @param name The name of the setting to retrieve.
+ *
+ * @return The setting's current value.
+ * @throws CMSettingNotFoundException Thrown if a setting by the given
+ * name can't be found or the setting value is not an integer.
+ */
+ public static long getLong(ContentResolver cr, String name)
+ throws CMSettingNotFoundException {
+ return getLongForUser(cr, name, UserHandle.myUserId());
+ }
+
+ /** @hide */
+ public static long getLongForUser(ContentResolver cr, String name, int userId)
+ throws CMSettingNotFoundException {
+ String valString = getStringForUser(cr, name, userId);
+ try {
+ return Long.parseLong(valString);
+ } catch (NumberFormatException e) {
+ throw new CMSettingNotFoundException(name);
+ }
+ }
+
+ /**
+ * Convenience function for updating a single settings value as a long
+ * integer. This will either create a new entry in the table if the
+ * given name does not exist, or modify the value of the existing row
+ * with that name. Note that internally setting values are always
+ * stored as strings, so this function converts the given value to a
+ * string before storing it.
+ *
+ * @param cr The ContentResolver to access.
+ * @param name The name of the setting to modify.
+ * @param value The new value for the setting.
+ * @return true if the value was set, false on database errors
+ */
+ public static boolean putLong(ContentResolver cr, String name, long value) {
+ return putLongForUser(cr, name, value, UserHandle.myUserId());
+ }
+
+ /** @hide */
+ public static boolean putLongForUser(ContentResolver cr, String name, long value,
+ int userId) {
+ return putStringForUser(cr, name, Long.toString(value), userId);
+ }
+
+ /**
+ * Convenience function for retrieving a single settings value
+ * as a floating point number. Note that internally setting values are
+ * always stored as strings; this function converts the string to an
+ * float for you. The default value will be returned if the setting
+ * is not defined or not a valid float.
+ *
+ * @param cr The ContentResolver to access.
+ * @param name The name of the setting to retrieve.
+ * @param def Value to return if the setting is not defined.
+ *
+ * @return The setting's current value, or 'def' if it is not defined
+ * or not a valid float.
+ */
+ public static float getFloat(ContentResolver cr, String name, float def) {
+ return getFloatForUser(cr, name, def, UserHandle.myUserId());
+ }
+
+ /** @hide */
+ public static float getFloatForUser(ContentResolver cr, String name, float def,
+ int userId) {
+ String v = getStringForUser(cr, name, userId);
+ try {
+ return v != null ? Float.parseFloat(v) : def;
+ } catch (NumberFormatException e) {
+ return def;
+ }
+ }
+
+ /**
+ * Convenience function for retrieving a single system settings value
+ * as a float. Note that internally setting values are always
+ * stored as strings; this function converts the string to a float
+ * for you.
+ * <p>
+ * This version does not take a default value. If the setting has not
+ * been set, or the string value is not a number,
+ * it throws {@link CMSettingNotFoundException}.
+ *
+ * @param cr The ContentResolver to access.
+ * @param name The name of the setting to retrieve.
+ *
+ * @throws CMSettingNotFoundException Thrown if a setting by the given
+ * name can't be found or the setting value is not a float.
+ *
+ * @return The setting's current value.
+ */
+ public static float getFloat(ContentResolver cr, String name)
+ throws CMSettingNotFoundException {
+ return getFloatForUser(cr, name, UserHandle.myUserId());
+ }
+
+ /** @hide */
+ public static float getFloatForUser(ContentResolver cr, String name, int userId)
+ throws CMSettingNotFoundException {
+ String v = getStringForUser(cr, name, userId);
+ if (v == null) {
+ throw new CMSettingNotFoundException(name);
+ }
+ try {
+ return Float.parseFloat(v);
+ } catch (NumberFormatException e) {
+ throw new CMSettingNotFoundException(name);
+ }
+ }
+
+ /**
+ * Convenience function for updating a single settings value as a
+ * floating point number. This will either create a new entry in the
+ * table if the given name does not exist, or modify the value of the
+ * existing row with that name. Note that internally setting values
+ * are always stored as strings, so this function converts the given
+ * value to a string before storing it.
+ *
+ * @param cr The ContentResolver to access.
+ * @param name The name of the setting to modify.
+ * @param value The new value for the setting.
+ * @return true if the value was set, false on database errors
+ */
+ public static boolean putFloat(ContentResolver cr, String name, float value) {
+ return putFloatForUser(cr, name, value, UserHandle.myUserId());
+ }
+
+ /** @hide */
+ public static boolean putFloatForUser(ContentResolver cr, String name, float value,
+ int userId) {
+ return putStringForUser(cr, name, Float.toString(value), userId);
+ }
+
+ // endregion
+
+ // region System Settings
+
+ /**
+ * Whether to attach a queue to media notifications.
+ * 0 = 0ff, 1 = on
+ */
+ public static final String NOTIFICATION_PLAY_QUEUE = "notification_play_queue";
+
+ /** @hide */
+ public static final Validator NOTIFICATION_PLAY_QUEUE_VALIDATOR = sBooleanValidator;
+
+ /**
+ * Whether the HighTouchSensitivity is activated or not.
+ * 0 = off, 1 = on
+ */
+ public static final String HIGH_TOUCH_SENSITIVITY_ENABLE =
+ "high_touch_sensitivity_enable";
+
+ /** @hide */
+ public static final Validator HIGH_TOUCH_SENSITIVITY_ENABLE_VALIDATOR =
+ sBooleanValidator;
+
+ /**
+ * Whether to enable system profiles feature
+ * 0 = off, 1 = on
+ */
+ public static final String SYSTEM_PROFILES_ENABLED = "system_profiles_enabled";
+
+ /** @hide */
+ public static final Validator SYSTEM_PROFILES_ENABLED_VALIDATOR =
+ sBooleanValidator;
+
+ /**
+ * Whether to hide the clock, show it in the right or left
+ * position or show it in the center
+ * 0: don't show the clock
+ * 1: show the clock in the right position (LTR)
+ * 2: show the clock in the center
+ * 3: show the clock in the left position (LTR)
+ * default: 1
+ */
+ public static final String STATUS_BAR_CLOCK = "status_bar_clock";
+
+ /** @hide */
+ public static final Validator STATUS_BAR_CLOCK_VALIDATOR =
+ new InclusiveIntegerRangeValidator(0, 3);
+
+ /**
+ * Whether the notification light will be allowed when in zen mode during downtime
+ */
+ public static final String ZEN_ALLOW_LIGHTS = "allow_lights";
+
+ /** @hide */
+ public static final Validator ZEN_ALLOW_LIGHTS_VALIDATOR = sBooleanValidator;
+
+ /**
+ * Whether the notification light will be allowed when in zen priority mode during downtime
+ */
+ public static final String ZEN_PRIORITY_ALLOW_LIGHTS = "zen_priority_allow_lights";
+
+ /** @hide */
+ public static final Validator ZEN_PRIORITY_ALLOW_LIGHTS_VALIDATOR = sBooleanValidator;
+
+ /**
+ * Display style of AM/PM next to clock in status bar
+ * 0: Normal display (Eclair stock)
+ * 1: Small display (Froyo stock)
+ * 2: No display (Gingerbread/ICS stock)
+ * default: 2
+ */
+ public static final String STATUS_BAR_AM_PM = "status_bar_am_pm";
+
+ /** @hide */
+ public static final Validator STATUS_BAR_AM_PM_VALIDATOR =
+ new InclusiveIntegerRangeValidator(0, 2);
+
+ /**
+ * Display style of the status bar battery information
+ * 0: Display the battery an icon in portrait mode
+ * 2: Display the battery as a circle
+ * 4: Hide the battery status information
+ * 5: Display the battery an icon in landscape mode
+ * 6: Display the battery as plain text
+ * default: 0
+ */
+ public static final String STATUS_BAR_BATTERY_STYLE = "status_bar_battery_style";
+
+ /** @hide */
+ public static final Validator STATUS_BAR_BATTERY_STYLE_VALIDATOR =
+ new DiscreteValueValidator(new String[] {"0", "2", "4", "5", "6"});
+
+ /**
+ * Status bar battery %
+ * 0: Hide the battery percentage
+ * 1: Display the battery percentage inside the icon
+ * 2: Display the battery percentage next to the icon
+ */
+ public static final String STATUS_BAR_SHOW_BATTERY_PERCENT =
+ "status_bar_show_battery_percent";
+
+ /** @hide */
+ public static final Validator STATUS_BAR_SHOW_BATTERY_PERCENT_VALIDATOR =
+ new InclusiveIntegerRangeValidator(0, 2);
+
+ /**
+ * Whether the phone ringtone should be played in an increasing manner
+ * 0 = 0ff, 1 = on
+ */
+ public static final String INCREASING_RING = "increasing_ring";
+
+ /** @hide */
+ public static final Validator INCREASING_RING_VALIDATOR = sBooleanValidator;
+
+ /**
+ * Start volume fraction for increasing ring volume
+ */
+ public static final String INCREASING_RING_START_VOLUME = "increasing_ring_start_vol";
+
+ /** @hide */
+ public static final Validator INCREASING_RING_START_VOLUME_VALIDATOR =
+ new InclusiveFloatRangeValidator(0, 1);
+
+ /**
+ * Ramp up time (seconds) for increasing ring
+ */
+ public static final String INCREASING_RING_RAMP_UP_TIME = "increasing_ring_ramp_up_time";
+
+ /** @hide */
+ public static final Validator INCREASING_RING_RAMP_UP_TIME_VALIDATOR =
+ new InclusiveIntegerRangeValidator(5, 60);
+
+ /**
+ * Volume Adjust Sounds Enable, This is the noise made when using volume hard buttons
+ * Defaults to 1 - sounds enabled
+ */
+ public static final String VOLUME_ADJUST_SOUNDS_ENABLED = "volume_adjust_sounds_enabled";
+
+ /** @hide */
+ public static final Validator VOLUME_ADJUST_SOUNDS_ENABLED_VALIDATOR =
+ sBooleanValidator;
+
+ /**
+ * Navigation controls to Use
+ */
+ public static final String NAV_BUTTONS = "nav_buttons";
+
+ /** @hide */
+ public static final Validator NAV_BUTTONS_VALIDATOR =
+ new DelimitedListValidator(new String[] {"empty", "home", "back", "search",
+ "recent", "menu0", "menu1", "menu2", "dpad_left", "dpad_right"}, "|", true);
+
+ /**
+ * Volume key controls ringtone or media sound stream
+ */
+ public static final String VOLUME_KEYS_CONTROL_RING_STREAM =
+ "volume_keys_control_ring_stream";
+
+ /** @hide */
+ public static final Validator VOLUME_KEYS_CONTROL_RING_STREAM_VALIDATOR =
+ sBooleanValidator;
+
+ /**
+ * boolean value. toggles using arrow key locations on nav bar
+ * as left and right dpad keys
+ */
+ public static final String NAVIGATION_BAR_MENU_ARROW_KEYS = "navigation_bar_menu_arrow_keys";
+
+ /** @hide */
+ public static final Validator NAVIGATION_BAR_MENU_ARROW_KEYS_VALIDATOR =
+ sBooleanValidator;
+
+ /**
+ * Action to perform when the home key is long-pressed.
+ * (Default can be configured via config_longPressOnHomeBehavior)
+ * 0 - Nothing
+ * 1 - Menu
+ * 2 - App-switch
+ * 3 - Search
+ * 4 - Voice search
+ * 5 - In-app search
+ * 6 - Launch Camera
+ * 7 - Action Sleep
+ * 8 - Last app
+ */
+ public static final String KEY_HOME_LONG_PRESS_ACTION = "key_home_long_press_action";
+
+ /** @hide */
+ public static final Validator KEY_HOME_LONG_PRESS_ACTION_VALIDATOR =
+ new InclusiveIntegerRangeValidator(0, 8);
+
+ /**
+ * Action to perform when the home key is double-tapped.
+ * (Default can be configured via config_doubleTapOnHomeBehavior)
+ * (See KEY_HOME_LONG_PRESS_ACTION for valid values)
+ */
+ public static final String KEY_HOME_DOUBLE_TAP_ACTION = "key_home_double_tap_action";
+
+ /** @hide */
+ public static final Validator KEY_HOME_DOUBLE_TAP_ACTION_VALIDATOR =
+ new InclusiveIntegerRangeValidator(0, 8);
+
+ /**
+ * Whether to wake the screen with the back key, the value is boolean.
+ * 0 = 0ff, 1 = on
+ */
+ public static final String BACK_WAKE_SCREEN = "back_wake_screen";
+
+ /** @hide */
+ public static final Validator BACK_WAKE_SCREEN_VALIDATOR =
+ sBooleanValidator;
+
+ /**
+ * Whether to wake the screen with the menu key, the value is boolean.
+ * 0 = 0ff, 1 = on
+ */
+ public static final String MENU_WAKE_SCREEN = "menu_wake_screen";
+
+ /** @hide */
+ public static final Validator MENU_WAKE_SCREENN_VALIDATOR =
+ sBooleanValidator;
+
+ /**
+ * Whether to wake the screen with the volume keys, the value is boolean.
+ * 0 = 0ff, 1 = on
+ */
+ public static final String VOLUME_WAKE_SCREEN = "volume_wake_screen";
+
+ /** @hide */
+ public static final Validator VOLUME_WAKE_SCREEN_VALIDATOR =
+ sBooleanValidator;
+
+ /**
+ * Action to perform when the menu key is pressed. (Default is 1)
+ * (See KEY_HOME_LONG_PRESS_ACTION for valid values)
+ */
+ public static final String KEY_MENU_ACTION = "key_menu_action";
+
+ /** @hide */
+ public static final Validator KEY_MENU_ACTION_VALIDATOR =
+ new InclusiveIntegerRangeValidator(0, 8);
+
+ /**
+ * Action to perform when the menu key is long-pressed.
+ * (Default is 0 on devices with a search key, 3 on devices without)
+ * (See KEY_HOME_LONG_PRESS_ACTION for valid values)
+ */
+ public static final String KEY_MENU_LONG_PRESS_ACTION = "key_menu_long_press_action";
+
+ /** @hide */
+ public static final Validator KEY_MENU_LONG_PRESS_ACTION_VALIDATOR =
+ new InclusiveIntegerRangeValidator(0, 8);
+
+ /**
+ * Action to perform when the assistant (search) key is pressed. (Default is 3)
+ * (See KEY_HOME_LONG_PRESS_ACTION for valid values)
+ */
+ public static final String KEY_ASSIST_ACTION = "key_assist_action";
+
+ /** @hide */
+ public static final Validator KEY_ASSIST_ACTION_VALIDATOR =
+ new InclusiveIntegerRangeValidator(0, 8);
+
+ /**
+ * Action to perform when the assistant (search) key is long-pressed. (Default is 4)
+ * (See KEY_HOME_LONG_PRESS_ACTION for valid values)
+ */
+ public static final String KEY_ASSIST_LONG_PRESS_ACTION = "key_assist_long_press_action";
+
+ /** @hide */
+ public static final Validator KEY_ASSIST_LONG_PRESS_ACTION_VALIDATOR =
+ new InclusiveIntegerRangeValidator(0, 8);
+
+ /**
+ * Action to perform when the app switch key is pressed. (Default is 2)
+ * (See KEY_HOME_LONG_PRESS_ACTION for valid values)
+ */
+ public static final String KEY_APP_SWITCH_ACTION = "key_app_switch_action";
+
+ /** @hide */
+ public static final Validator KEY_APP_SWITCH_ACTION_VALIDATOR =
+ new InclusiveIntegerRangeValidator(0, 8);
+
+ /**
+ * Action to perform when the app switch key is long-pressed. (Default is 0)
+ * (See KEY_HOME_LONG_PRESS_ACTION for valid values)
+ */
+ public static final String KEY_APP_SWITCH_LONG_PRESS_ACTION = "key_app_switch_long_press_action";
+
+ /** @hide */
+ public static final Validator KEY_APP_SWITCH_LONG_PRESS_ACTION_VALIDATOR =
+ new InclusiveIntegerRangeValidator(0, 8);
+
+ /**
+ * Whether to wake the screen with the home key, the value is boolean.
+ * 0 = 0ff, 1 = on
+ */
+ public static final String HOME_WAKE_SCREEN = "home_wake_screen";
+
+ /** @hide */
+ public static final Validator HOME_WAKE_SCREEN_VALIDATOR =
+ sBooleanValidator;
+
+ /**
+ * Whether to wake the screen with the assist key, the value is boolean.
+ * 0 = 0ff, 1 = on
+ */
+ public static final String ASSIST_WAKE_SCREEN = "assist_wake_screen";
+
+ /** @hide */
+ public static final Validator ASSIST_WAKE_SCREEN_VALIDATOR =
+ sBooleanValidator;
+
+ /**
+ * Whether to wake the screen with the app switch key, the value is boolean.
+ * 0 = 0ff, 1 = on
+ */
+ public static final String APP_SWITCH_WAKE_SCREEN = "app_switch_wake_screen";
+
+ /** @hide */
+ public static final Validator APP_SWITCH_WAKE_SCREEN_VALIDATOR =
+ sBooleanValidator;
+
+ /**
+ * Whether to wake the screen with the camera key half-press.
+ * 0 = 0ff, 1 = on
+ */
+ public static final String CAMERA_WAKE_SCREEN = "camera_wake_screen";
+
+ /** @hide */
+ public static final Validator CAMERA_WAKE_SCREEN_VALIDATOR =
+ sBooleanValidator;
+
+ /**
+ * Whether or not to send device back to sleep if Camera button is released ("Peek")
+ * 0 = 0ff, 1 = on
+ */
+ public static final String CAMERA_SLEEP_ON_RELEASE = "camera_sleep_on_release";
+
+ /** @hide */
+ public static final Validator CAMERA_SLEEP_ON_RELEASE_VALIDATOR =
+ sBooleanValidator;
+
+ /**
+ * Whether to launch secure camera app when key is longpressed
+ * 0 = 0ff, 1 = on
+ */
+ public static final String CAMERA_LAUNCH = "camera_launch";
+
+ /** @hide */
+ public static final Validator CAMERA_LAUNCH_VALIDATOR =
+ sBooleanValidator;
+
+ /**
+ * Swap volume buttons when the screen is rotated
+ * 0 - Disabled
+ * 1 - Enabled (screen is rotated by 90 or 180 degrees: phone, hybrid)
+ * 2 - Enabled (screen is rotated by 180 or 270 degrees: tablet)
+ */
+ public static final String SWAP_VOLUME_KEYS_ON_ROTATION = "swap_volume_keys_on_rotation";
+
+ /** @hide */
+ public static final Validator SWAP_VOLUME_KEYS_ON_ROTATION_VALIDATOR =
+ new InclusiveIntegerRangeValidator(0, 2);
+
+ /**
+ * Whether the battery light should be enabled (if hardware supports it)
+ * The value is boolean (1 or 0).
+ */
+ public static final String BATTERY_LIGHT_ENABLED = "battery_light_enabled";
+
+ /** @hide */
+ public static final Validator BATTERY_LIGHT_ENABLED_VALIDATOR =
+ sBooleanValidator;
+
+ /**
+ * Whether the battery LED should repeatedly flash when the battery is low
+ * on charge. The value is boolean (1 or 0).
+ */
+ public static final String BATTERY_LIGHT_PULSE = "battery_light_pulse";
+
+ /** @hide */
+ public static final Validator BATTERY_LIGHT_PULSE_VALIDATOR =
+ sBooleanValidator;
+
+ /**
+ * What color to use for the battery LED while charging - low
+ */
+ public static final String BATTERY_LIGHT_LOW_COLOR = "battery_light_low_color";
+
+ /** @hide */
+ public static final Validator BATTERY_LIGHT_LOW_COLOR_VALIDATOR =
+ sColorValidator;
+
+ /**
+ * What color to use for the battery LED while charging - medium
+ */
+ public static final String BATTERY_LIGHT_MEDIUM_COLOR = "battery_light_medium_color";
+
+ /** @hide */
+ public static final Validator BATTERY_LIGHT_MEDIUM_COLOR_VALIDATOR =
+ sColorValidator;
+
+ /**
+ * What color to use for the battery LED while charging - full
+ */
+ public static final String BATTERY_LIGHT_FULL_COLOR = "battery_light_full_color";
+
+ /** @hide */
+ public static final Validator BATTERY_LIGHT_FULL_COLOR_VALIDATOR =
+ sColorValidator;
+
+ /**
+ * Sprint MWI Quirk: Show message wait indicator notifications
+ * @hide
+ */
+ public static final String ENABLE_MWI_NOTIFICATION = "enable_mwi_notification";
+
+ /** @hide */
+ public static final Validator ENABLE_MWI_NOTIFICATION_VALIDATOR =
+ sBooleanValidator;
+
+ /**
+ * Check the proximity sensor during wakeup
+ * 0 = 0ff, 1 = on
+ */
+ public static final String PROXIMITY_ON_WAKE = "proximity_on_wake";
+
+ /** @hide */
+ public static final Validator PROXIMITY_ON_WAKE_VALIDATOR =
+ sBooleanValidator;
+
+ /**
+ * Enable looking up of phone numbers of nearby places
+ * 0 = 0ff, 1 = on
+ */
+ public static final String ENABLE_FORWARD_LOOKUP = "enable_forward_lookup";
+
+ /** @hide */
+ public static final Validator ENABLE_FORWARD_LOOKUP_VALIDATOR =
+ sBooleanValidator;
+
+ /**
+ * Enable looking up of phone numbers of people
+ * 0 = 0ff, 1 = on
+ */
+ public static final String ENABLE_PEOPLE_LOOKUP = "enable_people_lookup";
+
+ /** @hide */
+ public static final Validator ENABLE_PEOPLE_LOOKUP_VALIDATOR =
+ sBooleanValidator;
+
+ /**
+ * Enable looking up of information of phone numbers not in the contacts
+ * 0 = 0ff, 1 = on
+ */
+ public static final String ENABLE_REVERSE_LOOKUP = "enable_reverse_lookup";
+
+ /** @hide */
+ public static final Validator ENABLE_REVERSE_LOOKUP_VALIDATOR =
+ sBooleanValidator;
+
+ /**
+ * The forward lookup provider to be utilized by the Dialer
+ */
+ public static final String FORWARD_LOOKUP_PROVIDER = "forward_lookup_provider";
+
+ /** @hide */
+ public static final Validator FORWARD_LOOKUP_PROVIDER_VALIDATOR = sAlwaysTrueValidator;
+
+ /**
+ * The people lookup provider to be utilized by the Dialer
+ */
+ public static final String PEOPLE_LOOKUP_PROVIDER = "people_lookup_provider";
+
+ /** @hide */
+ public static final Validator PEOPLE_LOOKUP_PROVIDER_VALIDATOR = sAlwaysTrueValidator;
+
+ /**
+ * The reverse lookup provider to be utilized by the Dialer
+ */
+ public static final String REVERSE_LOOKUP_PROVIDER = "reverse_lookup_provider";
+
+ /** @hide */
+ public static final Validator REVERSE_LOOKUP_PROVIDER_VALIDATOR = sAlwaysTrueValidator;
+
+ /**
+ * The OpenCNAM paid account ID to be utilized by the Dialer
+ */
+ public static final String DIALER_OPENCNAM_ACCOUNT_SID = "dialer_opencnam_account_sid";
+
+ /** @hide */
+ public static final Validator DIALER_OPENCNAM_ACCOUNT_SID_VALIDATOR =
+ sAlwaysTrueValidator;
+
+ /**
+ * The OpenCNAM authentication token to be utilized by the Dialer
+ */
+ public static final String DIALER_OPENCNAM_AUTH_TOKEN = "dialer_opencnam_auth_token";
+
+ /** @hide */
+ public static final Validator DIALER_OPENCNAM_AUTH_TOKEN_VALIDATOR =
+ sAlwaysTrueValidator;
+
+ /**
+ * Color temperature of the display during the day
+ */
+ public static final String DISPLAY_TEMPERATURE_DAY = "display_temperature_day";
+
+ /** @hide */
+ public static final Validator DISPLAY_TEMPERATURE_DAY_VALIDATOR =
+ new InclusiveIntegerRangeValidator(1000, 10000);
+
+ /**
+ * Color temperature of the display at night
+ */
+ public static final String DISPLAY_TEMPERATURE_NIGHT = "display_temperature_night";
+
+ /** @hide */
+ public static final Validator DISPLAY_TEMPERATURE_NIGHT_VALIDATOR =
+ new InclusiveIntegerRangeValidator(1000, 10000);
+
+ /**
+ * Display color temperature adjustment mode, one of DAY (default), NIGHT, or AUTO.
+ */
+ public static final String DISPLAY_TEMPERATURE_MODE = "display_temperature_mode";
+
+ /** @hide */
+ public static final Validator DISPLAY_TEMPERATURE_MODE_VALIDATOR =
+ new InclusiveIntegerRangeValidator(0, 4);
+
+ /**
+ * Automatic outdoor mode
+ * 0 = 0ff, 1 = on
+ */
+ public static final String DISPLAY_AUTO_OUTDOOR_MODE = "display_auto_outdoor_mode";
+
+ /** @hide */
+ public static final Validator DISPLAY_AUTO_OUTDOOR_MODE_VALIDATOR =
+ sBooleanValidator;
+
+ /**
+ * Use display power saving features such as CABC or CABL
+ * 0 = 0ff, 1 = on
+ */
+ public static final String DISPLAY_LOW_POWER = "display_low_power";
+
+ /** @hide */
+ public static final Validator DISPLAY_LOW_POWER_VALIDATOR =
+ sBooleanValidator;
+
+ /**
+ * Use color enhancement feature of display
+ * 0 = 0ff, 1 = on
+ */
+ public static final String DISPLAY_COLOR_ENHANCE = "display_color_enhance";
+
+ /** @hide */
+ public static final Validator DISPLAY_COLOR_ENHANCE_VALIDATOR =
+ sBooleanValidator;
+
+ /**
+ * Manual display color adjustments (RGB values as floats, separated by spaces)
+ */
+ public static final String DISPLAY_COLOR_ADJUSTMENT = "display_color_adjustment";
+
+ /** @hide */
+ public static final Validator DISPLAY_COLOR_ADJUSTMENT_VALIDATOR =
+ new Validator() {
+ @Override
+ public boolean validate(String value) {
+ String[] colorAdjustment = value == null ?
+ null : value.split(" ");
+ if (colorAdjustment != null && colorAdjustment.length != 3) {
+ return false;
+ }
+ Validator floatValidator = new InclusiveFloatRangeValidator(0, 1);
+ return colorAdjustment == null ||
+ floatValidator.validate(colorAdjustment[0]) &&
+ floatValidator.validate(colorAdjustment[1]) &&
+ floatValidator.validate(colorAdjustment[2]);
+ }
+ };
+
+ /**
+ * Did we tell about how they can stop breaking their eyes?
+ * @hide
+ */
+ public static final String LIVE_DISPLAY_HINTED = "live_display_hinted";
+
+ /** @hide */
+ public static final Validator LIVE_DISPLAY_HINTED_VALIDATOR =
+ sBooleanValidator;
+
+ /**
+ * Enable statusbar double tap gesture on to put device to sleep
+ * 0 = 0ff, 1 = on
+ */
+ public static final String DOUBLE_TAP_SLEEP_GESTURE = "double_tap_sleep_gesture";
+
+ /** @hide */
+ public static final Validator DOUBLE_TAP_SLEEP_GESTURE_VALIDATOR =
+ sBooleanValidator;
+
+ /**
+ * Boolean value on whether to show weather in the statusbar
+ * 0 = 0ff, 1 = on
+ */
+ public static final String STATUS_BAR_SHOW_WEATHER = "status_bar_show_weather";
+
+ /** @hide */
+ public static final Validator STATUS_BAR_SHOW_WEATHER_VALIDATOR =
+ sBooleanValidator;
+
+ /**
+ * Show search bar in recents
+ * 0 = 0ff, 1 = on
+ */
+ public static final String RECENTS_SHOW_SEARCH_BAR = "recents_show_search_bar";
+
+ /** @hide */
+ public static final Validator RECENTS_SHOW_SEARCH_BAR_VALIDATOR =
+ sBooleanValidator;
+
+ /**
+ * Whether navigation bar is placed on the left side in landscape mode
+ * 0 = 0ff, 1 = on
+ */
+ public static final String NAVBAR_LEFT_IN_LANDSCAPE = "navigation_bar_left";
+
+ /** @hide */
+ public static final Validator NAVBAR_LEFT_IN_LANDSCAPE_VALIDATOR =
+ sBooleanValidator;
+
+ /**
+ * Locale for secondary overlay on dialer for t9 search input
+ */
+ public static final String T9_SEARCH_INPUT_LOCALE = "t9_search_input_locale";
+
+ /** @hide */
+ public static final Validator T9_SEARCH_INPUT_LOCALE_VALIDATOR =
+ new Validator() {
+ @Override
+ public boolean validate(String value) {
+ final Locale locale = new Locale(value);
+ return ArrayUtils.contains(Locale.getAvailableLocales(), locale);
+ }
+ };
+
+ /**
+ * If all file types can be accepted over Bluetooth OBEX.
+ * 0 = 0ff, 1 = on
+ */
+ public static final String BLUETOOTH_ACCEPT_ALL_FILES =
+ "bluetooth_accept_all_files";
+
+ /** @hide */
+ public static final Validator BLUETOOTH_ACCEPT_ALL_FILES_VALIDATOR =
+ sBooleanValidator;
+
+ /**
+ * Whether to scramble a pin unlock layout
+ * 0 = 0ff, 1 = on
+ */
+ public static final String LOCKSCREEN_PIN_SCRAMBLE_LAYOUT =
+ "lockscreen_scramble_pin_layout";
+
+ /** @hide */
+ public static final Validator LOCKSCREEN_PIN_SCRAMBLE_LAYOUT_VALIDATOR =
+ sBooleanValidator;
+
+ /**
+ * Whether to show the alarm clock icon in the status bar.
+ * 0 = 0ff, 1 = on
+ */
+ public static final String SHOW_ALARM_ICON = "show_alarm_icon";
+
+ /** @hide */
+ public static final Validator SHOW_ALARM_ICON_VALIDATOR =
+ sBooleanValidator;
+
+ /**
+ * Whether to show the IME switcher in the status bar
+ * 0 = 0ff, 1 = on
+ */
+ public static final String STATUS_BAR_IME_SWITCHER = "status_bar_ime_switcher";
+
+ /** @hide */
+ public static final Validator STATUS_BAR_IME_SWITCHER_VALIDATOR =
+ sBooleanValidator;
+
+ /**
+ * Whether to allow one finger quick settings expansion on the side of the statusbar.
+ * 0 = 0ff, 1 = right, 2 = left
+ */
+ public static final String STATUS_BAR_QUICK_QS_PULLDOWN = "qs_quick_pulldown";
+
+ /** @hide */
+ public static final Validator STATUS_BAR_QUICK_QS_PULLDOWN_VALIDATOR =
+ new InclusiveIntegerRangeValidator(0, 2);
+
+ /**
+ * Whether to show the brightness slider in quick settings panel.
+ * 0 = 0ff, 1 = on
+ */
+ public static final String QS_SHOW_BRIGHTNESS_SLIDER = "qs_show_brightness_slider";
+
+ /** @hide */
+ public static final Validator QS_SHOW_BRIGHTNESS_SLIDER_VALIDATOR =
+ sBooleanValidator;
+
+ /**
+ * Whether to control brightness from status bar
+ * 0 = 0ff, 1 = on
+ */
+ public static final String STATUS_BAR_BRIGHTNESS_CONTROL = "status_bar_brightness_control";
+
+ /** @hide */
+ public static final Validator STATUS_BAR_BRIGHTNESS_CONTROL_VALIDATOR =
+ sBooleanValidator;
+
+ /**
+ * Whether or not volume button music controls should be enabled to seek media tracks
+ * 0 = 0ff, 1 = on
+ */
+ public static final String VOLBTN_MUSIC_CONTROLS = "volbtn_music_controls";
+
+ /** @hide */
+ public static final Validator VOLBTN_MUSIC_CONTROLS_VALIDATOR =
+ sBooleanValidator;
+
+ /**
+ * Use EdgeGesture Service for system gestures in PhoneWindowManager
+ * 0 = 0ff, 1 = on
+ */
+ public static final String USE_EDGE_SERVICE_FOR_GESTURES = "edge_service_for_gestures";
+
+ /** @hide */
+ public static final Validator USE_EDGE_SERVICE_FOR_GESTURES_VALIDATOR =
+ sBooleanValidator;
+
+ /**
+ * Show the pending notification counts as overlays on the status bar
+ */
+ public static final String STATUS_BAR_NOTIF_COUNT = "status_bar_notif_count";
+
+ /** @hide */
+ public static final Validator STATUS_BAR_NOTIF_COUNT_VALIDATOR =
+ sBooleanValidator;
+
+ /**
+ * Call recording format value
+ * 0: AMR_WB
+ * 1: MPEG_4
+ * Default: 0
+ */
+ public static final String CALL_RECORDING_FORMAT = "call_recording_format";
+
+ /** @hide */
+ public static final Validator CALL_RECORDING_FORMAT_VALIDATOR =
+ new InclusiveIntegerRangeValidator(0, 1);
+
+ /**
+ * Contains the notifications light maximum brightness to use.
+ * Values range from 1 to 255
+ */
+ public static final String NOTIFICATION_LIGHT_BRIGHTNESS_LEVEL =
+ "notification_light_brightness_level";
+
+ /** @hide */
+ public static final Validator NOTIFICATION_LIGHT_BRIGHTNESS_LEVEL_VALIDATOR =
+ new InclusiveIntegerRangeValidator(1, 255);
+
+ /**
+ * Whether to use the all the LEDs for the notifications or just one.
+ * 0 = 0ff, 1 = on
+ */
+ public static final String NOTIFICATION_LIGHT_MULTIPLE_LEDS_ENABLE =
+ "notification_light_multiple_leds_enable";
+
+ /** @hide */
+ public static final Validator NOTIFICATION_LIGHT_MULTIPLE_LEDS_ENABLE_VALIDATOR =
+ sBooleanValidator;
+
+ /**
+ * Whether to allow notifications with the screen on or DayDreams.
+ * The value is boolean (1 or 0). Default will always be false.
+ */
+ public static final String NOTIFICATION_LIGHT_SCREEN_ON =
+ "notification_light_screen_on_enable";
+
+ /** @hide */
+ public static final Validator NOTIFICATION_LIGHT_SCREEN_ON_VALIDATOR =
+ sBooleanValidator;
+
+ /**
+ * What color to use for the notification LED by default
+ */
+ public static final String NOTIFICATION_LIGHT_PULSE_DEFAULT_COLOR =
+ "notification_light_pulse_default_color";
+
+ /** @hide */
+ public static final Validator NOTIFICATION_LIGHT_PULSE_DEFAULT_COLOR_VALIDATOR =
+ sColorValidator;
+
+ /**
+ * How long to flash the notification LED by default
+ */
+ public static final String NOTIFICATION_LIGHT_PULSE_DEFAULT_LED_ON =
+ "notification_light_pulse_default_led_on";
+
+ /** @hide */
+ public static final Validator NOTIFICATION_LIGHT_PULSE_DEFAULT_LED_ON_VALIDATOR =
+ sNonNegativeIntegerValidator;
+
+ /**
+ * How long to wait between flashes for the notification LED by default
+ */
+ public static final String NOTIFICATION_LIGHT_PULSE_DEFAULT_LED_OFF =
+ "notification_light_pulse_default_led_off";
+
+ /** @hide */
+ public static final Validator NOTIFICATION_LIGHT_PULSE_DEFAULT_LED_OFF_VALIDATOR =
+ sNonNegativeIntegerValidator;
+
+ /**
+ * What color to use for the missed call notification LED
+ */
+ public static final String NOTIFICATION_LIGHT_PULSE_CALL_COLOR =
+ "notification_light_pulse_call_color";
+
+ /** @hide */
+ public static final Validator NOTIFICATION_LIGHT_PULSE_CALL_COLOR_VALIDATOR =
+ sColorValidator;
+
+ /**
+ * How long to flash the missed call notification LED
+ */
+ public static final String NOTIFICATION_LIGHT_PULSE_CALL_LED_ON =
+ "notification_light_pulse_call_led_on";
+
+ /** @hide */
+ public static final Validator NOTIFICATION_LIGHT_PULSE_CALL_LED_ON_VALIDATOR =
+ sNonNegativeIntegerValidator;
+
+ /**
+ * How long to wait between flashes for the missed call notification LED
+ */
+ public static final String NOTIFICATION_LIGHT_PULSE_CALL_LED_OFF =
+ "notification_light_pulse_call_led_off";
+
+ /** @hide */
+ public static final Validator NOTIFICATION_LIGHT_PULSE_CALL_LED_OFF_VALIDATOR =
+ sNonNegativeIntegerValidator;
+
+ /**
+ * What color to use for the voicemail notification LED
+ */
+ public static final String NOTIFICATION_LIGHT_PULSE_VMAIL_COLOR =
+ "notification_light_pulse_vmail_color";
+
+ /** @hide */
+ public static final Validator NOTIFICATION_LIGHT_PULSE_VMAIL_COLOR_VALIDATOR =
+ sColorValidator;
+
+ /**
+ * How long to flash the voicemail notification LED
+ */
+ public static final String NOTIFICATION_LIGHT_PULSE_VMAIL_LED_ON =
+ "notification_light_pulse_vmail_led_on";
+
+ /** @hide */
+ public static final Validator NOTIFICATION_LIGHT_PULSE_VMAIL_LED_ON_VALIDATOR =
+ sNonNegativeIntegerValidator;
+
+ /**
+ * How long to wait between flashes for the voicemail notification LED
+ */
+ public static final String NOTIFICATION_LIGHT_PULSE_VMAIL_LED_OFF =
+ "notification_light_pulse_vmail_led_off";
+
+ /** @hide */
+ public static final Validator NOTIFICATION_LIGHT_PULSE_VMAIL_LED_OFF_VALIDATOR =
+ sNonNegativeIntegerValidator;
+
+ /**
+ * Whether to use the custom LED values for the notification pulse LED.
+ * 0 = 0ff, 1 = on
+ */
+ public static final String NOTIFICATION_LIGHT_PULSE_CUSTOM_ENABLE =
+ "notification_light_pulse_custom_enable";
+
+ /** @hide */
+ public static final Validator NOTIFICATION_LIGHT_PULSE_CUSTOM_ENABLE_VALIDATOR =
+ sBooleanValidator;
+
+ /**
+ * Which custom LED values to use for the notification pulse LED.
+ */
+ public static final String NOTIFICATION_LIGHT_PULSE_CUSTOM_VALUES =
+ "notification_light_pulse_custom_values";
+
+ /** @hide */
+ public static final Validator NOTIFICATION_LIGHT_PULSE_CUSTOM_VALUES_VALIDATOR =
+ new Validator() {
+ @Override
+ public boolean validate(String value) {
+ if (TextUtils.isEmpty(value)) {
+ return true;
+ }
+
+ for (String packageValuesString : value.split("\\|")) {
+ String[] packageValues = packageValuesString.split("=");
+ if (packageValues.length != 2) {
+ if (LOCAL_LOGV) {
+ Log.d(TAG, "Incorrect number of package values: "
+ + packageValues.length);
+ }
+ return false;
+ }
+ String packageName = packageValues[0];
+ if (TextUtils.isEmpty(packageName)) {
+ if (LOCAL_LOGV) Log.d(TAG, "Empty package name");
+ return false;
+ }
+ String[] values = packageValues[1].split(";");
+ if (values.length != 3) {
+ if (LOCAL_LOGV) {
+ Log.d(TAG, "Incorrect number of values: " + values.length);
+ }
+ return false;
+ }
+ try {
+ // values[0] is LED color
+ if (!sColorValidator.validate(values[0])) {
+ if (LOCAL_LOGV) {
+ Log.d(TAG, "Invalid LED color (" + values[0] + ") for "
+ + packageName);
+ }
+ return false;
+ }
+ // values[1] is the LED on time and should be non-negative
+ if (!sNonNegativeIntegerValidator.validate(values[1])) {
+ if (LOCAL_LOGV) {
+ Log.d(TAG, "Invalid LED on time (" + values[1] + ") for "
+ + packageName);
+ }
+ return false;
+ }
+ // values[1] is the LED off time and should be non-negative
+ if (!sNonNegativeIntegerValidator.validate(values[2])) {
+ if (LOCAL_LOGV) {
+ Log.d(TAG, "Invalid LED off time (" + values[2] + ") for "
+ + packageName);
+ }
+ return false;
+ }
+ } catch (NumberFormatException e) {
+ return false;
+ }
+ }
+ // if we make it all the way through then the data is considered valid
+ return true;
+ }
+ };
+
+ /**
+ * Whether we automatically generate notification LED colors or just
+ * use the boring default.
+ *
+ * @hide
+ */
+ public static final String NOTIFICATION_LIGHT_COLOR_AUTO =
+ "notification_light_color_auto";
+
+ /** @hide */
+ public static final Validator NOTIFICATION_LIGHT_COLOR_AUTO_VALIDATOR =
+ sBooleanValidator;
+
+ /**
+ * Whether or not to launch default music player when headset is connected
+ */
+ public static final String HEADSET_CONNECT_PLAYER = "headset_connect_player";
+
+ /** @hide */
+ public static final Validator HEADSET_CONNECT_PLAYER_VALIDATOR = sBooleanValidator;
+
+ /**
+ * Whether or not to vibrate when a touchscreen gesture is detected
+ */
+ public static final String TOUCHSCREEN_GESTURE_HAPTIC_FEEDBACK =
+ "touchscreen_gesture_haptic_feedback";
+
+ /** @hide */
+ public static final Validator TOUCHSCREEN_GESTURE_HAPTIC_FEEDBACK_VALIDATOR =
+ sBooleanValidator;
+
+ /**
+ * I can haz more bukkits
+ * @hide
+ */
+ public static final String __MAGICAL_TEST_PASSING_ENABLER =
+ "___magical_test_passing_enabler";
+
+ /**
+ * Don't
+ * @hide
+ * me bro
+ */
+ public static final Validator __MAGICAL_TEST_PASSING_ENABLER_VALIDATOR =
+ sBooleanValidator;
+
+ /**
+ * @hide
+ */
+ public static final String[] LEGACY_SYSTEM_SETTINGS = new String[]{
+ CMSettings.System.NAV_BUTTONS,
+ CMSettings.System.KEY_HOME_LONG_PRESS_ACTION,
+ CMSettings.System.KEY_HOME_DOUBLE_TAP_ACTION,
+ CMSettings.System.BACK_WAKE_SCREEN,
+ CMSettings.System.MENU_WAKE_SCREEN,
+ CMSettings.System.VOLUME_WAKE_SCREEN,
+ CMSettings.System.KEY_MENU_ACTION,
+ CMSettings.System.KEY_MENU_LONG_PRESS_ACTION,
+ CMSettings.System.KEY_ASSIST_ACTION,
+ CMSettings.System.KEY_ASSIST_LONG_PRESS_ACTION,
+ CMSettings.System.KEY_APP_SWITCH_ACTION,
+ CMSettings.System.KEY_APP_SWITCH_LONG_PRESS_ACTION,
+ CMSettings.System.HOME_WAKE_SCREEN,
+ CMSettings.System.ASSIST_WAKE_SCREEN,
+ CMSettings.System.APP_SWITCH_WAKE_SCREEN,
+ CMSettings.System.CAMERA_WAKE_SCREEN,
+ CMSettings.System.CAMERA_SLEEP_ON_RELEASE,
+ CMSettings.System.CAMERA_LAUNCH,
+ CMSettings.System.SWAP_VOLUME_KEYS_ON_ROTATION,
+ CMSettings.System.BATTERY_LIGHT_ENABLED,
+ CMSettings.System.BATTERY_LIGHT_PULSE,
+ CMSettings.System.BATTERY_LIGHT_LOW_COLOR,
+ CMSettings.System.BATTERY_LIGHT_MEDIUM_COLOR,
+ CMSettings.System.BATTERY_LIGHT_FULL_COLOR,
+ CMSettings.System.ENABLE_MWI_NOTIFICATION,
+ CMSettings.System.PROXIMITY_ON_WAKE,
+ CMSettings.System.ENABLE_FORWARD_LOOKUP,
+ CMSettings.System.ENABLE_PEOPLE_LOOKUP,
+ CMSettings.System.ENABLE_REVERSE_LOOKUP,
+ CMSettings.System.FORWARD_LOOKUP_PROVIDER,
+ CMSettings.System.PEOPLE_LOOKUP_PROVIDER,
+ CMSettings.System.REVERSE_LOOKUP_PROVIDER,
+ CMSettings.System.DIALER_OPENCNAM_ACCOUNT_SID,
+ CMSettings.System.DIALER_OPENCNAM_AUTH_TOKEN,
+ CMSettings.System.DISPLAY_TEMPERATURE_DAY,
+ CMSettings.System.DISPLAY_TEMPERATURE_NIGHT,
+ CMSettings.System.DISPLAY_TEMPERATURE_MODE,
+ CMSettings.System.DISPLAY_AUTO_OUTDOOR_MODE,
+ CMSettings.System.DISPLAY_LOW_POWER,
+ CMSettings.System.DISPLAY_COLOR_ENHANCE,
+ CMSettings.System.DISPLAY_COLOR_ADJUSTMENT,
+ CMSettings.System.LIVE_DISPLAY_HINTED,
+ CMSettings.System.DOUBLE_TAP_SLEEP_GESTURE,
+ CMSettings.System.STATUS_BAR_SHOW_WEATHER,
+ CMSettings.System.RECENTS_SHOW_SEARCH_BAR,
+ CMSettings.System.NAVBAR_LEFT_IN_LANDSCAPE,
+ CMSettings.System.T9_SEARCH_INPUT_LOCALE,
+ CMSettings.System.BLUETOOTH_ACCEPT_ALL_FILES,
+ CMSettings.System.LOCKSCREEN_PIN_SCRAMBLE_LAYOUT,
+ CMSettings.System.SHOW_ALARM_ICON,
+ CMSettings.System.STATUS_BAR_IME_SWITCHER,
+ CMSettings.System.QS_SHOW_BRIGHTNESS_SLIDER,
+ CMSettings.System.STATUS_BAR_BRIGHTNESS_CONTROL,
+ CMSettings.System.VOLBTN_MUSIC_CONTROLS,
+ CMSettings.System.SWAP_VOLUME_KEYS_ON_ROTATION,
+ CMSettings.System.USE_EDGE_SERVICE_FOR_GESTURES,
+ CMSettings.System.STATUS_BAR_NOTIF_COUNT,
+ CMSettings.System.CALL_RECORDING_FORMAT,
+ CMSettings.System.NOTIFICATION_LIGHT_BRIGHTNESS_LEVEL,
+ CMSettings.System.NOTIFICATION_LIGHT_MULTIPLE_LEDS_ENABLE,
+ CMSettings.System.NOTIFICATION_LIGHT_SCREEN_ON,
+ CMSettings.System.NOTIFICATION_LIGHT_PULSE_DEFAULT_COLOR,
+ CMSettings.System.NOTIFICATION_LIGHT_PULSE_DEFAULT_LED_ON,
+ CMSettings.System.NOTIFICATION_LIGHT_PULSE_DEFAULT_LED_OFF,
+ CMSettings.System.NOTIFICATION_LIGHT_PULSE_CALL_COLOR,
+ CMSettings.System.NOTIFICATION_LIGHT_PULSE_CALL_LED_ON,
+ CMSettings.System.NOTIFICATION_LIGHT_PULSE_CALL_LED_OFF,
+ CMSettings.System.NOTIFICATION_LIGHT_PULSE_VMAIL_COLOR,
+ CMSettings.System.NOTIFICATION_LIGHT_PULSE_VMAIL_LED_ON,
+ CMSettings.System.NOTIFICATION_LIGHT_PULSE_VMAIL_LED_OFF,
+ CMSettings.System.NOTIFICATION_LIGHT_PULSE_CUSTOM_ENABLE,
+ CMSettings.System.NOTIFICATION_LIGHT_PULSE_CUSTOM_VALUES,
+ CMSettings.System.STATUS_BAR_QUICK_QS_PULLDOWN,
+ CMSettings.System.VOLUME_ADJUST_SOUNDS_ENABLED,
+ CMSettings.System.SYSTEM_PROFILES_ENABLED,
+ CMSettings.System.INCREASING_RING,
+ CMSettings.System.INCREASING_RING_START_VOLUME,
+ CMSettings.System.INCREASING_RING_RAMP_UP_TIME,
+ CMSettings.System.STATUS_BAR_CLOCK,
+ CMSettings.System.STATUS_BAR_AM_PM,
+ CMSettings.System.STATUS_BAR_BATTERY_STYLE,
+ CMSettings.System.STATUS_BAR_SHOW_BATTERY_PERCENT,
+ CMSettings.System.VOLUME_KEYS_CONTROL_RING_STREAM,
+ CMSettings.System.NAVIGATION_BAR_MENU_ARROW_KEYS,
+ CMSettings.System.HEADSET_CONNECT_PLAYER,
+ CMSettings.System.ZEN_ALLOW_LIGHTS,
+ CMSettings.System.TOUCHSCREEN_GESTURE_HAPTIC_FEEDBACK,
+ };
+
+ /**
+ * @hide
+ */
+ public static boolean isLegacySetting(String key) {
+ return ArrayUtils.contains(LEGACY_SYSTEM_SETTINGS, key);
+ }
+
+ /**
+ * @hide
+ */
+ public static boolean shouldInterceptSystemProvider(String key) {
+ return key.equals(System.SYSTEM_PROFILES_ENABLED);
+ }
+
+ /**
+ * Mapping of validators for all system settings. This map is used to validate both valid
+ * keys as well as validating the values for those keys.
+ *
+ * Note: Make sure if you add a new System setting you create a Validator for it and add
+ * it to this map.
+ *
+ * @hide
+ */
+ public static final Map<String, Validator> VALIDATORS =
+ new ArrayMap<String, Validator>();
+ static {
+ VALIDATORS.put(NOTIFICATION_PLAY_QUEUE, NOTIFICATION_PLAY_QUEUE_VALIDATOR);
+ VALIDATORS.put(HIGH_TOUCH_SENSITIVITY_ENABLE,
+ HIGH_TOUCH_SENSITIVITY_ENABLE_VALIDATOR);
+ VALIDATORS.put(SYSTEM_PROFILES_ENABLED, SYSTEM_PROFILES_ENABLED_VALIDATOR);
+ VALIDATORS.put(STATUS_BAR_CLOCK, STATUS_BAR_CLOCK_VALIDATOR);
+ VALIDATORS.put(STATUS_BAR_AM_PM, STATUS_BAR_AM_PM_VALIDATOR);
+ VALIDATORS.put(STATUS_BAR_BATTERY_STYLE, STATUS_BAR_BATTERY_STYLE_VALIDATOR);
+ VALIDATORS.put(STATUS_BAR_SHOW_BATTERY_PERCENT,
+ STATUS_BAR_SHOW_BATTERY_PERCENT_VALIDATOR);
+ VALIDATORS.put(INCREASING_RING, INCREASING_RING_VALIDATOR);
+ VALIDATORS.put(INCREASING_RING_START_VOLUME,
+ INCREASING_RING_START_VOLUME_VALIDATOR);
+ VALIDATORS.put(INCREASING_RING_RAMP_UP_TIME,
+ INCREASING_RING_RAMP_UP_TIME_VALIDATOR);
+ VALIDATORS.put(VOLUME_ADJUST_SOUNDS_ENABLED,
+ VOLUME_ADJUST_SOUNDS_ENABLED_VALIDATOR);
+ VALIDATORS.put(NAV_BUTTONS, NAV_BUTTONS_VALIDATOR);
+ VALIDATORS.put(VOLUME_KEYS_CONTROL_RING_STREAM,
+ VOLUME_KEYS_CONTROL_RING_STREAM_VALIDATOR);
+ VALIDATORS.put(NAVIGATION_BAR_MENU_ARROW_KEYS,
+ NAVIGATION_BAR_MENU_ARROW_KEYS_VALIDATOR);
+ VALIDATORS.put(KEY_HOME_LONG_PRESS_ACTION, KEY_HOME_LONG_PRESS_ACTION_VALIDATOR);
+ VALIDATORS.put(KEY_HOME_DOUBLE_TAP_ACTION, KEY_HOME_DOUBLE_TAP_ACTION_VALIDATOR);
+ VALIDATORS.put(BACK_WAKE_SCREEN, BACK_WAKE_SCREEN_VALIDATOR);
+ VALIDATORS.put(MENU_WAKE_SCREEN, MENU_WAKE_SCREENN_VALIDATOR);
+ VALIDATORS.put(VOLUME_WAKE_SCREEN, VOLUME_WAKE_SCREEN_VALIDATOR);
+ VALIDATORS.put(KEY_MENU_ACTION, KEY_MENU_ACTION_VALIDATOR);
+ VALIDATORS.put(KEY_MENU_LONG_PRESS_ACTION, KEY_MENU_LONG_PRESS_ACTION_VALIDATOR);
+ VALIDATORS.put(KEY_ASSIST_ACTION, KEY_ASSIST_ACTION_VALIDATOR);
+ VALIDATORS.put(KEY_ASSIST_LONG_PRESS_ACTION,
+ KEY_ASSIST_LONG_PRESS_ACTION_VALIDATOR);
+ VALIDATORS.put(KEY_APP_SWITCH_ACTION, KEY_APP_SWITCH_ACTION_VALIDATOR);
+ VALIDATORS.put(KEY_APP_SWITCH_LONG_PRESS_ACTION,
+ KEY_APP_SWITCH_LONG_PRESS_ACTION_VALIDATOR);
+ VALIDATORS.put(HOME_WAKE_SCREEN, HOME_WAKE_SCREEN_VALIDATOR);
+ VALIDATORS.put(ASSIST_WAKE_SCREEN, ASSIST_WAKE_SCREEN_VALIDATOR);
+ VALIDATORS.put(APP_SWITCH_WAKE_SCREEN, APP_SWITCH_WAKE_SCREEN_VALIDATOR);
+ VALIDATORS.put(CAMERA_WAKE_SCREEN, CAMERA_WAKE_SCREEN_VALIDATOR);
+ VALIDATORS.put(CAMERA_SLEEP_ON_RELEASE, CAMERA_SLEEP_ON_RELEASE_VALIDATOR);
+ VALIDATORS.put(CAMERA_LAUNCH, CAMERA_LAUNCH_VALIDATOR);
+ VALIDATORS.put(SWAP_VOLUME_KEYS_ON_ROTATION,
+ SWAP_VOLUME_KEYS_ON_ROTATION_VALIDATOR);
+ VALIDATORS.put(BATTERY_LIGHT_ENABLED, BATTERY_LIGHT_ENABLED_VALIDATOR);
+ VALIDATORS.put(BATTERY_LIGHT_PULSE, BATTERY_LIGHT_PULSE_VALIDATOR);
+ VALIDATORS.put(BATTERY_LIGHT_LOW_COLOR, BATTERY_LIGHT_LOW_COLOR_VALIDATOR);
+ VALIDATORS.put(BATTERY_LIGHT_MEDIUM_COLOR, BATTERY_LIGHT_MEDIUM_COLOR_VALIDATOR);
+ VALIDATORS.put(BATTERY_LIGHT_FULL_COLOR, BATTERY_LIGHT_FULL_COLOR_VALIDATOR);
+ VALIDATORS.put(ENABLE_MWI_NOTIFICATION, ENABLE_MWI_NOTIFICATION_VALIDATOR);
+ VALIDATORS.put(PROXIMITY_ON_WAKE, PROXIMITY_ON_WAKE_VALIDATOR);
+ VALIDATORS.put(ENABLE_FORWARD_LOOKUP, ENABLE_FORWARD_LOOKUP_VALIDATOR);
+ VALIDATORS.put(ENABLE_PEOPLE_LOOKUP, ENABLE_PEOPLE_LOOKUP_VALIDATOR);
+ VALIDATORS.put(ENABLE_REVERSE_LOOKUP, ENABLE_REVERSE_LOOKUP_VALIDATOR);
+ VALIDATORS.put(FORWARD_LOOKUP_PROVIDER, FORWARD_LOOKUP_PROVIDER_VALIDATOR);
+ VALIDATORS.put(PEOPLE_LOOKUP_PROVIDER, PEOPLE_LOOKUP_PROVIDER_VALIDATOR);
+ VALIDATORS.put(REVERSE_LOOKUP_PROVIDER, REVERSE_LOOKUP_PROVIDER_VALIDATOR);
+ VALIDATORS.put(DIALER_OPENCNAM_ACCOUNT_SID,
+ DIALER_OPENCNAM_ACCOUNT_SID_VALIDATOR);
+ VALIDATORS.put(DIALER_OPENCNAM_AUTH_TOKEN, DIALER_OPENCNAM_AUTH_TOKEN_VALIDATOR);
+ VALIDATORS.put(DISPLAY_TEMPERATURE_DAY, DISPLAY_TEMPERATURE_DAY_VALIDATOR);
+ VALIDATORS.put(DISPLAY_TEMPERATURE_NIGHT, DISPLAY_TEMPERATURE_NIGHT_VALIDATOR);
+ VALIDATORS.put(DISPLAY_TEMPERATURE_MODE, DISPLAY_TEMPERATURE_MODE_VALIDATOR);
+ VALIDATORS.put(DISPLAY_AUTO_OUTDOOR_MODE, DISPLAY_AUTO_OUTDOOR_MODE_VALIDATOR);
+ VALIDATORS.put(DISPLAY_LOW_POWER, DISPLAY_LOW_POWER_VALIDATOR);
+ VALIDATORS.put(DISPLAY_COLOR_ENHANCE, DISPLAY_COLOR_ENHANCE_VALIDATOR);
+ VALIDATORS.put(DISPLAY_COLOR_ADJUSTMENT, DISPLAY_COLOR_ADJUSTMENT_VALIDATOR);
+ VALIDATORS.put(LIVE_DISPLAY_HINTED, LIVE_DISPLAY_HINTED_VALIDATOR);
+ VALIDATORS.put(DOUBLE_TAP_SLEEP_GESTURE, DOUBLE_TAP_SLEEP_GESTURE_VALIDATOR);
+ VALIDATORS.put(STATUS_BAR_SHOW_WEATHER, STATUS_BAR_SHOW_WEATHER_VALIDATOR);
+ VALIDATORS.put(RECENTS_SHOW_SEARCH_BAR, RECENTS_SHOW_SEARCH_BAR_VALIDATOR);
+ VALIDATORS.put(NAVBAR_LEFT_IN_LANDSCAPE, NAVBAR_LEFT_IN_LANDSCAPE_VALIDATOR);
+ VALIDATORS.put(T9_SEARCH_INPUT_LOCALE, T9_SEARCH_INPUT_LOCALE_VALIDATOR);
+ VALIDATORS.put(BLUETOOTH_ACCEPT_ALL_FILES, BLUETOOTH_ACCEPT_ALL_FILES_VALIDATOR);
+ VALIDATORS.put(LOCKSCREEN_PIN_SCRAMBLE_LAYOUT,
+ LOCKSCREEN_PIN_SCRAMBLE_LAYOUT_VALIDATOR);
+ VALIDATORS.put(SHOW_ALARM_ICON, SHOW_ALARM_ICON_VALIDATOR);
+ VALIDATORS.put(STATUS_BAR_IME_SWITCHER, STATUS_BAR_IME_SWITCHER_VALIDATOR);
+ VALIDATORS.put(STATUS_BAR_QUICK_QS_PULLDOWN,
+ STATUS_BAR_QUICK_QS_PULLDOWN_VALIDATOR);
+ VALIDATORS.put(QS_SHOW_BRIGHTNESS_SLIDER, QS_SHOW_BRIGHTNESS_SLIDER_VALIDATOR);
+ VALIDATORS.put(STATUS_BAR_BRIGHTNESS_CONTROL,
+ STATUS_BAR_BRIGHTNESS_CONTROL_VALIDATOR);
+ VALIDATORS.put(VOLBTN_MUSIC_CONTROLS, VOLBTN_MUSIC_CONTROLS_VALIDATOR);
+ VALIDATORS.put(USE_EDGE_SERVICE_FOR_GESTURES,
+ USE_EDGE_SERVICE_FOR_GESTURES_VALIDATOR);
+ VALIDATORS.put(STATUS_BAR_NOTIF_COUNT, STATUS_BAR_NOTIF_COUNT_VALIDATOR);
+ VALIDATORS.put(CALL_RECORDING_FORMAT, CALL_RECORDING_FORMAT_VALIDATOR);
+ VALIDATORS.put(NOTIFICATION_LIGHT_BRIGHTNESS_LEVEL,
+ NOTIFICATION_LIGHT_BRIGHTNESS_LEVEL_VALIDATOR);
+ VALIDATORS.put(NOTIFICATION_LIGHT_MULTIPLE_LEDS_ENABLE,
+ NOTIFICATION_LIGHT_MULTIPLE_LEDS_ENABLE_VALIDATOR);
+ VALIDATORS.put(NOTIFICATION_LIGHT_SCREEN_ON,
+ NOTIFICATION_LIGHT_SCREEN_ON_VALIDATOR);
+ VALIDATORS.put(NOTIFICATION_LIGHT_PULSE_DEFAULT_COLOR,
+ NOTIFICATION_LIGHT_PULSE_DEFAULT_COLOR_VALIDATOR);
+ VALIDATORS.put(NOTIFICATION_LIGHT_PULSE_DEFAULT_LED_ON,
+ NOTIFICATION_LIGHT_PULSE_DEFAULT_LED_ON_VALIDATOR);
+ VALIDATORS.put(NOTIFICATION_LIGHT_PULSE_DEFAULT_LED_OFF,
+ NOTIFICATION_LIGHT_PULSE_DEFAULT_LED_OFF_VALIDATOR);
+ VALIDATORS.put(NOTIFICATION_LIGHT_PULSE_CALL_COLOR,
+ NOTIFICATION_LIGHT_PULSE_CALL_COLOR_VALIDATOR);
+ VALIDATORS.put(NOTIFICATION_LIGHT_PULSE_CALL_LED_ON,
+ NOTIFICATION_LIGHT_PULSE_CALL_LED_ON_VALIDATOR);
+ VALIDATORS.put(NOTIFICATION_LIGHT_PULSE_CALL_LED_OFF,
+ NOTIFICATION_LIGHT_PULSE_CALL_LED_OFF_VALIDATOR);
+ VALIDATORS.put(NOTIFICATION_LIGHT_PULSE_VMAIL_COLOR,
+ NOTIFICATION_LIGHT_PULSE_VMAIL_COLOR_VALIDATOR);
+ VALIDATORS.put(NOTIFICATION_LIGHT_PULSE_VMAIL_LED_ON,
+ NOTIFICATION_LIGHT_PULSE_VMAIL_LED_ON_VALIDATOR);
+ VALIDATORS.put(NOTIFICATION_LIGHT_PULSE_VMAIL_LED_OFF,
+ NOTIFICATION_LIGHT_PULSE_VMAIL_LED_OFF_VALIDATOR);
+ VALIDATORS.put(NOTIFICATION_LIGHT_PULSE_CUSTOM_ENABLE,
+ NOTIFICATION_LIGHT_PULSE_CUSTOM_ENABLE_VALIDATOR);
+ VALIDATORS.put(NOTIFICATION_LIGHT_PULSE_CUSTOM_VALUES,
+ NOTIFICATION_LIGHT_PULSE_CUSTOM_VALUES_VALIDATOR);
+ VALIDATORS.put(NOTIFICATION_LIGHT_COLOR_AUTO,
+ NOTIFICATION_LIGHT_COLOR_AUTO_VALIDATOR);
+ VALIDATORS.put(HEADSET_CONNECT_PLAYER, HEADSET_CONNECT_PLAYER_VALIDATOR);
+ VALIDATORS.put(ZEN_ALLOW_LIGHTS, ZEN_ALLOW_LIGHTS_VALIDATOR);
+ VALIDATORS.put(ZEN_PRIORITY_ALLOW_LIGHTS, ZEN_PRIORITY_ALLOW_LIGHTS_VALIDATOR);
+ VALIDATORS.put(TOUCHSCREEN_GESTURE_HAPTIC_FEEDBACK,
+ TOUCHSCREEN_GESTURE_HAPTIC_FEEDBACK_VALIDATOR);
+ VALIDATORS.put(__MAGICAL_TEST_PASSING_ENABLER,
+ __MAGICAL_TEST_PASSING_ENABLER_VALIDATOR);
+ };
+ // endregion
+ }
+
+ /**
+ * Secure settings, containing miscellaneous CM secure preferences. This
+ * table holds simple name/value pairs. There are convenience
+ * functions for accessing individual settings entries.
+ */
+ public static final class Secure extends Settings.NameValueTable {
+ public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/secure");
+
+ public static final String SYS_PROP_CM_SETTING_VERSION = "sys.cm_settings_secure_version";
+
+ private static final NameValueCache sNameValueCache = new NameValueCache(
+ SYS_PROP_CM_SETTING_VERSION,
+ CONTENT_URI,
+ CALL_METHOD_GET_SECURE,
+ CALL_METHOD_PUT_SECURE);
+
+ // region Methods
+
+ /**
+ * Put a delimited list as a string
+ * @param resolver to access the database with
+ * @param name to store
+ * @param delimiter to split
+ * @param list to join and store
+ * @hide
+ */
+ public static void putListAsDelimitedString(ContentResolver resolver, String name,
+ String delimiter, List<String> list) {
+ String store = TextUtils.join(delimiter, list);
+ putString(resolver, name, store);
+ }
+
+ /**
+ * Get a delimited string returned as a list
+ * @param resolver to access the database with
+ * @param name to store
+ * @param delimiter to split the list with
+ * @return list of strings for a specific Settings.Secure item
+ * @hide
+ */
+ public static List<String> getDelimitedStringAsList(ContentResolver resolver, String name,
+ String delimiter) {
+ String baseString = getString(resolver, name);
+ List<String> list = new ArrayList<String>();
+ if (!TextUtils.isEmpty(baseString)) {
+ final String[] array = TextUtils.split(baseString, Pattern.quote(delimiter));
+ for (String item : array) {
+ if (TextUtils.isEmpty(item)) {
+ continue;
+ }
+ list.add(item);
+ }
+ }
+ return list;
+ }
+
+ /**
+ * Construct the content URI for a particular name/value pair, useful for monitoring changes
+ * with a ContentObserver.
+ * @param name to look up in the table
+ * @return the corresponding content URI
+ */
+ public static Uri getUriFor(String name) {
+ return Settings.NameValueTable.getUriFor(CONTENT_URI, name);
+ }
+
+ /**
+ * Look up a name in the database.
+ * @param resolver to access the database with
+ * @param name to look up in the table
+ * @return the corresponding value, or null if not present
+ */
+ public static String getString(ContentResolver resolver, String name) {
+ return getStringForUser(resolver, name, UserHandle.myUserId());
+ }
+
+ /** @hide */
+ public static String getStringForUser(ContentResolver resolver, String name,
+ int userId) {
+ return sNameValueCache.getStringForUser(resolver, name, userId);
+ }
+
+ /**
+ * Store a name/value pair into the database.
+ * @param resolver to access the database with
+ * @param name to store
+ * @param value to associate with the name
+ * @return true if the value was set, false on database errors
+ */
+ public static boolean putString(ContentResolver resolver, String name, String value) {
+ return putStringForUser(resolver, name, value, UserHandle.myUserId());
+ }
+
+ /** @hide */
+ public static boolean putStringForUser(ContentResolver resolver, String name, String value,
+ int userId) {
+ return sNameValueCache.putStringForUser(resolver, name, value, userId);
+ }
+
+ /**
+ * Convenience function for retrieving a single settings value
+ * as an integer. Note that internally setting values are always
+ * stored as strings; this function converts the string to an integer
+ * for you. The default value will be returned if the setting is
+ * not defined or not an integer.
+ *
+ * @param cr The ContentResolver to access.
+ * @param name The name of the setting to retrieve.
+ * @param def Value to return if the setting is not defined.
+ *
+ * @return The setting's current value, or 'def' if it is not defined
+ * or not a valid integer.
+ */
+ public static int getInt(ContentResolver cr, String name, int def) {
+ return getIntForUser(cr, name, def, UserHandle.myUserId());
+ }
+
+ /** @hide */
+ public static int getIntForUser(ContentResolver cr, String name, int def, int userId) {
+ String v = getStringForUser(cr, name, userId);
+ try {
+ return v != null ? Integer.parseInt(v) : def;
+ } catch (NumberFormatException e) {
+ return def;
+ }
+ }
+
+ /**
+ * Convenience function for retrieving a single settings value
+ * as an integer. Note that internally setting values are always
+ * stored as strings; this function converts the string to an integer
+ * for you.
+ * <p>
+ * This version does not take a default value. If the setting has not
+ * been set, or the string value is not a number,
+ * it throws {@link CMSettingNotFoundException}.
+ *
+ * @param cr The ContentResolver to access.
+ * @param name The name of the setting to retrieve.
+ *
+ * @throws CMSettingNotFoundException Thrown if a setting by the given
+ * name can't be found or the setting value is not an integer.
+ *
+ * @return The setting's current value.
+ */
+ public static int getInt(ContentResolver cr, String name)
+ throws CMSettingNotFoundException {
+ return getIntForUser(cr, name, UserHandle.myUserId());
+ }
+
+ /** @hide */
+ public static int getIntForUser(ContentResolver cr, String name, int userId)
+ throws CMSettingNotFoundException {
+ String v = getStringForUser(cr, name, userId);
+ try {
+ return Integer.parseInt(v);
+ } catch (NumberFormatException e) {
+ throw new CMSettingNotFoundException(name);
+ }
+ }
+
+ /**
+ * Convenience function for updating a single settings value as an
+ * integer. This will either create a new entry in the table if the
+ * given name does not exist, or modify the value of the existing row
+ * with that name. Note that internally setting values are always
+ * stored as strings, so this function converts the given value to a
+ * string before storing it.
+ *
+ * @param cr The ContentResolver to access.
+ * @param name The name of the setting to modify.
+ * @param value The new value for the setting.
+ * @return true if the value was set, false on database errors
+ */
+ public static boolean putInt(ContentResolver cr, String name, int value) {
+ return putIntForUser(cr, name, value, UserHandle.myUserId());
+ }
+
+ /** @hide */
+ public static boolean putIntForUser(ContentResolver cr, String name, int value,
+ int userId) {
+ return putStringForUser(cr, name, Integer.toString(value), userId);
+ }
+
+ /**
+ * Convenience function for retrieving a single settings value
+ * as a {@code long}. Note that internally setting values are always
+ * stored as strings; this function converts the string to a {@code long}
+ * for you. The default value will be returned if the setting is
+ * not defined or not a {@code long}.
+ *
+ * @param cr The ContentResolver to access.
+ * @param name The name of the setting to retrieve.
+ * @param def Value to return if the setting is not defined.
+ *
+ * @return The setting's current value, or 'def' if it is not defined
+ * or not a valid {@code long}.
+ */
+ public static long getLong(ContentResolver cr, String name, long def) {
+ return getLongForUser(cr, name, def, UserHandle.myUserId());
+ }
+
+ /** @hide */
+ public static long getLongForUser(ContentResolver cr, String name, long def,
+ int userId) {
+ String valString = getStringForUser(cr, name, userId);
+ long value;
+ try {
+ value = valString != null ? Long.parseLong(valString) : def;
+ } catch (NumberFormatException e) {
+ value = def;
+ }
+ return value;
+ }
+
+ /**
+ * Convenience function for retrieving a single settings value
+ * as a {@code long}. Note that internally setting values are always
+ * stored as strings; this function converts the string to a {@code long}
+ * for you.
+ * <p>
+ * This version does not take a default value. If the setting has not
+ * been set, or the string value is not a number,
+ * it throws {@link CMSettingNotFoundException}.
+ *
+ * @param cr The ContentResolver to access.
+ * @param name The name of the setting to retrieve.
+ *
+ * @return The setting's current value.
+ * @throws CMSettingNotFoundException Thrown if a setting by the given
+ * name can't be found or the setting value is not an integer.
+ */
+ public static long getLong(ContentResolver cr, String name)
+ throws CMSettingNotFoundException {
+ return getLongForUser(cr, name, UserHandle.myUserId());
+ }
+
+ /** @hide */
+ public static long getLongForUser(ContentResolver cr, String name, int userId)
+ throws CMSettingNotFoundException {
+ String valString = getStringForUser(cr, name, userId);
+ try {
+ return Long.parseLong(valString);
+ } catch (NumberFormatException e) {
+ throw new CMSettingNotFoundException(name);
+ }
+ }
+
+ /**
+ * Convenience function for updating a single settings value as a long
+ * integer. This will either create a new entry in the table if the
+ * given name does not exist, or modify the value of the existing row
+ * with that name. Note that internally setting values are always
+ * stored as strings, so this function converts the given value to a
+ * string before storing it.
+ *
+ * @param cr The ContentResolver to access.
+ * @param name The name of the setting to modify.
+ * @param value The new value for the setting.
+ * @return true if the value was set, false on database errors
+ */
+ public static boolean putLong(ContentResolver cr, String name, long value) {
+ return putLongForUser(cr, name, value, UserHandle.myUserId());
+ }
+
+ /** @hide */
+ public static boolean putLongForUser(ContentResolver cr, String name, long value,
+ int userId) {
+ return putStringForUser(cr, name, Long.toString(value), userId);
+ }
+
+ /**
+ * Convenience function for retrieving a single settings value
+ * as a floating point number. Note that internally setting values are
+ * always stored as strings; this function converts the string to an
+ * float for you. The default value will be returned if the setting
+ * is not defined or not a valid float.
+ *
+ * @param cr The ContentResolver to access.
+ * @param name The name of the setting to retrieve.
+ * @param def Value to return if the setting is not defined.
+ *
+ * @return The setting's current value, or 'def' if it is not defined
+ * or not a valid float.
+ */
+ public static float getFloat(ContentResolver cr, String name, float def) {
+ return getFloatForUser(cr, name, def, UserHandle.myUserId());
+ }
+
+ /** @hide */
+ public static float getFloatForUser(ContentResolver cr, String name, float def,
+ int userId) {
+ String v = getStringForUser(cr, name, userId);
+ try {
+ return v != null ? Float.parseFloat(v) : def;
+ } catch (NumberFormatException e) {
+ return def;
+ }
+ }
+
+ /**
+ * Convenience function for retrieving a single system settings value
+ * as a float. Note that internally setting values are always
+ * stored as strings; this function converts the string to a float
+ * for you.
+ * <p>
+ * This version does not take a default value. If the setting has not
+ * been set, or the string value is not a number,
+ * it throws {@link CMSettingNotFoundException}.
+ *
+ * @param cr The ContentResolver to access.
+ * @param name The name of the setting to retrieve.
+ *
+ * @throws CMSettingNotFoundException Thrown if a setting by the given
+ * name can't be found or the setting value is not a float.
+ *
+ * @return The setting's current value.
+ */
+ public static float getFloat(ContentResolver cr, String name)
+ throws CMSettingNotFoundException {
+ return getFloatForUser(cr, name, UserHandle.myUserId());
+ }
+
+ /** @hide */
+ public static float getFloatForUser(ContentResolver cr, String name, int userId)
+ throws CMSettingNotFoundException {
+ String v = getStringForUser(cr, name, userId);
+ if (v == null) {
+ throw new CMSettingNotFoundException(name);
+ }
+ try {
+ return Float.parseFloat(v);
+ } catch (NumberFormatException e) {
+ throw new CMSettingNotFoundException(name);
+ }
+ }
+
+ /**
+ * Convenience function for updating a single settings value as a
+ * floating point number. This will either create a new entry in the
+ * table if the given name does not exist, or modify the value of the
+ * existing row with that name. Note that internally setting values
+ * are always stored as strings, so this function converts the given
+ * value to a string before storing it.
+ *
+ * @param cr The ContentResolver to access.
+ * @param name The name of the setting to modify.
+ * @param value The new value for the setting.
+ * @return true if the value was set, false on database errors
+ */
+ public static boolean putFloat(ContentResolver cr, String name, float value) {
+ return putFloatForUser(cr, name, value, UserHandle.myUserId());
+ }
+
+ /** @hide */
+ public static boolean putFloatForUser(ContentResolver cr, String name, float value,
+ int userId) {
+ return putStringForUser(cr, name, Float.toString(value), userId);
+ }
+
+ // endregion
+
+ // region Secure Settings
+
+ /**
+ * Whether to enable "advanced mode" for the current user.
+ * Boolean setting. 0 = no, 1 = yes.
+ * @hide
+ */
+ public static final String ADVANCED_MODE = "advanced_mode";
+
+ /**
+ * The time in ms to keep the button backlight on after pressing a button.
+ * A value of 0 will keep the buttons on for as long as the screen is on.
+ * @hide
+ */
+ public static final String BUTTON_BACKLIGHT_TIMEOUT = "button_backlight_timeout";
+
+ /**
+ * The button brightness to be used while the screen is on or after a button press,
+ * depending on the value of {@link BUTTON_BACKLIGHT_TIMEOUT}.
+ * Valid value range is between 0 and {@link PowerManager#getMaximumButtonBrightness()}
+ * @hide
+ */
+ public static final String BUTTON_BRIGHTNESS = "button_brightness";
+
+ /**
+ * A '|' delimited list of theme components to apply from the default theme on first boot.
+ * Components can be one or more of the "mods_XXXXXXX" found in
+ * {@link ThemesContract$ThemesColumns}. Leaving this field blank assumes all components
+ * will be applied.
+ *
+ * ex: mods_icons|mods_overlays|mods_homescreen
+ *
+ * @hide
+ */
+ public static final String DEFAULT_THEME_COMPONENTS = "default_theme_components";
+
+ /**
+ * Default theme to use. If empty, use holo.
+ * @hide
+ */
+ public static final String DEFAULT_THEME_PACKAGE = "default_theme_package";
+
+ /**
+ * Developer options - Navigation Bar show switch
+ * @hide
+ */
+ public static final String DEV_FORCE_SHOW_NAVBAR = "dev_force_show_navbar";
+
+ /**
+ * The keyboard brightness to be used while the screen is on.
+ * Valid value range is between 0 and {@link PowerManager#getMaximumKeyboardBrightness()}
+ * @hide
+ */
+ public static final String KEYBOARD_BRIGHTNESS = "keyboard_brightness";
+
+ /**
+ * Custom navring actions
+ * @hide
+ */
+ public static final String[] NAVIGATION_RING_TARGETS = new String[] {
+ "navigation_ring_targets_0",
+ "navigation_ring_targets_1",
+ "navigation_ring_targets_2",
+ };
+
+ /**
+ * String to contain power menu actions
+ * @hide
+ */
+ public static final String POWER_MENU_ACTIONS = "power_menu_actions";
+
+ /**
+ * Whether to show the brightness slider in quick settings panel.
+ * @hide
+ */
+ public static final String QS_SHOW_BRIGHTNESS_SLIDER = "qs_show_brightness_slider";
+
+ /**
+ * List of QS tile names
+ * @hide
+ */
+ public static final String QS_TILES = "sysui_qs_tiles";
+
+ /**
+ * Use "main" tiles on the first row of the quick settings panel
+ * 0 = no, 1 = yes
+ * @hide
+ */
+ public static final String QS_USE_MAIN_TILES = "sysui_qs_main_tiles";
+
+ /**
+ * Global stats collection
+ * @hide
+ */
+ public static final String STATS_COLLECTION = "stats_collection";
+
+ /**
+ * Whether the global stats collection setting has been successfully reported to server
+ * @hide
+ */
+ public static final String STATS_COLLECTION_REPORTED = "stats_collection_reported";
+
+ /**
+ * Whether newly installed apps should run with privacy guard by default
+ * @hide
+ */
+ public static final String PRIVACY_GUARD_DEFAULT = "privacy_guard_default";
+
+ /**
+ * Whether a notification should be shown if privacy guard is enabled
+ * @hide
+ */
+ public static final String PRIVACY_GUARD_NOTIFICATION = "privacy_guard_notification";
+
+ /**
+ * The global recents long press activity chosen by the user.
+ * This setting is stored as a flattened component name as
+ * per {@link ComponentName#flattenToString()}.
+ *
+ * @hide
+ */
+ public static final String RECENTS_LONG_PRESS_ACTIVITY = "recents_long_press_activity";
+
+ /**
+ * What happens when the user presses the Home button when the
+ * phone is ringing.<br/>
+ * <b>Values:</b><br/>
+ * 1 - Nothing happens. (Default behavior)<br/>
+ * 2 - The Home button answer the current call.<br/>
+ *
+ * @hide
+ */
+ public static final String RING_HOME_BUTTON_BEHAVIOR = "ring_home_button_behavior";
+
+ /**
+ * RING_HOME_BUTTON_BEHAVIOR value for "do nothing".
+ * @hide
+ */
+ public static final int RING_HOME_BUTTON_BEHAVIOR_DO_NOTHING = 0x1;
+
+ /**
+ * RING_HOME_BUTTON_BEHAVIOR value for "answer".
+ * @hide
+ */
+ public static final int RING_HOME_BUTTON_BEHAVIOR_ANSWER = 0x2;
+
+ /**
+ * RING_HOME_BUTTON_BEHAVIOR default value.
+ * @hide
+ */
+ public static final int RING_HOME_BUTTON_BEHAVIOR_DEFAULT =
+ RING_HOME_BUTTON_BEHAVIOR_DO_NOTHING;
+
+ /**
+ * Performance profile
+ * @hide
+ */
+ public static final String PERFORMANCE_PROFILE = "performance_profile";
+
+ /**
+ * App-based performance profile selection
+ * @hide
+ */
+ public static final String APP_PERFORMANCE_PROFILES_ENABLED = "app_perf_profiles_enabled";
+
+ /**
+ * Launch actions for left/right lockscreen targets
+ * @hide
+ */
+ public static final String LOCKSCREEN_TARGETS = "lockscreen_target_actions";
+
+ /**
+ * Whether to display a menu containing 'Wipe data', 'Force close' and other options
+ * in the notification area and in the recent app list
+ * @hide
+ */
+ public static final String DEVELOPMENT_SHORTCUT = "development_shortcut";
+
+ /**
+ * Whether to display the ADB notification.
+ * @hide
+ */
+ public static final String ADB_NOTIFY = "adb_notify";
+
+ /**
+ * The TCP/IP port to run ADB on, or -1 for USB
+ * @hide
+ */
+ public static final String ADB_PORT = "adb_port";
+
+ /**
+ * The hostname for this device
+ * @hide
+ */
+ public static final String DEVICE_HOSTNAME = "device_hostname";
+
+ /**
+ * Whether to allow killing of the foreground app by long-pressing the Back button
+ * @hide
+ */
+ public static final String KILL_APP_LONGPRESS_BACK = "kill_app_longpress_back";
+
+ /** Protected Components
+ * @hide
+ */
+ public static final String PROTECTED_COMPONENTS = "protected_components";
+
+ /**
+ * Stored color matrix for LiveDisplay. This is used to allow co-existence with
+ * display tuning done by DisplayAdjustmentUtils when hardware support isn't
+ * available.
+ * @hide
+ */
+ public static final String LIVE_DISPLAY_COLOR_MATRIX = "live_display_color_matrix";
+
+ /**
+ * Whether to include options in power menu for rebooting into recovery or bootloader
+ * @hide
+ */
+ public static final String ADVANCED_REBOOT = "advanced_reboot";
+
+ /**
+ * This will be set to the system's current theme API version when ThemeService starts.
+ * It is useful for when an upgrade from one version of CM to another occurs.
+ * For example, after a user upgrades from CM11 to CM12, the value of this field
+ * might be 19. ThemeService would then change the value to 21. This is useful
+ * when an API change breaks a theme. Themeservice can identify old themes and
+ * unapply them from the system.
+ * @hide
+ */
+ public static final String THEME_PREV_BOOT_API_LEVEL = "theme_prev_boot_api_level";
+
+ /**
+ * Whether detail view for the location tile is enabled
+ * @hide
+ */
+ public static final String QS_LOCATION_ADVANCED = "qs_location_advanced";
+
+ /**
+ * Whether to show the keyguard visualizer.
+ * Boolean setting. 0 = off, 1 = on.
+ * @hide
+ */
+ public static final String LOCKSCREEN_VISUALIZER_ENABLED = "lockscreen_visualizer";
+
+ /**
+ * Whether the lock screen is currently enabled/disabled by SystemUI (the QS tile likely).
+ * Boolean settings. 0 = off. 1 = on.
+ * @hide
+ */
+ public static final String LOCKSCREEN_INTERNALLY_ENABLED = "lockscreen_internally_enabled";
+
+ /**
+ * Delimited list of packages allowed to manage/launch protected apps (used for filtering)
+ * @hide
+ */
+ public static final String PROTECTED_COMPONENT_MANAGERS = "protected_component_managers";
+
+ /**
+ * Whether live lock screen is currently enabled/disabled by the user.
+ * Boolean settings. 0 = off, 1 = on
+ * @hide
+ */
+ public static final String LIVE_LOCK_SCREEN_ENABLED = "live_lock_screen_enabled";
+
+ /**
+ * The user selected Live lock screen to display
+ * @hide
+ */
+ public static final String DEFAULT_LIVE_LOCK_SCREEN_COMPONENT =
+ "default_live_lock_screen_component";
+
+ /**
+ * Whether keyguard will direct show security view (0 = false, 1 = true)
+ * @hide
+ */
+ public static final String LOCK_PASS_TO_SECURITY_VIEW = "lock_screen_pass_to_security_view";
+
+ /**
+ * Whether touch hovering is enabled on supported hardware
+ * @hide
+ */
+ public static final String FEATURE_TOUCH_HOVERING = "feature_touch_hovering";
+
+ /**
+ * Vibrator intensity setting for supported devices
+ * @hide
+ */
+ public static final String VIBRATOR_INTENSITY = "vibrator_intensity";
+
+ /**
+ * Display gamma calibration values
+ * Suffix this with the control to set
+ * @hide
+ */
+ public static final String DISPLAY_GAMMA_CALIBRATION_PREFIX = "display_gamma_";
+
+ /**
+ * Enabled live lockscreen components. Delimited by "|"
+ * @hide
+ */
+ public static final String ENABLED_EVENT_LIVE_LOCKS_KEY = "live_lockscreens_events_enabled";
+
+ /**
+ * Current active & enabled Weather Provider Service
+ *
+ * @hide
+ */
+ public static final String WEATHER_PROVIDER_SERVICE = "weather_provider_service";
+
+ // endregion
+
+ /**
+ * I can haz more bukkits
+ * @hide
+ */
+ public static final String __MAGICAL_TEST_PASSING_ENABLER =
+ "___magical_test_passing_enabler";
+
+ /**
+ * @hide
+ */
+ public static final String[] LEGACY_SECURE_SETTINGS = new String[]{
+ CMSettings.Secure.ADVANCED_MODE,
+ CMSettings.Secure.BUTTON_BACKLIGHT_TIMEOUT,
+ CMSettings.Secure.BUTTON_BRIGHTNESS,
+ CMSettings.Secure.DEFAULT_THEME_COMPONENTS,
+ CMSettings.Secure.DEFAULT_THEME_PACKAGE,
+ CMSettings.Secure.DEV_FORCE_SHOW_NAVBAR,
+ CMSettings.Secure.KEYBOARD_BRIGHTNESS,
+ CMSettings.Secure.POWER_MENU_ACTIONS,
+ CMSettings.Secure.STATS_COLLECTION,
+ CMSettings.Secure.QS_SHOW_BRIGHTNESS_SLIDER,
+ CMSettings.Secure.QS_TILES,
+ CMSettings.Secure.QS_USE_MAIN_TILES,
+ CMSettings.Secure.NAVIGATION_RING_TARGETS[0],
+ CMSettings.Secure.NAVIGATION_RING_TARGETS[1],
+ CMSettings.Secure.NAVIGATION_RING_TARGETS[2],
+ CMSettings.Secure.RECENTS_LONG_PRESS_ACTIVITY,
+ CMSettings.Secure.ADB_NOTIFY,
+ CMSettings.Secure.ADB_PORT,
+ CMSettings.Secure.DEVICE_HOSTNAME,
+ CMSettings.Secure.KILL_APP_LONGPRESS_BACK,
+ CMSettings.Secure.PROTECTED_COMPONENTS,
+ CMSettings.Secure.LIVE_DISPLAY_COLOR_MATRIX,
+ CMSettings.Secure.ADVANCED_REBOOT,
+ CMSettings.Secure.THEME_PREV_BOOT_API_LEVEL,
+ CMSettings.Secure.LOCKSCREEN_TARGETS,
+ CMSettings.Secure.RING_HOME_BUTTON_BEHAVIOR,
+ CMSettings.Secure.PRIVACY_GUARD_DEFAULT,
+ CMSettings.Secure.PRIVACY_GUARD_NOTIFICATION,
+ CMSettings.Secure.DEVELOPMENT_SHORTCUT,
+ CMSettings.Secure.PERFORMANCE_PROFILE,
+ CMSettings.Secure.APP_PERFORMANCE_PROFILES_ENABLED,
+ CMSettings.Secure.QS_LOCATION_ADVANCED,
+ CMSettings.Secure.LOCKSCREEN_VISUALIZER_ENABLED,
+ CMSettings.Secure.LOCK_PASS_TO_SECURITY_VIEW
+ };
+
+ /**
+ * @hide
+ */
+ public static boolean isLegacySetting(String key) {
+ return ArrayUtils.contains(LEGACY_SECURE_SETTINGS, key);
+ }
+
+ /**
+ * @hide
+ */
+ public static final Validator PROTECTED_COMPONENTS_VALIDATOR = new Validator() {
+ private final String mDelimiter = "|";
+
+ @Override
+ public boolean validate(String value) {
+ if (!TextUtils.isEmpty(value)) {
+ final String[] array = TextUtils.split(value, Pattern.quote(mDelimiter));
+ for (String item : array) {
+ if (TextUtils.isEmpty(item)) {
+ return false; // Empty components not allowed
+ }
+ }
+ }
+ return true; // Empty list is allowed though.
+ }
+ };
+
+ /**
+ * @hide
+ */
+ public static final Validator PROTECTED_COMPONENTS_MANAGER_VALIDATOR = new Validator() {
+ private final String mDelimiter = "|";
+
+ @Override
+ public boolean validate(String value) {
+ if (!TextUtils.isEmpty(value)) {
+ final String[] array = TextUtils.split(value, Pattern.quote(mDelimiter));
+ for (String item : array) {
+ if (TextUtils.isEmpty(item)) {
+ return false; // Empty components not allowed
+ }
+ }
+ }
+ return true; // Empty list is allowed though.
+ }
+ };
+
+ /**
+ * Mapping of validators for all secure settings. This map is used to validate both valid
+ * keys as well as validating the values for those keys.
+ *
+ * Note: Make sure if you add a new Secure setting you create a Validator for it and add
+ * it to this map.
+ *
+ * @hide
+ */
+ public static final Map<String, Validator> VALIDATORS =
+ new ArrayMap<String, Validator>();
+ static {
+ VALIDATORS.put(PROTECTED_COMPONENTS, PROTECTED_COMPONENTS_VALIDATOR);
+ VALIDATORS.put(PROTECTED_COMPONENT_MANAGERS, PROTECTED_COMPONENTS_MANAGER_VALIDATOR);
+ }
+
+ /**
+ * @hide
+ */
+ public static boolean shouldInterceptSystemProvider(String key) {
+ return false;
+ }
+ }
+
+ /**
+ * Global settings, containing miscellaneous CM global preferences. This
+ * table holds simple name/value pairs. There are convenience
+ * functions for accessing individual settings entries.
+ */
+ public static final class Global extends Settings.NameValueTable {
+ public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/global");
+
+ public static final String SYS_PROP_CM_SETTING_VERSION = "sys.cm_settings_global_version";
+
+ private static final NameValueCache sNameValueCache = new NameValueCache(
+ SYS_PROP_CM_SETTING_VERSION,
+ CONTENT_URI,
+ CALL_METHOD_GET_GLOBAL,
+ CALL_METHOD_PUT_GLOBAL);
+
+ // region Methods
+
+
+ /**
+ * Put a delimited list as a string
+ * @param resolver to access the database with
+ * @param name to store
+ * @param delimiter to split
+ * @param list to join and store
+ * @hide
+ */
+ public static void putListAsDelimitedString(ContentResolver resolver, String name,
+ String delimiter, List<String> list) {
+ String store = TextUtils.join(delimiter, list);
+ putString(resolver, name, store);
+ }
+
+ /**
+ * Get a delimited string returned as a list
+ * @param resolver to access the database with
+ * @param name to store
+ * @param delimiter to split the list with
+ * @return list of strings for a specific Settings.Secure item
+ * @hide
+ */
+ public static List<String> getDelimitedStringAsList(ContentResolver resolver, String name,
+ String delimiter) {
+ String baseString = getString(resolver, name);
+ List<String> list = new ArrayList<String>();
+ if (!TextUtils.isEmpty(baseString)) {
+ final String[] array = TextUtils.split(baseString, Pattern.quote(delimiter));
+ for (String item : array) {
+ if (TextUtils.isEmpty(item)) {
+ continue;
+ }
+ list.add(item);
+ }
+ }
+ return list;
+ }
+
+ /**
+ * Construct the content URI for a particular name/value pair, useful for monitoring changes
+ * with a ContentObserver.
+ * @param name to look up in the table
+ * @return the corresponding content URI
+ */
+ public static Uri getUriFor(String name) {
+ return Settings.NameValueTable.getUriFor(CONTENT_URI, name);
+ }
+
+ /**
+ * Look up a name in the database.
+ * @param resolver to access the database with
+ * @param name to look up in the table
+ * @return the corresponding value, or null if not present
+ */
+ public static String getString(ContentResolver resolver, String name) {
+ return getStringForUser(resolver, name, UserHandle.myUserId());
+ }
+
+ /** @hide */
+ public static String getStringForUser(ContentResolver resolver, String name,
+ int userId) {
+ return sNameValueCache.getStringForUser(resolver, name, userId);
+ }
+
+ /**
+ * Store a name/value pair into the database.
+ * @param resolver to access the database with
+ * @param name to store
+ * @param value to associate with the name
+ * @return true if the value was set, false on database errors
+ */
+ public static boolean putString(ContentResolver resolver, String name, String value) {
+ return putStringForUser(resolver, name, value, UserHandle.myUserId());
+ }
+
+ /** @hide */
+ public static boolean putStringForUser(ContentResolver resolver, String name, String value,
+ int userId) {
+ return sNameValueCache.putStringForUser(resolver, name, value, userId);
+ }
+
+ /**
+ * Convenience function for retrieving a single settings value
+ * as an integer. Note that internally setting values are always
+ * stored as strings; this function converts the string to an integer
+ * for you. The default value will be returned if the setting is
+ * not defined or not an integer.
+ *
+ * @param cr The ContentResolver to access.
+ * @param name The name of the setting to retrieve.
+ * @param def Value to return if the setting is not defined.
+ *
+ * @return The setting's current value, or 'def' if it is not defined
+ * or not a valid integer.
+ */
+ public static int getInt(ContentResolver cr, String name, int def) {
+ return getIntForUser(cr, name, def, UserHandle.myUserId());
+ }
+
+ /** @hide */
+ public static int getIntForUser(ContentResolver cr, String name, int def, int userId) {
+ String v = getStringForUser(cr, name, userId);
+ try {
+ return v != null ? Integer.parseInt(v) : def;
+ } catch (NumberFormatException e) {
+ return def;
+ }
+ }
+
+ /**
+ * Convenience function for retrieving a single settings value
+ * as an integer. Note that internally setting values are always
+ * stored as strings; this function converts the string to an integer
+ * for you.
+ * <p>
+ * This version does not take a default value. If the setting has not
+ * been set, or the string value is not a number,
+ * it throws {@link CMSettingNotFoundException}.
+ *
+ * @param cr The ContentResolver to access.
+ * @param name The name of the setting to retrieve.
+ *
+ * @throws CMSettingNotFoundException Thrown if a setting by the given
+ * name can't be found or the setting value is not an integer.
+ *
+ * @return The setting's current value.
+ */
+ public static int getInt(ContentResolver cr, String name)
+ throws CMSettingNotFoundException {
+ return getIntForUser(cr, name, UserHandle.myUserId());
+ }
+
+ /** @hide */
+ public static int getIntForUser(ContentResolver cr, String name, int userId)
+ throws CMSettingNotFoundException {
+ String v = getStringForUser(cr, name, userId);
+ try {
+ return Integer.parseInt(v);
+ } catch (NumberFormatException e) {
+ throw new CMSettingNotFoundException(name);
+ }
+ }
+
+ /**
+ * Convenience function for updating a single settings value as an
+ * integer. This will either create a new entry in the table if the
+ * given name does not exist, or modify the value of the existing row
+ * with that name. Note that internally setting values are always
+ * stored as strings, so this function converts the given value to a
+ * string before storing it.
+ *
+ * @param cr The ContentResolver to access.
+ * @param name The name of the setting to modify.
+ * @param value The new value for the setting.
+ * @return true if the value was set, false on database errors
+ */
+ public static boolean putInt(ContentResolver cr, String name, int value) {
+ return putIntForUser(cr, name, value, UserHandle.myUserId());
+ }
+
+ /** @hide */
+ public static boolean putIntForUser(ContentResolver cr, String name, int value,
+ int userId) {
+ return putStringForUser(cr, name, Integer.toString(value), userId);
+ }
+
+ /**
+ * Convenience function for retrieving a single settings value
+ * as a {@code long}. Note that internally setting values are always
+ * stored as strings; this function converts the string to a {@code long}
+ * for you. The default value will be returned if the setting is
+ * not defined or not a {@code long}.
+ *
+ * @param cr The ContentResolver to access.
+ * @param name The name of the setting to retrieve.
+ * @param def Value to return if the setting is not defined.
+ *
+ * @return The setting's current value, or 'def' if it is not defined
+ * or not a valid {@code long}.
+ */
+ public static long getLong(ContentResolver cr, String name, long def) {
+ return getLongForUser(cr, name, def, UserHandle.myUserId());
+ }
+
+ /** @hide */
+ public static long getLongForUser(ContentResolver cr, String name, long def,
+ int userId) {
+ String valString = getStringForUser(cr, name, userId);
+ long value;
+ try {
+ value = valString != null ? Long.parseLong(valString) : def;
+ } catch (NumberFormatException e) {
+ value = def;
+ }
+ return value;
+ }
+
+ /**
+ * Convenience function for retrieving a single settings value
+ * as a {@code long}. Note that internally setting values are always
+ * stored as strings; this function converts the string to a {@code long}
+ * for you.
+ * <p>
+ * This version does not take a default value. If the setting has not
+ * been set, or the string value is not a number,
+ * it throws {@link CMSettingNotFoundException}.
+ *
+ * @param cr The ContentResolver to access.
+ * @param name The name of the setting to retrieve.
+ *
+ * @return The setting's current value.
+ * @throws CMSettingNotFoundException Thrown if a setting by the given
+ * name can't be found or the setting value is not an integer.
+ */
+ public static long getLong(ContentResolver cr, String name)
+ throws CMSettingNotFoundException {
+ return getLongForUser(cr, name, UserHandle.myUserId());
+ }
+
+ /** @hide */
+ public static long getLongForUser(ContentResolver cr, String name, int userId)
+ throws CMSettingNotFoundException {
+ String valString = getStringForUser(cr, name, userId);
+ try {
+ return Long.parseLong(valString);
+ } catch (NumberFormatException e) {
+ throw new CMSettingNotFoundException(name);
+ }
+ }
+
+ /**
+ * Convenience function for updating a single settings value as a long
+ * integer. This will either create a new entry in the table if the
+ * given name does not exist, or modify the value of the existing row
+ * with that name. Note that internally setting values are always
+ * stored as strings, so this function converts the given value to a
+ * string before storing it.
+ *
+ * @param cr The ContentResolver to access.
+ * @param name The name of the setting to modify.
+ * @param value The new value for the setting.
+ * @return true if the value was set, false on database errors
+ */
+ public static boolean putLong(ContentResolver cr, String name, long value) {
+ return putLongForUser(cr, name, value, UserHandle.myUserId());
+ }
+
+ /** @hide */
+ public static boolean putLongForUser(ContentResolver cr, String name, long value,
+ int userId) {
+ return putStringForUser(cr, name, Long.toString(value), userId);
+ }
+
+ /**
+ * Convenience function for retrieving a single settings value
+ * as a floating point number. Note that internally setting values are
+ * always stored as strings; this function converts the string to an
+ * float for you. The default value will be returned if the setting
+ * is not defined or not a valid float.
+ *
+ * @param cr The ContentResolver to access.
+ * @param name The name of the setting to retrieve.
+ * @param def Value to return if the setting is not defined.
+ *
+ * @return The setting's current value, or 'def' if it is not defined
+ * or not a valid float.
+ */
+ public static float getFloat(ContentResolver cr, String name, float def) {
+ return getFloatForUser(cr, name, def, UserHandle.myUserId());
+ }
+
+ /** @hide */
+ public static float getFloatForUser(ContentResolver cr, String name, float def,
+ int userId) {
+ String v = getStringForUser(cr, name, userId);
+ try {
+ return v != null ? Float.parseFloat(v) : def;
+ } catch (NumberFormatException e) {
+ return def;
+ }
+ }
+
+ /**
+ * Convenience function for retrieving a single system settings value
+ * as a float. Note that internally setting values are always
+ * stored as strings; this function converts the string to a float
+ * for you.
+ * <p>
+ * This version does not take a default value. If the setting has not
+ * been set, or the string value is not a number,
+ * it throws {@link CMSettingNotFoundException}.
+ *
+ * @param cr The ContentResolver to access.
+ * @param name The name of the setting to retrieve.
+ *
+ * @throws CMSettingNotFoundException Thrown if a setting by the given
+ * name can't be found or the setting value is not a float.
+ *
+ * @return The setting's current value.
+ */
+ public static float getFloat(ContentResolver cr, String name)
+ throws CMSettingNotFoundException {
+ return getFloatForUser(cr, name, UserHandle.myUserId());
+ }
+
+ /** @hide */
+ public static float getFloatForUser(ContentResolver cr, String name, int userId)
+ throws CMSettingNotFoundException {
+ String v = getStringForUser(cr, name, userId);
+ if (v == null) {
+ throw new CMSettingNotFoundException(name);
+ }
+ try {
+ return Float.parseFloat(v);
+ } catch (NumberFormatException e) {
+ throw new CMSettingNotFoundException(name);
+ }
+ }
+
+ /**
+ * Convenience function for updating a single settings value as a
+ * floating point number. This will either create a new entry in the
+ * table if the given name does not exist, or modify the value of the
+ * existing row with that name. Note that internally setting values
+ * are always stored as strings, so this function converts the given
+ * value to a string before storing it.
+ *
+ * @param cr The ContentResolver to access.
+ * @param name The name of the setting to modify.
+ * @param value The new value for the setting.
+ * @return true if the value was set, false on database errors
+ */
+ public static boolean putFloat(ContentResolver cr, String name, float value) {
+ return putFloatForUser(cr, name, value, UserHandle.myUserId());
+ }
+
+ /** @hide */
+ public static boolean putFloatForUser(ContentResolver cr, String name, float value,
+ int userId) {
+ return putStringForUser(cr, name, Float.toString(value), userId);
+ }
+
+ // endregion
+
+ // region Global Settings
+ /**
+ * Whether to wake the display when plugging or unplugging the charger
+ *
+ * @hide
+ */
+ public static final String WAKE_WHEN_PLUGGED_OR_UNPLUGGED =
+ "wake_when_plugged_or_unplugged";
+
+ /**
+ * Whether to sound when charger power is connected/disconnected
+ * @hide
+ * @deprecated Use {@link android.provider.Settings.Global#CHARGING_SOUNDS_ENABLED} instead
+ */
+ @Deprecated
+ public static final String POWER_NOTIFICATIONS_ENABLED = "power_notifications_enabled";
+
+ /**
+ * Whether to vibrate when charger power is connected/disconnected
+ * @hide
+ */
+ public static final String POWER_NOTIFICATIONS_VIBRATE = "power_notifications_vibrate";
+
+ /**
+ * URI for power notification sounds
+ * @hide
+ */
+ public static final String POWER_NOTIFICATIONS_RINGTONE = "power_notifications_ringtone";
+
+ /**
+ * @hide
+ */
+ public static final String ZEN_DISABLE_DUCKING_DURING_MEDIA_PLAYBACK =
+ "zen_disable_ducking_during_media_playback";
+
+ /**
+ * Whether the system auto-configure the priority of the wifi ap's or use
+ * the manual settings established by the user.
+ * <> 0 to autoconfigure, 0 to manual settings. Default is <> 0.
+ * @hide
+ */
+ public static final String WIFI_AUTO_PRIORITIES_CONFIGURATION = "wifi_auto_priority";
+ // endregion
+
+ /**
+ * @hide
+ */
+ public static final String[] LEGACY_GLOBAL_SETTINGS = new String[]{
+ CMSettings.Global.WAKE_WHEN_PLUGGED_OR_UNPLUGGED,
+ CMSettings.Global.POWER_NOTIFICATIONS_VIBRATE,
+ CMSettings.Global.POWER_NOTIFICATIONS_RINGTONE,
+ CMSettings.Global.ZEN_DISABLE_DUCKING_DURING_MEDIA_PLAYBACK,
+ CMSettings.Global.WIFI_AUTO_PRIORITIES_CONFIGURATION
+ };
+
+ /**
+ * @hide
+ */
+ public static boolean isLegacySetting(String key) {
+ return ArrayUtils.contains(LEGACY_GLOBAL_SETTINGS, key);
+ }
+
+ /**
+ * @hide
+ */
+ public static boolean shouldInterceptSystemProvider(String key) {
+ return false;
+ }
+ }
+}
diff --git a/sdk/src/java/cyanogenmod/providers/DataUsageContract.java b/sdk/src/java/cyanogenmod/providers/DataUsageContract.java
new file mode 100644
index 0000000..2371cd7
--- /dev/null
+++ b/sdk/src/java/cyanogenmod/providers/DataUsageContract.java
@@ -0,0 +1,164 @@
+/**
+ * Copyright (c) 2016, The CyanogenMod Project
+ * <p/>
+ * 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
+ * <p/>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p/>
+ * 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.providers;
+
+import android.content.ContentResolver;
+import android.net.Uri;
+
+/**
+ * <p>
+ * The DataUsageProvdier contract containing definitions for the supported URIs and columns
+ * </p>
+ */
+
+public final class DataUsageContract {
+
+ /** The authority for the DataUsage provider */
+ public static final String DATAUSAGE_AUTHORITY = "org.cyanogenmod.providers.datausage";
+ public static final String DATAUSAGE_TABLE = "datausage";
+
+ /** The content URI for the top-level datausage authority */
+ public static final Uri BASE_CONTENT_URI = Uri.parse("content://" + DATAUSAGE_AUTHORITY);
+
+ /** The content URI for this table */
+ public static final Uri CONTENT_URI =
+ Uri.withAppendedPath(BASE_CONTENT_URI, DATAUSAGE_TABLE);
+
+ /** Define database columns */
+ /**
+ * The unique ID for a row
+ * <P>Type: INTEGER </P>
+ */
+ public static final String _ID = "_id";
+
+ /**
+ * The UID of the application whose bandwidth is stored in this row
+ * <P>Type: INTEGER </P>
+ */
+ public static final String UID = "uid";
+
+ /**
+ * DataUsage Enable Configuration - statically set by the Settings App
+ * 0 - no data usage warning are generated
+ * 1 - data usage algorithm is evaluated and warnings are generated
+ * <P>Type: INTEGER</P>
+ */
+ public static final String ENABLE = "enable";
+
+ /**
+ * DataUsage Active State - dynamically computed by the DataUsage algorithm to
+ * determine warning type to display to the user
+ * 0 - first warning type is generated, once a warning generation is triggered
+ * 1 - Nth warning type is generated, once a warning generation is triggered
+ * <P>Type: INTEGER</P>
+ */
+ public static final String ACTIVE = "active";
+
+ /**
+ * The Name of the Application that corresponds to the uid
+ * <P>Type: TEXT</P>
+ */
+ public static final String LABEL = "label";
+
+ /**
+ * Number of bytes consumed by the App so far. It is used to determine the number
+ * of bytes consumed between samples
+ * <P>Type: INTEGER (long) </P>
+ */
+ public static final String BYTES = "bytes";
+
+ /**
+ * The slow bandwidth consumption average accumulated over 'SLOW' number of samples
+ * <P>Type: INTEGER (long)</P>
+ */
+ public static final String SLOW_AVG = "slow_avg";
+
+ /**
+ * Number of slow samples accumulated so far, once the number of samples reaches a
+ * MAX number of samples, the 'slow_samples' pegs at MAX and new samples
+ * are factored into 'slow_avg' by "taking out" one sample.
+ * slow_samples < MAX: slow_avg = (slow_avg * slow_samples + new_sample)/(slow_samples+1)
+ * slow_samples == MAX: slow_avg = (slow_avg * (MAX-1) + new_sample)/MAX
+ * <P>Type: Integer (long></P>
+ */
+ public static final String SLOW_SAMPLES = "slow_samples";
+
+ /**
+ * The fast bandwidth consumption average accumulated over 'fast' number of samples
+ * <P>Type: INTEGER (long)</P>
+ */
+ public static final String FAST_AVG = "fast_avg";
+
+ /**
+ * Number of fast samples accumulated so far, analogous algorithm to 'slow_samples'
+ * <P>Type: INTEGER (long)</P>
+ */
+ public static final String FAST_SAMPLES = "fast_samples";
+
+ /**
+ * Extra information used debugging purposes - collects up to 1000 samples so that
+ * algorithm can be manually verified
+ * <P>Type: TEXT</P>
+ */
+ public static final String EXTRA = "extra";
+
+ /**
+ * The mime type of a directory of items
+ */
+ public static final String CONTENT_TYPE =
+ ContentResolver.CURSOR_DIR_BASE_TYPE + "datausage_item";
+
+ /**
+ * The mime type of a single item
+ */
+ public static final String CONTENT_ITEM_TYPE =
+ ContentResolver.CURSOR_ITEM_BASE_TYPE + "datausage_item";
+
+ /**
+ * A projection of all columns in the datausage table.
+ */
+ public static final String [] PROJECTION_ALL = {
+ _ID,
+ UID,
+ ENABLE,
+ ACTIVE,
+ LABEL,
+ BYTES,
+ SLOW_AVG,
+ SLOW_SAMPLES,
+ FAST_AVG,
+ FAST_SAMPLES,
+ EXTRA
+ };
+
+ /**
+ * Column index for each field in the database row
+ */
+ public static final int COLUMN_OF_ID = 0;
+ public static final int COLUMN_OF_UID = 1;
+ public static final int COLUMN_OF_ENABLE = 2;
+ public static final int COLUMN_OF_ACTIVE = 3;
+ public static final int COLUMN_OF_LABEL = 4;
+ public static final int COLUMN_OF_BYTES = 5;
+ public static final int COLUMN_OF_SLOW_AVG = 6;
+ public static final int COLUMN_OF_SLOW_SAMPLES = 7;
+ public static final int COLUMN_OF_FAST_AVG = 8;
+ public static final int COLUMN_OF_FAST_SAMPLES = 9;
+ public static final int COLUMN_OF_EXTRA = 10;
+
+
+}
diff --git a/sdk/src/java/cyanogenmod/providers/ThemesContract.java b/sdk/src/java/cyanogenmod/providers/ThemesContract.java
new file mode 100644
index 0000000..4cdfeb9
--- /dev/null
+++ b/sdk/src/java/cyanogenmod/providers/ThemesContract.java
@@ -0,0 +1,717 @@
+/*
+ * Copyright (C) 2016 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.providers;
+
+import android.net.Uri;
+
+/**
+ * <p>
+ * The contract between the themes provider and applications. Contains
+ * definitions for the supported URIs and columns.
+ * </p>
+ */
+public class ThemesContract {
+ /** The authority for the themes provider */
+ public static final String AUTHORITY = "com.cyanogenmod.themes";
+ /** A content:// style uri to the authority for the themes provider */
+ public static final Uri AUTHORITY_URI = Uri.parse("content://" + AUTHORITY);
+
+ public static class ThemesColumns {
+ public static final Uri CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, "themes");
+
+ /**
+ * The unique ID for a row.
+ * <P>Type: INTEGER (long)</P>
+ */
+ public static final String _ID = "_id";
+
+ /**
+ * The user visible title.
+ * <P>Type: TEXT</P>
+ */
+ public static final String TITLE = "title";
+
+ /**
+ * Unique text to identify the apk pkg. ie "com.foo.bar"
+ * <P>Type: TEXT</P>
+ */
+ public static final String PKG_NAME = "pkg_name";
+
+ /**
+ * A 32 bit RRGGBB color representative of the themes color scheme
+ * <P>Type: INTEGER</P>
+ */
+ public static final String PRIMARY_COLOR = "primary_color";
+
+ /**
+ * A 2nd 32 bit RRGGBB color representative of the themes color scheme
+ * <P>Type: INTEGER</P>
+ */
+ public static final String SECONDARY_COLOR = "secondary_color";
+
+ /**
+ * Name of the author of the theme
+ * <P>Type: TEXT</P>
+ */
+ public static final String AUTHOR = "author";
+
+ /**
+ * The time that this row was created on its originating client (msecs
+ * since the epoch).
+ * <P>Type: INTEGER</P>
+ */
+ public static final String DATE_CREATED = "created";
+
+ /**
+ * URI to an image that shows the homescreen with the theme applied
+ * since the epoch).
+ * <P>Type: TEXT</P>
+ */
+ public static final String HOMESCREEN_URI = "homescreen_uri";
+
+ /**
+ * URI to an image that shows the lockscreen with theme applied
+ * <P>Type: TEXT</P>
+ */
+ public static final String LOCKSCREEN_URI = "lockscreen_uri";
+
+ /**
+ * URI to an image that shows the style (aka skin) with theme applied
+ * <P>Type: TEXT</P>
+ */
+ public static final String STYLE_URI = "style_uri";
+
+ /**
+ * TODO: Figure structure for actual animation instead of static
+ * URI to an image of the boot_anim.
+ * <P>Type: TEXT</P>
+ */
+ public static final String BOOT_ANIM_URI = "bootanim_uri";
+
+ /**
+ * URI to an image of the status bar for this theme.
+ * <P>Type: TEXT</P>
+ */
+ public static final String STATUSBAR_URI = "status_uri";
+
+ /**
+ * URI to an image of the fonts in this theme.
+ * <P>Type: TEXT</P>
+ */
+ public static final String FONT_URI = "font_uri";
+
+ /**
+ * URI to an image of the fonts in this theme.
+ * <P>Type: TEXT</P>
+ */
+ public static final String ICON_URI = "icon_uri";
+
+ /**
+ * URI to an image of the fonts in this theme.
+ * <P>Type: TEXT</P>
+ */
+ public static final String OVERLAYS_URI = "overlays_uri";
+
+ /**
+ * 1 if theme modifies the launcher/homescreen else 0
+ * <P>Type: INTEGER</P>
+ * <P>Default: 0</P>
+ */
+ public static final String MODIFIES_LAUNCHER = "mods_homescreen";
+
+ /**
+ * 1 if theme modifies the lockscreen else 0
+ * <P>Type: INTEGER</P>
+ * <P>Default: 0</P>
+ */
+ public static final String MODIFIES_LOCKSCREEN = "mods_lockscreen";
+
+ /**
+ * 1 if theme modifies icons else 0
+ * <P>Type: INTEGER</P>
+ * <P>Default: 0</P>
+ */
+ public static final String MODIFIES_ICONS = "mods_icons";
+
+ /**
+ * 1 if theme modifies fonts
+ * <P>Type: INTEGER</P>
+ * <P>Default: 0</P>
+ */
+ public static final String MODIFIES_FONTS = "mods_fonts";
+
+ /**
+ * 1 if theme modifies boot animation
+ * <P>Type: INTEGER</P>
+ * <P>Default: 0</P>
+ */
+ public static final String MODIFIES_BOOT_ANIM = "mods_bootanim";
+
+ /**
+ * 1 if theme modifies notifications
+ * <P>Type: INTEGER</P>
+ * <P>Default: 0</P>
+ */
+ public static final String MODIFIES_NOTIFICATIONS = "mods_notifications";
+
+ /**
+ * 1 if theme modifies alarm sounds
+ * <P>Type: INTEGER</P>
+ * <P>Default: 0</P>
+ */
+ public static final String MODIFIES_ALARMS = "mods_alarms";
+
+ /**
+ * 1 if theme modifies ringtones
+ * <P>Type: INTEGER</P>
+ * <P>Default: 0</P>
+ */
+ public static final String MODIFIES_RINGTONES = "mods_ringtones";
+
+ /**
+ * 1 if theme has overlays
+ * <P>Type: INTEGER</P>
+ * <P>Default: 0</P>
+ */
+ public static final String MODIFIES_OVERLAYS = "mods_overlays";
+
+ /**
+ * 1 if theme has an overlay for SystemUI/StatusBar
+ * <P>Type: INTEGER</P>
+ * <P>Default: 0</P>
+ */
+ public static final String MODIFIES_STATUS_BAR = "mods_status_bar";
+
+ /**
+ * 1 if theme has an overlay for SystemUI/NavBar
+ * <P>Type: INTEGER</P>
+ * <P>Default: 0</P>
+ */
+ public static final String MODIFIES_NAVIGATION_BAR = "mods_navigation_bar";
+
+ /**
+ * 1 if theme has a live lock screen
+ * <P>Type: INTEGER</P>
+ * <P>Default: 0</P>
+ */
+ public static final String MODIFIES_LIVE_LOCK_SCREEN = "mods_live_lock_screen";
+
+ /**
+ * URI to the theme's wallpaper. We should support multiple wallpaper
+ * but for now we will just have 1.
+ * <P>Type: TEXT</P>
+ */
+ public static final String WALLPAPER_URI = "wallpaper_uri";
+
+ /**
+ * 1 if this row should actually be presented as a theme to the user.
+ * For example if a "theme" only modifies one component (ex icons) then
+ * we do not present it to the user under the themes table.
+ * <P>Type: INTEGER</P>
+ * <P>Default: 0</P>
+ */
+ public static final String PRESENT_AS_THEME = "present_as_theme";
+
+ /**
+ * 1 if this theme is a legacy theme.
+ * <P>Type: INTEGER</P>
+ * <P>Default: 0</P>
+ */
+ public static final String IS_LEGACY_THEME = "is_legacy_theme";
+
+ /**
+ * 1 if this theme is the system default theme.
+ * <P>Type: INTEGER</P>
+ * <P>Default: 0</P>
+ */
+ public static final String IS_DEFAULT_THEME = "is_default_theme";
+
+ /**
+ * 1 if this theme is a legacy iconpack. A legacy icon pack is an APK that was written
+ * for Trebuchet or a 3rd party launcher.
+ * <P>Type: INTEGER</P>
+ * <P>Default: 0</P>
+ */
+ public static final String IS_LEGACY_ICONPACK = "is_legacy_iconpack";
+
+ /**
+ * install/update time in millisecs. When the row is inserted this column
+ * is populated by the PackageInfo. It is used for syncing to PM
+ * <P>Type: INTEGER</P>
+ * <P>Default: 0</P>
+ */
+ public static final String LAST_UPDATE_TIME = "updateTime";
+
+ /**
+ * install time in millisecs. When the row is inserted this column
+ * is populated by the PackageInfo.
+ * <P>Type: INTEGER</P>
+ * <P>Default: 0</P>
+ */
+ public static final String INSTALL_TIME = "install_time";
+
+ /**
+ * The target API this theme supports
+ * is populated by the PackageInfo.
+ * <P>Type: INTEGER</P>
+ * <P>Default: 0</P>
+ */
+ public static final String TARGET_API = "target_api";
+
+ /**
+ * The install state of the theme.
+ * Can be one of the following:
+ * {@link InstallState#UNKNOWN}
+ * {@link InstallState#INSTALLING}
+ * {@link InstallState#UPDATING}
+ * {@link InstallState#INSTALLED}
+ * <P>Type: INTEGER</P>
+ * <P>Default: 0</P>
+ */
+ public static final String INSTALL_STATE = "install_state";
+
+ public static class InstallState {
+ public static final int UNKNOWN = 0;
+ public static final int INSTALLING = 1;
+ public static final int UPDATING = 2;
+ public static final int INSTALLED = 3;
+ }
+ }
+
+ /**
+ * Key-value table which assigns a component (ex wallpaper) to a theme's package
+ */
+ public static class MixnMatchColumns {
+ public static final Uri CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, "mixnmatch");
+
+ /**
+ * The unique key for a row. See the KEY_* constants
+ * for valid examples
+ * <P>Type: TEXT</P>
+ */
+ public static final String COL_KEY = "key";
+
+ /**
+ * The package name that corresponds to a given component.
+ * <P>Type: String</P>
+ */
+ public static final String COL_VALUE = "value";
+
+ /**
+ * The package name that corresponds to where this component was applied from previously
+ * <P>Type: String</P>
+ */
+ public static final String COL_PREV_VALUE = "previous_value";
+
+ /**
+ * Time when this entry was last updated
+ * <P>Type: INTEGER</P>
+ */
+ public static final String COL_UPDATE_TIME = "update_time";
+
+ /*
+ * The unique ID for the component within a theme.
+ * Always 0 unless multiples of a component exist.
+ * <P>Type: INTEGER (long)</P>
+ */
+ public static final String COL_COMPONENT_ID = "component_id";
+
+ /**
+ * Valid keys
+ */
+ public static final String KEY_HOMESCREEN = "mixnmatch_homescreen";
+ public static final String KEY_LOCKSCREEN = "mixnmatch_lockscreen";
+ public static final String KEY_ICONS = "mixnmatch_icons";
+ public static final String KEY_STATUS_BAR = "mixnmatch_status_bar";
+ public static final String KEY_BOOT_ANIM = "mixnmatch_boot_anim";
+ public static final String KEY_FONT = "mixnmatch_font";
+ public static final String KEY_ALARM = "mixnmatch_alarm";
+ public static final String KEY_NOTIFICATIONS = "mixnmatch_notifications";
+ public static final String KEY_RINGTONE = "mixnmatch_ringtone";
+ public static final String KEY_OVERLAYS = "mixnmatch_overlays";
+ public static final String KEY_NAVIGATION_BAR = "mixnmatch_navigation_bar";
+ public static final String KEY_LIVE_LOCK_SCREEN = "mixnmatch_live_lock_screen";
+
+ public static final String[] ROWS = { KEY_HOMESCREEN,
+ KEY_LOCKSCREEN,
+ KEY_ICONS,
+ KEY_STATUS_BAR,
+ KEY_BOOT_ANIM,
+ KEY_FONT,
+ KEY_NOTIFICATIONS,
+ KEY_RINGTONE,
+ KEY_ALARM,
+ KEY_OVERLAYS,
+ KEY_NAVIGATION_BAR,
+ KEY_LIVE_LOCK_SCREEN
+ };
+
+ /**
+ * For a given key value in the MixNMatch table, return the column
+ * associated with it in the Themes Table. This is useful for URI based
+ * elements like wallpaper where the caller wishes to determine the
+ * wallpaper URI.
+ */
+ public static String componentToImageColName(String component) {
+ if (component.equals(MixnMatchColumns.KEY_HOMESCREEN)) {
+ return ThemesColumns.HOMESCREEN_URI;
+ } else if (component.equals(MixnMatchColumns.KEY_LOCKSCREEN)) {
+ return ThemesColumns.LOCKSCREEN_URI;
+ } else if (component.equals(MixnMatchColumns.KEY_BOOT_ANIM)) {
+ return ThemesColumns.BOOT_ANIM_URI;
+ } else if (component.equals(MixnMatchColumns.KEY_FONT)) {
+ return ThemesColumns.FONT_URI;
+ } else if (component.equals(MixnMatchColumns.KEY_ICONS)) {
+ return ThemesColumns.ICON_URI;
+ } else if (component.equals(MixnMatchColumns.KEY_STATUS_BAR)) {
+ return ThemesColumns.STATUSBAR_URI;
+ } else if (component.equals(MixnMatchColumns.KEY_NOTIFICATIONS)) {
+ throw new IllegalArgumentException("Notifications mixnmatch component does not have a related column");
+ } else if (component.equals(MixnMatchColumns.KEY_RINGTONE)) {
+ throw new IllegalArgumentException("Ringtone mixnmatch component does not have a related column");
+ } else if (component.equals(MixnMatchColumns.KEY_OVERLAYS)) {
+ return ThemesColumns.OVERLAYS_URI;
+ } else if (component.equals(MixnMatchColumns.KEY_STATUS_BAR)) {
+ throw new IllegalArgumentException(
+ "Status bar mixnmatch component does not have a related column");
+ } else if (component.equals(MixnMatchColumns.KEY_NAVIGATION_BAR)) {
+ throw new IllegalArgumentException(
+ "Navigation bar mixnmatch component does not have a related column");
+ } else if (component.equals(MixnMatchColumns.KEY_LIVE_LOCK_SCREEN)) {
+ throw new IllegalArgumentException(
+ "Live lock screen mixnmatch component does not have a related column");
+ }
+ return null;
+ }
+
+ /**
+ * A component in the themes table (IE "mods_wallpaper") has an
+ * equivalent key in mixnmatch table
+ */
+ public static String componentToMixNMatchKey(String component) {
+ if (component.equals(ThemesColumns.MODIFIES_LAUNCHER)) {
+ return MixnMatchColumns.KEY_HOMESCREEN;
+ } else if (component.equals(ThemesColumns.MODIFIES_ICONS)) {
+ return MixnMatchColumns.KEY_ICONS;
+ } else if (component.equals(ThemesColumns.MODIFIES_LOCKSCREEN)) {
+ return MixnMatchColumns.KEY_LOCKSCREEN;
+ } else if (component.equals(ThemesColumns.MODIFIES_FONTS)) {
+ return MixnMatchColumns.KEY_FONT;
+ } else if (component.equals(ThemesColumns.MODIFIES_BOOT_ANIM)) {
+ return MixnMatchColumns.KEY_BOOT_ANIM;
+ } else if (component.equals(ThemesColumns.MODIFIES_ALARMS)) {
+ return MixnMatchColumns.KEY_ALARM;
+ } else if (component.equals(ThemesColumns.MODIFIES_NOTIFICATIONS)) {
+ return MixnMatchColumns.KEY_NOTIFICATIONS;
+ } else if (component.equals(ThemesColumns.MODIFIES_RINGTONES)) {
+ return MixnMatchColumns.KEY_RINGTONE;
+ } else if (component.equals(ThemesColumns.MODIFIES_OVERLAYS)) {
+ return MixnMatchColumns.KEY_OVERLAYS;
+ } else if (component.equals(ThemesColumns.MODIFIES_STATUS_BAR)) {
+ return MixnMatchColumns.KEY_STATUS_BAR;
+ } else if (component.equals(ThemesColumns.MODIFIES_NAVIGATION_BAR)) {
+ return MixnMatchColumns.KEY_NAVIGATION_BAR;
+ } else if (component.equals(ThemesColumns.MODIFIES_LIVE_LOCK_SCREEN)) {
+ return MixnMatchColumns.KEY_LIVE_LOCK_SCREEN;
+ }
+ return null;
+ }
+
+ /**
+ * A mixnmatch key in has an
+ * equivalent value in the themes table
+ */
+ public static String mixNMatchKeyToComponent(String mixnmatchKey) {
+ if (mixnmatchKey.equals(MixnMatchColumns.KEY_HOMESCREEN)) {
+ return ThemesColumns.MODIFIES_LAUNCHER;
+ } else if (mixnmatchKey.equals(MixnMatchColumns.KEY_ICONS)) {
+ return ThemesColumns.MODIFIES_ICONS;
+ } else if (mixnmatchKey.equals(MixnMatchColumns.KEY_LOCKSCREEN)) {
+ return ThemesColumns.MODIFIES_LOCKSCREEN;
+ } else if (mixnmatchKey.equals(MixnMatchColumns.KEY_FONT)) {
+ return ThemesColumns.MODIFIES_FONTS;
+ } else if (mixnmatchKey.equals(MixnMatchColumns.KEY_BOOT_ANIM)) {
+ return ThemesColumns.MODIFIES_BOOT_ANIM;
+ } else if (mixnmatchKey.equals(MixnMatchColumns.KEY_ALARM)) {
+ return ThemesColumns.MODIFIES_ALARMS;
+ } else if (mixnmatchKey.equals(MixnMatchColumns.KEY_NOTIFICATIONS)) {
+ return ThemesColumns.MODIFIES_NOTIFICATIONS;
+ } else if (mixnmatchKey.equals(MixnMatchColumns.KEY_RINGTONE)) {
+ return ThemesColumns.MODIFIES_RINGTONES;
+ } else if (mixnmatchKey.equals(MixnMatchColumns.KEY_OVERLAYS)) {
+ return ThemesColumns.MODIFIES_OVERLAYS;
+ } else if (mixnmatchKey.equals(MixnMatchColumns.KEY_STATUS_BAR)) {
+ return ThemesColumns.MODIFIES_STATUS_BAR;
+ } else if (mixnmatchKey.equals(MixnMatchColumns.KEY_NAVIGATION_BAR)) {
+ return ThemesColumns.MODIFIES_NAVIGATION_BAR;
+ } else if (mixnmatchKey.equals(MixnMatchColumns.KEY_LIVE_LOCK_SCREEN)) {
+ return ThemesColumns.MODIFIES_LIVE_LOCK_SCREEN;
+ }
+ return null;
+ }
+ }
+
+ /**
+ * Table containing cached preview files for a given theme
+ */
+ public static class PreviewColumns {
+ /**
+ * Uri for retrieving the previews table.
+ * Querying the themes provider using this URI will return a cursor with a key and value
+ * columns, and a row for each component.
+ */
+ public static final Uri CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, "previews");
+
+ /**
+ * Uri for retrieving the previews for the currently applied components.
+ * Querying the themes provider using this URI will return a cursor with a single row
+ * containing all the previews for the components that are currently applied.
+ */
+ public static final Uri APPLIED_URI = Uri.withAppendedPath(AUTHORITY_URI,
+ "applied_previews");
+
+ /**
+ * Uri for retrieving the default previews for the theme.
+ * Querying the themes provider using this URI will return a cursor with a single row
+ * containing all the previews for the default components of the current theme.
+ */
+ public static final Uri COMPONENTS_URI = Uri.withAppendedPath(AUTHORITY_URI,
+ "components_previews");
+
+ /**
+ * The unique ID for a row.
+ * <P>Type: INTEGER (long)</P>
+ */
+ public static final String _ID = "_id";
+
+ /**
+ * The unique ID for the theme these previews belong to.
+ * <P>Type: INTEGER (long)</P>
+ */
+ public static final String THEME_ID = "theme_id";
+
+ /**
+ * The unique ID for the component within a theme.
+ * <P>Type: INTEGER (long)</P>
+ */
+ public static final String COMPONENT_ID = "component_id";
+
+ /**
+ * The unique key for a row. See the Valid key constants section below
+ * for valid examples
+ * <P>Type: TEXT</P>
+ */
+ public static final String COL_KEY = "key";
+
+ /**
+ * The package name that corresponds to a given component.
+ * <P>Type: String</P>
+ */
+ public static final String COL_VALUE = "value";
+
+ /**
+ * Valid keys
+ */
+
+ /**
+ * Cached image of the themed status bar background.
+ * <P>Type: String (file path)</P>
+ */
+ public static final String STATUSBAR_BACKGROUND = "statusbar_background";
+
+ /**
+ * Cached image of the themed bluetooth status icon.
+ * <P>Type: String (file path)</P>
+ */
+ public static final String STATUSBAR_BLUETOOTH_ICON = "statusbar_bluetooth_icon";
+
+ /**
+ * Cached image of the themed wifi status icon.
+ * <P>Type: String (file path)</P>
+ */
+ public static final String STATUSBAR_WIFI_ICON = "statusbar_wifi_icon";
+
+ /**
+ * Cached image of the themed cellular signal status icon.
+ * <P>Type: String (file path)</P>
+ */
+ public static final String STATUSBAR_SIGNAL_ICON = "statusbar_signal_icon";
+
+ /**
+ * Cached image of the themed battery using portrait style.
+ * <P>Type: String (file path)</P>
+ */
+ public static final String STATUSBAR_BATTERY_PORTRAIT = "statusbar_battery_portrait";
+
+ /**
+ * Cached image of the themed battery using landscape style.
+ * <P>Type: String (file path)</P>
+ */
+ public static final String STATUSBAR_BATTERY_LANDSCAPE = "statusbar_battery_landscape";
+
+ /**
+ * Cached image of the themed battery using circle style.
+ * <P>Type: String (file path)</P>
+ */
+ public static final String STATUSBAR_BATTERY_CIRCLE = "statusbar_battery_circle";
+
+ /**
+ * The themed color used for clock text in the status bar.
+ * <P>Type: INTEGER (int)</P>
+ */
+ public static final String STATUSBAR_CLOCK_TEXT_COLOR = "statusbar_clock_text_color";
+
+ /**
+ * The themed margin value between the wifi and rssi signal icons.
+ * <P>Type: INTEGER (int)</P>
+ */
+ public static final String STATUSBAR_WIFI_COMBO_MARGIN_END = "wifi_combo_margin_end";
+
+ /**
+ * Cached image of the themed navigation bar background.
+ * <P>Type: String (file path)</P>
+ */
+ public static final String NAVBAR_BACKGROUND = "navbar_background";
+
+ /**
+ * Cached image of the themed back button.
+ * <P>Type: String (file path)</P>
+ */
+ public static final String NAVBAR_BACK_BUTTON = "navbar_back_button";
+
+ /**
+ * Cached image of the themed home button.
+ * <P>Type: String (file path)</P>
+ */
+ public static final String NAVBAR_HOME_BUTTON = "navbar_home_button";
+
+ /**
+ * Cached image of the themed recents button.
+ * <P>Type: String (file path)</P>
+ */
+ public static final String NAVBAR_RECENT_BUTTON = "navbar_recent_button";
+
+ /**
+ * Cached image of the 1/3 icons
+ * <P>Type: String (file path)</P>
+ */
+ public static final String ICON_PREVIEW_1 = "icon_preview_1";
+
+ /**
+ * Cached image of the 2/3 icons
+ * <P>Type: String (file path)</P>
+ */
+ public static final String ICON_PREVIEW_2 = "icon_preview_2";
+
+ /**
+ * Cached image of the 3/3 icons
+ * <P>Type: String (file path)</P>
+ */
+ public static final String ICON_PREVIEW_3 = "icon_preview_3";
+
+ /**
+ * Full path to the theme's wallpaper asset.
+ * <P>Type: String (file path)</P>
+ */
+ public static final String WALLPAPER_FULL = "wallpaper_full";
+
+ /**
+ * Cached preview of the theme's wallpaper which is larger than the thumbnail
+ * but smaller than the full sized wallpaper.
+ * <P>Type: String (file path)</P>
+ */
+ public static final String WALLPAPER_PREVIEW = "wallpaper_preview";
+
+ /**
+ * Cached thumbnail of the theme's wallpaper
+ * <P>Type: String (file path)</P>
+ */
+ public static final String WALLPAPER_THUMBNAIL = "wallpaper_thumbnail";
+
+ /**
+ * Cached preview of the theme's lockscreen wallpaper which is larger than the thumbnail
+ * but smaller than the full sized lockscreen wallpaper.
+ * <P>Type: String (file path)</P>
+ */
+ public static final String LOCK_WALLPAPER_PREVIEW = "lock_wallpaper_preview";
+
+ /**
+ * Cached thumbnail of the theme's lockscreen wallpaper
+ * <P>Type: String (file path)</P>
+ */
+ public static final String LOCK_WALLPAPER_THUMBNAIL = "lock_wallpaper_thumbnail";
+
+ /**
+ * Cached preview of UI controls representing the theme's style
+ * <P>Type: String (file path)</P>
+ */
+ public static final String STYLE_PREVIEW = "style_preview";
+
+ /**
+ * Cached thumbnail preview of UI controls representing the theme's style
+ * <P>Type: String (file path)</P>
+ */
+ public static final String STYLE_THUMBNAIL = "style_thumbnail";
+
+ /**
+ * Cached thumbnail of the theme's boot animation
+ * <P>Type: String (file path)</P>
+ */
+ public static final String BOOTANIMATION_THUMBNAIL = "bootanimation_thumbnail";
+
+ /**
+ * Cached preview of live lock screen
+ * <P>Type: String (file path)</P>
+ */
+ public static final String LIVE_LOCK_SCREEN_PREVIEW = "live_lock_screen_preview";
+
+ /**
+ * Cached thumbnail preview of live lock screen
+ * <P>Type: String (file path)</P>
+ */
+ public static final String LIVE_LOCK_SCREEN_THUMBNAIL = "live_lock_screen_thumbnail";
+
+ public static final String[] VALID_KEYS = {
+ STATUSBAR_BACKGROUND,
+ STATUSBAR_BLUETOOTH_ICON,
+ STATUSBAR_WIFI_ICON,
+ STATUSBAR_SIGNAL_ICON,
+ STATUSBAR_BATTERY_PORTRAIT,
+ STATUSBAR_BATTERY_LANDSCAPE,
+ STATUSBAR_BATTERY_CIRCLE,
+ STATUSBAR_CLOCK_TEXT_COLOR,
+ STATUSBAR_WIFI_COMBO_MARGIN_END,
+ NAVBAR_BACKGROUND,
+ NAVBAR_BACK_BUTTON,
+ NAVBAR_HOME_BUTTON,
+ NAVBAR_RECENT_BUTTON,
+ ICON_PREVIEW_1,
+ ICON_PREVIEW_2,
+ ICON_PREVIEW_3,
+ WALLPAPER_FULL,
+ WALLPAPER_PREVIEW,
+ WALLPAPER_THUMBNAIL,
+ LOCK_WALLPAPER_PREVIEW,
+ LOCK_WALLPAPER_THUMBNAIL,
+ STYLE_PREVIEW,
+ STYLE_THUMBNAIL,
+ BOOTANIMATION_THUMBNAIL,
+ LIVE_LOCK_SCREEN_PREVIEW,
+ LIVE_LOCK_SCREEN_THUMBNAIL,
+ };
+ }
+}
diff --git a/sdk/src/java/cyanogenmod/providers/WeatherContract.java b/sdk/src/java/cyanogenmod/providers/WeatherContract.java
new file mode 100644
index 0000000..e8e3726
--- /dev/null
+++ b/sdk/src/java/cyanogenmod/providers/WeatherContract.java
@@ -0,0 +1,240 @@
+/*
+ * Copyright (C) 2016 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.providers;
+
+import android.net.Uri;
+
+/**
+ * The contract between the weather provider and applications.
+ */
+public class WeatherContract {
+
+ /**
+ * The authority of the weather content provider
+ */
+ public static final String AUTHORITY = "com.cyanogenmod.weather";
+
+ /**
+ * A content:// style uri to the authority for the weather provider
+ */
+ public static final Uri AUTHORITY_URI = Uri.parse("content://" + AUTHORITY);
+
+ public static class WeatherColumns {
+ public static final Uri CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, "weather");
+
+ public static final Uri CURRENT_AND_FORECAST_WEATHER_URI
+ = Uri.withAppendedPath(CONTENT_URI, "current_and_forecast");
+ public static final Uri CURRENT_WEATHER_URI
+ = Uri.withAppendedPath(CONTENT_URI, "current");
+ public static final Uri FORECAST_WEATHER_URI
+ = Uri.withAppendedPath(CONTENT_URI, "forecast");
+
+ /**
+ * A unique ID for the city. NOTE: this value fully depends on the implementation of the
+ * weather provider service and can potentially change when you switch providers.
+ * <P>Type: TEXT</P>
+ */
+ public static final String CURRENT_CITY_ID = "city_id";
+
+ /**
+ * The city name
+ * <P>Type: TEXT</P>
+ */
+ public static final String CURRENT_CITY = "city";
+
+ /**
+ * A Valid {@link WeatherCode}
+ * <P>Type: INTEGER</P>
+ */
+ public static final String CURRENT_CONDITION_CODE = "condition_code";
+
+
+ /**
+ * A localized string mapped to the current weather condition code. Note that, if no
+ * locale is found, the string will be in english
+ * <P>Type: TEXT</P>
+ */
+ public static final String CURRENT_CONDITION = "condition";
+
+ /**
+ * The current weather temperature
+ * <P>Type: FLOAT</P>
+ */
+ public static final String CURRENT_TEMPERATURE = "temperature";
+
+ /**
+ * The unit in which current temperature is reported
+ * <P>Type: INTEGER</P>
+ * Can be one of the following:
+ * <ul>
+ * <li>{@link TempUnit#CELSIUS}</li>
+ * <li>{@link TempUnit#FAHRENHEIT}</li>
+ * </ul>
+ */
+ public static final String CURRENT_TEMPERATURE_UNIT = "temperature_unit";
+
+ /**
+ * The current weather humidity
+ * <P>Type: FLOAT</P>
+ */
+ public static final String CURRENT_HUMIDITY = "humidity";
+
+ /**
+ * The current wind direction (in degrees)
+ * <P>Type: FLOAT</P>
+ */
+ public static final String CURRENT_WIND_DIRECTION = "wind_direction";
+
+ /**
+ * The current wind speed
+ * <P>Type: FLOAT</P>
+ */
+ public static final String CURRENT_WIND_SPEED = "wind_speed";
+
+ /**
+ * The unit in which the wind speed is reported
+ * <P>Type: INTEGER</P>
+ * Can be one of the following:
+ * <ul>
+ * <li>{@link WindSpeedUnit#KPH}</li>
+ * <li>{@link WindSpeedUnit#MPH}</li>
+ * </ul>
+ */
+ public static final String CURRENT_WIND_SPEED_UNIT = "wind_speed_unit";
+
+ /**
+ * The timestamp when this weather was reported
+ * <P>Type: LONG</P>
+ */
+ public static final String CURRENT_TIMESTAMP = "timestamp";
+
+ /**
+ * The forecasted low temperature
+ * <P>Type: FLOAT</P>
+ */
+ public static final String FORECAST_LOW = "forecast_low";
+
+ /**
+ * The forecasted high temperature
+ * <P>Type: FLOAT</P>
+ */
+ public static final String FORECAST_HIGH = "forecast_high";
+
+ /**
+ * A localized string mapped to the forecasted weather condition code. Note that, if no
+ * locale is found, the string will be in english
+ * <P>Type: TEXT</P>
+ */
+ public static final String FORECAST_CONDITION = "forecast_condition";
+
+ /**
+ * The code identifying the forecasted weather condition.
+ * @see #CURRENT_CONDITION_CODE
+ */
+ public static final String FORECAST_CONDITION_CODE = "forecast_condition_code";
+
+ /**
+ * Temperature units
+ */
+ public static final class TempUnit {
+ private TempUnit() {}
+ public final static int CELSIUS = 1;
+ public final static int FAHRENHEIT = 2;
+ }
+
+ /**
+ * Wind speed units
+ */
+ public static final class WindSpeedUnit {
+ private WindSpeedUnit() {}
+ /**
+ * Kilometers per hour
+ */
+ public final static int KPH = 1;
+
+ /**
+ * Miles per hour
+ */
+ public final static int MPH = 2;
+ }
+
+ /**
+ * Weather condition codes
+ */
+ public static final class WeatherCode {
+ private WeatherCode() {}
+
+ /**
+ * @hide
+ */
+ public final static int WEATHER_CODE_MIN = 0;
+
+ public final static int TORNADO = 0;
+ public final static int TROPICAL_STORM = 1;
+ public final static int HURRICANE = 2;
+ public final static int SEVERE_THUNDERSTORMS = 3;
+ public final static int THUNDERSTORMS = 4;
+ public final static int MIXED_RAIN_AND_SNOW = 5;
+ public final static int MIXED_RAIN_AND_SLEET = 6;
+ public final static int MIXED_SNOW_AND_SLEET = 7;
+ public final static int FREEZING_DRIZZLE = 8;
+ public final static int DRIZZLE = 9;
+ public final static int FREEZING_RAIN = 10;
+ public final static int SHOWERS = 11;
+ public final static int SNOW_FLURRIES = 12;
+ public final static int LIGHT_SNOW_SHOWERS = 13;
+ public final static int BLOWING_SNOW = 14;
+ public final static int SNOW = 15;
+ public final static int HAIL = 16;
+ public final static int SLEET = 17;
+ public final static int DUST = 18;
+ public final static int FOGGY = 19;
+ public final static int HAZE = 20;
+ public final static int SMOKY = 21;
+ public final static int BLUSTERY = 22;
+ public final static int WINDY = 23;
+ public final static int COLD = 24;
+ public final static int CLOUDY = 25;
+ public final static int MOSTLY_CLOUDY_NIGHT = 26;
+ public final static int MOSTLY_CLOUDY_DAY = 27;
+ public final static int PARTLY_CLOUDY_NIGHT = 28;
+ public final static int PARTLY_CLOUDY_DAY = 29;
+ public final static int CLEAR_NIGHT = 30;
+ public final static int SUNNY = 31;
+ public final static int FAIR_NIGHT = 32;
+ public final static int FAIR_DAY = 33;
+ public final static int MIXED_RAIN_AND_HAIL = 34;
+ public final static int HOT = 35;
+ public final static int ISOLATED_THUNDERSTORMS = 36;
+ public final static int SCATTERED_THUNDERSTORMS = 37;
+ public final static int SCATTERED_SHOWERS = 38;
+ public final static int HEAVY_SNOW = 39;
+ public final static int SCATTERED_SNOW_SHOWERS = 40;
+ public final static int PARTLY_CLOUDY = 41;
+ public final static int THUNDERSHOWER = 42;
+ public final static int SNOW_SHOWERS = 43;
+ public final static int ISOLATED_THUNDERSHOWERS = 44;
+
+ /**
+ * @hide
+ */
+ public final static int WEATHER_CODE_MAX = 44;
+
+ public final static int NOT_AVAILABLE = 3200;
+ }
+ }
+} \ No newline at end of file
diff --git a/sdk/src/java/cyanogenmod/themes/IThemeChangeListener.aidl b/sdk/src/java/cyanogenmod/themes/IThemeChangeListener.aidl
new file mode 100644
index 0000000..0700eb6
--- /dev/null
+++ b/sdk/src/java/cyanogenmod/themes/IThemeChangeListener.aidl
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2014-2016 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.themes;
+
+/** {@hide} */
+oneway interface IThemeChangeListener {
+ void onProgress(int progress);
+ void onFinish(boolean isSuccess);
+}
diff --git a/sdk/src/java/cyanogenmod/themes/IThemeProcessingListener.aidl b/sdk/src/java/cyanogenmod/themes/IThemeProcessingListener.aidl
new file mode 100644
index 0000000..648e1a9
--- /dev/null
+++ b/sdk/src/java/cyanogenmod/themes/IThemeProcessingListener.aidl
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2014-2016 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.themes;
+
+/** {@hide} */
+oneway interface IThemeProcessingListener {
+ void onFinishedProcessing(String pkgName);
+}
diff --git a/sdk/src/java/cyanogenmod/themes/IThemeService.aidl b/sdk/src/java/cyanogenmod/themes/IThemeService.aidl
new file mode 100644
index 0000000..fa186e9
--- /dev/null
+++ b/sdk/src/java/cyanogenmod/themes/IThemeService.aidl
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2014-2016 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.themes;
+
+import cyanogenmod.themes.IThemeChangeListener;
+import cyanogenmod.themes.IThemeProcessingListener;
+import cyanogenmod.themes.ThemeChangeRequest;
+
+import java.util.Map;
+
+/** {@hide} */
+interface IThemeService {
+ oneway void requestThemeChangeUpdates(in IThemeChangeListener listener);
+ oneway void removeUpdates(in IThemeChangeListener listener);
+
+ oneway void requestThemeChange(in ThemeChangeRequest request, boolean removePerAppThemes);
+ oneway void applyDefaultTheme();
+ boolean isThemeApplying();
+ int getProgress();
+
+ boolean processThemeResources(String themePkgName);
+ boolean isThemeBeingProcessed(String themePkgName);
+ oneway void registerThemeProcessingListener(in IThemeProcessingListener listener);
+ oneway void unregisterThemeProcessingListener(in IThemeProcessingListener listener);
+
+ oneway void rebuildResourceCache();
+
+ long getLastThemeChangeTime();
+ int getLastThemeChangeRequestType();
+}
diff --git a/sdk/src/java/cyanogenmod/themes/ThemeChangeRequest.aidl b/sdk/src/java/cyanogenmod/themes/ThemeChangeRequest.aidl
new file mode 100644
index 0000000..e1d9e4f
--- /dev/null
+++ b/sdk/src/java/cyanogenmod/themes/ThemeChangeRequest.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2015-2016 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.themes;
+
+parcelable ThemeChangeRequest;
diff --git a/sdk/src/java/cyanogenmod/themes/ThemeChangeRequest.java b/sdk/src/java/cyanogenmod/themes/ThemeChangeRequest.java
new file mode 100644
index 0000000..f8eeeb5
--- /dev/null
+++ b/sdk/src/java/cyanogenmod/themes/ThemeChangeRequest.java
@@ -0,0 +1,322 @@
+/*
+ * Copyright (C) 2015-2016 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.themes;
+
+import android.content.pm.ThemeUtils;
+import android.content.res.ThemeConfig;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import cyanogenmod.os.Concierge;
+import cyanogenmod.os.Concierge.ParcelInfo;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import static cyanogenmod.providers.ThemesContract.ThemesColumns.*;
+
+public final class ThemeChangeRequest implements Parcelable {
+ public static final int DEFAULT_WALLPAPER_ID = -1;
+
+ private final Map<String, String> mThemeComponents = new HashMap<>();
+ private final Map<String, String> mPerAppOverlays = new HashMap<>();
+ private RequestType mRequestType;
+ private long mWallpaperId = -1;
+
+ public String getOverlayThemePackageName() {
+ return getThemePackageNameForComponent(MODIFIES_OVERLAYS);
+ }
+
+ public String getStatusBarThemePackageName() {
+ return getThemePackageNameForComponent(MODIFIES_STATUS_BAR);
+ }
+
+ public String getNavBarThemePackageName() {
+ return getThemePackageNameForComponent(MODIFIES_NAVIGATION_BAR);
+ }
+
+ public String getFontThemePackageName() {
+ return getThemePackageNameForComponent(MODIFIES_FONTS);
+ }
+
+ public String getIconsThemePackageName() {
+ return getThemePackageNameForComponent(MODIFIES_ICONS);
+ }
+
+ public String getBootanimationThemePackageName() {
+ return getThemePackageNameForComponent(MODIFIES_BOOT_ANIM);
+ }
+
+ public String getWallpaperThemePackageName() {
+ return getThemePackageNameForComponent(MODIFIES_LAUNCHER);
+ }
+
+ public String getLockWallpaperThemePackageName() {
+ return getThemePackageNameForComponent(MODIFIES_LOCKSCREEN);
+ }
+
+ public String getAlarmThemePackageName() {
+ return getThemePackageNameForComponent(MODIFIES_ALARMS);
+ }
+
+ public String getNotificationThemePackageName() {
+ return getThemePackageNameForComponent(MODIFIES_NOTIFICATIONS);
+ }
+
+ public String getRingtoneThemePackageName() {
+ return getThemePackageNameForComponent(MODIFIES_RINGTONES);
+ }
+
+ public String getLiveLockScreenThemePackageName() {
+ return getThemePackageNameForComponent(MODIFIES_LIVE_LOCK_SCREEN);
+ }
+
+ public final Map<String, String> getThemeComponentsMap() {
+ return Collections.unmodifiableMap(mThemeComponents);
+ }
+
+ public long getWallpaperId() {
+ return mWallpaperId;
+ }
+
+ /**
+ * Get the mapping for per app themes
+ * @return A mapping of apps and the theme to apply for each one. or null if none set.
+ */
+ public final Map<String, String> getPerAppOverlays() {
+ return Collections.unmodifiableMap(mPerAppOverlays);
+ }
+
+ public int getNumChangesRequested() {
+ return mThemeComponents.size() + mPerAppOverlays.size();
+ }
+
+ public RequestType getReqeustType() {
+ return mRequestType;
+ }
+
+ private String getThemePackageNameForComponent(String componentName) {
+ return mThemeComponents.get(componentName);
+ }
+
+ private ThemeChangeRequest(Map<String, String> components, Map<String, String> perAppThemes,
+ RequestType requestType, long wallpaperId) {
+ if (components != null) {
+ mThemeComponents.putAll(components);
+ }
+ if (perAppThemes != null) {
+ mPerAppOverlays.putAll(perAppThemes);
+ }
+ mRequestType = requestType;
+ mWallpaperId = wallpaperId;
+ }
+
+ private ThemeChangeRequest(Parcel source) {
+ // Read parcelable version via the Concierge
+ ParcelInfo parcelInfo = Concierge.receiveParcel(source);
+ int parcelableVersion = parcelInfo.getParcelVersion();
+
+ int numComponents = source.readInt();
+ for (int i = 0; i < numComponents; i++) {
+ mThemeComponents.put(source.readString(), source.readString());
+ }
+
+ numComponents = source.readInt();
+ for (int i = 0 ; i < numComponents; i++) {
+ mPerAppOverlays.put(source.readString(), source.readString());
+ }
+ mRequestType = RequestType.values()[source.readInt()];
+ mWallpaperId = source.readLong();
+
+ // Complete parcel info for the concierge
+ parcelInfo.complete();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ // Tell the concierge to prepare the parcel
+ ParcelInfo parcelInfo = Concierge.prepareParcel(dest);
+
+ dest.writeInt(mThemeComponents.size());
+ for (String component : mThemeComponents.keySet()) {
+ dest.writeString(component);
+ dest.writeString(mThemeComponents.get(component));
+ }
+ dest.writeInt((mPerAppOverlays.size()));
+ for (String appPkgName : mPerAppOverlays.keySet()) {
+ dest.writeString(appPkgName);
+ dest.writeString(mPerAppOverlays.get(appPkgName));
+ }
+ dest.writeInt(mRequestType.ordinal());
+ dest.writeLong(mWallpaperId);
+
+ // Complete the parcel info for the concierge
+ parcelInfo.complete();
+ }
+
+ public static final Parcelable.Creator<ThemeChangeRequest> CREATOR =
+ new Parcelable.Creator<ThemeChangeRequest>() {
+ @Override
+ public ThemeChangeRequest createFromParcel(Parcel source) {
+ return new ThemeChangeRequest(source);
+ }
+
+ @Override
+ public ThemeChangeRequest[] newArray(int size) {
+ return new ThemeChangeRequest[size];
+ }
+ };
+
+ public enum RequestType {
+ USER_REQUEST,
+ USER_REQUEST_MIXNMATCH,
+ THEME_UPDATED,
+ THEME_REMOVED,
+ THEME_RESET
+ }
+
+ public static class Builder {
+ Map<String, String> mThemeComponents = new HashMap<>();
+ Map<String, String> mPerAppOverlays = new HashMap<>();
+ RequestType mRequestType = RequestType.USER_REQUEST;
+ long mWallpaperId;
+
+ public Builder() {}
+
+ public Builder(ThemeConfig themeConfig) {
+ if (themeConfig != null) {
+ buildChangeRequestFromThemeConfig(themeConfig);
+ }
+ }
+
+ public Builder setOverlay(String pkgName) {
+ return setComponent(MODIFIES_OVERLAYS, pkgName);
+ }
+
+ public Builder setStatusBar(String pkgName) {
+ return setComponent(MODIFIES_STATUS_BAR, pkgName);
+ }
+
+ public Builder setNavBar(String pkgName) {
+ return setComponent(MODIFIES_NAVIGATION_BAR, pkgName);
+ }
+
+ public Builder setFont(String pkgName) {
+ return setComponent(MODIFIES_FONTS, pkgName);
+ }
+
+ public Builder setIcons(String pkgName) {
+ return setComponent(MODIFIES_ICONS, pkgName);
+ }
+
+ public Builder setBootanimation(String pkgName) {
+ return setComponent(MODIFIES_BOOT_ANIM, pkgName);
+ }
+
+ public Builder setWallpaper(String pkgName) {
+ return setComponent(MODIFIES_LAUNCHER, pkgName);
+ }
+
+ // Used in the case that more than one wallpaper exists for a given pkg name
+ public Builder setWallpaperId(long id) {
+ mWallpaperId = id;
+ return this;
+ }
+
+ public Builder setLockWallpaper(String pkgName) {
+ return setComponent(MODIFIES_LOCKSCREEN, pkgName);
+ }
+
+ public Builder setAlarm(String pkgName) {
+ return setComponent(MODIFIES_ALARMS, pkgName);
+ }
+
+ public Builder setNotification(String pkgName) {
+ return setComponent(MODIFIES_NOTIFICATIONS, pkgName);
+ }
+
+ public Builder setRingtone(String pkgName) {
+ return setComponent(MODIFIES_RINGTONES, pkgName);
+ }
+
+ public Builder setLiveLockScreen(String pkgName) {
+ return setComponent(MODIFIES_LIVE_LOCK_SCREEN, pkgName);
+ }
+
+ public Builder setComponent(String component, String pkgName) {
+ if (pkgName != null) {
+ mThemeComponents.put(component, pkgName);
+ } else {
+ mThemeComponents.remove(component);
+ }
+ return this;
+ }
+
+ public Builder setAppOverlay(String appPkgName, String themePkgName) {
+ if (appPkgName != null) {
+ if (themePkgName != null) {
+ mPerAppOverlays.put(appPkgName, themePkgName);
+ } else {
+ mPerAppOverlays.remove(appPkgName);
+ }
+ }
+
+ return this;
+ }
+
+ public Builder setRequestType(RequestType requestType) {
+ mRequestType = requestType != null ? requestType : RequestType.USER_REQUEST;
+ return this;
+ }
+
+ public ThemeChangeRequest build() {
+ return new ThemeChangeRequest(mThemeComponents, mPerAppOverlays,
+ mRequestType, mWallpaperId);
+ }
+
+ private void buildChangeRequestFromThemeConfig(ThemeConfig themeConfig) {
+ if (themeConfig.getFontPkgName() != null) {
+ this.setFont(themeConfig.getFontPkgName());
+ }
+ if (themeConfig.getIconPackPkgName() != null) {
+ this.setIcons(themeConfig.getIconPackPkgName());
+ }
+ if (themeConfig.getOverlayPkgName() != null) {
+ this.setOverlay(themeConfig.getOverlayPkgName());
+ }
+ if (themeConfig.getOverlayForStatusBar() != null) {
+ this.setStatusBar(themeConfig.getOverlayForStatusBar());
+ }
+ if (themeConfig.getOverlayForNavBar() != null) {
+ this.setNavBar(themeConfig.getOverlayForNavBar());
+ }
+
+ // Check if there are any per-app overlays using this theme
+ final Map<String, ThemeConfig.AppTheme> themes = themeConfig.getAppThemes();
+ for (String appPkgName : themes.keySet()) {
+ if (ThemeUtils.isPerAppThemeComponent(appPkgName)) {
+ this.setAppOverlay(appPkgName, themes.get(appPkgName).getOverlayPkgName());
+ }
+ }
+ }
+ }
+}
diff --git a/sdk/src/java/cyanogenmod/themes/ThemeManager.java b/sdk/src/java/cyanogenmod/themes/ThemeManager.java
new file mode 100644
index 0000000..5fbbde7
--- /dev/null
+++ b/sdk/src/java/cyanogenmod/themes/ThemeManager.java
@@ -0,0 +1,391 @@
+/*
+ * Copyright (C) 2014-2016 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.themes;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.ArraySet;
+import android.util.Log;
+
+import cyanogenmod.app.CMContextConstants;
+import cyanogenmod.themes.ThemeChangeRequest.RequestType;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Manages changing and applying of themes.
+ * <p>Get an instance of this class by calling blah blah blah</p>
+ */
+public class ThemeManager {
+ private static final String TAG = ThemeManager.class.getName();
+ private static IThemeService sService;
+ private static ThemeManager sInstance;
+ private static Handler mHandler;
+
+ private Set<ThemeChangeListener> mChangeListeners = new ArraySet<>();
+
+ private Set<ThemeProcessingListener> mProcessingListeners = new ArraySet<>();
+
+ private ThemeManager(Context context) {
+ sService = getService();
+ if (context.getPackageManager().hasSystemFeature(
+ CMContextConstants.Features.THEMES) && sService == null) {
+ throw new RuntimeException("Unable to get ThemeManagerService. The service either" +
+ " crashed, was not started, or the interface has been called to early in" +
+ " SystemServer init");
+ }
+ mHandler = new Handler(Looper.getMainLooper());
+ }
+
+ public static ThemeManager getInstance(Context context) {
+ if (sInstance == null) {
+ sInstance = new ThemeManager(context);
+ }
+
+ return sInstance;
+ }
+
+ /** @hide */
+ public static IThemeService getService() {
+ if (sService != null) {
+ return sService;
+ }
+ IBinder b = ServiceManager.getService(CMContextConstants.CM_THEME_SERVICE);
+ if (b != null) {
+ sService = IThemeService.Stub.asInterface(b);
+ return sService;
+ }
+ return null;
+ }
+
+ private final IThemeChangeListener mThemeChangeListener = new IThemeChangeListener.Stub() {
+ @Override
+ public void onProgress(final int progress) throws RemoteException {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ synchronized (mChangeListeners) {
+ List<ThemeChangeListener> listenersToRemove = new ArrayList<>();
+ for (ThemeChangeListener listener : mChangeListeners) {
+ try {
+ listener.onProgress(progress);
+ } catch (Throwable e) {
+ Log.w(TAG, "Unable to update theme change progress", e);
+ listenersToRemove.add(listener);
+ }
+ }
+ if (listenersToRemove.size() > 0) {
+ for (ThemeChangeListener listener : listenersToRemove) {
+ mChangeListeners.remove(listener);
+ }
+ }
+ }
+ }
+ });
+ }
+
+ @Override
+ public void onFinish(final boolean isSuccess) throws RemoteException {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ synchronized (mChangeListeners) {
+ List<ThemeChangeListener> listenersToRemove = new ArrayList<>();
+ for (ThemeChangeListener listener : mChangeListeners) {
+ try {
+ listener.onFinish(isSuccess);
+ } catch (Throwable e) {
+ Log.w(TAG, "Unable to update theme change listener", e);
+ listenersToRemove.add(listener);
+ }
+ }
+ if (listenersToRemove.size() > 0) {
+ for (ThemeChangeListener listener : listenersToRemove) {
+ mChangeListeners.remove(listener);
+ }
+ }
+ }
+ }
+ });
+ }
+ };
+
+ private final IThemeProcessingListener mThemeProcessingListener =
+ new IThemeProcessingListener.Stub() {
+ @Override
+ public void onFinishedProcessing(final String pkgName) throws RemoteException {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ synchronized (mProcessingListeners) {
+ List<ThemeProcessingListener> listenersToRemove = new ArrayList<>();
+ for (ThemeProcessingListener listener : mProcessingListeners) {
+ try {
+ listener.onFinishedProcessing(pkgName);
+ } catch (Throwable e) {
+ Log.w(TAG, "Unable to update theme change progress", e);
+ listenersToRemove.add(listener);
+ }
+ }
+ if (listenersToRemove.size() > 0) {
+ for (ThemeProcessingListener listener : listenersToRemove) {
+ mProcessingListeners.remove(listener);
+ }
+ }
+ }
+ }
+ });
+ }
+ };
+
+
+ /**
+ * @deprecated Use {@link ThemeManager#registerThemeChangeListener(ThemeChangeListener)} instead
+ */
+ public void addClient(ThemeChangeListener listener) {
+ registerThemeChangeListener(listener);
+ }
+
+ /**
+ * @deprecated Use {@link ThemeManager#unregisterThemeChangeListener(ThemeChangeListener)}
+ * instead
+ */
+ public void removeClient(ThemeChangeListener listener) {
+ unregisterThemeChangeListener(listener);
+ }
+
+ /**
+ * @deprecated Use {@link ThemeManager#unregisterThemeChangeListener(ThemeChangeListener)}
+ * instead
+ */
+ public void onClientPaused(ThemeChangeListener listener) {
+ unregisterThemeChangeListener(listener);
+ }
+
+ /**
+ * @deprecated Use {@link ThemeManager#registerThemeChangeListener(ThemeChangeListener)} instead
+ */
+ public void onClientResumed(ThemeChangeListener listener) {
+ registerThemeChangeListener(listener);
+ }
+
+ /**
+ * @deprecated Use {@link ThemeManager#unregisterThemeChangeListener(ThemeChangeListener)}
+ * instead
+ */
+ public void onClientDestroyed(ThemeChangeListener listener) {
+ unregisterThemeChangeListener(listener);
+ }
+
+ /**
+ * Register a {@link ThemeChangeListener} to be notified when a theme is done being processed.
+ * @param listener {@link ThemeChangeListener} to register
+ */
+ public void registerThemeChangeListener(ThemeChangeListener listener) {
+ synchronized (mChangeListeners) {
+ if (mChangeListeners.contains(listener)) {
+ throw new IllegalArgumentException("Listener already registered");
+ }
+ if (mChangeListeners.size() == 0) {
+ try {
+ sService.requestThemeChangeUpdates(mThemeChangeListener);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Unable to register listener", e);
+ }
+ }
+ mChangeListeners.add(listener);
+ }
+ }
+
+ /**
+ * Unregister a {@link ThemeChangeListener}
+ * @param listener {@link ThemeChangeListener} to unregister
+ */
+ public void unregisterThemeChangeListener(ThemeChangeListener listener) {
+ synchronized (mChangeListeners) {
+ mChangeListeners.remove(listener);
+ if (mChangeListeners.size() == 0) {
+ try {
+ sService.removeUpdates(mThemeChangeListener);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Unable to unregister listener", e);
+ }
+ }
+ }
+ }
+
+ /**
+ * Register a {@link ThemeProcessingListener} to be notified when a theme is done being
+ * processed.
+ * @param listener {@link ThemeProcessingListener} to register
+ */
+ public void registerProcessingListener(ThemeProcessingListener listener) {
+ synchronized (mProcessingListeners) {
+ if (mProcessingListeners.contains(listener)) {
+ throw new IllegalArgumentException("Listener already registered");
+ }
+ if (mProcessingListeners.size() == 0) {
+ try {
+ sService.registerThemeProcessingListener(mThemeProcessingListener);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Unable to register listener", e);
+ }
+ }
+ mProcessingListeners.add(listener);
+ }
+ }
+
+ /**
+ * Unregister a {@link ThemeProcessingListener}.
+ * @param listener {@link ThemeProcessingListener} to unregister
+ */
+ public void unregisterProcessingListener(ThemeProcessingListener listener) {
+ synchronized (mProcessingListeners) {
+ mProcessingListeners.remove(listener);
+ if (mProcessingListeners.size() == 0) {
+ try {
+ sService.unregisterThemeProcessingListener(mThemeProcessingListener);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Unable to unregister listener", e);
+ }
+ }
+ }
+ }
+
+ public void requestThemeChange(String pkgName, List<String> components) {
+ requestThemeChange(pkgName, components, true);
+ }
+
+ public void requestThemeChange(String pkgName, List<String> components,
+ boolean removePerAppThemes) {
+ Map<String, String> componentMap = new HashMap<>(components.size());
+ for (String component : components) {
+ componentMap.put(component, pkgName);
+ }
+ requestThemeChange(componentMap, removePerAppThemes);
+ }
+
+ public void requestThemeChange(Map<String, String> componentMap) {
+ requestThemeChange(componentMap, true);
+ }
+
+ public void requestThemeChange(Map<String, String> componentMap, boolean removePerAppThemes) {
+ ThemeChangeRequest.Builder builder = new ThemeChangeRequest.Builder();
+ for (String component : componentMap.keySet()) {
+ builder.setComponent(component, componentMap.get(component));
+ }
+
+ requestThemeChange(builder.build(), removePerAppThemes);
+ }
+
+ public void requestThemeChange(ThemeChangeRequest request, boolean removePerAppThemes) {
+ try {
+ sService.requestThemeChange(request, removePerAppThemes);
+ } catch (RemoteException e) {
+ logThemeServiceException(e);
+ }
+ }
+
+ public void applyDefaultTheme() {
+ try {
+ sService.applyDefaultTheme();
+ } catch (RemoteException e) {
+ logThemeServiceException(e);
+ }
+ }
+
+ public boolean isThemeApplying() {
+ try {
+ return sService.isThemeApplying();
+ } catch (RemoteException e) {
+ logThemeServiceException(e);
+ }
+
+ return false;
+ }
+
+ public boolean isThemeBeingProcessed(String themePkgName) {
+ try {
+ return sService.isThemeBeingProcessed(themePkgName);
+ } catch (RemoteException e) {
+ logThemeServiceException(e);
+ }
+ return false;
+ }
+
+ public int getProgress() {
+ try {
+ return sService.getProgress();
+ } catch (RemoteException e) {
+ logThemeServiceException(e);
+ }
+ return -1;
+ }
+
+ public boolean processThemeResources(String themePkgName) {
+ try {
+ return sService.processThemeResources(themePkgName);
+ } catch (RemoteException e) {
+ logThemeServiceException(e);
+ }
+ return false;
+ }
+
+ public long getLastThemeChangeTime() {
+ try {
+ return sService.getLastThemeChangeTime();
+ } catch (RemoteException e) {
+ logThemeServiceException(e);
+ }
+ return 0;
+ }
+
+ public ThemeChangeRequest.RequestType getLastThemeChangeRequestType() {
+ try {
+ int type = sService.getLastThemeChangeRequestType();
+ return (type >= 0 && type < RequestType.values().length)
+ ? RequestType.values()[type]
+ : null;
+ } catch (RemoteException e) {
+ logThemeServiceException(e);
+ }
+
+ return null;
+ }
+
+ private void logThemeServiceException(Exception e) {
+ Log.w(TAG, "Unable to access ThemeService", e);
+ }
+
+ public interface ThemeChangeListener {
+ void onProgress(int progress);
+ void onFinish(boolean isSuccess);
+ }
+
+ public interface ThemeProcessingListener {
+ void onFinishedProcessing(String pkgName);
+ }
+}
+
diff --git a/sdk/src/java/cyanogenmod/util/ColorUtils.java b/sdk/src/java/cyanogenmod/util/ColorUtils.java
new file mode 100644
index 0000000..0142396
--- /dev/null
+++ b/sdk/src/java/cyanogenmod/util/ColorUtils.java
@@ -0,0 +1,526 @@
+/*
+ * Copyright (c) 2011-2016 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.util;
+
+import android.graphics.Bitmap;
+import android.graphics.Color;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.util.MathUtils;
+
+import com.android.internal.util.cm.palette.Palette;
+
+import java.util.Collections;
+import java.util.Comparator;
+
+/**
+ * Helper class for colorspace conversions, and color-related
+ * algorithms which may be generally useful.
+ */
+public class ColorUtils {
+
+ private static int[] SOLID_COLORS = new int[] {
+ Color.RED, 0xFFFFA500, Color.YELLOW, Color.GREEN, Color.CYAN,
+ Color.BLUE, Color.MAGENTA, Color.WHITE, Color.BLACK
+ };
+
+ /**
+ * Drop the alpha component from an RGBA packed int and return
+ * a non sign-extended RGB int.
+ *
+ * @param rgba
+ * @return rgb
+ */
+ public static int dropAlpha(int rgba) {
+ return rgba & 0x00FFFFFF;
+ }
+
+ /**
+ * Converts an RGB packed int into L*a*b space, which is well-suited for finding
+ * perceptual differences in color
+ *
+ * @param rgb A 32-bit value of packed RGB ints
+ * @return array of Lab values of size 3
+ */
+ public static float[] convertRGBtoLAB(int rgb) {
+ float[] lab = new float[3];
+ float fx, fy, fz;
+ float eps = 216.f / 24389.f;
+ float k = 24389.f / 27.f;
+
+ float Xr = 0.964221f; // reference white D50
+ float Yr = 1.0f;
+ float Zr = 0.825211f;
+
+ // RGB to XYZ
+ float r = Color.red(rgb) / 255.f; //R 0..1
+ float g = Color.green(rgb) / 255.f; //G 0..1
+ float b = Color.blue(rgb) / 255.f; //B 0..1
+
+ // assuming sRGB (D65)
+ if (r <= 0.04045)
+ r = r / 12;
+ else
+ r = (float) Math.pow((r + 0.055) / 1.055, 2.4);
+
+ if (g <= 0.04045)
+ g = g / 12;
+ else
+ g = (float) Math.pow((g + 0.055) / 1.055, 2.4);
+
+ if (b <= 0.04045)
+ b = b / 12;
+ else
+ b = (float) Math.pow((b + 0.055) / 1.055, 2.4);
+
+ float X = 0.436052025f * r + 0.385081593f * g + 0.143087414f * b;
+ float Y = 0.222491598f * r + 0.71688606f * g + 0.060621486f * b;
+ float Z = 0.013929122f * r + 0.097097002f * g + 0.71418547f * b;
+
+ // XYZ to Lab
+ float xr = X / Xr;
+ float yr = Y / Yr;
+ float zr = Z / Zr;
+
+ if (xr > eps)
+ fx = (float) Math.pow(xr, 1 / 3.);
+ else
+ fx = (float) ((k * xr + 16.) / 116.);
+
+ if (yr > eps)
+ fy = (float) Math.pow(yr, 1 / 3.);
+ else
+ fy = (float) ((k * yr + 16.) / 116.);
+
+ if (zr > eps)
+ fz = (float) Math.pow(zr, 1 / 3.);
+ else
+ fz = (float) ((k * zr + 16.) / 116);
+
+ float Ls = (116 * fy) - 16;
+ float as = 500 * (fx - fy);
+ float bs = 200 * (fy - fz);
+
+ lab[0] = (2.55f * Ls + .5f);
+ lab[1] = (as + .5f);
+ lab[2] = (bs + .5f);
+
+ return lab;
+ }
+
+ /**
+ * Calculate the colour difference value between two colours in lab space.
+ * This code is from OpenIMAJ under BSD License
+ *
+ * @param L1 first colour's L component
+ * @param a1 first colour's a component
+ * @param b1 first colour's b component
+ * @param L2 second colour's L component
+ * @param a2 second colour's a component
+ * @param b2 second colour's b component
+ * @return the CIE 2000 colour difference
+ */
+ public static double calculateDeltaE(double L1, double a1, double b1,
+ double L2, double a2, double b2) {
+ double Lmean = (L1 + L2) / 2.0;
+ double C1 = Math.sqrt(a1 * a1 + b1 * b1);
+ double C2 = Math.sqrt(a2 * a2 + b2 * b2);
+ double Cmean = (C1 + C2) / 2.0;
+
+ double G = (1 - Math.sqrt(Math.pow(Cmean, 7) / (Math.pow(Cmean, 7) + Math.pow(25, 7)))) / 2;
+ double a1prime = a1 * (1 + G);
+ double a2prime = a2 * (1 + G);
+
+ double C1prime = Math.sqrt(a1prime * a1prime + b1 * b1);
+ double C2prime = Math.sqrt(a2prime * a2prime + b2 * b2);
+ double Cmeanprime = (C1prime + C2prime) / 2;
+
+ double h1prime = Math.atan2(b1, a1prime)
+ + 2 * Math.PI * (Math.atan2(b1, a1prime) < 0 ? 1 : 0);
+ double h2prime = Math.atan2(b2, a2prime)
+ + 2 * Math.PI * (Math.atan2(b2, a2prime) < 0 ? 1 : 0);
+ double Hmeanprime = ((Math.abs(h1prime - h2prime) > Math.PI)
+ ? (h1prime + h2prime + 2 * Math.PI) / 2 : (h1prime + h2prime) / 2);
+
+ double T = 1.0 - 0.17 * Math.cos(Hmeanprime - Math.PI / 6.0)
+ + 0.24 * Math.cos(2 * Hmeanprime) + 0.32 * Math.cos(3 * Hmeanprime + Math.PI / 30)
+ - 0.2 * Math.cos(4 * Hmeanprime - 21 * Math.PI / 60);
+
+ double deltahprime = ((Math.abs(h1prime - h2prime) <= Math.PI) ? h2prime - h1prime
+ : (h2prime <= h1prime) ? h2prime - h1prime + 2 * Math.PI
+ : h2prime - h1prime - 2 * Math.PI);
+
+ double deltaLprime = L2 - L1;
+ double deltaCprime = C2prime - C1prime;
+ double deltaHprime = 2.0 * Math.sqrt(C1prime * C2prime) * Math.sin(deltahprime / 2.0);
+ double SL = 1.0 + ((0.015 * (Lmean - 50) * (Lmean - 50))
+ / (Math.sqrt(20 + (Lmean - 50) * (Lmean - 50))));
+ double SC = 1.0 + 0.045 * Cmeanprime;
+ double SH = 1.0 + 0.015 * Cmeanprime * T;
+
+ double deltaTheta = (30 * Math.PI / 180)
+ * Math.exp(-((180 / Math.PI * Hmeanprime - 275) / 25)
+ * ((180 / Math.PI * Hmeanprime - 275) / 25));
+ double RC = (2
+ * Math.sqrt(Math.pow(Cmeanprime, 7) / (Math.pow(Cmeanprime, 7) + Math.pow(25, 7))));
+ double RT = (-RC * Math.sin(2 * deltaTheta));
+
+ double KL = 1;
+ double KC = 1;
+ double KH = 1;
+
+ double deltaE = Math.sqrt(
+ ((deltaLprime / (KL * SL)) * (deltaLprime / (KL * SL))) +
+ ((deltaCprime / (KC * SC)) * (deltaCprime / (KC * SC))) +
+ ((deltaHprime / (KH * SH)) * (deltaHprime / (KH * SH))) +
+ (RT * (deltaCprime / (KC * SC)) * (deltaHprime / (KH * SH))));
+
+ return deltaE;
+ }
+
+ /**
+ * Finds the "perceptually nearest" color from a list of colors to
+ * the given RGB value. This is done by converting to
+ * L*a*b colorspace and using the CIE2000 deltaE algorithm.
+ *
+ * @param rgb The original color to start with
+ * @param colors An array of colors to test
+ * @return RGB packed int of nearest color in the list
+ */
+ public static int findPerceptuallyNearestColor(int rgb, int[] colors) {
+ int nearestColor = 0;
+ double closest = Double.MAX_VALUE;
+
+ float[] original = convertRGBtoLAB(rgb);
+
+ for (int i = 0; i < colors.length; i++) {
+ float[] cl = convertRGBtoLAB(colors[i]);
+ double deltaE = calculateDeltaE(original[0], original[1], original[2],
+ cl[0], cl[1], cl[2]);
+ if (deltaE < closest) {
+ nearestColor = colors[i];
+ closest = deltaE;
+ }
+ }
+ return nearestColor;
+ }
+
+ /**
+ * Convenience method to find the nearest "solid" color (having RGB components
+ * of either 0 or 255) to the given color. This is useful for cases such as
+ * LED notification lights which may not be able to display the full range
+ * of colors due to hardware limitations.
+ *
+ * @param rgb
+ * @return the perceptually nearest color in RGB
+ */
+ public static int findPerceptuallyNearestSolidColor(int rgb) {
+ return findPerceptuallyNearestColor(rgb, SOLID_COLORS);
+ }
+
+ /**
+ * Given a Palette, pick out the dominant swatch based on population
+ *
+ * @param palette
+ * @return the dominant Swatch
+ */
+ public static Palette.Swatch getDominantSwatch(Palette palette) {
+ if (palette == null || palette.getSwatches().size() == 0) {
+ return null;
+ }
+ // find most-represented swatch based on population
+ return Collections.max(palette.getSwatches(), new Comparator<Palette.Swatch>() {
+ @Override
+ public int compare(Palette.Swatch sw1, Palette.Swatch sw2) {
+ return Integer.compare(sw1.getPopulation(), sw2.getPopulation());
+ }
+ });
+ }
+
+ /**
+ * Takes a drawable and uses Palette to generate a suitable "alert"
+ * color which can be used for an external notification mechanism
+ * such as an RGB LED. This will always pick a solid color having
+ * RGB components of 255 or 0.
+ *
+ * @param drawable The drawable to generate a color for
+ * @return a suitable solid color which corresponds to the image
+ */
+ public static int generateAlertColorFromDrawable(Drawable drawable) {
+ int alertColor = Color.BLACK;
+ Bitmap bitmap = null;
+
+ if (drawable == null) {
+ return alertColor;
+ }
+
+ if (drawable instanceof BitmapDrawable) {
+ bitmap = ((BitmapDrawable) drawable).getBitmap();
+ } else {
+ bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(),
+ drawable.getIntrinsicHeight(),
+ Bitmap.Config.ARGB_8888);
+ }
+
+ if (bitmap != null) {
+ Palette p = Palette.from(bitmap).generate();
+ if (p == null) {
+ return alertColor;
+ }
+
+ // First try the dominant color
+ final Palette.Swatch dominantSwatch = getDominantSwatch(p);
+ int iconColor = alertColor;
+ if (dominantSwatch != null) {
+ iconColor = dominantSwatch.getRgb();
+ alertColor = findPerceptuallyNearestSolidColor(iconColor);
+ }
+
+ // Try the most saturated color if we got white or black (boring)
+ if (alertColor == Color.BLACK || alertColor == Color.WHITE) {
+ iconColor = p.getVibrantColor(Color.WHITE);
+ alertColor = findPerceptuallyNearestSolidColor(iconColor);
+ }
+
+ if (!(drawable instanceof BitmapDrawable)) {
+ bitmap.recycle();
+ }
+ }
+
+ return alertColor;
+ }
+
+ /**
+ * Convert a color temperature value (in Kelvin) to a RGB units as floats.
+ * This can be used in a transform matrix or hardware gamma control.
+ *
+ * @param tempK
+ * @return
+ */
+ public static float[] temperatureToRGB(int degreesK) {
+ int k = MathUtils.constrain(degreesK, 1000, 20000);
+ float a = (k % 100) / 100.0f;
+ int i = ((k - 1000)/ 100) * 3;
+
+ return new float[] { interp(i, a), interp(i+1, a), interp(i+2, a) };
+ }
+
+ private static float interp(int i, float a) {
+ return MathUtils.lerp((float)sColorTable[i], (float)sColorTable[i+3], a);
+ }
+
+ /**
+ * This table is a modified version of the original blackbody chart, found here:
+ * http://www.vendian.org/mncharity/dir3/blackbody/UnstableURLs/bbr_color.html
+ *
+ * Created by Ingo Thiel.
+ */
+ private static final double[] sColorTable = new double[] {
+ 1.00000000, 0.18172716, 0.00000000,
+ 1.00000000, 0.25503671, 0.00000000,
+ 1.00000000, 0.30942099, 0.00000000,
+ 1.00000000, 0.35357379, 0.00000000,
+ 1.00000000, 0.39091524, 0.00000000,
+ 1.00000000, 0.42322816, 0.00000000,
+ 1.00000000, 0.45159884, 0.00000000,
+ 1.00000000, 0.47675916, 0.00000000,
+ 1.00000000, 0.49923747, 0.00000000,
+ 1.00000000, 0.51943421, 0.00000000,
+ 1.00000000, 0.54360078, 0.08679949,
+ 1.00000000, 0.56618736, 0.14065513,
+ 1.00000000, 0.58734976, 0.18362641,
+ 1.00000000, 0.60724493, 0.22137978,
+ 1.00000000, 0.62600248, 0.25591950,
+ 1.00000000, 0.64373109, 0.28819679,
+ 1.00000000, 0.66052319, 0.31873863,
+ 1.00000000, 0.67645822, 0.34786758,
+ 1.00000000, 0.69160518, 0.37579588,
+ 1.00000000, 0.70602449, 0.40267128,
+ 1.00000000, 0.71976951, 0.42860152,
+ 1.00000000, 0.73288760, 0.45366838,
+ 1.00000000, 0.74542112, 0.47793608,
+ 1.00000000, 0.75740814, 0.50145662,
+ 1.00000000, 0.76888303, 0.52427322,
+ 1.00000000, 0.77987699, 0.54642268,
+ 1.00000000, 0.79041843, 0.56793692,
+ 1.00000000, 0.80053332, 0.58884417,
+ 1.00000000, 0.81024551, 0.60916971,
+ 1.00000000, 0.81957693, 0.62893653,
+ 1.00000000, 0.82854786, 0.64816570,
+ 1.00000000, 0.83717703, 0.66687674,
+ 1.00000000, 0.84548188, 0.68508786,
+ 1.00000000, 0.85347859, 0.70281616,
+ 1.00000000, 0.86118227, 0.72007777,
+ 1.00000000, 0.86860704, 0.73688797,
+ 1.00000000, 0.87576611, 0.75326132,
+ 1.00000000, 0.88267187, 0.76921169,
+ 1.00000000, 0.88933596, 0.78475236,
+ 1.00000000, 0.89576933, 0.79989606,
+ 1.00000000, 0.90198230, 0.81465502,
+ 1.00000000, 0.90963069, 0.82838210,
+ 1.00000000, 0.91710889, 0.84190889,
+ 1.00000000, 0.92441842, 0.85523742,
+ 1.00000000, 0.93156127, 0.86836903,
+ 1.00000000, 0.93853986, 0.88130458,
+ 1.00000000, 0.94535695, 0.89404470,
+ 1.00000000, 0.95201559, 0.90658983,
+ 1.00000000, 0.95851906, 0.91894041,
+ 1.00000000, 0.96487079, 0.93109690,
+ 1.00000000, 0.97107439, 0.94305985,
+ 1.00000000, 0.97713351, 0.95482993,
+ 1.00000000, 0.98305189, 0.96640795,
+ 1.00000000, 0.98883326, 0.97779486,
+ 1.00000000, 0.99448139, 0.98899179,
+ 1.00000000, 1.00000000, 1.00000000,
+ 0.98947904, 0.99348723, 1.00000000,
+ 0.97940448, 0.98722715, 1.00000000,
+ 0.96975025, 0.98120637, 1.00000000,
+ 0.96049223, 0.97541240, 1.00000000,
+ 0.95160805, 0.96983355, 1.00000000,
+ 0.94303638, 0.96443333, 1.00000000,
+ 0.93480451, 0.95923080, 1.00000000,
+ 0.92689056, 0.95421394, 1.00000000,
+ 0.91927697, 0.94937330, 1.00000000,
+ 0.91194747, 0.94470005, 1.00000000,
+ 0.90488690, 0.94018594, 1.00000000,
+ 0.89808115, 0.93582323, 1.00000000,
+ 0.89151710, 0.93160469, 1.00000000,
+ 0.88518247, 0.92752354, 1.00000000,
+ 0.87906581, 0.92357340, 1.00000000,
+ 0.87315640, 0.91974827, 1.00000000,
+ 0.86744421, 0.91604254, 1.00000000,
+ 0.86191983, 0.91245088, 1.00000000,
+ 0.85657444, 0.90896831, 1.00000000,
+ 0.85139976, 0.90559011, 1.00000000,
+ 0.84638799, 0.90231183, 1.00000000,
+ 0.84153180, 0.89912926, 1.00000000,
+ 0.83682430, 0.89603843, 1.00000000,
+ 0.83225897, 0.89303558, 1.00000000,
+ 0.82782969, 0.89011714, 1.00000000,
+ 0.82353066, 0.88727974, 1.00000000,
+ 0.81935641, 0.88452017, 1.00000000,
+ 0.81530175, 0.88183541, 1.00000000,
+ 0.81136180, 0.87922257, 1.00000000,
+ 0.80753191, 0.87667891, 1.00000000,
+ 0.80380769, 0.87420182, 1.00000000,
+ 0.80018497, 0.87178882, 1.00000000,
+ 0.79665980, 0.86943756, 1.00000000,
+ 0.79322843, 0.86714579, 1.00000000,
+ 0.78988728, 0.86491137, 1.00000000,
+ 0.78663296, 0.86273225, 1.00000000,
+ 0.78346225, 0.86060650, 1.00000000,
+ 0.78037207, 0.85853224, 1.00000000,
+ 0.77735950, 0.85650771, 1.00000000,
+ 0.77442176, 0.85453121, 1.00000000,
+ 0.77155617, 0.85260112, 1.00000000,
+ 0.76876022, 0.85071588, 1.00000000,
+ 0.76603147, 0.84887402, 1.00000000,
+ 0.76336762, 0.84707411, 1.00000000,
+ 0.76076645, 0.84531479, 1.00000000,
+ 0.75822586, 0.84359476, 1.00000000,
+ 0.75574383, 0.84191277, 1.00000000,
+ 0.75331843, 0.84026762, 1.00000000,
+ 0.75094780, 0.83865816, 1.00000000,
+ 0.74863017, 0.83708329, 1.00000000,
+ 0.74636386, 0.83554194, 1.00000000,
+ 0.74414722, 0.83403311, 1.00000000,
+ 0.74197871, 0.83255582, 1.00000000,
+ 0.73985682, 0.83110912, 1.00000000,
+ 0.73778012, 0.82969211, 1.00000000,
+ 0.73574723, 0.82830393, 1.00000000,
+ 0.73375683, 0.82694373, 1.00000000,
+ 0.73180765, 0.82561071, 1.00000000,
+ 0.72989845, 0.82430410, 1.00000000,
+ 0.72802807, 0.82302316, 1.00000000,
+ 0.72619537, 0.82176715, 1.00000000,
+ 0.72439927, 0.82053539, 1.00000000,
+ 0.72263872, 0.81932722, 1.00000000,
+ 0.72091270, 0.81814197, 1.00000000,
+ 0.71922025, 0.81697905, 1.00000000,
+ 0.71756043, 0.81583783, 1.00000000,
+ 0.71593234, 0.81471775, 1.00000000,
+ 0.71433510, 0.81361825, 1.00000000,
+ 0.71276788, 0.81253878, 1.00000000,
+ 0.71122987, 0.81147883, 1.00000000,
+ 0.70972029, 0.81043789, 1.00000000,
+ 0.70823838, 0.80941546, 1.00000000,
+ 0.70678342, 0.80841109, 1.00000000,
+ 0.70535469, 0.80742432, 1.00000000,
+ 0.70395153, 0.80645469, 1.00000000,
+ 0.70257327, 0.80550180, 1.00000000,
+ 0.70121928, 0.80456522, 1.00000000,
+ 0.69988894, 0.80364455, 1.00000000,
+ 0.69858167, 0.80273941, 1.00000000,
+ 0.69729688, 0.80184943, 1.00000000,
+ 0.69603402, 0.80097423, 1.00000000,
+ 0.69479255, 0.80011347, 1.00000000,
+ 0.69357196, 0.79926681, 1.00000000,
+ 0.69237173, 0.79843391, 1.00000000,
+ 0.69119138, 0.79761446, 1.00000000,
+ 0.69003044, 0.79680814, 1.00000000,
+ 0.68888844, 0.79601466, 1.00000000,
+ 0.68776494, 0.79523371, 1.00000000,
+ 0.68665951, 0.79446502, 1.00000000,
+ 0.68557173, 0.79370830, 1.00000000,
+ 0.68450119, 0.79296330, 1.00000000,
+ 0.68344751, 0.79222975, 1.00000000,
+ 0.68241029, 0.79150740, 1.00000000,
+ 0.68138918, 0.79079600, 1.00000000,
+ 0.68038380, 0.79009531, 1.00000000,
+ 0.67939381, 0.78940511, 1.00000000,
+ 0.67841888, 0.78872517, 1.00000000,
+ 0.67745866, 0.78805526, 1.00000000,
+ 0.67651284, 0.78739518, 1.00000000,
+ 0.67558112, 0.78674472, 1.00000000,
+ 0.67466317, 0.78610368, 1.00000000,
+ 0.67375872, 0.78547186, 1.00000000,
+ 0.67286748, 0.78484907, 1.00000000,
+ 0.67198916, 0.78423512, 1.00000000,
+ 0.67112350, 0.78362984, 1.00000000,
+ 0.67027024, 0.78303305, 1.00000000,
+ 0.66942911, 0.78244457, 1.00000000,
+ 0.66859988, 0.78186425, 1.00000000,
+ 0.66778228, 0.78129191, 1.00000000,
+ 0.66697610, 0.78072740, 1.00000000,
+ 0.66618110, 0.78017057, 1.00000000,
+ 0.66539706, 0.77962127, 1.00000000,
+ 0.66462376, 0.77907934, 1.00000000,
+ 0.66386098, 0.77854465, 1.00000000,
+ 0.66310852, 0.77801705, 1.00000000,
+ 0.66236618, 0.77749642, 1.00000000,
+ 0.66163375, 0.77698261, 1.00000000,
+ 0.66091106, 0.77647551, 1.00000000,
+ 0.66019791, 0.77597498, 1.00000000,
+ 0.65949412, 0.77548090, 1.00000000,
+ 0.65879952, 0.77499315, 1.00000000,
+ 0.65811392, 0.77451161, 1.00000000,
+ 0.65743716, 0.77403618, 1.00000000,
+ 0.65676908, 0.77356673, 1.00000000,
+ 0.65610952, 0.77310316, 1.00000000,
+ 0.65545831, 0.77264537, 1.00000000,
+ 0.65481530, 0.77219324, 1.00000000,
+ 0.65418036, 0.77174669, 1.00000000,
+ 0.65355332, 0.77130560, 1.00000000,
+ 0.65293404, 0.77086988, 1.00000000,
+ 0.65232240, 0.77043944, 1.00000000,
+ 0.65171824, 0.77001419, 1.00000000,
+ 0.65112144, 0.76959404, 1.00000000,
+ 0.65053187, 0.76917889, 1.00000000,
+ 0.64994941, 0.76876866, 1.00000000,
+ 0.64937392, 0.76836326, 1.00000000
+ };
+
+}
diff --git a/sdk/src/java/cyanogenmod/weather/CMWeatherManager.java b/sdk/src/java/cyanogenmod/weather/CMWeatherManager.java
new file mode 100644
index 0000000..32eab52
--- /dev/null
+++ b/sdk/src/java/cyanogenmod/weather/CMWeatherManager.java
@@ -0,0 +1,373 @@
+/*
+ * Copyright (C) 2016 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.weather;
+
+import android.annotation.NonNull;
+import android.content.ComponentName;
+import android.content.Context;
+import android.location.Location;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.ArraySet;
+import cyanogenmod.app.CMContextConstants;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Provides access to the weather services in the device.
+ */
+public class CMWeatherManager {
+
+ private static ICMWeatherManager sWeatherManagerService;
+ private static CMWeatherManager sInstance;
+ private Context mContext;
+ private Map<RequestInfo,WeatherUpdateRequestListener> mWeatherUpdateRequestListeners
+ = Collections.synchronizedMap(new HashMap<RequestInfo,WeatherUpdateRequestListener>());
+ private Map<RequestInfo,LookupCityRequestListener> mLookupNameRequestListeners
+ = Collections.synchronizedMap(new HashMap<RequestInfo,LookupCityRequestListener>());
+ private Handler mHandler;
+ private Set<WeatherServiceProviderChangeListener> mProviderChangedListeners = new ArraySet<>();
+
+ private static final String TAG = CMWeatherManager.class.getSimpleName();
+
+ /**
+ * Weather update request state: Successfully completed
+ */
+ public static final int WEATHER_REQUEST_COMPLETED = 1;
+
+ /**
+ * Weather update request state: You need to wait a bit longer before requesting an update
+ * again.
+ * <p>Please bear in mind that the weather does not change very often. A threshold of 10 minutes
+ * is enforced by the system</p>
+ */
+ public static final int WEATHER_REQUEST_SUBMITTED_TOO_SOON = -1;
+
+ /**
+ * Weather update request state: An error occurred while trying to update the weather. You
+ * should wait before trying again, or your request will be rejected with
+ * {@link #WEATHER_REQUEST_SUBMITTED_TOO_SOON}
+ */
+ public static final int WEATHER_REQUEST_FAILED = -2;
+
+ /**
+ * Weather update request state: Only one update request can be processed at a given time.
+ */
+ public static final int WEATHER_REQUEST_ALREADY_IN_PROGRESS = -3;
+
+ /** @hide */
+ public static final int LOOKUP_REQUEST_COMPLETED = 100;
+
+ /** @hide */
+ public static final int LOOKUP_REQUEST_FAILED = -100;
+
+ /** @hide */
+ public static final int LOOKUP_REQUEST_NO_MATCH_FOUND = -101;
+
+
+ private CMWeatherManager(Context context) {
+ Context appContext = context.getApplicationContext();
+ mContext = (appContext != null) ? appContext : context;
+ sWeatherManagerService = getService();
+
+ if (context.getPackageManager().hasSystemFeature(
+ CMContextConstants.Features.WEATHER_SERVICES) && (sWeatherManagerService == null)) {
+ throw new RuntimeException("Unable to bind the CMWeatherManagerService");
+ }
+ mHandler = new Handler(appContext.getMainLooper());
+ }
+
+ /**
+ * Gets or creates an instance of the {@link cyanogenmod.weather.CMWeatherManager}
+ * @param context
+ * @return {@link CMWeatherManager}
+ */
+ public static CMWeatherManager getInstance(Context context) {
+ if (sInstance == null) {
+ sInstance = new CMWeatherManager(context);
+ }
+ return sInstance;
+ }
+
+ /** @hide */
+ public static ICMWeatherManager getService() {
+ if (sWeatherManagerService != null) {
+ return sWeatherManagerService;
+ }
+ IBinder binder = ServiceManager.getService(CMContextConstants.CM_WEATHER_SERVICE);
+ if (binder != null) {
+ sWeatherManagerService = ICMWeatherManager.Stub.asInterface(binder);
+ return sWeatherManagerService;
+ }
+ return null;
+ }
+
+ /**
+ * Forces the weather service to request the latest available weather information for
+ * the supplied {@link android.location.Location} location.
+ *
+ * @param location The location you want to get the latest weather data from.
+ * @param listener {@link WeatherUpdateRequestListener} To be notified once the active weather
+ * service provider has finished
+ * processing your request
+ */
+ public void requestWeatherUpdate(@NonNull Location location,
+ @NonNull WeatherUpdateRequestListener listener) {
+ if (sWeatherManagerService == null) {
+ return;
+ }
+
+ try {
+ RequestInfo info = new RequestInfo
+ .Builder(mRequestInfoListener)
+ .setLocation(location)
+ .build();
+ if (listener != null) mWeatherUpdateRequestListeners.put(info, listener);
+ sWeatherManagerService.updateWeather(info);
+ } catch (RemoteException e) {
+ }
+ }
+
+ /**
+ * Forces the weather service to request the latest weather information for the provided
+ * WeatherLocation. This is the preferred method for requesting a weather update.
+ *
+ * @param weatherLocation A {@link cyanogenmod.weather.WeatherLocation} that was previously
+ * obtained by calling
+ * {@link #lookupCity(String, LookupCityRequestListener)}
+ * @param listener {@link WeatherUpdateRequestListener} To be notified once the active weather
+ * service provider has finished
+ * processing your request
+ */
+ public void requestWeatherUpdate(@NonNull WeatherLocation weatherLocation,
+ @NonNull WeatherUpdateRequestListener listener) {
+ if (sWeatherManagerService == null) {
+ return;
+ }
+
+ try {
+ RequestInfo info = new RequestInfo
+ .Builder(mRequestInfoListener)
+ .setWeatherLocation(weatherLocation)
+ .build();
+ if (listener != null) mWeatherUpdateRequestListeners.put(info, listener);
+ sWeatherManagerService.updateWeather(info);
+ } catch (RemoteException e) {
+ }
+ }
+
+ /**
+ * Request the active weather provider service to lookup the supplied city name.
+ *
+ * @param city The city name
+ * @param listener {@link LookupCityRequestListener} To be notified once the request has been
+ * completed. Upon success, a list of
+ * {@link cyanogenmod.weather.WeatherLocation}
+ * will be provided
+ */
+ public void lookupCity(@NonNull String city, @NonNull LookupCityRequestListener listener) {
+ if (sWeatherManagerService == null) {
+ return;
+ }
+ try {
+ RequestInfo info = new RequestInfo
+ .Builder(mRequestInfoListener)
+ .setCityName(city)
+ .build();
+ if (listener != null) mLookupNameRequestListeners.put(info, listener);
+ sWeatherManagerService.lookupCity(info);
+ } catch (RemoteException e) {
+ }
+ }
+
+ /**
+ * Registers a {@link WeatherServiceProviderChangeListener} to be notified when a new weather
+ * service provider becomes active.
+ * @param listener {@link WeatherServiceProviderChangeListener} to register
+ */
+ public void registerWeatherServiceProviderChangeListener(
+ @NonNull WeatherServiceProviderChangeListener listener) {
+ synchronized (mProviderChangedListeners) {
+ if (mProviderChangedListeners.contains(listener)) {
+ throw new IllegalArgumentException("Listener already registered");
+ }
+ if (mProviderChangedListeners.size() == 0) {
+ try {
+ sWeatherManagerService.registerWeatherServiceProviderChangeListener(
+ mProviderChangeListener);
+ } catch (RemoteException e){
+ }
+ }
+ mProviderChangedListeners.add(listener);
+ }
+ }
+
+ /**
+ * Unregisters a listener
+ * @param listener A previously registered {@link WeatherServiceProviderChangeListener}
+ */
+ public void unregisterWeatherServiceProviderChangeListener(
+ @NonNull WeatherServiceProviderChangeListener listener) {
+ synchronized (mProviderChangedListeners) {
+ if (!mProviderChangedListeners.contains(listener)) {
+ throw new IllegalArgumentException("Listener was never registered");
+ }
+ mProviderChangedListeners.remove(listener);
+ if (mProviderChangedListeners.size() == 0) {
+ try {
+ sWeatherManagerService.unregisterWeatherServiceProviderChangeListener(
+ mProviderChangeListener);
+ } catch(RemoteException e){
+ }
+ }
+ }
+ }
+
+ /**
+ * Gets the service's label as declared by the active weather service provider in its manifest
+ * @return the service's label
+ */
+ public String getActiveWeatherServiceProviderLabel() {
+ try {
+ return sWeatherManagerService.getActiveWeatherServiceProviderLabel();
+ } catch(RemoteException e){
+ }
+ return null;
+ }
+
+ private final IWeatherServiceProviderChangeListener mProviderChangeListener =
+ new IWeatherServiceProviderChangeListener.Stub() {
+ @Override
+ public void onWeatherServiceProviderChanged(final String providerName) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ synchronized (mProviderChangedListeners) {
+ List<WeatherServiceProviderChangeListener> deadListeners
+ = new ArrayList<>();
+ for (WeatherServiceProviderChangeListener listener
+ : mProviderChangedListeners) {
+ try {
+ listener.onWeatherServiceProviderChanged(providerName);
+ } catch (Throwable e) {
+ deadListeners.add(listener);
+ }
+ }
+ if (deadListeners.size() > 0) {
+ for (WeatherServiceProviderChangeListener listener : deadListeners) {
+ mProviderChangedListeners.remove(listener);
+ }
+ }
+ }
+ }
+ });
+ }
+ };
+
+ private final IRequestInfoListener mRequestInfoListener = new IRequestInfoListener.Stub() {
+
+ @Override
+ public void onWeatherRequestCompleted(final RequestInfo requestInfo, final int state,
+ final WeatherInfo weatherInfo) {
+ final WeatherUpdateRequestListener listener
+ = mWeatherUpdateRequestListeners.remove(requestInfo);
+ if (listener != null) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ listener.onWeatherRequestCompleted(state, weatherInfo);
+ }
+ });
+ }
+ }
+
+ @Override
+ public void onLookupCityRequestCompleted(RequestInfo requestInfo,
+ final List<WeatherLocation> weatherLocations) {
+
+ final LookupCityRequestListener listener
+ = mLookupNameRequestListeners.remove(requestInfo);
+ if (listener != null) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ ArrayList<WeatherLocation> list = null;
+ if (weatherLocations != null) {
+ list = new ArrayList<>(weatherLocations);
+ }
+ listener.onLookupCityRequestCompleted(list);
+ }
+ });
+ }
+ }
+ };
+
+ /**
+ * Interface used to receive notifications upon completion of a weather update request
+ */
+ public interface WeatherUpdateRequestListener {
+ /**
+ * This method will be called when the weather service provider has finished processing the
+ * request
+ *
+ * @param state Any of the following values
+ * {@link #WEATHER_REQUEST_COMPLETED}
+ * {@link #WEATHER_REQUEST_ALREADY_IN_PROGRESS}
+ * {@link #WEATHER_REQUEST_SUBMITTED_TOO_SOON}
+ * {@link #WEATHER_REQUEST_FAILED}
+ *
+ * @param weatherInfo A fully populated {@link WeatherInfo} if state is
+ * {@link #WEATHER_REQUEST_COMPLETED}, null otherwise
+ */
+ void onWeatherRequestCompleted(int state, WeatherInfo weatherInfo);
+ }
+
+ /**
+ * Interface used to receive notifications upon completion of a request to lookup a city name
+ */
+ public interface LookupCityRequestListener {
+ /**
+ * This method will be called when the weather service provider has finished processing the
+ * request. The argument can be null if the provider couldn't find a match
+ *
+ * @param locations
+ */
+ void onLookupCityRequestCompleted(ArrayList<WeatherLocation> locations);
+ }
+
+ /**
+ * Interface used to be notified when the user changes the weather service provider
+ */
+ public interface WeatherServiceProviderChangeListener {
+ /**
+ * This method will be called when a new weather service provider becomes active in the
+ * system. The parameter can be null when
+ * <p>The user removed the active weather service provider from the system </p>
+ * <p>The active weather provider was disabled.</p>
+ *
+ * @param providerLabel The label as declared on the weather service provider manifest
+ */
+ void onWeatherServiceProviderChanged(String providerLabel);
+ }
+}
diff --git a/sdk/src/java/cyanogenmod/weather/ICMWeatherManager.aidl b/sdk/src/java/cyanogenmod/weather/ICMWeatherManager.aidl
new file mode 100644
index 0000000..21e1d26
--- /dev/null
+++ b/sdk/src/java/cyanogenmod/weather/ICMWeatherManager.aidl
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2016 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.weather;
+
+import cyanogenmod.weather.IWeatherServiceProviderChangeListener;
+import cyanogenmod.weather.RequestInfo;
+
+/** @hide */
+interface ICMWeatherManager {
+ oneway void updateWeather(in RequestInfo info);
+ oneway void lookupCity(in RequestInfo info);
+ oneway void registerWeatherServiceProviderChangeListener(
+ in IWeatherServiceProviderChangeListener listener);
+ oneway void unregisterWeatherServiceProviderChangeListener(
+ in IWeatherServiceProviderChangeListener listener);
+ String getActiveWeatherServiceProviderLabel();
+} \ No newline at end of file
diff --git a/sdk/src/java/cyanogenmod/weather/IRequestInfoListener.aidl b/sdk/src/java/cyanogenmod/weather/IRequestInfoListener.aidl
new file mode 100644
index 0000000..553da71
--- /dev/null
+++ b/sdk/src/java/cyanogenmod/weather/IRequestInfoListener.aidl
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2016 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.weather;
+
+import cyanogenmod.weather.RequestInfo;
+import cyanogenmod.weather.WeatherInfo;
+import cyanogenmod.weather.WeatherLocation;
+
+import java.util.List;
+
+/** @hide */
+oneway interface IRequestInfoListener {
+ void onWeatherRequestCompleted(in RequestInfo requestInfo, int state,
+ in WeatherInfo weatherInfo);
+ void onLookupCityRequestCompleted(in RequestInfo requestInfo,
+ in List<WeatherLocation> weatherLocation);
+} \ No newline at end of file
diff --git a/sdk/src/java/cyanogenmod/weather/IWeatherServiceProviderChangeListener.aidl b/sdk/src/java/cyanogenmod/weather/IWeatherServiceProviderChangeListener.aidl
new file mode 100644
index 0000000..7c823b6
--- /dev/null
+++ b/sdk/src/java/cyanogenmod/weather/IWeatherServiceProviderChangeListener.aidl
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2016 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.weather;
+
+/** @hide */
+oneway interface IWeatherServiceProviderChangeListener {
+ void onWeatherServiceProviderChanged(String providerLabel);
+} \ No newline at end of file
diff --git a/sdk/src/java/cyanogenmod/weather/RequestInfo.aidl b/sdk/src/java/cyanogenmod/weather/RequestInfo.aidl
new file mode 100644
index 0000000..5e53b93
--- /dev/null
+++ b/sdk/src/java/cyanogenmod/weather/RequestInfo.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2016 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.weather;
+
+parcelable RequestInfo; \ No newline at end of file
diff --git a/sdk/src/java/cyanogenmod/weather/RequestInfo.java b/sdk/src/java/cyanogenmod/weather/RequestInfo.java
new file mode 100644
index 0000000..353b5fd
--- /dev/null
+++ b/sdk/src/java/cyanogenmod/weather/RequestInfo.java
@@ -0,0 +1,356 @@
+/*
+ * Copyright (C) 2016 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.weather;
+
+import android.location.Location;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import cyanogenmod.os.Build;
+import cyanogenmod.os.Concierge;
+import cyanogenmod.os.Concierge.ParcelInfo;
+import cyanogenmod.providers.WeatherContract;
+
+/**
+ * This class holds the information of a request submitted to the active weather provider service
+ */
+public final class RequestInfo implements Parcelable {
+
+ private Location mLocation;
+ private String mCityName;
+ private WeatherLocation mWeatherLocation;
+ private int mRequestType;
+ private IRequestInfoListener mListener;
+ private int mTempUnit;
+ private int mKey;
+ private boolean mIsQueryOnly;
+
+ /**
+ * A request to update the weather data using a geographical {@link android.location.Location}
+ */
+ public static final int TYPE_GEO_LOCATION_REQ = 1;
+ /**
+ * A request to update the weather data using a {@link WeatherLocation}
+ */
+ public static final int TYPE_WEATHER_LOCATION_REQ = 2;
+
+ /**
+ * A request to look up a city name
+ */
+ public static final int TYPE_LOOKUP_CITY_NAME_REQ = 3;
+
+ private RequestInfo() {}
+
+ /* package */ static class Builder {
+ private Location mLocation;
+ private String mCityName;
+ private WeatherLocation mWeatherLocation;
+ private int mRequestType;
+ private IRequestInfoListener mListener;
+ private int mTempUnit = WeatherContract.WeatherColumns.TempUnit.FAHRENHEIT;
+ private boolean mIsQueryOnly = false;
+
+ public Builder(IRequestInfoListener listener) {
+ this.mListener = listener;
+ }
+
+ /**
+ * Sets the city name and identifies this request as a {@link #TYPE_LOOKUP_CITY_NAME_REQ}
+ * request. If set, will null out the location and weather location.
+ */
+ public Builder setCityName(String cityName) {
+ this.mCityName = cityName;
+ this.mRequestType = TYPE_LOOKUP_CITY_NAME_REQ;
+ this.mLocation = null;
+ this.mWeatherLocation = null;
+ return this;
+ }
+
+ /**
+ * Sets the Location and identifies this request as a {@link #TYPE_GEO_LOCATION_REQ}. If
+ * set, will null out the city name and weather location.
+ */
+ public Builder setLocation(Location location) {
+ this.mLocation = location;
+ this.mCityName = null;
+ this.mWeatherLocation = null;
+ this.mRequestType = TYPE_GEO_LOCATION_REQ;
+ return this;
+ }
+
+ /**
+ * Sets the weather location and identifies this request as a
+ * {@link #TYPE_WEATHER_LOCATION_REQ}. If set, will null out the location and city name
+ */
+ public Builder setWeatherLocation(WeatherLocation weatherLocation) {
+ this.mWeatherLocation = weatherLocation;
+ this.mLocation = null;
+ this.mCityName = null;
+ this.mRequestType = TYPE_WEATHER_LOCATION_REQ;
+ return this;
+ }
+
+ /**
+ * Sets the unit in which the temperature will be reported if the request is honored.
+ * Valid values are:
+ * <ul>
+ * {@link cyanogenmod.providers.WeatherContract.WeatherColumns.TempUnit#CELSIUS}
+ * {@link cyanogenmod.providers.WeatherContract.WeatherColumns.TempUnit#FAHRENHEIT}
+ * </ul>
+ * Any other value will generate an IllegalArgumentException. If the temperature unit is not
+ * set, the default will be degrees Fahrenheit
+ * @param unit A valid temperature unit
+ */
+ public Builder setTemperatureUnit(int unit) {
+ if (!isValidTempUnit(unit)) {
+ throw new IllegalArgumentException("Invalid temperature unit");
+ }
+ this.mTempUnit = unit;
+ return this;
+ }
+
+ /**
+ * If this is a weather request, marks the request as a query only, meaning that the
+ * content provider won't be updated after the active weather service has finished
+ * processing the request.
+ */
+ public Builder queryOnly() {
+ switch (mRequestType) {
+ case TYPE_GEO_LOCATION_REQ:
+ case TYPE_WEATHER_LOCATION_REQ:
+ this.mIsQueryOnly = true;
+ break;
+ default:
+ this.mIsQueryOnly = false;
+ break;
+ }
+ return this;
+ }
+
+ public RequestInfo build() {
+ RequestInfo info = new RequestInfo();
+ info.mListener = this.mListener;
+ info.mRequestType = this.mRequestType;
+ info.mCityName = this.mCityName;
+ info.mWeatherLocation = this.mWeatherLocation;
+ info.mLocation = this.mLocation;
+ info.mTempUnit = this.mTempUnit;
+ info.mIsQueryOnly = this.mIsQueryOnly;
+ info.mKey = this.hashCode();
+ return info;
+ }
+
+ private boolean isValidTempUnit(int unit) {
+ switch (unit) {
+ case WeatherContract.WeatherColumns.TempUnit.CELSIUS:
+ case WeatherContract.WeatherColumns.TempUnit.FAHRENHEIT:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ }
+
+ private RequestInfo(Parcel parcel) {
+ // Read parcelable version via the Concierge
+ ParcelInfo parcelInfo = Concierge.receiveParcel(parcel);
+ int parcelableVersion = parcelInfo.getParcelVersion();
+
+ if (parcelableVersion >= Build.CM_VERSION_CODES.ELDERBERRY) {
+ mKey = parcel.readInt();
+ mRequestType = parcel.readInt();
+ switch (mRequestType) {
+ case TYPE_GEO_LOCATION_REQ:
+ mLocation = Location.CREATOR.createFromParcel(parcel);
+ mTempUnit = parcel.readInt();
+ break;
+ case TYPE_WEATHER_LOCATION_REQ:
+ mWeatherLocation = WeatherLocation.CREATOR.createFromParcel(parcel);
+ mTempUnit = parcel.readInt();
+ break;
+ case TYPE_LOOKUP_CITY_NAME_REQ:
+ mCityName = parcel.readString();
+ break;
+ }
+ mIsQueryOnly = (parcel.readInt() == 1);
+ mListener = IRequestInfoListener.Stub.asInterface(parcel.readStrongBinder());
+ }
+
+ // Complete parcel info for the concierge
+ parcelInfo.complete();
+ }
+
+
+ /**
+ * @return The request type
+ */
+ public int getRequestType() {
+ return mRequestType;
+ }
+
+ /**
+ * @return the {@link android.location.Location} if this is a request by location, null
+ * otherwise
+ */
+ public Location getLocation() {
+ return mLocation;
+ }
+
+ /**
+ * @return the {@link cyanogenmod.weather.WeatherLocation} if this is a request by weather
+ * location, null otherwise
+ */
+ public WeatherLocation getWeatherLocation() {
+ return mWeatherLocation;
+ }
+
+ /**
+ * @hide
+ */
+ public IRequestInfoListener getRequestListener() {
+ return mListener;
+ }
+
+ /**
+ * @return the city name if this is a lookup request, null otherwise
+ */
+ public String getCityName() {
+ return mCityName;
+ }
+
+ /**
+ * @return the temperature unit if this is a weather request, -1 otherwise
+ */
+ public int getTemperatureUnit() {
+ switch (mRequestType) {
+ case TYPE_GEO_LOCATION_REQ:
+ case TYPE_WEATHER_LOCATION_REQ:
+ return mTempUnit;
+ default:
+ return -1;
+ }
+ }
+
+ /**
+ * @return if this is a weather request, whether the request will update the content provider.
+ * False for other kind of requests
+ * @hide
+ */
+ public boolean isQueryOnlyWeatherRequest() {
+ switch (mRequestType) {
+ case TYPE_GEO_LOCATION_REQ:
+ case TYPE_WEATHER_LOCATION_REQ:
+ return mIsQueryOnly;
+ default:
+ return false;
+ }
+ }
+
+ public static final Creator<RequestInfo> CREATOR = new Creator<RequestInfo>() {
+ @Override
+ public RequestInfo createFromParcel(Parcel in) {
+ return new RequestInfo(in);
+ }
+
+ @Override
+ public RequestInfo[] newArray(int size) {
+ return new RequestInfo[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ // Tell the concierge to prepare the parcel
+ ParcelInfo parcelInfo = Concierge.prepareParcel(dest);
+
+ // ==== ELDERBERRY =====
+ dest.writeInt(mKey);
+ dest.writeInt(mRequestType);
+ switch (mRequestType) {
+ case TYPE_GEO_LOCATION_REQ:
+ mLocation.writeToParcel(dest, 0);
+ dest.writeInt(mTempUnit);
+ break;
+ case TYPE_WEATHER_LOCATION_REQ:
+ mWeatherLocation.writeToParcel(dest, 0);
+ dest.writeInt(mTempUnit);
+ break;
+ case TYPE_LOOKUP_CITY_NAME_REQ:
+ dest.writeString(mCityName);
+ break;
+ }
+ dest.writeInt(mIsQueryOnly == true ? 1 : 0);
+ dest.writeStrongBinder(mListener.asBinder());
+
+ // Complete parcel info for the concierge
+ parcelInfo.complete();
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append("{ Request for ");
+ switch (mRequestType) {
+ case TYPE_GEO_LOCATION_REQ:
+ builder.append("Location: ").append(mLocation);
+ builder.append(" Temp Unit: ");
+ if (mTempUnit == WeatherContract.WeatherColumns.TempUnit.FAHRENHEIT) {
+ builder.append("Fahrenheit");
+ } else {
+ builder.append(" Celsius");
+ }
+ break;
+ case TYPE_WEATHER_LOCATION_REQ:
+ builder.append("WeatherLocation: ").append(mWeatherLocation);
+ builder.append(" Temp Unit: ");
+ if (mTempUnit == WeatherContract.WeatherColumns.TempUnit.FAHRENHEIT) {
+ builder.append("Fahrenheit");
+ } else {
+ builder.append(" Celsius");
+ }
+ break;
+ case TYPE_LOOKUP_CITY_NAME_REQ:
+ builder.append("Lookup City: ").append(mCityName);
+ break;
+ }
+ return builder.append(" }").toString();
+ }
+
+ @Override
+ public int hashCode() {
+ //The hashcode of this object was stored when it was built. This is an
+ //immutable object but we need to preserve the hashcode since this object is parcelable and
+ //it's reconstructed over IPC, and clients of this object might want to store it in a
+ //collection that relies on this code to identify the object
+ return mKey;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof RequestInfo) {
+ RequestInfo info = (RequestInfo) obj;
+ return (info.hashCode() == this.mKey);
+ }
+ return false;
+ }
+}
diff --git a/sdk/src/java/cyanogenmod/weather/WeatherInfo.aidl b/sdk/src/java/cyanogenmod/weather/WeatherInfo.aidl
new file mode 100644
index 0000000..ffff02c
--- /dev/null
+++ b/sdk/src/java/cyanogenmod/weather/WeatherInfo.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2016 The CyanongenMod 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.weather;
+
+parcelable WeatherInfo; \ No newline at end of file
diff --git a/sdk/src/java/cyanogenmod/weather/WeatherInfo.java b/sdk/src/java/cyanogenmod/weather/WeatherInfo.java
new file mode 100755
index 0000000..7d8686e
--- /dev/null
+++ b/sdk/src/java/cyanogenmod/weather/WeatherInfo.java
@@ -0,0 +1,524 @@
+/*
+ * Copyright (C) 2016 The CyanongenMod 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.weather;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import cyanogenmod.os.Build;
+import cyanogenmod.os.Concierge;
+import cyanogenmod.os.Concierge.ParcelInfo;
+import cyanogenmod.providers.WeatherContract;
+import cyanogenmod.weatherservice.ServiceRequest;
+import cyanogenmod.weatherservice.ServiceRequestResult;
+
+import java.util.ArrayList;
+
+/**
+ * This class represents the weather information that a
+ * {@link cyanogenmod.weatherservice.WeatherProviderService} will use to update the weather content
+ * provider. A weather provider service will be called by the system to process an update
+ * request at any time. If the service successfully processes the request, then the weather provider
+ * service is responsible of calling
+ * {@link ServiceRequest#complete(ServiceRequestResult)} to notify the
+ * system that the request was completed and that the weather content provider should be updated
+ * with the supplied weather information.
+ */
+public final class WeatherInfo implements Parcelable {
+
+ private String mCityId;
+ private String mCity;
+ private int mConditionCode;
+ private float mTemperature;
+ private int mTempUnit;
+ private float mHumidity;
+ private float mWindSpeed;
+ private float mWindDirection;
+ private int mWindSpeedUnit;
+ private long mTimestamp;
+ private ArrayList<DayForecast> mForecastList;
+ int mKey;
+
+ private WeatherInfo() {}
+
+ public static class Builder {
+ private String mCityId;
+ private String mCity;
+ private int mConditionCode;
+ private float mTemperature;
+ private int mTempUnit;
+ private float mHumidity;
+ private float mWindSpeed;
+ private float mWindDirection;
+ private int mWindSpeedUnit;
+ private long mTimestamp;
+ private ArrayList<DayForecast> mForecastList;
+
+ public Builder(long timestamp) {
+ mTimestamp = timestamp;
+ }
+
+ public Builder setCity(String cityId, @NonNull String cityName) {
+ if (cityName == null || cityId == null) {
+ throw new IllegalArgumentException("City name and id can't be null");
+ }
+ mCityId = cityId;
+ mCity = cityName;
+ return this;
+ }
+
+ public Builder setTemperature(float temperature, int tempUnit) {
+ if (!isValidTempUnit(tempUnit)) {
+ throw new IllegalArgumentException("Invalid temperature unit");
+ }
+
+ if (Float.isNaN(temperature)) {
+ throw new IllegalArgumentException("Invalid temperature value");
+ }
+
+ mTemperature = temperature;
+ mTempUnit = tempUnit;
+ return this;
+ }
+
+ public Builder setHumidity(float humidity) {
+ if (Float.isNaN(humidity)) {
+ throw new IllegalArgumentException("Invalid humidity value");
+ }
+
+ mHumidity = humidity;
+ return this;
+ }
+
+ public Builder setWind(float windSpeed, float windDirection, int windSpeedUnit) {
+ if (Float.isNaN(windSpeed)) {
+ throw new IllegalArgumentException("Invalid wind speed value");
+ }
+ if (Float.isNaN(windDirection)) {
+ throw new IllegalArgumentException("Invalid wind direction value");
+ }
+ if (!isValidWindSpeedUnit(windSpeedUnit)) {
+ throw new IllegalArgumentException("Invalid speed unit");
+ }
+ mWindSpeed = windSpeed;
+ mWindSpeedUnit = windSpeedUnit;
+ mWindDirection = windDirection;
+ return this;
+ }
+
+ public Builder setWeatherCondition(int conditionCode) {
+ if (!isValidWeatherCode(conditionCode)) {
+ throw new IllegalArgumentException("Invalid weather condition code");
+ }
+ mConditionCode = conditionCode;
+ return this;
+ }
+
+ public Builder setForecast(@NonNull ArrayList<DayForecast> forecasts) {
+ if (forecasts == null) {
+ throw new IllegalArgumentException("Forecast list can't be null");
+ }
+ mForecastList = forecasts;
+ return this;
+ }
+
+ public WeatherInfo build() {
+ WeatherInfo info = new WeatherInfo();
+ info.mCityId = this.mCityId;
+ info.mCity = this.mCity;
+ info.mConditionCode = this.mConditionCode;
+ info.mTemperature = this.mTemperature;
+ info.mTempUnit = this.mTempUnit;
+ info.mHumidity = this.mHumidity;
+ info.mWindSpeed = this.mWindSpeed;
+ info.mWindDirection = this.mWindDirection;
+ info.mWindSpeedUnit = this.mWindSpeedUnit;
+ info.mTimestamp = this.mTimestamp;
+ info.mForecastList = this.mForecastList;
+ info.mKey = this.hashCode();
+ return info;
+ }
+
+ private boolean isValidTempUnit(int unit) {
+ switch (unit) {
+ case WeatherContract.WeatherColumns.TempUnit.CELSIUS:
+ case WeatherContract.WeatherColumns.TempUnit.FAHRENHEIT:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ private boolean isValidWindSpeedUnit(int unit) {
+ switch (unit) {
+ case WeatherContract.WeatherColumns.WindSpeedUnit.KPH:
+ case WeatherContract.WeatherColumns.WindSpeedUnit.MPH:
+ return true;
+ default:
+ return false;
+ }
+ }
+ }
+
+
+ private static boolean isValidWeatherCode(int code) {
+ if (code < WeatherContract.WeatherColumns.WeatherCode.WEATHER_CODE_MIN
+ || code > WeatherContract.WeatherColumns.WeatherCode.WEATHER_CODE_MAX) {
+ if (code != WeatherContract.WeatherColumns.WeatherCode.NOT_AVAILABLE) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * @return city id
+ */
+ public String getCityId() {
+ return mCityId;
+ }
+
+ /**
+ * @return city name
+ */
+ public String getCity() {
+ return mCity;
+ }
+
+ /**
+ * @return An implementation specific weather condition code
+ */
+ public int getConditionCode() {
+ return mConditionCode;
+ }
+
+ /**
+ * @return humidity
+ */
+ public float getHumidity() {
+ return mHumidity;
+ }
+
+ /**
+ * @return time stamp when the request was processed
+ */
+ public long getTimestamp() {
+ return mTimestamp;
+ }
+
+ /**
+ * @return wind direction (degrees)
+ */
+ public float getWindDirection() {
+ return mWindDirection;
+ }
+
+ /**
+ * @return wind speed
+ */
+ public float getWindSpeed() {
+ return mWindSpeed;
+ }
+
+ /**
+ * @return wind speed unit
+ */
+ public int getWindSpeedUnit() {
+ return mWindSpeedUnit;
+ }
+
+ /**
+ * @return current temperature
+ */
+ public float getTemperature() {
+ return mTemperature;
+ }
+
+ /**
+ * @return temperature unit
+ */
+ public int getTemperatureUnit() {
+ return mTempUnit;
+ }
+
+ /**
+ * @return List of {@link cyanogenmod.weather.WeatherInfo.DayForecast}
+ */
+ public ArrayList<DayForecast> getForecasts() {
+ return mForecastList;
+ }
+
+ private WeatherInfo(Parcel parcel) {
+ // Read parcelable version via the Concierge
+ ParcelInfo parcelInfo = Concierge.receiveParcel(parcel);
+ int parcelableVersion = parcelInfo.getParcelVersion();
+
+ if (parcelableVersion >= Build.CM_VERSION_CODES.ELDERBERRY) {
+ mKey = parcel.readInt();
+ mCityId = parcel.readString();
+ mCity = parcel.readString();
+ mConditionCode = parcel.readInt();
+ mTemperature = parcel.readFloat();
+ mTempUnit = parcel.readInt();
+ mHumidity = parcel.readFloat();
+ mWindSpeed = parcel.readFloat();
+ mWindDirection = parcel.readFloat();
+ mWindSpeedUnit = parcel.readInt();
+ mTimestamp = parcel.readLong();
+ int forecastListSize = parcel.readInt();
+ mForecastList = new ArrayList<>();
+ while (forecastListSize > 0) {
+ mForecastList.add(DayForecast.CREATOR.createFromParcel(parcel));
+ forecastListSize--;
+ }
+ }
+
+ // Complete parcel info for the concierge
+ parcelInfo.complete();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ // Tell the concierge to prepare the parcel
+ ParcelInfo parcelInfo = Concierge.prepareParcel(dest);
+
+ // ==== ELDERBERRY =====
+ dest.writeInt(mKey);
+ dest.writeString(mCityId);
+ dest.writeString(mCity);
+ dest.writeInt(mConditionCode);
+ dest.writeFloat(mTemperature);
+ dest.writeInt(mTempUnit);
+ dest.writeFloat(mHumidity);
+ dest.writeFloat(mWindSpeed);
+ dest.writeFloat(mWindDirection);
+ dest.writeInt(mWindSpeedUnit);
+ dest.writeLong(mTimestamp);
+ dest.writeInt(mForecastList.size());
+ for (DayForecast dayForecast : mForecastList) {
+ dayForecast.writeToParcel(dest, 0);
+ }
+
+ // Complete parcel info for the concierge
+ parcelInfo.complete();
+ }
+
+ public static final Parcelable.Creator<WeatherInfo> CREATOR =
+ new Parcelable.Creator<WeatherInfo>() {
+
+ @Override
+ public WeatherInfo createFromParcel(Parcel source) {
+ return new WeatherInfo(source);
+ }
+
+ @Override
+ public WeatherInfo[] newArray(int size) {
+ return new WeatherInfo[size];
+ }
+ };
+
+ /**
+ * This class represents the weather forecast for a given day
+ */
+ public static class DayForecast implements Parcelable{
+ float mLow;
+ float mHigh;
+ int mConditionCode;
+ int mKey;
+
+ private DayForecast() {}
+
+ public static class Builder {
+ float mLow;
+ float mHigh;
+ int mConditionCode;
+
+ public Builder() {}
+ public Builder setHigh(float high) {
+ if (Float.isNaN(high)) {
+ throw new IllegalArgumentException("Invalid high forecast temperature");
+ }
+ mHigh = high;
+ return this;
+ }
+ public Builder setLow(float low) {
+ if (Float.isNaN(low)) {
+ throw new IllegalArgumentException("Invalid low forecast temperature");
+ }
+ mLow = low;
+ return this;
+ }
+
+ public Builder setWeatherCondition(int code) {
+ if (!isValidWeatherCode(code)) {
+ throw new IllegalArgumentException("Invalid weather condition code");
+ }
+ mConditionCode = code;
+ return this;
+ }
+
+ public DayForecast build() {
+ DayForecast forecast = new DayForecast();
+ forecast.mLow = this.mLow;
+ forecast.mHigh = this.mHigh;
+ forecast.mConditionCode = this.mConditionCode;
+ forecast.mKey = this.hashCode();
+ return forecast;
+ }
+ }
+
+ /**
+ * @return forecasted low temperature
+ */
+ public float getLow() {
+ return mLow;
+ }
+
+ /**
+ * @return not what you think. Returns the forecasted high temperature
+ */
+ public float getHigh() {
+ return mHigh;
+ }
+
+ /**
+ * @return forecasted weather condition code. Implementation specific
+ */
+ public int getConditionCode() {
+ return mConditionCode;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ // Tell the concierge to prepare the parcel
+ ParcelInfo parcelInfo = Concierge.prepareParcel(dest);
+
+ // ==== ELDERBERRY =====
+ dest.writeInt(mKey);
+ dest.writeFloat(mLow);
+ dest.writeFloat(mHigh);
+ dest.writeInt(mConditionCode);
+
+ // Complete parcel info for the concierge
+ parcelInfo.complete();
+ }
+
+ public static final Parcelable.Creator<DayForecast> CREATOR =
+ new Parcelable.Creator<DayForecast>() {
+ @Override
+ public DayForecast createFromParcel(Parcel source) {
+ return new DayForecast(source);
+ }
+
+ @Override
+ public DayForecast[] newArray(int size) {
+ return new DayForecast[size];
+ }
+ };
+
+ private DayForecast(Parcel parcel) {
+ // Read parcelable version via the Concierge
+ ParcelInfo parcelInfo = Concierge.receiveParcel(parcel);
+ int parcelableVersion = parcelInfo.getParcelVersion();
+
+ if (parcelableVersion >= Build.CM_VERSION_CODES.ELDERBERRY) {
+ mKey = parcel.readInt();
+ mLow = parcel.readFloat();
+ mHigh = parcel.readFloat();
+ mConditionCode = parcel.readInt();
+ }
+
+ // Complete parcel info for the concierge
+ parcelInfo.complete();
+ }
+
+ @Override
+ public String toString() {
+ return new StringBuilder()
+ .append("{Low temp: ").append(mLow)
+ .append(" High temp: ").append(mHigh)
+ .append(" Condition code: ").append(mConditionCode)
+ .append("}").toString();
+ }
+
+ @Override
+ public int hashCode() {
+ //The hashcode of this object was stored when it was built. This is an
+ //immutable object but we need to preserve the hashcode since this object is parcelable
+ //and it's reconstructed over IPC, and clients of this object might want to store it in
+ //a collection that relies on this code to identify the object
+ return mKey;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof DayForecast) {
+ DayForecast forecast = (DayForecast) obj;
+ return (forecast.hashCode() == this.mKey);
+ }
+ return false;
+ }
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder()
+ .append("{CityId: ").append(mCityId)
+ .append(" City Name: ").append(mCity)
+ .append(" Condition Code: ").append(mConditionCode)
+ .append(" Temperature: ").append(mTemperature)
+ .append(" Temperature Unit: ").append(mTempUnit)
+ .append(" Humidity: ").append(mHumidity)
+ .append(" Wind speed: ").append(mWindSpeed)
+ .append(" Wind direction: ").append(mWindDirection)
+ .append(" Wind Speed Unit: ").append(mWindSpeedUnit)
+ .append(" Timestamp: ").append(mTimestamp).append(" Forecasts: [");
+ for (DayForecast dayForecast : mForecastList) {
+ builder.append(dayForecast.toString());
+ }
+ return builder.append("]}").toString();
+ }
+
+ @Override
+ public int hashCode() {
+ //The hashcode of this object was stored when it was built. This is an
+ //immutable object but we need to preserve the hashcode since this object is parcelable and
+ //it's reconstructed over IPC, and clients of this object might want to store it in a
+ //collection that relies on this code to identify the object
+ return mKey;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof WeatherInfo) {
+ WeatherInfo info = (WeatherInfo) obj;
+ return (info.hashCode() == this.mKey);
+ }
+ return false;
+ }
+} \ No newline at end of file
diff --git a/sdk/src/java/cyanogenmod/weather/WeatherLocation.aidl b/sdk/src/java/cyanogenmod/weather/WeatherLocation.aidl
new file mode 100644
index 0000000..23f7b24
--- /dev/null
+++ b/sdk/src/java/cyanogenmod/weather/WeatherLocation.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2016 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.weather;
+
+parcelable WeatherLocation; \ No newline at end of file
diff --git a/sdk/src/java/cyanogenmod/weather/WeatherLocation.java b/sdk/src/java/cyanogenmod/weather/WeatherLocation.java
new file mode 100644
index 0000000..3afcd8e
--- /dev/null
+++ b/sdk/src/java/cyanogenmod/weather/WeatherLocation.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2016 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.weather;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import cyanogenmod.os.Build;
+import cyanogenmod.os.Concierge;
+import cyanogenmod.os.Concierge.ParcelInfo;
+
+/**
+ * A class representing a geographical location that a weather service provider can use to
+ * get weather data from. Each service provider will potentially populate objects of this class
+ * with different content, so make sure you don't preserve the values when a service provider
+ * is changed
+ */
+public final class WeatherLocation implements Parcelable{
+ private String mCityId;
+ private String mCity;
+ private String mPostal;
+ private String mCountryId;
+ private String mCountry;
+ private int mKey;
+
+ private WeatherLocation() {}
+
+ public static class Builder {
+ String mCityId;
+ String mCity;
+ String mPostal;
+ String mCountryId;
+ String mCountry;
+
+ public Builder(String cityId, String cityName) {
+ this.mCityId = cityId;
+ this.mCity = cityName;
+ }
+
+ public Builder setCountry(String countyId, String country) {
+ this.mCountryId = countyId;
+ this.mCountry = country;
+ return this;
+ }
+
+ public Builder setPostalCode(String postalCode) {
+ this.mPostal = postalCode;
+ return this;
+ }
+
+ public WeatherLocation build() {
+ WeatherLocation weatherLocation = new WeatherLocation();
+ weatherLocation.mCityId = this.mCityId;
+ weatherLocation.mCity = this.mCity;
+ weatherLocation.mPostal = this.mPostal;
+ weatherLocation.mCountryId = this.mCountryId;
+ weatherLocation.mCountry = this.mCountry;
+ weatherLocation.mKey = this.hashCode();
+ return weatherLocation;
+ }
+ }
+
+ public String getCityId() {
+ return mCityId;
+ }
+
+ public String getCity() {
+ return mCity;
+ }
+
+ public String getPostalCode() {
+ return mPostal;
+ }
+
+ public String getCountryId() {
+ return mCountryId;
+ }
+
+ public String getCountry() {
+ return mCountry;
+ }
+
+ private WeatherLocation(Parcel in) {
+ // Read parcelable version via the Concierge
+ ParcelInfo parcelInfo = Concierge.receiveParcel(in);
+ int parcelableVersion = parcelInfo.getParcelVersion();
+
+ if (parcelableVersion >= Build.CM_VERSION_CODES.ELDERBERRY) {
+ mKey = in.readInt();
+ mCityId = in.readString();
+ mCity = in.readString();
+ mPostal = in.readString();
+ mCountryId = in.readString();
+ mCountry = in.readString();
+ }
+
+ // Complete parcel info for the concierge
+ parcelInfo.complete();
+ }
+
+ public static final Creator<WeatherLocation> CREATOR = new Creator<WeatherLocation>() {
+ @Override
+ public WeatherLocation createFromParcel(Parcel in) {
+ return new WeatherLocation(in);
+ }
+
+ @Override
+ public WeatherLocation[] newArray(int size) {
+ return new WeatherLocation[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ // Tell the concierge to prepare the parcel
+ ParcelInfo parcelInfo = Concierge.prepareParcel(dest);
+
+ // ==== ELDERBERRY =====
+ dest.writeInt(mKey);
+ dest.writeString(mCityId);
+ dest.writeString(mCity);
+ dest.writeString(mPostal);
+ dest.writeString(mCountryId);
+ dest.writeString(mCountry);
+
+ // Complete parcel info for the concierge
+ parcelInfo.complete();
+ }
+
+ @Override
+ public String toString() {
+ return new StringBuilder()
+ .append("{ City ID: ").append(mCityId)
+ .append(" City: ").append(mCity)
+ .append(" Postal Code: ").append(mPostal)
+ .append(" Country Id: ").append(mCountryId)
+ .append(" Country: ").append(mCountry).append("}")
+ .toString();
+ }
+
+ @Override
+ public int hashCode() {
+ return mKey;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof WeatherLocation) {
+ WeatherLocation info = (WeatherLocation) obj;
+ return (info.hashCode() == this.mKey);
+ }
+ return false;
+ }
+}
diff --git a/sdk/src/java/cyanogenmod/weather/util/WeatherUtils.java b/sdk/src/java/cyanogenmod/weather/util/WeatherUtils.java
new file mode 100644
index 0000000..c89213b
--- /dev/null
+++ b/sdk/src/java/cyanogenmod/weather/util/WeatherUtils.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2016 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.weather.util;
+
+
+import cyanogenmod.providers.WeatherContract;
+
+import java.text.DecimalFormat;
+
+/**
+ * Helper class to perform operations and formatting of weather data
+ */
+public class WeatherUtils {
+
+ /**
+ * Converts a temperature expressed in degrees Celsius to degrees Fahrenheit
+ * @param celsius temperature in Celsius
+ * @return the temperature in degrees Fahrenheit
+ */
+ public static float celsiusToFahrenheit(float celsius) {
+ return ((celsius * (9f/5f)) + 32f);
+ }
+
+ /**
+ * Converts a temperature expressed in degrees Fahrenheit to degrees Celsius
+ * @param fahrenheit temperature in Fahrenheit
+ * @return the temperature in degrees Celsius
+ */
+ public static float fahrenheitToCelsius(float fahrenheit) {
+ return ((fahrenheit - 32f) * (5f/9f));
+ }
+
+ /**
+ * Returns a string representation of the temperature and unit supplied. The temperature value
+ * will be half-even rounded.
+ * @param temperature the temperature value
+ * @param tempUnit A valid {@link WeatherContract.WeatherColumns.TempUnit}
+ * @return A string with the format XX&deg;F or XX&deg;C (where XX is the temperature)
+ * depending on the temperature unit that was provided or null if an invalid unit is supplied
+ */
+ public static String formatTemperature(float temperature, int tempUnit) {
+ if (!isValidTempUnit(tempUnit)) return null;
+ if (Float.isNaN(temperature)) return "-";
+
+ DecimalFormat noDigitsFormat = new DecimalFormat("0");
+ String noDigitsTemp = noDigitsFormat.format(temperature);
+ if (noDigitsTemp.equals("-0")) {
+ noDigitsTemp = "0";
+ }
+
+ StringBuilder formatted = new StringBuilder()
+ .append(noDigitsTemp).append("\u00b0");
+ if (tempUnit == WeatherContract.WeatherColumns.TempUnit.CELSIUS) {
+ formatted.append("C");
+ } else if (tempUnit == WeatherContract.WeatherColumns.TempUnit.FAHRENHEIT) {
+ formatted.append("F");
+ }
+ return formatted.toString();
+ }
+
+ private static boolean isValidTempUnit(int unit) {
+ switch (unit) {
+ case WeatherContract.WeatherColumns.TempUnit.CELSIUS:
+ case WeatherContract.WeatherColumns.TempUnit.FAHRENHEIT:
+ return true;
+ default:
+ return false;
+ }
+ }
+}
diff --git a/sdk/src/java/cyanogenmod/weatherservice/IWeatherProviderService.aidl b/sdk/src/java/cyanogenmod/weatherservice/IWeatherProviderService.aidl
new file mode 100644
index 0000000..d9eceb3
--- /dev/null
+++ b/sdk/src/java/cyanogenmod/weatherservice/IWeatherProviderService.aidl
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2016 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.weatherservice;
+
+import cyanogenmod.weatherservice.IWeatherProviderServiceClient;
+import cyanogenmod.weather.RequestInfo;
+
+/** @hide */
+oneway interface IWeatherProviderService {
+ void processWeatherUpdateRequest(in RequestInfo request);
+ void processCityNameLookupRequest(in RequestInfo request);
+ void setServiceClient(in IWeatherProviderServiceClient client);
+ void cancelOngoingRequests();
+} \ No newline at end of file
diff --git a/sdk/src/java/cyanogenmod/weatherservice/IWeatherProviderServiceClient.aidl b/sdk/src/java/cyanogenmod/weatherservice/IWeatherProviderServiceClient.aidl
new file mode 100644
index 0000000..a4baa4c
--- /dev/null
+++ b/sdk/src/java/cyanogenmod/weatherservice/IWeatherProviderServiceClient.aidl
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2016 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.weatherservice;
+
+import cyanogenmod.weather.RequestInfo;
+import cyanogenmod.weatherservice.ServiceRequestResult;
+
+/** @hide */
+oneway interface IWeatherProviderServiceClient {
+ void setServiceRequestState(in RequestInfo requestInfo, in ServiceRequestResult result,
+ int state);
+} \ No newline at end of file
diff --git a/sdk/src/java/cyanogenmod/weatherservice/ServiceRequest.java b/sdk/src/java/cyanogenmod/weatherservice/ServiceRequest.java
new file mode 100644
index 0000000..e43218d
--- /dev/null
+++ b/sdk/src/java/cyanogenmod/weatherservice/ServiceRequest.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2016 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.weatherservice;
+
+import android.annotation.NonNull;
+import android.os.RemoteException;
+import cyanogenmod.weather.CMWeatherManager;
+import cyanogenmod.weather.RequestInfo;
+
+/**
+ * This class represents a request submitted by the system to the active weather provider service
+ */
+public final class ServiceRequest {
+
+ private final RequestInfo mInfo;
+ private final IWeatherProviderServiceClient mClient;
+
+ /**
+ * If a request is marked as cancelled, it means the client does not want to know anything about
+ * this request anymore
+ */
+ private volatile boolean mCancelled;
+
+ /* package */ ServiceRequest(RequestInfo info, IWeatherProviderServiceClient client) {
+ mInfo = info;
+ mClient = client;
+ }
+
+ /**
+ * Obtains the request information
+ * @return {@link cyanogenmod.weather.RequestInfo}
+ */
+ public RequestInfo getRequestInfo() {
+ return mInfo;
+ }
+
+ /**
+ * This method should be called once the request has been completed
+ */
+ public void complete(@NonNull ServiceRequestResult result) {
+ if (!mCancelled) {
+ try {
+ final int requestType = mInfo.getRequestType();
+ switch (requestType) {
+ case RequestInfo.TYPE_GEO_LOCATION_REQ:
+ case RequestInfo.TYPE_WEATHER_LOCATION_REQ:
+ if (result.getWeatherInfo() == null) {
+ throw new IllegalStateException("The service request result does not"
+ + " contain a valid WeatherInfo object");
+ }
+ mClient.setServiceRequestState(mInfo, result,
+ CMWeatherManager.WEATHER_REQUEST_COMPLETED);
+ break;
+ case RequestInfo.TYPE_LOOKUP_CITY_NAME_REQ:
+ if (result.getLocationLookupList() == null) {
+ //In case the user decided to mark this request as completed with an
+ //empty list. It's not necessarily a failure
+ mClient.setServiceRequestState(mInfo, null,
+ CMWeatherManager.LOOKUP_REQUEST_NO_MATCH_FOUND);
+ } else {
+ mClient.setServiceRequestState(mInfo, result,
+ CMWeatherManager.LOOKUP_REQUEST_COMPLETED);
+ }
+ break;
+ }
+ } catch (RemoteException e) {
+ }
+ }
+ }
+
+ /**
+ * This method should be called if the service failed to process the request
+ * (no internet connection, time out, etc.)
+ */
+ public void fail() {
+ if (!mCancelled) {
+ try {
+ final int requestType = mInfo.getRequestType();
+ switch (requestType) {
+ case RequestInfo.TYPE_GEO_LOCATION_REQ:
+ case RequestInfo.TYPE_WEATHER_LOCATION_REQ:
+ mClient.setServiceRequestState(mInfo, null,
+ CMWeatherManager.WEATHER_REQUEST_FAILED);
+ break;
+ case RequestInfo.TYPE_LOOKUP_CITY_NAME_REQ:
+ mClient.setServiceRequestState(mInfo, null,
+ CMWeatherManager.LOOKUP_REQUEST_FAILED);
+ break;
+ }
+ } catch (RemoteException e) {
+ }
+ }
+ }
+
+ /**
+ * Called by the WeatherProviderService base class to notify we don't want this request anymore.
+ * The service implementing the WeatherProviderService will be notified of this action
+ * via onRequestCancelled()
+ * @hide
+ */
+ public void cancel() {
+ mCancelled = true;
+ }
+}
diff --git a/sdk/src/java/cyanogenmod/weatherservice/ServiceRequestResult.aidl b/sdk/src/java/cyanogenmod/weatherservice/ServiceRequestResult.aidl
new file mode 100644
index 0000000..669ece5
--- /dev/null
+++ b/sdk/src/java/cyanogenmod/weatherservice/ServiceRequestResult.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2016 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.weatherservice;
+
+parcelable ServiceRequestResult; \ No newline at end of file
diff --git a/sdk/src/java/cyanogenmod/weatherservice/ServiceRequestResult.java b/sdk/src/java/cyanogenmod/weatherservice/ServiceRequestResult.java
new file mode 100644
index 0000000..07f4cd9
--- /dev/null
+++ b/sdk/src/java/cyanogenmod/weatherservice/ServiceRequestResult.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2016 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.weatherservice;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import cyanogenmod.os.Build;
+import cyanogenmod.os.Concierge;
+import cyanogenmod.os.Concierge.ParcelInfo;
+import cyanogenmod.weather.WeatherLocation;
+import cyanogenmod.weather.WeatherInfo;
+
+import java.util.ArrayList;
+
+/**
+ * Use this class to build a request result.
+ */
+public final class ServiceRequestResult implements Parcelable {
+
+ private WeatherInfo mWeatherInfo;
+ private ArrayList<WeatherLocation> mLocationLookupList;
+ private int mKey;
+
+ private ServiceRequestResult() {}
+
+ private ServiceRequestResult(Parcel in) {
+ // Read parcelable version via the Concierge
+ ParcelInfo parcelInfo = Concierge.receiveParcel(in);
+ int parcelableVersion = parcelInfo.getParcelVersion();
+
+ if (parcelableVersion >= Build.CM_VERSION_CODES.ELDERBERRY) {
+ mKey = in.readInt();
+ int hasWeatherInfo = in.readInt();
+ if (hasWeatherInfo == 1) {
+ mWeatherInfo = WeatherInfo.CREATOR.createFromParcel(in);
+ }
+ int hasLocationLookupList = in.readInt();
+ if (hasLocationLookupList == 1) {
+ mLocationLookupList = new ArrayList<>();
+ int listSize = in.readInt();
+ while (listSize > 0) {
+ mLocationLookupList.add(WeatherLocation.CREATOR.createFromParcel(in));
+ listSize--;
+ }
+ }
+ }
+
+ // Complete parcel info for the concierge
+ parcelInfo.complete();
+ }
+
+ public static final Creator<ServiceRequestResult> CREATOR
+ = new Creator<ServiceRequestResult>() {
+ @Override
+ public ServiceRequestResult createFromParcel(Parcel in) {
+ return new ServiceRequestResult(in);
+ }
+
+ @Override
+ public ServiceRequestResult[] newArray(int size) {
+ return new ServiceRequestResult[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ // Tell the concierge to prepare the parcel
+ ParcelInfo parcelInfo = Concierge.prepareParcel(dest);
+
+ // ==== ELDERBERRY =====
+ dest.writeInt(mKey);
+ if (mWeatherInfo != null) {
+ dest.writeInt(1);
+ mWeatherInfo.writeToParcel(dest, 0);
+ } else {
+ dest.writeInt(0);
+ }
+ if (mLocationLookupList != null) {
+ dest.writeInt(1);
+ dest.writeInt(mLocationLookupList.size());
+ for (WeatherLocation lookup : mLocationLookupList) {
+ lookup.writeToParcel(dest, 0);
+ }
+ } else {
+ dest.writeInt(0);
+ }
+
+ // Complete parcel info for the concierge
+ parcelInfo.complete();
+ }
+
+ public static class Builder {
+ private WeatherInfo mBuilderWeatherInfo;
+ private ArrayList<WeatherLocation> mBuilderLocationLookupList;
+ public Builder() {
+ this.mBuilderWeatherInfo = null;
+ this.mBuilderLocationLookupList = null;
+ }
+
+ /**
+ * Add the supplied weather information to the result. Attempting to add a WeatherLocation
+ * list to the same builder will cause the system to throw IllegalArgumentException
+ *
+ * @param weatherInfo The WeatherInfo object holding the data that will be used to update
+ * the weather content provider
+ */
+ public Builder setWeatherInfo(@NonNull WeatherInfo weatherInfo) {
+ if (mBuilderLocationLookupList != null) {
+ throw new IllegalArgumentException("Can't add weather information when you have"
+ + " already added a WeatherLocation list");
+ }
+
+ if (weatherInfo == null) {
+ throw new IllegalArgumentException("WeatherInfo can't be null");
+ }
+
+ mBuilderWeatherInfo = weatherInfo;
+ return this;
+ }
+
+ /**
+ * Add the supplied list of WeatherLocation objects to the result. Attempting to add a
+ * WeatherInfo object to the same builder will cause the system to throw
+ * IllegalArgumentException
+ *
+ * @param locations The list of WeatherLocation objects. The list should not be null
+ */
+ public Builder setLocationLookupList(@NonNull ArrayList<WeatherLocation> locations) {
+ if (mBuilderWeatherInfo != null) {
+ throw new IllegalArgumentException("Can't add a WeatherLocation list when you have"
+ + " already added weather information");
+ }
+
+ mBuilderLocationLookupList = locations;
+ return this;
+ }
+
+ /**
+ * Creates a {@link ServiceRequest} with the arguments
+ * supplied to this builder
+ * @return {@link ServiceRequestResult}
+ */
+ public ServiceRequestResult build() {
+ ServiceRequestResult result = new ServiceRequestResult();
+ result.mWeatherInfo = this.mBuilderWeatherInfo;
+ result.mLocationLookupList = this.mBuilderLocationLookupList;
+ result.mKey = this.hashCode();
+ return result;
+ }
+ }
+
+ /**
+ * @return The WeatherInfo object supplied by the weather provider service
+ */
+ public WeatherInfo getWeatherInfo() {
+ return mWeatherInfo;
+ }
+
+ /**
+ * @return The list of WeatherLocation objects supplied by the weather provider service
+ */
+ public ArrayList<WeatherLocation> getLocationLookupList() {
+ return mLocationLookupList;
+ }
+
+ @Override
+ public int hashCode() {
+ //The hashcode of this object was stored when it was built. This is an
+ //immutable object but we need to preserve the hashcode since this object is parcelable and
+ //it's reconstructed over IPC, and clients of this object might want to store it in a
+ //collection that relies on this code to identify the object
+ return mKey;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof ServiceRequestResult) {
+ ServiceRequestResult request = (ServiceRequestResult) obj;
+ return (request.hashCode() == this.mKey);
+ }
+ return false;
+ }
+}
diff --git a/sdk/src/java/cyanogenmod/weatherservice/WeatherProviderService.java b/sdk/src/java/cyanogenmod/weatherservice/WeatherProviderService.java
new file mode 100644
index 0000000..759d5fc
--- /dev/null
+++ b/sdk/src/java/cyanogenmod/weatherservice/WeatherProviderService.java
@@ -0,0 +1,196 @@
+/*
+ * Copyright (C) 2016 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.weatherservice;
+
+import android.annotation.SdkConstant;
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import cyanogenmod.weather.RequestInfo;
+
+import java.util.Collections;
+import java.util.Set;
+import java.util.WeakHashMap;
+
+/**
+ * This is the base class for implementing a weather provider service. A weather provider service
+ * can handle weather update requests and update the weather content provider data by processing
+ * a {@link ServiceRequest}
+ *
+ * A print service is declared as any other service in an AndroidManifest.xml but it must also
+ * specify that in handles the {@link android.content.Intent} with action
+ * {@link #SERVICE_INTERFACE cyanogenmod.weatherservice.WeatherProviderService}. Failure to declare
+ * this intent will cause the system to ignore the weather provider service. Additionally, a
+ * weather provider service must request the
+ * {@link cyanogenmod.platform.Manifest.permission#BIND_WEATHER_PROVIDER_SERVICE} permission to
+ * ensure that only the system can bind to it. Failure to request this permission will cause the
+ * system to ignore this weather provider service. Following is an example declaration:
+ *
+ * <pre>
+ * &lt;service android:name=".MyWeatherProviderService"
+ * android:permission="cyanogenmod.permission.BIND_WEATHER_PROVIDER_SERVICE"&gt;
+ * &lt;intent-filter&gt;
+ * &lt;action android:name="cyanogenmod.weatherservice.WeatherProviderService" /&gt;
+ * &lt;intent-filter&gt;
+ * . . .
+ * &lt;/service&gt;
+ * </pre>
+ *
+ */
+public abstract class WeatherProviderService extends Service {
+
+ private Handler mHandler;
+ private IWeatherProviderServiceClient mClient;
+ private Set<ServiceRequest> mWeakRequestsSet
+ = Collections.newSetFromMap(new WeakHashMap<ServiceRequest, Boolean>());
+
+ /**
+ * The {@link android.content.Intent} action that must be declared as handled by a service in
+ * its manifest for the system to recognize it as a weather provider service
+ */
+ @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
+ public static final String SERVICE_INTERFACE
+ = "cyanogenmod.weatherservice.WeatherProviderService";
+
+ /**
+ * Name under which a {@link WeatherProviderService} publishes information about itself.
+ * This meta-data must reference an XML resource containing
+ * a <code>&lt;weather-provider-service&gt;</code>
+ * tag.
+ */
+ public static final String SERVICE_META_DATA = "cyanogenmod.weatherservice";
+
+ @Override
+ protected final void attachBaseContext(Context base) {
+ super.attachBaseContext(base);
+ mHandler = new ServiceHandler(base.getMainLooper());
+ }
+
+ @Override
+ public final IBinder onBind(Intent intent) {
+ return mBinder;
+ }
+
+ private final IWeatherProviderService.Stub mBinder = new IWeatherProviderService.Stub() {
+
+ @Override
+ public void processWeatherUpdateRequest(final RequestInfo info) {
+ mHandler.obtainMessage(ServiceHandler.MSG_ON_NEW_REQUEST, info).sendToTarget();
+ }
+
+ @Override
+ public void processCityNameLookupRequest(final RequestInfo info) {
+ mHandler.obtainMessage(ServiceHandler.MSG_ON_NEW_REQUEST, info).sendToTarget();
+ }
+
+ @Override
+ public void setServiceClient(IWeatherProviderServiceClient client) {
+ mHandler.obtainMessage(ServiceHandler.MSG_SET_CLIENT, client).sendToTarget();
+ }
+
+ @Override
+ public void cancelOngoingRequests() {
+ mHandler.obtainMessage(ServiceHandler.MSG_CANCEL_ONGOING_REQUESTS).sendToTarget();
+ }
+ };
+
+ private class ServiceHandler extends Handler {
+
+ public ServiceHandler(Looper looper) {
+ super(looper);
+ }
+ public static final int MSG_SET_CLIENT = 1;
+ public static final int MSG_ON_NEW_REQUEST = 2;
+ public static final int MSG_CANCEL_ONGOING_REQUESTS = 3;
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_SET_CLIENT: {
+ mClient = (IWeatherProviderServiceClient) msg.obj;
+ if (mClient != null) {
+ onConnected();
+ } else {
+ onDisconnected();
+ }
+ return;
+ }
+ case MSG_ON_NEW_REQUEST: {
+ RequestInfo info = (RequestInfo) msg.obj;
+ if (info != null) {
+ ServiceRequest request = new ServiceRequest(info, mClient);
+ synchronized (mWeakRequestsSet) {
+ mWeakRequestsSet.add(request);
+ }
+ onRequestSubmitted(request);
+ }
+ return;
+ }
+ case MSG_CANCEL_ONGOING_REQUESTS: {
+ synchronized (mWeakRequestsSet) {
+ for (final ServiceRequest request : mWeakRequestsSet) {
+ if (request != null) {
+ request.cancel();
+ mWeakRequestsSet.remove(request);
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ onRequestCancelled(request);
+ }
+ });
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * The system has connected to this service.
+ */
+ protected void onConnected() {
+ /* Do nothing */
+ }
+
+ /**
+ * The system has disconnected from this service.
+ */
+ protected void onDisconnected() {
+ /* Do nothing */
+ }
+
+ /**
+ * A new request has been submitted to this service
+ * @param request The service request to be processed by this service
+ */
+ protected abstract void onRequestSubmitted(ServiceRequest request);
+
+ /**
+ * Called when the system is not interested on this request anymore. Note that the service
+ * <b>has marked the request as cancelled</b> and you must stop any ongoing operation
+ * (such as pulling data from internet) that this service could've been performing to honor the
+ * request.
+ *
+ * @param request The request cancelled by the system
+ */
+ protected abstract void onRequestCancelled(ServiceRequest request);
+} \ No newline at end of file
diff --git a/sdk/src/java/org/cyanogenmod/internal/logging/CMMetricsLogger.java b/sdk/src/java/org/cyanogenmod/internal/logging/CMMetricsLogger.java
new file mode 100644
index 0000000..e3303d5
--- /dev/null
+++ b/sdk/src/java/org/cyanogenmod/internal/logging/CMMetricsLogger.java
@@ -0,0 +1,70 @@
+/**
+ * Copyright (c) 2016, 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 org.cyanogenmod.internal.logging;
+
+import com.android.internal.logging.MetricsLogger;
+
+/**
+ * Serves as a central location for logging constants that is android release agnostic.
+ */
+public class CMMetricsLogger extends MetricsLogger {
+ private static final int BASE = -Integer.MAX_VALUE;
+ //Since we never want to collide, lets start at the back and move inward
+ public static final int DONT_LOG = BASE + 1;
+
+ public static final int ANONYMOUS_STATS = BASE + 2;
+ public static final int APP_GROUP_CONFIG = BASE + 3;
+ public static final int APP_GROUP_LIST = BASE + 4;
+ public static final int BATTERY_LIGHT_SETTINGS = BASE + 5;
+ public static final int BUTTON_SETTINGS = BASE + 6;
+ public static final int CHOOSE_LOCK_PATTERN_SIZE = BASE + 7;
+ public static final int DISPLAY_ROTATION = BASE + 8;
+ public static final int LIVE_DISPLAY = BASE + 9;
+ public static final int NOTIFICATION_LIGHT_SETTINGS = BASE + 10;
+ public static final int NOTIFICATION_MANAGER_SETTINGS = BASE + 11;
+ public static final int POWER_MENU_ACTIONS = BASE + 12;
+ public static final int PREVIEW_DATA = BASE + 13;
+ public static final int PRIVACY_GUARD_PREFS = BASE + 14;
+ public static final int PRIVACY_SETTINGS = BASE + 15;
+ public static final int PROFILE_GROUP_CONFIG = BASE + 16;
+ public static final int PROFILES_SETTINGS = BASE + 17;
+ public static final int SETUP_ACTIONS_FRAGMENT = BASE + 18;
+ public static final int SETUP_TRIGGERS_FRAGMENT = BASE + 19;
+ public static final int STYLUS_GESTURES = BASE + 20;
+ public static final int TILE_ADB_OVER_NETWORK = BASE + 21;
+ public static final int TILE_AMBIENT_DISPLAY = BASE + 22;
+ public static final int TILE_COMPASS = BASE + 23;
+ public static final int TILE_CUSTOM_QS = BASE + 24;
+ public static final int TILE_CUSTOM_QS_DETAIL = BASE + 25;
+ public static final int TILE_EDIT = BASE + 26;
+ public static final int TILE_LIVE_DISPLAY = BASE + 27;
+ public static final int TILE_LOCKSCREEN_TOGGLE = BASE + 28;
+ public static final int TILE_NFC = BASE + 29;
+ public static final int TILE_PERF_PROFILE = BASE + 30;
+ public static final int TILE_PERF_PROFILE_DETAIL = BASE + 31;
+ public static final int TILE_PROFILES = BASE + 32;
+ public static final int TILE_PROFILES_DETAIL = BASE + 33;
+ public static final int TILE_SCREEN_TIME_OUT = BASE + 34;
+ public static final int TILE_SCREEN_TIME_OUT_DETAIL = BASE + 35;
+ public static final int TILE_SYNC = BASE + 36;
+ public static final int TILE_USB_TETHER = BASE + 37;
+ public static final int TILE_VOLUME = BASE + 38;
+ public static final int TILE_HEADS_UP = BASE + 39;
+ public static final int TILE_BATTERY_SAVER = BASE + 40;
+ public static final int TILE_CAFFEINE = BASE + 41;
+ public static final int WEATHER_SETTINGS = BASE + 42;
+}
diff --git a/sdk/src/java/org/cyanogenmod/internal/statusbar/ExternalQuickSettingsRecord.java b/sdk/src/java/org/cyanogenmod/internal/statusbar/ExternalQuickSettingsRecord.java
new file mode 100644
index 0000000..05f8edf
--- /dev/null
+++ b/sdk/src/java/org/cyanogenmod/internal/statusbar/ExternalQuickSettingsRecord.java
@@ -0,0 +1,53 @@
+/**
+ * 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 org.cyanogenmod.internal.statusbar;
+
+import android.os.UserHandle;
+import com.android.internal.annotations.VisibleForTesting;
+
+import cyanogenmod.app.CustomTile;
+import cyanogenmod.app.StatusBarPanelCustomTile;
+
+/**
+ * @hide
+ */
+public class ExternalQuickSettingsRecord {
+ public final StatusBarPanelCustomTile sbTile;
+ public boolean isUpdate;
+ public boolean isCanceled;
+
+ @VisibleForTesting
+ public ExternalQuickSettingsRecord(StatusBarPanelCustomTile tile) {
+ sbTile = tile;
+ }
+
+ public CustomTile getCustomTile() {
+ return sbTile.getCustomTile();
+ }
+
+ public UserHandle getUser() {
+ return sbTile.getUser();
+ }
+
+ public int getUserId() {
+ return sbTile.getUserId();
+ }
+
+ public String getKey() {
+ return sbTile.getKey();
+ }
+}
diff --git a/sdk/src/java/org/cyanogenmod/internal/statusbar/IStatusBarCustomTileHolder.aidl b/sdk/src/java/org/cyanogenmod/internal/statusbar/IStatusBarCustomTileHolder.aidl
new file mode 100644
index 0000000..90e04de
--- /dev/null
+++ b/sdk/src/java/org/cyanogenmod/internal/statusbar/IStatusBarCustomTileHolder.aidl
@@ -0,0 +1,25 @@
+/**
+ * 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 org.cyanogenmod.internal.statusbar;
+
+import cyanogenmod.app.StatusBarPanelCustomTile;
+
+/** @hide */
+interface IStatusBarCustomTileHolder {
+ /** Fetch the held StatusBarPanelCustomTile. This method should only be called once per Holder */
+ StatusBarPanelCustomTile get();
+} \ No newline at end of file
diff --git a/sdk/src/java/org/cyanogenmod/internal/themes/IIconCacheManager.aidl b/sdk/src/java/org/cyanogenmod/internal/themes/IIconCacheManager.aidl
new file mode 100644
index 0000000..c69e082
--- /dev/null
+++ b/sdk/src/java/org/cyanogenmod/internal/themes/IIconCacheManager.aidl
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2016 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 org.cyanogenmod.internal.themes;
+
+import android.graphics.Bitmap;
+
+/** @hide */
+interface IIconCacheManager {
+ boolean cacheComposedIcon(in Bitmap icon, String path);
+}
diff --git a/sdk/src/java/org/cyanogenmod/internal/util/CmLockPatternUtils.java b/sdk/src/java/org/cyanogenmod/internal/util/CmLockPatternUtils.java
new file mode 100644
index 0000000..75ab0b3
--- /dev/null
+++ b/sdk/src/java/org/cyanogenmod/internal/util/CmLockPatternUtils.java
@@ -0,0 +1,106 @@
+/*
+ * 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 org.cyanogenmod.internal.util;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.UserHandle;
+import android.text.TextUtils;
+
+import com.android.internal.widget.LockPatternUtils;
+
+import cyanogenmod.platform.Manifest;
+import cyanogenmod.providers.CMSettings;
+
+public class CmLockPatternUtils extends LockPatternUtils {
+
+ /**
+ * Third party keyguard component to be displayed within the keyguard
+ */
+ public static final String THIRD_PARTY_KEYGUARD_COMPONENT = "lockscreen.third_party";
+
+ /**
+ * Action to be broadcasted when the third party keyguard component has been changed
+ */
+ public static final String ACTION_THIRD_PARTY_KEYGUARD_COMPONENT_CHANGED =
+ "org.cyanogenmod.internal.action.THIRD_PARTY_KEYGUARD_COMPONENT_CHANGED";
+
+ private Context mContext;
+
+ public CmLockPatternUtils(Context context) {
+ super(context);
+ mContext = context;
+ }
+
+ /**
+ * Sets a third party lock screen.
+ * @param component
+ */
+ public void setThirdPartyKeyguard(ComponentName component)
+ throws PackageManager.NameNotFoundException {
+ if (component != null) {
+ // Check that the package this component belongs to has the third party keyguard perm
+ final PackageManager pm = mContext.getPackageManager();
+ final boolean hasThirdPartyKeyguardPermission = pm.checkPermission(
+ Manifest.permission.THIRD_PARTY_KEYGUARD, component.getPackageName()) ==
+ PackageManager.PERMISSION_GRANTED;
+ if (!hasThirdPartyKeyguardPermission) {
+ throw new SecurityException("Package " + component.getPackageName() + " does not" +
+ "have " + Manifest.permission.THIRD_PARTY_KEYGUARD);
+ }
+ }
+
+ setString(THIRD_PARTY_KEYGUARD_COMPONENT,
+ component != null ? component.flattenToString() : "", getCurrentUser());
+
+ // notify systemui, or whatever other process needs to know, that the third party keyguard
+ // component has changed. What it changed to is up to the receiver to figure out using
+ // the methods provided in this class.
+ mContext.sendOrderedBroadcast(new Intent(ACTION_THIRD_PARTY_KEYGUARD_COMPONENT_CHANGED),
+ null);
+ }
+
+ /**
+ * Get the currently applied 3rd party keyguard component
+ * @return
+ */
+ public ComponentName getThirdPartyKeyguardComponent() {
+ String component = getString(THIRD_PARTY_KEYGUARD_COMPONENT, getCurrentUser());
+ return component != null ? ComponentName.unflattenFromString(component) : null;
+ }
+
+ /**
+ * @return Whether a third party keyguard is set
+ */
+ public boolean isThirdPartyKeyguardEnabled() {
+ String component = getString(THIRD_PARTY_KEYGUARD_COMPONENT, getCurrentUser());
+ return !TextUtils.isEmpty(component);
+ }
+
+ private int getCurrentUser() {
+ return UserHandle.USER_CURRENT;
+ }
+
+ public boolean shouldPassToSecurityView(int userId) {
+ return getBoolean(CMSettings.Secure.LOCK_PASS_TO_SECURITY_VIEW, false, userId);
+ }
+
+ public void setPassToSecurityView(boolean enabled, int userId) {
+ setBoolean(CMSettings.Secure.LOCK_PASS_TO_SECURITY_VIEW, enabled, userId);
+ }
+}
diff --git a/sdk/src/java/org/cyanogenmod/internal/util/ImageUtils.java b/sdk/src/java/org/cyanogenmod/internal/util/ImageUtils.java
new file mode 100644
index 0000000..c67c23c
--- /dev/null
+++ b/sdk/src/java/org/cyanogenmod/internal/util/ImageUtils.java
@@ -0,0 +1,332 @@
+/*
+ * Copyright (C) 2013-2014 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 org.cyanogenmod.internal.util;
+
+import android.app.WallpaperManager;
+import android.content.Context;
+import android.content.res.AssetManager;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Point;
+import android.net.Uri;
+import android.text.TextUtils;
+import android.util.Log;
+import android.webkit.URLUtil;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+
+import cyanogenmod.providers.ThemesContract.PreviewColumns;
+import cyanogenmod.providers.ThemesContract.ThemesColumns;
+
+import libcore.io.IoUtils;
+
+public class ImageUtils {
+ private static final String TAG = ImageUtils.class.getSimpleName();
+
+ private static final String ASSET_URI_PREFIX = "file:///android_asset/";
+ private static final int DEFAULT_IMG_QUALITY = 100;
+
+ /**
+ * Gets the Width and Height of the image
+ *
+ * @param inputStream The input stream of the image
+ *
+ * @return A point structure that holds the Width and Height (x and y)/*"
+ */
+ public static Point getImageDimension(InputStream inputStream) {
+ if (inputStream == null) {
+ throw new IllegalArgumentException("'inputStream' cannot be null!");
+ }
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inJustDecodeBounds = true;
+ BitmapFactory.decodeStream(inputStream, null, options);
+ Point point = new Point(options.outWidth,options.outHeight);
+ return point;
+ }
+
+ /**
+ * Crops the input image and returns a new InputStream of the cropped area
+ *
+ * @param inputStream The input stream of the image
+ * @param imageWidth Width of the input image
+ * @param imageHeight Height of the input image
+ * @param inputStream Desired Width
+ * @param inputStream Desired Width
+ *
+ * @return a new InputStream of the cropped area/*"
+ */
+ public static InputStream cropImage(InputStream inputStream, int imageWidth, int imageHeight,
+ int outWidth, int outHeight) throws IllegalArgumentException {
+ if (inputStream == null){
+ throw new IllegalArgumentException("inputStream cannot be null");
+ }
+
+ if (imageWidth <= 0 || imageHeight <= 0) {
+ throw new IllegalArgumentException(
+ String.format("imageWidth and imageHeight must be > 0: imageWidth=%d" +
+ " imageHeight=%d", imageWidth, imageHeight));
+ }
+
+ if (outWidth <= 0 || outHeight <= 0) {
+ throw new IllegalArgumentException(
+ String.format("outWidth and outHeight must be > 0: outWidth=%d" +
+ " outHeight=%d", imageWidth, outHeight));
+ }
+
+ int scaleDownSampleSize = Math.min(imageWidth / outWidth, imageHeight / outHeight);
+ if (scaleDownSampleSize > 0) {
+ imageWidth /= scaleDownSampleSize;
+ imageHeight /= scaleDownSampleSize;
+ } else {
+ float ratio = (float) outWidth / outHeight;
+ if (imageWidth < imageHeight * ratio) {
+ outWidth = imageWidth;
+ outHeight = (int) (outWidth / ratio);
+ } else {
+ outHeight = imageHeight;
+ outWidth = (int) (outHeight * ratio);
+ }
+ }
+ int left = (imageWidth - outWidth) / 2;
+ int top = (imageHeight - outHeight) / 2;
+ InputStream compressed = null;
+ try {
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ if (scaleDownSampleSize > 1) {
+ options.inSampleSize = scaleDownSampleSize;
+ }
+ Bitmap bitmap = BitmapFactory.decodeStream(inputStream, null, options);
+ if (bitmap == null) {
+ return null;
+ }
+ Bitmap cropped = Bitmap.createBitmap(bitmap, left, top, outWidth, outHeight);
+ ByteArrayOutputStream tmpOut = new ByteArrayOutputStream(2048);
+ if (cropped.compress(Bitmap.CompressFormat.PNG, DEFAULT_IMG_QUALITY, tmpOut)) {
+ byte[] outByteArray = tmpOut.toByteArray();
+ compressed = new ByteArrayInputStream(outByteArray);
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Exception " + e);
+ }
+ return compressed;
+ }
+
+ /**
+ * Crops the lock screen image and returns a new InputStream of the cropped area
+ *
+ * @param pkgName Name of the theme package
+ * @param context The context
+ *
+ * @return a new InputStream of the cropped image/*"
+ */
+ public static InputStream getCroppedKeyguardStream(String pkgName, Context context)
+ throws IllegalArgumentException {
+ if (TextUtils.isEmpty(pkgName)) {
+ throw new IllegalArgumentException("'pkgName' cannot be null or empty!");
+ }
+ if (context == null) {
+ throw new IllegalArgumentException("'context' cannot be null!");
+ }
+
+ InputStream cropped = null;
+ InputStream stream = null;
+ try {
+ stream = getOriginalKeyguardStream(pkgName, context);
+ if (stream == null) {
+ return null;
+ }
+ Point point = getImageDimension(stream);
+ IoUtils.closeQuietly(stream);
+ if (point == null || point.x == 0 || point.y == 0) {
+ return null;
+ }
+ WallpaperManager wm = WallpaperManager.getInstance(context);
+ int outWidth = wm.getDesiredMinimumWidth();
+ int outHeight = wm.getDesiredMinimumHeight();
+ stream = getOriginalKeyguardStream(pkgName, context);
+ if (stream == null) {
+ return null;
+ }
+ cropped = cropImage(stream, point.x, point.y, outWidth, outHeight);
+ } catch (Exception e) {
+ Log.e(TAG, "Exception " + e);
+ } finally {
+ IoUtils.closeQuietly(stream);
+ }
+ return cropped;
+ }
+
+ /**
+ * Crops the wallpaper image and returns a new InputStream of the cropped area
+ *
+ * @param pkgName Name of the theme package
+ * @param context The context
+ *
+ * @return a new InputStream of the cropped image/*"
+ */
+ public static InputStream getCroppedWallpaperStream(String pkgName, long wallpaperId,
+ Context context) {
+ if (TextUtils.isEmpty(pkgName)) {
+ throw new IllegalArgumentException("'pkgName' cannot be null or empty!");
+ }
+ if (context == null) {
+ throw new IllegalArgumentException("'context' cannot be null!");
+ }
+
+ InputStream cropped = null;
+ InputStream stream = null;
+ try {
+ stream = getOriginalWallpaperStream(pkgName, wallpaperId, context);
+ if (stream == null) {
+ return null;
+ }
+ Point point = getImageDimension(stream);
+ IoUtils.closeQuietly(stream);
+ if (point == null || point.x == 0 || point.y == 0) {
+ return null;
+ }
+ WallpaperManager wm = WallpaperManager.getInstance(context);
+ int outWidth = wm.getDesiredMinimumWidth();
+ int outHeight = wm.getDesiredMinimumHeight();
+ stream = getOriginalWallpaperStream(pkgName, wallpaperId, context);
+ if (stream == null) {
+ return null;
+ }
+ cropped = cropImage(stream, point.x, point.y, outWidth, outHeight);
+ } catch (Exception e) {
+ Log.e(TAG, "Exception " + e);
+ } finally {
+ IoUtils.closeQuietly(stream);
+ }
+ return cropped;
+ }
+
+ private static InputStream getOriginalKeyguardStream(String pkgName, Context context) {
+ if (TextUtils.isEmpty(pkgName) || context == null) {
+ return null;
+ }
+
+ InputStream inputStream = null;
+ try {
+ //Get input WP stream from the theme
+ Context themeCtx = context.createPackageContext(pkgName,
+ Context.CONTEXT_IGNORE_SECURITY);
+ AssetManager assetManager = themeCtx.getAssets();
+ String wpPath = ThemeUtils.getLockscreenWallpaperPath(assetManager);
+ if (wpPath == null) {
+ Log.w(TAG, "Not setting lockscreen wp because wallpaper file was not found.");
+ } else {
+ inputStream = ThemeUtils.getInputStreamFromAsset(themeCtx,
+ ASSET_URI_PREFIX + wpPath);
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "There was an error setting lockscreen wp for pkg " + pkgName, e);
+ }
+ return inputStream;
+ }
+
+ private static InputStream getOriginalWallpaperStream(String pkgName, long componentId,
+ Context context) {
+ String wpPath;
+ if (TextUtils.isEmpty(pkgName) || context == null) {
+ return null;
+ }
+
+ InputStream inputStream = null;
+ String selection = ThemesColumns.PKG_NAME + "= ?";
+ String[] selectionArgs = {pkgName};
+ Cursor c = context.getContentResolver().query(ThemesColumns.CONTENT_URI,
+ null, selection,
+ selectionArgs, null);
+ if (c == null || c.getCount() < 1) {
+ if (c != null) c.close();
+ return null;
+ } else {
+ c.moveToFirst();
+ }
+
+ try {
+ Context themeContext = context.createPackageContext(pkgName,
+ Context.CONTEXT_IGNORE_SECURITY);
+ boolean isLegacyTheme = c.getInt(
+ c.getColumnIndex(ThemesColumns.IS_LEGACY_THEME)) == 1;
+ String wallpaper = c.getString(
+ c.getColumnIndex(ThemesColumns.WALLPAPER_URI));
+ if (wallpaper != null) {
+ if (URLUtil.isAssetUrl(wallpaper)) {
+ inputStream = ThemeUtils.getInputStreamFromAsset(themeContext, wallpaper);
+ } else {
+ inputStream = context.getContentResolver().openInputStream(
+ Uri.parse(wallpaper));
+ }
+ } else {
+ // try and get the wallpaper directly from the apk if the URI was null
+ Context themeCtx = context.createPackageContext(pkgName,
+ Context.CONTEXT_IGNORE_SECURITY);
+ AssetManager assetManager = themeCtx.getAssets();
+ wpPath = queryWpPathFromComponentId(context, pkgName, componentId);
+ if (wpPath == null) wpPath = ThemeUtils.getWallpaperPath(assetManager);
+ if (wpPath == null) {
+ Log.e(TAG, "Not setting wp because wallpaper file was not found.");
+ } else {
+ inputStream = ThemeUtils.getInputStreamFromAsset(themeCtx,
+ ASSET_URI_PREFIX + wpPath);
+ }
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "getWallpaperStream: " + e);
+ } finally {
+ c.close();
+ }
+
+ return inputStream;
+ }
+
+ private static String queryWpPathFromComponentId(Context context, String pkgName,
+ long componentId) {
+ String wpPath = null;
+ String[] projection = new String[] { PreviewColumns.COL_VALUE };
+ String selection = ThemesColumns.PKG_NAME + "=? AND " +
+ PreviewColumns.COMPONENT_ID + "=? AND " +
+ PreviewColumns.COL_KEY + "=?";
+ String[] selectionArgs = new String[] {
+ pkgName,
+ Long.toString(componentId),
+ PreviewColumns.WALLPAPER_FULL
+ };
+
+ Cursor c = context.getContentResolver()
+ .query(PreviewColumns.COMPONENTS_URI,
+ projection, selection, selectionArgs, null);
+ if (c != null) {
+ try {
+ if (c.moveToFirst()) {
+ int valIdx = c.getColumnIndex(PreviewColumns.COL_VALUE);
+ wpPath = c.getString(valIdx);
+ }
+ } catch(Exception e) {
+ Log.e(TAG, "Could not get wallpaper path", e);
+ } finally {
+ c.close();
+ }
+ }
+ return wpPath;
+ }
+}
+
diff --git a/sdk/src/java/org/cyanogenmod/internal/util/QSConstants.java b/sdk/src/java/org/cyanogenmod/internal/util/QSConstants.java
new file mode 100644
index 0000000..d47e683
--- /dev/null
+++ b/sdk/src/java/org/cyanogenmod/internal/util/QSConstants.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2015 The CyanogenMod 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 org.cyanogenmod.internal.util;
+
+import java.util.ArrayList;
+
+public class QSConstants {
+ private QSConstants() {}
+
+ public static final String TILE_WIFI = "wifi";
+ public static final String TILE_BLUETOOTH = "bt";
+ public static final String TILE_INVERSION = "inversion";
+ public static final String TILE_CELLULAR = "cell";
+ public static final String TILE_AIRPLANE = "airplane";
+ public static final String TILE_ROTATION = "rotation";
+ public static final String TILE_FLASHLIGHT = "flashlight";
+ public static final String TILE_LOCATION = "location";
+ public static final String TILE_CAST = "cast";
+ public static final String TILE_HOTSPOT = "hotspot";
+ public static final String TILE_NOTIFICATIONS = "notifications";
+ public static final String TILE_DATA = "data";
+ public static final String TILE_ROAMING = "roaming";
+ public static final String TILE_DDS = "dds";
+ public static final String TILE_APN = "apn";
+ public static final String TILE_PROFILES = "profiles";
+ public static final String TILE_PERFORMANCE = "performance";
+ public static final String TILE_ADB_NETWORK = "adb_network";
+ public static final String TILE_NFC = "nfc";
+ public static final String TILE_COMPASS = "compass";
+ public static final String TILE_LOCKSCREEN = "lockscreen";
+ public static final String TILE_LTE = "lte";
+ public static final String TILE_VISUALIZER = "visualizer";
+ public static final String TILE_VOLUME = "volume_panel";
+ public static final String TILE_SCREEN_TIMEOUT = "screen_timeout";
+ public static final String TILE_LIVE_DISPLAY = "live_display";
+ public static final String TILE_USB_TETHER = "usb_tether";
+ public static final String TILE_HEADS_UP = "heads_up";
+ public static final String TILE_AMBIENT_DISPLAY = "ambient_display";
+ public static final String TILE_SYNC = "sync";
+ public static final String TILE_BATTERY_SAVER = "battery_saver";
+ public static final String TILE_CAFFEINE = "caffeine";
+ public static final String TILE_EDIT = "edit";
+ public static final String TILE_DND = "dnd";
+
+ public static final String DYNAMIC_TILE_NEXT_ALARM = "next_alarm";
+ public static final String DYNAMIC_TILE_IME_SELECTOR = "ime_selector";
+ public static final String DYNAMIC_TILE_SU = "su";
+ public static final String DYNAMIC_TILE_ADB = "adb";
+
+ protected static final ArrayList<String> STATIC_TILES_AVAILABLE = new ArrayList<String>();
+ protected static final ArrayList<String> DYNAMIC_TILES_AVAILBLE = new ArrayList<String>();
+ protected static final ArrayList<String> TILES_AVAILABLE = new ArrayList<String>();
+
+ static {
+ STATIC_TILES_AVAILABLE.add(TILE_WIFI);
+ STATIC_TILES_AVAILABLE.add(TILE_BLUETOOTH);
+ STATIC_TILES_AVAILABLE.add(TILE_CELLULAR);
+ STATIC_TILES_AVAILABLE.add(TILE_AIRPLANE);
+ STATIC_TILES_AVAILABLE.add(TILE_ROTATION);
+ STATIC_TILES_AVAILABLE.add(TILE_FLASHLIGHT);
+ STATIC_TILES_AVAILABLE.add(TILE_LOCATION);
+ STATIC_TILES_AVAILABLE.add(TILE_EDIT);
+ STATIC_TILES_AVAILABLE.add(TILE_CAST);
+ STATIC_TILES_AVAILABLE.add(TILE_HOTSPOT);
+ STATIC_TILES_AVAILABLE.add(TILE_INVERSION);
+ STATIC_TILES_AVAILABLE.add(TILE_DND);
+// STATIC_TILES_AVAILABLE.add(TILE_NOTIFICATIONS);
+// STATIC_TILES_AVAILABLE.add(TILE_DATA);
+// STATIC_TILES_AVAILABLE.add(TILE_ROAMING);
+// STATIC_TILES_AVAILABLE.add(TILE_DDS);
+// STATIC_TILES_AVAILABLE.add(TILE_APN);
+ STATIC_TILES_AVAILABLE.add(TILE_PROFILES);
+ STATIC_TILES_AVAILABLE.add(TILE_PERFORMANCE);
+ STATIC_TILES_AVAILABLE.add(TILE_ADB_NETWORK);
+ STATIC_TILES_AVAILABLE.add(TILE_NFC);
+ STATIC_TILES_AVAILABLE.add(TILE_COMPASS);
+ STATIC_TILES_AVAILABLE.add(TILE_LOCKSCREEN);
+// STATIC_TILES_AVAILABLE.add(TILE_LTE);
+// STATIC_TILES_AVAILABLE.add(TILE_VISUALIZER);
+ STATIC_TILES_AVAILABLE.add(TILE_VOLUME);
+ STATIC_TILES_AVAILABLE.add(TILE_SCREEN_TIMEOUT);
+ STATIC_TILES_AVAILABLE.add(TILE_LIVE_DISPLAY);
+ STATIC_TILES_AVAILABLE.add(TILE_USB_TETHER);
+ STATIC_TILES_AVAILABLE.add(TILE_HEADS_UP);
+ STATIC_TILES_AVAILABLE.add(TILE_AMBIENT_DISPLAY);
+ STATIC_TILES_AVAILABLE.add(TILE_SYNC);
+ STATIC_TILES_AVAILABLE.add(TILE_BATTERY_SAVER);
+ STATIC_TILES_AVAILABLE.add(TILE_CAFFEINE);
+
+ TILES_AVAILABLE.addAll(STATIC_TILES_AVAILABLE);
+
+ DYNAMIC_TILES_AVAILBLE.add(DYNAMIC_TILE_ADB);
+ DYNAMIC_TILES_AVAILBLE.add(DYNAMIC_TILE_IME_SELECTOR);
+ DYNAMIC_TILES_AVAILBLE.add(DYNAMIC_TILE_NEXT_ALARM);
+ DYNAMIC_TILES_AVAILBLE.add(DYNAMIC_TILE_SU);
+ }
+}
diff --git a/sdk/src/java/org/cyanogenmod/internal/util/QSUtils.java b/sdk/src/java/org/cyanogenmod/internal/util/QSUtils.java
new file mode 100644
index 0000000..6aedaf8
--- /dev/null
+++ b/sdk/src/java/org/cyanogenmod/internal/util/QSUtils.java
@@ -0,0 +1,312 @@
+/*
+ * Copyright (C) 2015 The CyanogenMod 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 org.cyanogenmod.internal.util;
+
+import android.bluetooth.BluetoothAdapter;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.database.ContentObserver;
+import android.hardware.Sensor;
+import android.hardware.SensorManager;
+import android.hardware.camera2.CameraAccessException;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraManager;
+import android.net.ConnectivityManager;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Handler;
+import android.os.PowerManager;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+import android.util.SparseArray;
+
+import com.android.internal.telephony.PhoneConstants;
+import cyanogenmod.power.PerformanceManager;
+import cyanogenmod.providers.CMSettings;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+import java.util.regex.Pattern;
+
+public class QSUtils {
+ private static boolean sAvailableTilesFiltered;
+ private static final SparseArray<Context> sSystemUiContextForUser = new SparseArray<>();
+
+ public interface OnQSChanged {
+ void onQSChanged();
+ }
+
+ private QSUtils() {}
+
+ public static boolean isStaticQsTile(String tileSpec) {
+ return QSConstants.STATIC_TILES_AVAILABLE.contains(tileSpec);
+ }
+
+ public static boolean isDynamicQsTile(String tileSpec) {
+ return QSConstants.DYNAMIC_TILES_AVAILBLE.contains(tileSpec);
+ }
+
+ @SuppressWarnings("unchecked")
+ public static List<String> getAvailableTiles(Context context) {
+ filterTiles(context);
+ return (List<String>) QSConstants.TILES_AVAILABLE.clone();
+ }
+
+ public static List<String> getDefaultTiles(Context context) {
+ final List<String> tiles = new ArrayList<>();
+ final String defaults = context.getString(
+ org.cyanogenmod.platform.internal.R.string.config_defaultQuickSettingsTiles);
+ if (!TextUtils.isEmpty(defaults)) {
+ final String[] array = TextUtils.split(defaults, Pattern.quote(","));
+ for (String item : array) {
+ if (TextUtils.isEmpty(item)) {
+ continue;
+ }
+ tiles.add(item);
+ }
+ filterTiles(context, tiles);
+ }
+ return tiles;
+ }
+
+ public static String getDefaultTilesAsString(Context context) {
+ List<String> list = getDefaultTiles(context);
+ return TextUtils.join(",", list);
+ }
+
+ private static void filterTiles(Context context, List<String> tiles) {
+ boolean deviceSupportsMobile = deviceSupportsMobileData(context);
+
+ // Tiles that need conditional filtering
+ Iterator<String> iterator = tiles.iterator();
+ while (iterator.hasNext()) {
+ String tileKey = iterator.next();
+ boolean removeTile = false;
+ switch (tileKey) {
+ case QSConstants.TILE_CELLULAR:
+ case QSConstants.TILE_HOTSPOT:
+ case QSConstants.TILE_DATA:
+ case QSConstants.TILE_ROAMING:
+ case QSConstants.TILE_APN:
+ removeTile = !deviceSupportsMobile;
+ break;
+ case QSConstants.TILE_DDS:
+ removeTile = !deviceSupportsDdsSupported(context);
+ break;
+ case QSConstants.TILE_FLASHLIGHT:
+ removeTile = !deviceSupportsFlashLight(context);
+ break;
+ case QSConstants.TILE_BLUETOOTH:
+ removeTile = !deviceSupportsBluetooth();
+ break;
+ case QSConstants.TILE_NFC:
+ removeTile = !deviceSupportsNfc(context);
+ break;
+ case QSConstants.TILE_COMPASS:
+ removeTile = !deviceSupportsCompass(context);
+ break;
+ case QSConstants.TILE_AMBIENT_DISPLAY:
+ removeTile = !deviceSupportsDoze(context);
+ break;
+ case QSConstants.TILE_PERFORMANCE:
+ removeTile = !deviceSupportsPowerProfiles(context);
+ break;
+ case QSConstants.TILE_BATTERY_SAVER:
+ removeTile = deviceSupportsPowerProfiles(context);
+ break;
+ }
+ if (removeTile) {
+ iterator.remove();
+ }
+ }
+ }
+
+ private static void filterTiles(Context context) {
+ if (!sAvailableTilesFiltered) {
+ filterTiles(context, QSConstants.TILES_AVAILABLE);
+ sAvailableTilesFiltered = true;
+ }
+ }
+
+ public static int getDynamicQSTileResIconId(Context context, int userId, String tileSpec) {
+ Context ctx = getQSTileContext(context, userId);
+ int index = translateDynamicQsTileSpecToIndex(ctx, tileSpec);
+ if (index == -1) {
+ return 0;
+ }
+
+ try {
+ String resourceName = ctx.getResources().getStringArray(
+ ctx.getResources().getIdentifier("dynamic_qs_tiles_icons_resources_ids",
+ "array", ctx.getPackageName()))[index];
+ return ctx.getResources().getIdentifier(
+ resourceName, "drawable", ctx.getPackageName());
+ } catch (Exception ex) {
+ // Ignore
+ }
+ return 0;
+ }
+
+ public static String getDynamicQSTileLabel(Context context, int userId, String tileSpec) {
+ Context ctx = getQSTileContext(context, userId);
+ int index = translateDynamicQsTileSpecToIndex(ctx, tileSpec);
+ if (index == -1) {
+ return null;
+ }
+
+ try {
+ return ctx.getResources().getStringArray(
+ ctx.getResources().getIdentifier("dynamic_qs_tiles_labels",
+ "array", ctx.getPackageName()))[index];
+ } catch (Exception ex) {
+ // Ignore
+ }
+ return null;
+ }
+
+ private static int translateDynamicQsTileSpecToIndex(Context context, String tileSpec) {
+ String[] keys = context.getResources().getStringArray(context.getResources().getIdentifier(
+ "dynamic_qs_tiles_values", "array", context.getPackageName()));
+ int count = keys.length;
+ for (int i = 0; i < count; i++) {
+ if (keys[i].equals(tileSpec)) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ public static Context getQSTileContext(Context context, int userId) {
+ Context ctx = sSystemUiContextForUser.get(userId);
+ if (ctx == null) {
+ try {
+ ctx = context.createPackageContextAsUser(
+ "com.android.systemui", 0, new UserHandle(userId));
+ sSystemUiContextForUser.put(userId, ctx);
+ } catch (NameNotFoundException ex) {
+ // We can safely ignore this
+ }
+ }
+ return ctx;
+ }
+
+ public static boolean isQSTileEnabledForUser(
+ Context context, String tileSpec, int userId) {
+ final ContentResolver resolver = context.getContentResolver();
+ String order = CMSettings.Secure.getStringForUser(resolver,
+ CMSettings.Secure.QS_TILES, userId);
+ return !TextUtils.isEmpty(order) && Arrays.asList(order.split(",")).contains(tileSpec);
+ }
+
+ public static ContentObserver registerObserverForQSChanges(Context ctx, final OnQSChanged cb) {
+ ContentObserver observer = new ContentObserver(new Handler()) {
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ cb.onQSChanged();
+ }
+ };
+
+ ctx.getContentResolver().registerContentObserver(
+ CMSettings.Secure.getUriFor(CMSettings.Secure.QS_TILES),
+ false, observer, UserHandle.USER_ALL);
+ return observer;
+ }
+
+ public static void unregisterObserverForQSChanges(Context ctx, ContentObserver observer) {
+ ctx.getContentResolver().unregisterContentObserver(observer);
+ }
+
+
+ public static boolean deviceSupportsLte(Context ctx) {
+ final TelephonyManager tm = (TelephonyManager)
+ ctx.getSystemService(Context.TELEPHONY_SERVICE);
+ return (tm.getLteOnCdmaMode() == PhoneConstants.LTE_ON_CDMA_TRUE)
+ || tm.getLteOnGsmMode() != 0;
+ }
+
+ public static boolean deviceSupportsDdsSupported(Context context) {
+ TelephonyManager tm = (TelephonyManager)
+ context.getSystemService(Context.TELEPHONY_SERVICE);
+ return tm.isMultiSimEnabled()
+ && tm.getMultiSimConfiguration() == TelephonyManager.MultiSimVariants.DSDA;
+ }
+
+ public static boolean deviceSupportsMobileData(Context ctx) {
+ ConnectivityManager cm = (ConnectivityManager) ctx.getSystemService(
+ Context.CONNECTIVITY_SERVICE);
+ return cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE);
+ }
+
+ public static boolean deviceSupportsBluetooth() {
+ return BluetoothAdapter.getDefaultAdapter() != null;
+ }
+
+ public static boolean deviceSupportsNfc(Context context) {
+ PackageManager packageManager = context.getPackageManager();
+ return packageManager.hasSystemFeature(PackageManager.FEATURE_NFC);
+ }
+
+ public static boolean deviceSupportsFlashLight(Context context) {
+ CameraManager cameraManager = (CameraManager) context.getSystemService(
+ Context.CAMERA_SERVICE);
+ try {
+ String[] ids = cameraManager.getCameraIdList();
+ for (String id : ids) {
+ CameraCharacteristics c = cameraManager.getCameraCharacteristics(id);
+ Boolean flashAvailable = c.get(CameraCharacteristics.FLASH_INFO_AVAILABLE);
+ Integer lensFacing = c.get(CameraCharacteristics.LENS_FACING);
+ if (flashAvailable != null
+ && flashAvailable
+ && lensFacing != null
+ && lensFacing == CameraCharacteristics.LENS_FACING_BACK) {
+ return true;
+ }
+ }
+ } catch (CameraAccessException | AssertionError e) {
+ // Ignore
+ }
+ return false;
+ }
+
+ public static boolean deviceSupportsCompass(Context context) {
+ SensorManager sm = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
+ return sm.getDefaultSensor(Sensor.TYPE_ACCELEROMETER) != null
+ && sm.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD) != null;
+ }
+
+ public static boolean deviceSupportsDoze(Context context) {
+ String name = context.getResources().getString(
+ com.android.internal.R.string.config_dozeComponent);
+ return !TextUtils.isEmpty(name);
+ }
+
+
+ public static boolean deviceSupportsPowerProfiles(Context context) {
+ PerformanceManager pm = PerformanceManager.getInstance(context);
+ return pm.getNumberOfProfiles() > 0;
+ }
+
+ private static boolean supportsRootAccess() {
+ return Build.IS_DEBUGGABLE || "eng".equals(Build.TYPE);
+ }
+} \ No newline at end of file
diff --git a/sdk/src/java/org/cyanogenmod/internal/util/ScreenType.java b/sdk/src/java/org/cyanogenmod/internal/util/ScreenType.java
new file mode 100644
index 0000000..23bd9ad
--- /dev/null
+++ b/sdk/src/java/org/cyanogenmod/internal/util/ScreenType.java
@@ -0,0 +1,66 @@
+/*
+ * 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 org.cyanogenmod.internal.util;
+
+import android.content.Context;
+import android.util.DisplayMetrics;
+import android.view.DisplayInfo;
+import android.view.WindowManager;
+
+public class ScreenType {
+ // Device type reference
+ private static int sDeviceType = -1;
+
+ // Device types
+ private static final int DEVICE_PHONE = 0;
+ private static final int DEVICE_HYBRID = 1;
+ private static final int DEVICE_TABLET = 2;
+
+ private static int getScreenType(Context context) {
+ if (sDeviceType == -1) {
+ WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+ DisplayInfo outDisplayInfo = new DisplayInfo();
+ wm.getDefaultDisplay().getDisplayInfo(outDisplayInfo);
+ int shortSize = Math.min(outDisplayInfo.logicalHeight, outDisplayInfo.logicalWidth);
+ int shortSizeDp = shortSize * DisplayMetrics.DENSITY_DEFAULT
+ / outDisplayInfo.logicalDensityDpi;
+ if (shortSizeDp < 600) {
+ // 0-599dp: "phone" UI with a separate status & navigation bar
+ sDeviceType = DEVICE_PHONE;
+ } else if (shortSizeDp < 720) {
+ // 600-719dp: "phone" UI with modifications for larger screens
+ sDeviceType = DEVICE_HYBRID;
+ } else {
+ // 720dp: "tablet" UI with a single combined status & navigation bar
+ sDeviceType = DEVICE_TABLET;
+ }
+ }
+ return sDeviceType;
+ }
+
+ public static boolean isPhone(Context context) {
+ return getScreenType(context) == DEVICE_PHONE;
+ }
+
+ public static boolean isHybrid(Context context) {
+ return getScreenType(context) == DEVICE_HYBRID;
+ }
+
+ public static boolean isTablet(Context context) {
+ return getScreenType(context) == DEVICE_TABLET;
+ }
+}
diff --git a/sdk/src/java/org/cyanogenmod/internal/util/ThemeUtils.java b/sdk/src/java/org/cyanogenmod/internal/util/ThemeUtils.java
new file mode 100644
index 0000000..ef51ced
--- /dev/null
+++ b/sdk/src/java/org/cyanogenmod/internal/util/ThemeUtils.java
@@ -0,0 +1,687 @@
+/*
+ * Copyright (C) 2016 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 org.cyanogenmod.internal.util;
+
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.IntentFilter;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageParser;
+import android.content.res.AssetManager;
+import android.content.res.ThemeConfig;
+import android.database.Cursor;
+import android.media.RingtoneManager;
+import android.net.Uri;
+import android.os.FileUtils;
+import android.os.SystemProperties;
+import android.provider.MediaStore;
+import android.text.TextUtils;
+import android.util.ArraySet;
+import android.util.DisplayMetrics;
+import android.util.Log;
+
+import android.view.WindowManager;
+import cyanogenmod.providers.CMSettings;
+import cyanogenmod.providers.ThemesContract.ThemesColumns;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.zip.CRC32;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+import java.util.zip.ZipOutputStream;
+
+import static android.content.res.ThemeConfig.SYSTEM_DEFAULT;
+
+/**
+ * @hide
+ */
+public class ThemeUtils {
+ private static final String TAG = ThemeUtils.class.getSimpleName();
+
+ // Package name for any app which does not have a specific theme applied
+ private static final String DEFAULT_PKG = "default";
+
+ private static final Set<String> SUPPORTED_THEME_COMPONENTS = new ArraySet<>();
+
+ static {
+ SUPPORTED_THEME_COMPONENTS.add(ThemesColumns.MODIFIES_ALARMS);
+ SUPPORTED_THEME_COMPONENTS.add(ThemesColumns.MODIFIES_BOOT_ANIM);
+ SUPPORTED_THEME_COMPONENTS.add(ThemesColumns.MODIFIES_FONTS);
+ SUPPORTED_THEME_COMPONENTS.add(ThemesColumns.MODIFIES_ICONS);
+ SUPPORTED_THEME_COMPONENTS.add(ThemesColumns.MODIFIES_LAUNCHER);
+ SUPPORTED_THEME_COMPONENTS.add(ThemesColumns.MODIFIES_LIVE_LOCK_SCREEN);
+ SUPPORTED_THEME_COMPONENTS.add(ThemesColumns.MODIFIES_LOCKSCREEN);
+ SUPPORTED_THEME_COMPONENTS.add(ThemesColumns.MODIFIES_NAVIGATION_BAR);
+ SUPPORTED_THEME_COMPONENTS.add(ThemesColumns.MODIFIES_NOTIFICATIONS);
+ SUPPORTED_THEME_COMPONENTS.add(ThemesColumns.MODIFIES_OVERLAYS);
+ SUPPORTED_THEME_COMPONENTS.add(ThemesColumns.MODIFIES_RINGTONES);
+ SUPPORTED_THEME_COMPONENTS.add(ThemesColumns.MODIFIES_STATUS_BAR);
+ }
+
+ // Constants for theme change broadcast
+ public static final String ACTION_THEME_CHANGED = "org.cyanogenmod.intent.action.THEME_CHANGED";
+ public static final String EXTRA_COMPONENTS = "components";
+ public static final String EXTRA_REQUEST_TYPE = "request_type";
+ public static final String EXTRA_UPDATE_TIME = "update_time";
+
+ // path to asset lockscreen and wallpapers directory
+ public static final String LOCKSCREEN_WALLPAPER_PATH = "lockscreen";
+ public static final String WALLPAPER_PATH = "wallpapers";
+
+ // path to external theme resources, i.e. bootanimation.zip
+ public static final String SYSTEM_THEME_PATH = "/data/system/theme";
+ public static final String SYSTEM_THEME_FONT_PATH = SYSTEM_THEME_PATH + File.separator + "fonts";
+ public static final String SYSTEM_THEME_RINGTONE_PATH = SYSTEM_THEME_PATH
+ + File.separator + "ringtones";
+ public static final String SYSTEM_THEME_NOTIFICATION_PATH = SYSTEM_THEME_PATH
+ + File.separator + "notifications";
+ public static final String SYSTEM_THEME_ALARM_PATH = SYSTEM_THEME_PATH
+ + File.separator + "alarms";
+ public static final String SYSTEM_THEME_ICON_CACHE_DIR = SYSTEM_THEME_PATH
+ + File.separator + "icons";
+ // internal path to bootanimation.zip inside theme apk
+ public static final String THEME_BOOTANIMATION_PATH = "assets/bootanimation/bootanimation.zip";
+
+ public static final String SYSTEM_MEDIA_PATH = "/system/media/audio";
+ public static final String SYSTEM_ALARMS_PATH = SYSTEM_MEDIA_PATH + File.separator
+ + "alarms";
+ public static final String SYSTEM_RINGTONES_PATH = SYSTEM_MEDIA_PATH + File.separator
+ + "ringtones";
+ public static final String SYSTEM_NOTIFICATIONS_PATH = SYSTEM_MEDIA_PATH + File.separator
+ + "notifications";
+
+ private static final String MEDIA_CONTENT_URI = "content://media/internal/audio/media";
+
+ public static final int SYSTEM_TARGET_API = 0;
+
+ /* Path to cached theme resources */
+ public static final String RESOURCE_CACHE_DIR = "/data/resource-cache/";
+
+ /* Path inside a theme APK to the overlay folder */
+ public static final String OVERLAY_PATH = "assets/overlays/";
+ public static final String ICONS_PATH = "assets/icons/";
+ public static final String COMMON_RES_PATH = "assets/overlays/common/";
+
+ public static final String IDMAP_SUFFIX = "@idmap";
+ public static final String COMMON_RES_TARGET = "common";
+
+ public static final String ICON_HASH_FILENAME = "hash";
+
+ public static final String FONT_XML = "fonts.xml";
+
+ public static String getDefaultThemePackageName(Context context) {
+ final String defaultThemePkg = CMSettings.Secure.getString(context.getContentResolver(),
+ CMSettings.Secure.DEFAULT_THEME_PACKAGE);
+ if (!TextUtils.isEmpty(defaultThemePkg)) {
+ PackageManager pm = context.getPackageManager();
+ try {
+ if (pm.getPackageInfo(defaultThemePkg, 0) != null) {
+ return defaultThemePkg;
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ // doesn't exist so system will be default
+ Log.w(TAG, "Default theme " + defaultThemePkg + " not found", e);
+ }
+ }
+
+ return SYSTEM_DEFAULT;
+ }
+
+ /**
+ * Returns a mutable list of all theme components
+ * @return
+ */
+ public static List<String> getAllComponents() {
+ List<String> components = new ArrayList<>(SUPPORTED_THEME_COMPONENTS.size());
+ components.addAll(SUPPORTED_THEME_COMPONENTS);
+ return components;
+ }
+
+ /**
+ * Returns a mutable list of all the theme components supported by a given package
+ * NOTE: This queries the themes content provider. If there isn't a provider installed
+ * or if it is too early in the boot process this method will not work.
+ */
+ public static List<String> getSupportedComponents(Context context, String pkgName) {
+ List<String> supportedComponents = new ArrayList<>();
+
+ String selection = ThemesColumns.PKG_NAME + "= ?";
+ String[] selectionArgs = new String[]{ pkgName };
+ Cursor c = context.getContentResolver().query(ThemesColumns.CONTENT_URI,
+ null, selection, selectionArgs, null);
+
+ if (c != null) {
+ if (c.moveToFirst()) {
+ List<String> allComponents = getAllComponents();
+ for (String component : allComponents) {
+ int index = c.getColumnIndex(component);
+ if (c.getInt(index) == 1) {
+ supportedComponents.add(component);
+ }
+ }
+ }
+ c.close();
+ }
+ return supportedComponents;
+ }
+
+ /**
+ * Get the components from the default theme. If the default theme is not SYSTEM then any
+ * components that are not in the default theme will come from SYSTEM to create a complete
+ * component map.
+ * @param context
+ * @return
+ */
+ public static Map<String, String> getDefaultComponents(Context context) {
+ String defaultThemePkg = getDefaultThemePackageName(context);
+ List<String> defaultComponents = null;
+ List<String> systemComponents = getSupportedComponents(context, SYSTEM_DEFAULT);
+ if (!DEFAULT_PKG.equals(defaultThemePkg)) {
+ defaultComponents = getSupportedComponents(context, defaultThemePkg);
+ }
+
+ Map<String, String> componentMap = new HashMap<>(systemComponents.size());
+ if (defaultComponents != null) {
+ for (String component : defaultComponents) {
+ componentMap.put(component, defaultThemePkg);
+ }
+ }
+ for (String component : systemComponents) {
+ if (!componentMap.containsKey(component)) {
+ componentMap.put(component, SYSTEM_DEFAULT);
+ }
+ }
+
+ return componentMap;
+ }
+
+ /**
+ * Get the path to the icons for the given theme
+ * @param pkgName
+ * @return
+ */
+ public static String getIconPackDir(String pkgName) {
+ return getOverlayResourceCacheDir(pkgName) + File.separator + "icons";
+ }
+
+ public static String getIconHashFile(String pkgName) {
+ return getIconPackDir(pkgName) + File.separator + ICON_HASH_FILENAME;
+ }
+
+ public static String getIconPackApkPath(String pkgName) {
+ return getIconPackDir(pkgName) + "/resources.apk";
+ }
+
+ public static String getIconPackResPath(String pkgName) {
+ return getIconPackDir(pkgName) + "/resources.arsc";
+ }
+
+ public static String getIdmapPath(String targetPkgName, String overlayPkgName) {
+ return getTargetCacheDir(targetPkgName, overlayPkgName) + File.separator + "idmap";
+ }
+
+ public static String getOverlayPathToTarget(String targetPkgName) {
+ StringBuilder sb = new StringBuilder();
+ sb.append(OVERLAY_PATH);
+ sb.append(targetPkgName);
+ sb.append('/');
+ return sb.toString();
+ }
+
+ public static String getCommonPackageName(String themePackageName) {
+ if (TextUtils.isEmpty(themePackageName)) return null;
+
+ return COMMON_RES_TARGET;
+ }
+
+ /**
+ * Create SYSTEM_THEME_PATH directory if it does not exist
+ */
+ public static void createThemeDirIfNotExists() {
+ createDirIfNotExists(SYSTEM_THEME_PATH);
+ }
+
+ /**
+ * Create SYSTEM_FONT_PATH directory if it does not exist
+ */
+ public static void createFontDirIfNotExists() {
+ createDirIfNotExists(SYSTEM_THEME_FONT_PATH);
+ }
+
+ /**
+ * Create SYSTEM_THEME_RINGTONE_PATH directory if it does not exist
+ */
+ public static void createRingtoneDirIfNotExists() {
+ createDirIfNotExists(SYSTEM_THEME_RINGTONE_PATH);
+ }
+
+ /**
+ * Create SYSTEM_THEME_NOTIFICATION_PATH directory if it does not exist
+ */
+ public static void createNotificationDirIfNotExists() {
+ createDirIfNotExists(SYSTEM_THEME_NOTIFICATION_PATH);
+ }
+
+ /**
+ * Create SYSTEM_THEME_ALARM_PATH directory if it does not exist
+ */
+ public static void createAlarmDirIfNotExists() {
+ createDirIfNotExists(SYSTEM_THEME_ALARM_PATH);
+ }
+
+ /**
+ * Create SYSTEM_THEME_ICON_CACHE_DIR directory if it does not exist
+ */
+ public static void createIconCacheDirIfNotExists() {
+ createDirIfNotExists(SYSTEM_THEME_ICON_CACHE_DIR);
+ }
+
+ public static void createCacheDirIfNotExists() throws IOException {
+ File file = new File(RESOURCE_CACHE_DIR);
+ if (!file.exists() && !file.mkdir()) {
+ throw new IOException("Could not create dir: " + file.toString());
+ }
+ FileUtils.setPermissions(file, FileUtils.S_IRWXU
+ | FileUtils.S_IRWXG | FileUtils.S_IROTH | FileUtils.S_IXOTH, -1, -1);
+ }
+
+ public static void createResourcesDirIfNotExists(String targetPkgName, String overlayPkgName)
+ throws IOException {
+ createDirIfNotExists(getOverlayResourceCacheDir(overlayPkgName));
+ File file = new File(getTargetCacheDir(targetPkgName, overlayPkgName));
+ if (!file.exists() && !file.mkdir()) {
+ throw new IOException("Could not create dir: " + file.toString());
+ }
+ FileUtils.setPermissions(file, FileUtils.S_IRWXU
+ | FileUtils.S_IRWXG | FileUtils.S_IROTH | FileUtils.S_IXOTH, -1, -1);
+ }
+
+ public static void createIconDirIfNotExists(String pkgName) throws IOException {
+ createDirIfNotExists(getOverlayResourceCacheDir(pkgName));
+ File file = new File(getIconPackDir(pkgName));
+ if (!file.exists() && !file.mkdir()) {
+ throw new IOException("Could not create dir: " + file.toString());
+ }
+ FileUtils.setPermissions(file, FileUtils.S_IRWXU
+ | FileUtils.S_IRWXG | FileUtils.S_IROTH | FileUtils.S_IXOTH, -1, -1);
+ }
+
+ public static void clearIconCache() {
+ FileUtils.deleteContents(new File(SYSTEM_THEME_ICON_CACHE_DIR));
+ }
+
+ public static void registerThemeChangeReceiver(final Context context,
+ final BroadcastReceiver receiver) {
+ IntentFilter filter = new IntentFilter(ACTION_THEME_CHANGED);
+
+ context.registerReceiver(receiver, filter);
+ }
+
+ public static String getLockscreenWallpaperPath(AssetManager assetManager) throws IOException {
+ String[] assets = assetManager.list(LOCKSCREEN_WALLPAPER_PATH);
+ String asset = getFirstNonEmptyAsset(assets);
+ if (asset == null) return null;
+ return LOCKSCREEN_WALLPAPER_PATH + File.separator + asset;
+ }
+
+ public static String getWallpaperPath(AssetManager assetManager) throws IOException {
+ String[] assets = assetManager.list(WALLPAPER_PATH);
+ String asset = getFirstNonEmptyAsset(assets);
+ if (asset == null) return null;
+ return WALLPAPER_PATH + File.separator + asset;
+ }
+
+ public static List<String> getWallpaperPathList(AssetManager assetManager)
+ throws IOException {
+ List<String> wallpaperList = new ArrayList<String>();
+ String[] assets = assetManager.list(WALLPAPER_PATH);
+ for (String asset : assets) {
+ if (!TextUtils.isEmpty(asset)) {
+ wallpaperList.add(WALLPAPER_PATH + File.separator + asset);
+ }
+ }
+ return wallpaperList;
+ }
+
+ /**
+ * Get the root path of the resource cache for the given theme
+ * @param themePkgName
+ * @return Root resource cache path for the given theme
+ */
+ public static String getOverlayResourceCacheDir(String themePkgName) {
+ return RESOURCE_CACHE_DIR + themePkgName;
+ }
+
+ /**
+ * Get the path of the resource cache for the given target and theme
+ * @param targetPkgName
+ * @param themePkg
+ * @return Path to the resource cache for this target and theme
+ */
+ public static String getTargetCacheDir(String targetPkgName, PackageInfo themePkg) {
+ return getTargetCacheDir(targetPkgName, themePkg.packageName);
+ }
+
+ public static String getTargetCacheDir(String targetPkgName, PackageParser.Package themePkg) {
+ return getTargetCacheDir(targetPkgName, themePkg.packageName);
+ }
+
+ public static String getTargetCacheDir(String targetPkgName, String themePkgName) {
+ return getOverlayResourceCacheDir(themePkgName) + File.separator + targetPkgName;
+ }
+
+ /**
+ * Creates a theme'd context using the overlay applied to SystemUI
+ * @param context Base context
+ * @return Themed context
+ */
+ public static Context createUiContext(final Context context) {
+ try {
+ Context uiContext = context.createPackageContext("com.android.systemui",
+ Context.CONTEXT_RESTRICTED);
+ return new ThemedUiContext(uiContext, context.getApplicationContext());
+ } catch (PackageManager.NameNotFoundException e) {
+ }
+
+ return null;
+ }
+
+ /**
+ * Scale the boot animation to better fit the device by editing the desc.txt found
+ * in the bootanimation.zip
+ * @param context Context to use for getting an instance of the WindowManager
+ * @param input InputStream of the original bootanimation.zip
+ * @param dst Path to store the newly created bootanimation.zip
+ * @throws IOException
+ */
+ public static void copyAndScaleBootAnimation(Context context, InputStream input, String dst)
+ throws IOException {
+ final OutputStream os = new FileOutputStream(dst);
+ final ZipOutputStream zos = new ZipOutputStream(new BufferedOutputStream(os));
+ final ZipInputStream bootAni = new ZipInputStream(new BufferedInputStream(input));
+ ZipEntry ze;
+
+ zos.setMethod(ZipOutputStream.STORED);
+ final byte[] bytes = new byte[4096];
+ int len;
+ while ((ze = bootAni.getNextEntry()) != null) {
+ ZipEntry entry = new ZipEntry(ze.getName());
+ entry.setMethod(ZipEntry.STORED);
+ entry.setCrc(ze.getCrc());
+ entry.setSize(ze.getSize());
+ entry.setCompressedSize(ze.getSize());
+ if (!ze.getName().equals("desc.txt")) {
+ // just copy this entry straight over into the output zip
+ zos.putNextEntry(entry);
+ while ((len = bootAni.read(bytes)) > 0) {
+ zos.write(bytes, 0, len);
+ }
+ } else {
+ String line;
+ BufferedReader reader = new BufferedReader(new InputStreamReader(bootAni));
+ final String[] info = reader.readLine().split(" ");
+
+ int scaledWidth;
+ int scaledHeight;
+ WindowManager wm = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
+ DisplayMetrics dm = new DisplayMetrics();
+ wm.getDefaultDisplay().getRealMetrics(dm);
+ // just in case the device is in landscape orientation we will
+ // swap the values since most (if not all) animations are portrait
+ if (dm.widthPixels > dm.heightPixels) {
+ scaledWidth = dm.heightPixels;
+ scaledHeight = dm.widthPixels;
+ } else {
+ scaledWidth = dm.widthPixels;
+ scaledHeight = dm.heightPixels;
+ }
+
+ int width = Integer.parseInt(info[0]);
+ int height = Integer.parseInt(info[1]);
+
+ if (width == height)
+ scaledHeight = scaledWidth;
+ else {
+ // adjust scaledHeight to retain original aspect ratio
+ float scale = (float)scaledWidth / (float)width;
+ int newHeight = (int)((float)height * scale);
+ if (newHeight < scaledHeight)
+ scaledHeight = newHeight;
+ }
+
+ CRC32 crc32 = new CRC32();
+ int size = 0;
+ ByteBuffer buffer = ByteBuffer.wrap(bytes);
+ line = String.format("%d %d %s\n", scaledWidth, scaledHeight, info[2]);
+ buffer.put(line.getBytes());
+ size += line.getBytes().length;
+ crc32.update(line.getBytes());
+ while ((line = reader.readLine()) != null) {
+ line = String.format("%s\n", line);
+ buffer.put(line.getBytes());
+ size += line.getBytes().length;
+ crc32.update(line.getBytes());
+ }
+ entry.setCrc(crc32.getValue());
+ entry.setSize(size);
+ entry.setCompressedSize(size);
+ zos.putNextEntry(entry);
+ zos.write(buffer.array(), 0, size);
+ }
+ zos.closeEntry();
+ }
+ zos.close();
+ }
+
+ public static boolean isValidAudible(String fileName) {
+ return (fileName != null &&
+ (fileName.endsWith(".mp3") || fileName.endsWith(".ogg")));
+ }
+
+ public static boolean setAudible(Context context, File ringtone, int type, String name) {
+ final String path = ringtone.getAbsolutePath();
+ final String mimeType = name.endsWith(".ogg") ? "audio/ogg" : "audio/mp3";
+ ContentValues values = new ContentValues();
+ values.put(MediaStore.MediaColumns.DATA, path);
+ values.put(MediaStore.MediaColumns.TITLE, name);
+ values.put(MediaStore.MediaColumns.MIME_TYPE, mimeType);
+ values.put(MediaStore.MediaColumns.SIZE, ringtone.length());
+ values.put(MediaStore.Audio.Media.IS_RINGTONE, type == RingtoneManager.TYPE_RINGTONE);
+ values.put(MediaStore.Audio.Media.IS_NOTIFICATION,
+ type == RingtoneManager.TYPE_NOTIFICATION);
+ values.put(MediaStore.Audio.Media.IS_ALARM, type == RingtoneManager.TYPE_ALARM);
+ values.put(MediaStore.Audio.Media.IS_MUSIC, false);
+
+ Uri uri = MediaStore.Audio.Media.getContentUriForPath(path);
+ Uri newUri = null;
+ Cursor c = context.getContentResolver().query(uri,
+ new String[] {MediaStore.MediaColumns._ID},
+ MediaStore.MediaColumns.DATA + "='" + path + "'",
+ null, null);
+ if (c != null && c.getCount() > 0) {
+ c.moveToFirst();
+ long id = c.getLong(0);
+ c.close();
+ newUri = Uri.withAppendedPath(Uri.parse(MEDIA_CONTENT_URI), "" + id);
+ context.getContentResolver().update(uri, values,
+ MediaStore.MediaColumns._ID + "=" + id, null);
+ }
+ if (newUri == null)
+ newUri = context.getContentResolver().insert(uri, values);
+ try {
+ RingtoneManager.setActualDefaultRingtoneUri(context, type, newUri);
+ } catch (Exception e) {
+ return false;
+ }
+ return true;
+ }
+
+ public static boolean setDefaultAudible(Context context, int type) {
+ final String audiblePath = getDefaultAudiblePath(type);
+ if (audiblePath != null) {
+ Uri uri = MediaStore.Audio.Media.getContentUriForPath(audiblePath);
+ Cursor c = context.getContentResolver().query(uri,
+ new String[] {MediaStore.MediaColumns._ID},
+ MediaStore.MediaColumns.DATA + "='" + audiblePath + "'",
+ null, null);
+ if (c != null && c.getCount() > 0) {
+ c.moveToFirst();
+ long id = c.getLong(0);
+ c.close();
+ uri = Uri.withAppendedPath(
+ Uri.parse(MEDIA_CONTENT_URI), "" + id);
+ }
+ if (uri != null)
+ RingtoneManager.setActualDefaultRingtoneUri(context, type, uri);
+ } else {
+ return false;
+ }
+ return true;
+ }
+
+ public static String getDefaultAudiblePath(int type) {
+ final String name;
+ final String path;
+ switch (type) {
+ case RingtoneManager.TYPE_ALARM:
+ name = SystemProperties.get("ro.config.alarm_alert", null);
+ path = name != null ? SYSTEM_ALARMS_PATH + File.separator + name : null;
+ break;
+ case RingtoneManager.TYPE_NOTIFICATION:
+ name = SystemProperties.get("ro.config.notification_sound", null);
+ path = name != null ? SYSTEM_NOTIFICATIONS_PATH + File.separator + name : null;
+ break;
+ case RingtoneManager.TYPE_RINGTONE:
+ name = SystemProperties.get("ro.config.ringtone", null);
+ path = name != null ? SYSTEM_RINGTONES_PATH + File.separator + name : null;
+ break;
+ default:
+ path = null;
+ break;
+ }
+ return path;
+ }
+
+ public static void clearAudibles(Context context, String audiblePath) {
+ final File audibleDir = new File(audiblePath);
+ if (audibleDir.exists()) {
+ String[] files = audibleDir.list();
+ final ContentResolver resolver = context.getContentResolver();
+ for (String s : files) {
+ final String filePath = audiblePath + File.separator + s;
+ Uri uri = MediaStore.Audio.Media.getContentUriForPath(filePath);
+ resolver.delete(uri, MediaStore.MediaColumns.DATA + "=\""
+ + filePath + "\"", null);
+ (new File(filePath)).delete();
+ }
+ }
+ }
+
+ public static InputStream getInputStreamFromAsset(Context ctx, String path) throws IOException {
+ if (ctx == null || path == null) return null;
+
+ InputStream is;
+ String ASSET_BASE = "file:///android_asset/";
+ path = path.substring(ASSET_BASE.length());
+ AssetManager assets = ctx.getAssets();
+ is = assets.open(path);
+ return is;
+ }
+
+ /**
+ * Convenience method to determine if a theme component is a per app theme and not a standard
+ * component.
+ * @param component
+ * @return
+ */
+ public static boolean isPerAppThemeComponent(String component) {
+ return !(DEFAULT_PKG.equals(component)
+ || ThemeConfig.SYSTEMUI_STATUS_BAR_PKG.equals(component)
+ || ThemeConfig.SYSTEMUI_NAVBAR_PKG.equals(component));
+ }
+
+ /**
+ * Returns the first non-empty asset name. Empty assets can occur if the APK is built
+ * with folders included as zip entries in the APK. Searching for files inside "folderName" via
+ * assetManager.list("folderName") can cause these entries to be included as empty strings.
+ * @param assets
+ * @return
+ */
+ private static String getFirstNonEmptyAsset(String[] assets) {
+ if (assets == null) return null;
+ String filename = null;
+ for(String asset : assets) {
+ if (!TextUtils.isEmpty(asset)) {
+ filename = asset;
+ break;
+ }
+ }
+ return filename;
+ }
+
+ private static boolean dirExists(String dirPath) {
+ final File dir = new File(dirPath);
+ return dir.exists() && dir.isDirectory();
+ }
+
+ private static void createDirIfNotExists(String dirPath) {
+ if (!dirExists(dirPath)) {
+ File dir = new File(dirPath);
+ if (dir.mkdir()) {
+ FileUtils.setPermissions(dir, FileUtils.S_IRWXU |
+ FileUtils.S_IRWXG| FileUtils.S_IROTH | FileUtils.S_IXOTH, -1, -1);
+ }
+ }
+ }
+
+ private static class ThemedUiContext extends ContextWrapper {
+ private Context mAppContext;
+
+ public ThemedUiContext(Context context, Context appContext) {
+ super(context);
+ mAppContext = appContext;
+ }
+
+ @Override
+ public Context getApplicationContext() {
+ return mAppContext;
+ }
+
+ @Override
+ public String getPackageName() {
+ return mAppContext.getPackageName();
+ }
+ }
+}