cmparts: Add lights settings
* Converted from Settings app
* Create a new PartsActivity to launch fragments. This will be improved
to allow parts to register some metadata which can be queried by
the Settings app. Ideally we should be able to require nothing
more than one-liners in the preference XML files inside the Settings
app for adding new items when complete.
Change-Id: Ic67f65cf2366b32c94bc79cd13492bda159b691f
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 4bf7e61..513b9f0 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -54,5 +54,12 @@
android:resource="@drawable/ic_settings_privacy" />
</activity>
+ <activity android:name=".PartsActivity">
+ <intent-filter>
+ <action android:name="org.cyanogenmod.cmparts.PART" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
+
</application>
</manifest>
diff --git a/res/drawable-hdpi/ic_menu_add_white.png b/res/drawable-hdpi/ic_menu_add_white.png
new file mode 100644
index 0000000..7638c64
--- /dev/null
+++ b/res/drawable-hdpi/ic_menu_add_white.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_menu_add_white.png b/res/drawable-mdpi/ic_menu_add_white.png
new file mode 100644
index 0000000..40cdc82
--- /dev/null
+++ b/res/drawable-mdpi/ic_menu_add_white.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_menu_add_white.png b/res/drawable-xhdpi/ic_menu_add_white.png
new file mode 100644
index 0000000..1d53989
--- /dev/null
+++ b/res/drawable-xhdpi/ic_menu_add_white.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_menu_add_white.png b/res/drawable-xxhdpi/ic_menu_add_white.png
new file mode 100644
index 0000000..16a8b8a
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_menu_add_white.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_menu_add_white.png b/res/drawable-xxxhdpi/ic_menu_add_white.png
new file mode 100644
index 0000000..27a15f5
--- /dev/null
+++ b/res/drawable-xxxhdpi/ic_menu_add_white.png
Binary files differ
diff --git a/res/drawable/ic_settings_24dp.xml b/res/drawable/ic_settings_24dp.xml
new file mode 100644
index 0000000..545bc2d
--- /dev/null
+++ b/res/drawable/ic_settings_24dp.xml
@@ -0,0 +1,25 @@
+<!--
+ Copyright (C) 2015 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:pathData="M19.4,13.0c0.0,-0.3 0.1,-0.6 0.1,-1.0s0.0,-0.7 -0.1,-1.0l2.1,-1.7c0.2,-0.2 0.2,-0.4 0.1,-0.6l-2.0,-3.5C19.5,5.1 19.3,5.0 19.0,5.1l-2.5,1.0c-0.5,-0.4 -1.1,-0.7 -1.7,-1.0l-0.4,-2.6C14.5,2.2 14.2,2.0 14.0,2.0l-4.0,0.0C9.8,2.0 9.5,2.2 9.5,2.4L9.1,5.1C8.5,5.3 8.0,5.7 7.4,6.1L5.0,5.1C4.7,5.0 4.5,5.1 4.3,5.3l-2.0,3.5C2.2,8.9 2.3,9.2 2.5,9.4L4.6,11.0c0.0,0.3 -0.1,0.6 -0.1,1.0s0.0,0.7 0.1,1.0l-2.1,1.7c-0.2,0.2 -0.2,0.4 -0.1,0.6l2.0,3.5C4.5,18.9 4.7,19.0 5.0,18.9l2.5,-1.0c0.5,0.4 1.1,0.7 1.7,1.0l0.4,2.6c0.0,0.2 0.2,0.4 0.5,0.4l4.0,0.0c0.2,0.0 0.5,-0.2 0.5,-0.4l0.4,-2.6c0.6,-0.3 1.2,-0.6 1.7,-1.0l2.5,1.0c0.2,0.1 0.5,0.0 0.6,-0.2l2.0,-3.5c0.1,-0.2 0.1,-0.5 -0.1,-0.6L19.4,13.0zM12.0,15.5c-1.9,0.0 -3.5,-1.6 -3.5,-3.5s1.6,-3.5 3.5,-3.5s3.5,1.6 3.5,3.5S13.9,15.5 12.0,15.5z"
+ android:fillColor="#ffffffff" />
+</vector>
diff --git a/res/layout/dialog_light_settings.xml b/res/layout/dialog_light_settings.xml
new file mode 100644
index 0000000..02ef466
--- /dev/null
+++ b/res/layout/dialog_light_settings.xml
@@ -0,0 +1,116 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2010 Daniel Nilsson
+ Copyright (C) 2012 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.
+-->
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:paddingStart="8dp"
+ android:paddingEnd="8dp"
+ android:scrollbars="vertical"
+ android:scrollbarStyle="outsideOverlay"
+ android:scrollbarDefaultDelayBeforeFade="1500"
+ android:scrollbarAlwaysDrawVerticalTrack="true"
+ android:fillViewport="true">
+
+ <RelativeLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" >
+
+ <org.cyanogenmod.cmparts.notificationlight.ColorPickerView
+ android:id="@+id/color_picker_view"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_centerHorizontal="true"
+ android:layout_marginStart="10dp"
+ android:layout_marginEnd="10dp" />
+
+ <LinearLayout
+ android:id="@+id/color_panel_view"
+ android:layout_width="match_parent"
+ android:layout_height="40dp"
+ android:layout_alignStart="@id/color_picker_view"
+ android:layout_alignEnd="@id/color_picker_view"
+ android:layout_below="@id/color_picker_view"
+ android:layout_marginBottom="4dp"
+ android:layout_marginTop="4dp"
+ android:orientation="horizontal" >
+
+ <EditText
+ android:id="@+id/hex_color_input"
+ android:layout_width="0px"
+ android:maxLength="6"
+ android:digits="0123456789ABCDEFabcdef"
+ android:inputType="textNoSuggestions"
+ android:layout_height="match_parent"
+ android:layout_weight="0.5" />
+
+ <org.cyanogenmod.cmparts.notificationlight.ColorPanelView
+ android:id="@+id/color_panel"
+ android:layout_width="0px"
+ android:layout_height="match_parent"
+ android:layout_weight="0.5" />
+ </LinearLayout>
+
+ <LinearLayout
+ android:id="@+id/speed_title_view"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_below="@id/color_panel_view"
+ android:layout_marginStart="10dp"
+ android:layout_marginEnd="10dp"
+ android:layout_marginTop="4dp"
+ android:orientation="vertical" >
+
+ <View
+ android:id="@+id/lights_dialog_divider"
+ android:layout_width="match_parent"
+ android:layout_height="2dp"
+ android:background="@android:drawable/divider_horizontal_dark" />
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="4dp"
+ android:text="@string/pulse_speed_title"
+ android:textAppearance="?android:attr/textAppearanceSmall" />
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:paddingBottom="4dip" >
+
+ <Spinner
+ android:id="@+id/on_spinner"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_weight="1" />
+
+ <View
+ android:layout_width="8dip"
+ android:layout_height="match_parent" />
+
+ <Spinner
+ android:id="@+id/off_spinner"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_weight="1" />
+ </LinearLayout>
+ </LinearLayout>
+
+ </RelativeLayout>
+</ScrollView>
diff --git a/res/layout/preference_application_light.xml b/res/layout/preference_application_light.xml
new file mode 100644
index 0000000..9f9135e
--- /dev/null
+++ b/res/layout/preference_application_light.xml
@@ -0,0 +1,94 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/app_light_pref"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="?android:attr/listPreferredItemHeightSmall"
+ android:gravity="center_vertical"
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+ android:background="?android:attr/selectableItemBackground">
+
+ <ImageView
+ android:id="@android:id/icon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginEnd="12dip"
+ android:padding="2dp"
+ android:maxWidth="36dip"
+ android:maxHeight="36dip"
+ android:adjustViewBounds="true"
+ android:layout_gravity="center" />
+
+ <RelativeLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:paddingTop="16dip"
+ android:paddingBottom="16dip">
+
+ <TextView
+ android:id="@android:id/title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:singleLine="true"
+ android:textAppearance="@android:style/TextAppearance.Material.Subhead"
+ android:textColor="?android:attr/textColorPrimary"
+ android:ellipsize="marquee"
+ android:fadingEdge="horizontal" />
+
+ <TextView
+ android:id="@android:id/summary"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_below="@android:id/title"
+ android:layout_alignStart="@android:id/title"
+ android:visibility="gone"
+ android:textAlignment="viewStart"
+ android:textAppearance="@android:style/TextAppearance.Material.Body1"
+ android:textColor="?android:attr/textColorSecondary"
+ android:maxLines="1" />
+ </RelativeLayout>
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="vertical" >
+
+ <TextView
+ android:id="@+id/textViewTimeOnValue"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="end"
+ android:textAppearance="@android:style/TextAppearance.Material.Notification.Line2" />
+
+ <TextView
+ android:id="@+id/textViewTimeOffValue"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="end"
+ android:textAppearance="@android:style/TextAppearance.Material.Notification.Line2" />
+ </LinearLayout>
+
+ <ImageView
+ android:id="@+id/light_color"
+ android:layout_width="32dip"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"/>
+
+</LinearLayout>
diff --git a/res/layout/preference_icon.xml b/res/layout/preference_icon.xml
new file mode 100644
index 0000000..adbd5c9
--- /dev/null
+++ b/res/layout/preference_icon.xml
@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2006 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.
+-->
+
+<!-- Layout for a Preference in a PreferenceActivity. The
+ Preference is able to place a specific widget for its particular
+ type in the "widget_frame" layout. -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@android:id/widget_frame"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="?android:attr/listPreferredItemHeightSmall"
+ android:gravity="center_vertical"
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+ android:background="?android:attr/selectableItemBackground">
+
+ <ImageView
+ android:id="@+id/icon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginEnd="12dip"
+ android:padding="2dp"
+ android:maxWidth="36dip"
+ android:maxHeight="36dip"
+ android:adjustViewBounds="true"
+ android:layout_gravity="center" />
+
+ <RelativeLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="2dip"
+ android:layout_marginEnd="6dip"
+ android:layout_marginTop="6dip"
+ android:layout_marginBottom="6dip"
+ android:layout_weight="1">
+
+ <TextView android:id="@android:id/title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:singleLine="true"
+ android:textAppearance="@android:style/TextAppearance.Material.Subhead"
+ android:textColor="?android:attr/textColorPrimary"
+ android:ellipsize="marquee"
+ android:fadingEdge="horizontal" />
+
+ <TextView android:id="@android:id/summary"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_below="@android:id/title"
+ android:layout_alignStart="@android:id/title"
+ android:textAppearance="@android:style/TextAppearance.Material.Body1"
+ android:textColor="?android:attr/textColorSecondary"
+ android:maxLines="2" />
+
+ </RelativeLayout>
+
+</LinearLayout>
diff --git a/res/layout/pulse_time_item.xml b/res/layout/pulse_time_item.xml
new file mode 100644
index 0000000..56d3005
--- /dev/null
+++ b/res/layout/pulse_time_item.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/textViewName"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:paddingStart="4dp"
+ android:paddingEnd="4dp"
+ android:paddingTop="8dp"
+ android:paddingBottom="8dp"
+ android:textAppearance="@android:style/TextAppearance.Material.Subhead" >
+
+</TextView>
diff --git a/res/values/arrays.xml b/res/values/arrays.xml
new file mode 100644
index 0000000..47dc7ab
--- /dev/null
+++ b/res/values/arrays.xml
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2012-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.
+-->
+<resources>
+ <!-- Values for the notification light pulse spinners -->
+ <string-array name="notification_pulse_length_entries" translatable="false">
+ <item>@string/pulse_length_always_on</item>
+ <item>@string/pulse_length_very_short</item>
+ <item>@string/pulse_length_short</item>
+ <item>@string/pulse_length_normal</item>
+ <item>@string/pulse_length_long</item>
+ <item>@string/pulse_length_very_long</item>
+ </string-array>
+
+ <string-array name="notification_pulse_length_values" translatable="false">
+ <item>1</item>
+ <item>250</item>
+ <item>500</item>
+ <item>1000</item>
+ <item>2500</item>
+ <item>5000</item>
+ </string-array>
+
+ <string-array name="notification_pulse_speed_entries" translatable="false">
+ <item>@string/pulse_speed_very_fast</item>
+ <item>@string/pulse_speed_fast</item>
+ <item>@string/pulse_speed_normal</item>
+ <item>@string/pulse_speed_slow</item>
+ <item>@string/pulse_speed_very_slow</item>
+ </string-array>
+
+ <string-array name="notification_pulse_speed_values" translatable="false">
+ <item>250</item>
+ <item>500</item>
+ <item>1000</item>
+ <item>2500</item>
+ <item>5000</item>
+ </string-array>
+</resources>
diff --git a/res/values/bools.xml b/res/values/bools.xml
new file mode 100644
index 0000000..38d748f
--- /dev/null
+++ b/res/values/bools.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+-->
+<resources>
+
+ <!-- Default value for battery_light_enabled preference -->
+ <bool name="def_battery_light_enabled">true</bool>
+ <!-- Default value for battery light pulse -->
+ <bool name="def_battery_light_pulse">true</bool>
+
+</resources>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index a10b307..1600171 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -33,5 +33,6 @@
<dimen name="settings_side_margin">0dip</dimen>
+ <dimen name="oval_notification_size">26dp</dimen>
</resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 19dc337..bf1c733 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -46,5 +46,55 @@
<string name="privacy_guard_advanced_settings_title">Advanced</string>
<string name="privacy_guard_notification_title">Show notification</string>
+ <!-- Notification light dialogs -->
+ <string name="edit_light_settings">Edit light settings</string>
+ <string name="pulse_speed_title">Pulse length and speed</string>
+ <string name="default_time">Normal</string>
+ <string name="custom_time">Custom</string>
+ <string name="dialog_delete_title">Delete</string>
+ <string name="dialog_delete_message">Remove selected item?</string>
+ <string name="brightness">Brightness level</string>
+
+ <!-- Values for the notification light pulse spinners -->
+ <string name="pulse_length_always_on">Always on</string>
+ <string name="pulse_length_very_short">Very short</string>
+ <string name="pulse_length_short">Short</string>
+ <string name="pulse_length_normal">Normal</string>
+ <string name="pulse_length_long">Long</string>
+ <string name="pulse_length_very_long">Very long</string>
+ <string name="pulse_speed_very_fast">Very fast</string>
+ <string name="pulse_speed_fast">Fast</string>
+ <string name="pulse_speed_normal">Normal</string>
+ <string name="pulse_speed_slow">Slow</string>
+ <string name="pulse_speed_very_slow">Very slow</string>
+
+ <!-- Battery light settings -->
+ <string name="battery_low_pulse_title">Pulse if battery low</string>
+ <string name="battery_light_list_title">Colors</string>
+ <string name="battery_light_low_color_title">Battery low</string>
+ <string name="battery_light_medium_color_title">Charging</string>
+ <string name="battery_light_full_color_title">Fully charged</string>
+
+ <!-- Lights settings screen, notification light settings -->
+ <string name="notification_light_general_title">General</string>
+ <string name="notification_light_advanced_title">Advanced</string>
+ <string name="notification_light_applist_title">Apps</string>
+ <string name="notification_light_phonelist_title">Phone</string>
+ <string name="notification_light_use_custom">Use custom values</string>
+ <string name="notification_light_default_value">Default</string>
+ <string name="notification_light_missed_call_title">Missed call</string>
+ <string name="notification_light_voicemail_title">Voicemail</string>
+ <string name="notification_light_brightness" translatable="false">@string/brightness</string>
+ <string name="notification_light_screen_on">Lights with screen on</string>
+ <string name="notification_light_zen_mode">Lights in Do Not Disturb mode</string>
+ <string name="notification_light_use_multiple_leds">Multiple LEDs</string>
+ <string name="keywords_lights_brightness_level">dim leds brightness</string>
+ <string name="notification_light_automagic">Choose colors automatically</string>
+
+ <!-- Lights settings, LED notification -->
+ <string name="led_notification_title">Light settings</string>
+ <string name="led_notification_text">LED light enabled by settings</string>
+ <string name="notification_light_no_apps_summary">To add per app control, activate \'%1$s\' and press \'\u002b\' on the menu bar</string>
+
</resources>
diff --git a/res/xml/battery_light_settings.xml b/res/xml/battery_light_settings.xml
new file mode 100644
index 0000000..1338d2e
--- /dev/null
+++ b/res/xml/battery_light_settings.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2012 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.
+-->
+
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:settings="http://schemas.android.com/apk/res/com.android.settings"
+ android:key="battery_light"
+ android:title="@*cyanogenmod.platform:string/battery_light_title">
+
+ <PreferenceCategory
+ android:key="general_section"
+ android:title="@string/notification_light_general_title">
+
+ <cyanogenmod.preference.CMSystemSettingSwitchPreference
+ android:key="battery_light_enabled"
+ android:title="@*cyanogenmod.platform:string/battery_light_title"
+ android:defaultValue="@bool/def_battery_light_enabled" />
+
+ <cyanogenmod.preference.CMSystemSettingSwitchPreference
+ android:key="battery_light_pulse"
+ android:title="@string/battery_low_pulse_title"
+ android:defaultValue="@bool/def_battery_light_pulse"
+ android:dependency="battery_light_enabled" />
+
+ </PreferenceCategory>
+
+ <PreferenceCategory
+ android:key="colors_list"
+ android:title="@string/battery_light_list_title"
+ android:dependency="battery_light_enabled" >
+
+ <org.cyanogenmod.cmparts.notificationlight.ApplicationLightPreference
+ android:key="low_color"
+ android:title="@string/battery_light_low_color_title"
+ android:persistent="false" />
+
+ <org.cyanogenmod.cmparts.notificationlight.ApplicationLightPreference
+ android:key="medium_color"
+ android:title="@string/battery_light_medium_color_title"
+ android:persistent="false" />
+
+ <org.cyanogenmod.cmparts.notificationlight.ApplicationLightPreference
+ android:key="full_color"
+ android:title="@string/battery_light_full_color_title"
+ android:persistent="false" />
+
+ </PreferenceCategory>
+
+</PreferenceScreen>
diff --git a/res/xml/notification_light_settings.xml b/res/xml/notification_light_settings.xml
new file mode 100644
index 0000000..fe7bd18
--- /dev/null
+++ b/res/xml/notification_light_settings.xml
@@ -0,0 +1,101 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2012 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.
+-->
+
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+ android:key="notification_light"
+ android:title="@*cyanogenmod.platform:string/notification_light_title">
+
+ <PreferenceCategory
+ android:key="general_section"
+ android:title="@string/notification_light_general_title">
+
+ <cyanogenmod.preference.SystemSettingSwitchPreference
+ android:key="notification_light_pulse"
+ android:title="@*cyanogenmod.platform:string/notification_light_title" />
+
+ <cyanogenmod.preference.CMSystemSettingSwitchPreference
+ android:key="notification_light_color_auto"
+ android:title="@string/notification_light_automagic"
+ android:dependency="notification_light_pulse"
+ android:defaultValue="true" />
+
+ <org.cyanogenmod.cmparts.notificationlight.ApplicationLightPreference
+ android:key="default"
+ android:title="@string/notification_light_default_value"
+ android:persistent="false"
+ android:dependency="notification_light_pulse" />
+
+ </PreferenceCategory>
+
+ <PreferenceCategory
+ android:key="advanced_section"
+ android:title="@string/advanced">
+
+ <PreferenceScreen
+ android:key="notification_light_brightness_level"
+ android:title="@string/notification_light_brightness"
+ android:dependency="notification_light_pulse">
+ <intent android:action="android.intent.action.SHOW_NOTIFICATION_BRIGHTNESS_DIALOG" />
+ </PreferenceScreen>
+
+ <cyanogenmod.preference.CMSystemSettingSwitchPreference
+ android:key="notification_light_multiple_leds_enable"
+ android:title="@string/notification_light_use_multiple_leds"
+ android:dependency="notification_light_pulse" />
+
+ <cyanogenmod.preference.CMSystemSettingSwitchPreference
+ android:key="notification_light_screen_on_enable"
+ android:title="@string/notification_light_screen_on"
+ android:dependency="notification_light_pulse" />
+
+ <cyanogenmod.preference.CMSystemSettingSwitchPreference
+ android:key="allow_lights"
+ android:title="@string/notification_light_zen_mode"
+ android:dependency="notification_light_pulse"
+ android:defaultValue="true" />
+
+ <cyanogenmod.preference.CMSystemSettingSwitchPreference
+ android:key="notification_light_pulse_custom_enable"
+ android:title="@string/notification_light_use_custom"
+ android:dependency="notification_light_pulse" />
+
+ </PreferenceCategory>
+
+ <PreferenceCategory
+ android:key="phone_list"
+ android:title="@string/notification_light_phonelist_title" >
+
+ <org.cyanogenmod.cmparts.notificationlight.ApplicationLightPreference
+ android:key="missed_call"
+ android:title="@string/notification_light_missed_call_title"
+ android:persistent="false"
+ android:dependency="notification_light_pulse_custom_enable" />
+
+ <org.cyanogenmod.cmparts.notificationlight.ApplicationLightPreference
+ android:key="voicemail"
+ android:title="@string/notification_light_voicemail_title"
+ android:persistent="false"
+ android:dependency="notification_light_pulse_custom_enable" />
+
+ </PreferenceCategory>
+
+ <PreferenceCategory
+ android:key="applications_list"
+ android:title="@string/notification_light_applist_title"
+ android:dependency="notification_light_pulse_custom_enable" >
+ </PreferenceCategory>
+
+</PreferenceScreen>
diff --git a/src/org/cyanogenmod/cmparts/PackageListAdapter.java b/src/org/cyanogenmod/cmparts/PackageListAdapter.java
new file mode 100644
index 0000000..249a180
--- /dev/null
+++ b/src/org/cyanogenmod/cmparts/PackageListAdapter.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2012-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.cmparts;
+
+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.graphics.drawable.Drawable;
+import android.os.Handler;
+import android.os.Message;
+import android.text.TextUtils;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.TreeSet;
+
+public class PackageListAdapter extends BaseAdapter implements Runnable {
+ private PackageManager mPm;
+ private LayoutInflater mInflater;
+ private List<PackageItem> mInstalledPackages = new LinkedList<PackageItem>();
+
+ // Packages which don't have launcher icons, but which we want to show nevertheless
+ private static final String[] PACKAGE_WHITELIST = new String[] {
+ "android", /* system server */
+ "com.android.systemui", /* system UI */
+ "com.android.providers.downloads" /* download provider */
+ };
+
+ private final Handler mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ PackageItem item = (PackageItem) msg.obj;
+ int index = Collections.binarySearch(mInstalledPackages, item);
+ if (index < 0) {
+ mInstalledPackages.add(-index - 1, item);
+ } else {
+ mInstalledPackages.get(index).activityTitles.addAll(item.activityTitles);
+ }
+ notifyDataSetChanged();
+ }
+ };
+
+ public static class PackageItem implements Comparable<PackageItem> {
+ public final String packageName;
+ public final CharSequence title;
+ private final TreeSet<CharSequence> activityTitles = new TreeSet<CharSequence>();
+ public final Drawable icon;
+
+ PackageItem(String packageName, CharSequence title, Drawable icon) {
+ this.packageName = packageName;
+ this.title = title;
+ this.icon = icon;
+ }
+
+ @Override
+ public int compareTo(PackageItem another) {
+ int result = title.toString().compareToIgnoreCase(another.title.toString());
+ return result != 0 ? result : packageName.compareTo(another.packageName);
+ }
+ }
+
+ public PackageListAdapter(Context context) {
+ mPm = context.getPackageManager();
+ mInflater = LayoutInflater.from(context);
+ reloadList();
+ }
+
+ @Override
+ public int getCount() {
+ synchronized (mInstalledPackages) {
+ return mInstalledPackages.size();
+ }
+ }
+
+ @Override
+ public PackageItem getItem(int position) {
+ synchronized (mInstalledPackages) {
+ return mInstalledPackages.get(position);
+ }
+ }
+
+ @Override
+ public long getItemId(int position) {
+ synchronized (mInstalledPackages) {
+ // packageName is guaranteed to be unique in mInstalledPackages
+ return mInstalledPackages.get(position).packageName.hashCode();
+ }
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ ViewHolder holder;
+ if (convertView != null) {
+ holder = (ViewHolder) convertView.getTag();
+ } else {
+ convertView = mInflater.inflate(R.layout.preference_icon, null, false);
+ holder = new ViewHolder();
+ convertView.setTag(holder);
+ holder.title = (TextView) convertView.findViewById(com.android.internal.R.id.title);
+ holder.summary = (TextView) convertView.findViewById(com.android.internal.R.id.summary);
+ holder.icon = (ImageView) convertView.findViewById(R.id.icon);
+ }
+
+ PackageItem applicationInfo = getItem(position);
+ holder.title.setText(applicationInfo.title);
+ holder.icon.setImageDrawable(applicationInfo.icon);
+
+ boolean needSummary = applicationInfo.activityTitles.size() > 0;
+ if (applicationInfo.activityTitles.size() == 1) {
+ if (TextUtils.equals(applicationInfo.title, applicationInfo.activityTitles.first())) {
+ needSummary = false;
+ }
+ }
+
+ if (needSummary) {
+ holder.summary.setText(TextUtils.join(", ", applicationInfo.activityTitles));
+ holder.summary.setVisibility(View.VISIBLE);
+ } else {
+ holder.summary.setVisibility(View.GONE);
+ }
+
+ return convertView;
+ }
+
+ private void reloadList() {
+ mInstalledPackages.clear();
+ new Thread(this).start();
+ }
+
+ @Override
+ public void run() {
+ final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
+ mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
+ List<ResolveInfo> installedAppsInfo = mPm.queryIntentActivities(mainIntent, 0);
+
+ for (ResolveInfo info : installedAppsInfo) {
+ ApplicationInfo appInfo = info.activityInfo.applicationInfo;
+ final PackageItem item = new PackageItem(appInfo.packageName,
+ appInfo.loadLabel(mPm), appInfo.loadIcon(mPm));
+ item.activityTitles.add(info.loadLabel(mPm));
+ mHandler.obtainMessage(0, item).sendToTarget();
+ }
+
+ for (String packageName : PACKAGE_WHITELIST) {
+ try {
+ ApplicationInfo appInfo = mPm.getApplicationInfo(packageName, 0);
+ final PackageItem item = new PackageItem(appInfo.packageName,
+ appInfo.loadLabel(mPm), appInfo.loadIcon(mPm));
+ mHandler.obtainMessage(0, item).sendToTarget();
+ } catch (PackageManager.NameNotFoundException ignored) {
+ // package not present, so nothing to add -> ignore it
+ }
+ }
+ }
+
+ private static class ViewHolder {
+ TextView title;
+ TextView summary;
+ ImageView icon;
+ }
+}
diff --git a/src/org/cyanogenmod/cmparts/PartsActivity.java b/src/org/cyanogenmod/cmparts/PartsActivity.java
new file mode 100644
index 0000000..b463b9a
--- /dev/null
+++ b/src/org/cyanogenmod/cmparts/PartsActivity.java
@@ -0,0 +1,75 @@
+/*
+ * 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.cmparts;
+
+import android.app.ActionBar;
+import android.app.FragmentTransaction;
+import android.os.Bundle;
+import android.preference.PreferenceActivity;
+
+import org.cyanogenmod.cmparts.notificationlight.BatteryLightSettings;
+import org.cyanogenmod.cmparts.notificationlight.NotificationLightSettings;
+
+public class PartsActivity extends PreferenceActivity {
+
+ public static final String EXTRA_PART = "part";
+ public static final String EXTRA_FRAGMENT_ARG_KEY = ":settings:fragment_args_key";
+
+ public static final String FRAGMENT_PREFIX = "cmparts:";
+
+ public static final String FRAGMENT_NOTIFICATION_LIGHTS = "notification_lights";
+ public static final String FRAGMENT_BATTERY_LIGHTS = "battery_lights";
+
+ private ActionBar mActionBar;
+
+ @Override
+ public void onCreate(Bundle bundle) {
+ super.onCreate(bundle);
+
+ String partExtra = getIntent().getStringExtra(EXTRA_PART);
+ if (partExtra != null && partExtra.startsWith(FRAGMENT_PREFIX)) {
+ String[] keys = partExtra.split(":");
+ if (keys.length < 2) {
+ return;
+ }
+ String part = keys[1];
+ SettingsPreferenceFragment fragment = null;
+ if (part.equals(FRAGMENT_NOTIFICATION_LIGHTS)) {
+ fragment = new NotificationLightSettings();
+ } else if (part.equals(FRAGMENT_BATTERY_LIGHTS)) {
+ fragment = new BatteryLightSettings();
+ }
+
+ mActionBar = getActionBar();
+ if (mActionBar != null) {
+ mActionBar.setDisplayHomeAsUpEnabled(true);
+ mActionBar.setHomeButtonEnabled(true);
+ }
+
+ if (fragment != null) {
+ getFragmentManager().beginTransaction()
+ .replace(android.R.id.content, fragment)
+ .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
+ .commitAllowingStateLoss();
+ getFragmentManager().executePendingTransactions();
+ }
+ }
+ }
+
+
+}
+
diff --git a/src/org/cyanogenmod/cmparts/notificationlight/AlphaPatternDrawable.java b/src/org/cyanogenmod/cmparts/notificationlight/AlphaPatternDrawable.java
new file mode 100644
index 0000000..807dc0a
--- /dev/null
+++ b/src/org/cyanogenmod/cmparts/notificationlight/AlphaPatternDrawable.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2010 Daniel Nilsson
+ * Copyright (C) 2012 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.cmparts.notificationlight;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.Bitmap.Config;
+import android.graphics.drawable.Drawable;
+
+/**
+ * This drawable that draws a simple white and gray chess board pattern. It's
+ * pattern you will often see as a background behind a partly transparent image
+ * in many applications.
+ *
+ * @author Daniel Nilsson
+ */
+public class AlphaPatternDrawable extends Drawable {
+
+ private int mRectangleSize = 10;
+
+ private Paint mPaint = new Paint();
+ private Paint mPaintWhite = new Paint();
+ private Paint mPaintGray = new Paint();
+
+ private int numRectanglesHorizontal;
+ private int numRectanglesVertical;
+
+ /**
+ * Bitmap in which the pattern will be cached.
+ */
+ private Bitmap mBitmap;
+
+ public AlphaPatternDrawable(int rectangleSize) {
+ mRectangleSize = rectangleSize;
+ mPaintWhite.setColor(0xffffffff);
+ mPaintGray.setColor(0xffcbcbcb);
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ if (mBitmap != null) {
+ canvas.drawBitmap(mBitmap, null, getBounds(), mPaint);
+ }
+ }
+
+ @Override
+ public int getOpacity() {
+ return 0;
+ }
+
+ @Override
+ public void setAlpha(int alpha) {
+ throw new UnsupportedOperationException("Alpha is not supported by this drawwable.");
+ }
+
+ @Override
+ public void setColorFilter(ColorFilter cf) {
+ throw new UnsupportedOperationException("ColorFilter is not supported by this drawwable.");
+ }
+
+ @Override
+ protected void onBoundsChange(Rect bounds) {
+ super.onBoundsChange(bounds);
+
+ int height = bounds.height();
+ int width = bounds.width();
+
+ numRectanglesHorizontal = (int) Math.ceil((width / mRectangleSize));
+ numRectanglesVertical = (int) Math.ceil(height / mRectangleSize);
+
+ generatePatternBitmap();
+ }
+
+ /**
+ * This will generate a bitmap with the pattern as big as the rectangle we
+ * were allow to draw on. We do this to cache the bitmap so we don't need
+ * to recreate it each time draw() is called since it takes a few
+ * milliseconds.
+ */
+ private void generatePatternBitmap() {
+
+ if (getBounds().width() <= 0 || getBounds().height() <= 0) {
+ return;
+ }
+
+ mBitmap = Bitmap.createBitmap(getBounds().width(), getBounds().height(), Config.ARGB_8888);
+ Canvas canvas = new Canvas(mBitmap);
+
+ Rect r = new Rect();
+ boolean verticalStartWhite = true;
+ for (int i = 0; i <= numRectanglesVertical; i++) {
+ boolean isWhite = verticalStartWhite;
+ for (int j = 0; j <= numRectanglesHorizontal; j++) {
+ r.top = i * mRectangleSize;
+ r.left = j * mRectangleSize;
+ r.bottom = r.top + mRectangleSize;
+ r.right = r.left + mRectangleSize;
+
+ canvas.drawRect(r, isWhite ? mPaintWhite : mPaintGray);
+
+ isWhite = !isWhite;
+ }
+
+ verticalStartWhite = !verticalStartWhite;
+ }
+ }
+}
diff --git a/src/org/cyanogenmod/cmparts/notificationlight/ApplicationLightPreference.java b/src/org/cyanogenmod/cmparts/notificationlight/ApplicationLightPreference.java
new file mode 100644
index 0000000..18f687f
--- /dev/null
+++ b/src/org/cyanogenmod/cmparts/notificationlight/ApplicationLightPreference.java
@@ -0,0 +1,299 @@
+/*
+ * Copyright (C) 2012 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.cmparts.notificationlight;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.graphics.drawable.ShapeDrawable;
+import android.graphics.drawable.shapes.OvalShape;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.v7.preference.PreferenceViewHolder;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import org.cyanogenmod.cmparts.CustomDialogPreference;
+import org.cyanogenmod.cmparts.R;
+
+public class ApplicationLightPreference extends CustomDialogPreference<LightSettingsDialog>
+ implements View.OnLongClickListener {
+
+ private static String TAG = "AppLightPreference";
+ public static final int DEFAULT_TIME = 1000;
+ public static final int DEFAULT_COLOR = 0xffffff;
+
+ private ImageView mLightColorView;
+ private TextView mOnValueView;
+ private TextView mOffValueView;
+
+ private int mColorValue;
+ private int mOnValue;
+ private int mOffValue;
+ private boolean mOnOffChangeable;
+
+ private LightSettingsDialog mDialog;
+
+ public interface ItemLongClickListener {
+ public boolean onItemLongClick(String key);
+ }
+
+ private ItemLongClickListener mLongClickListener;
+
+ /**
+ * @param context
+ * @param attrs
+ */
+ public ApplicationLightPreference(Context context, AttributeSet attrs) {
+ this(context, attrs, DEFAULT_COLOR, DEFAULT_TIME, DEFAULT_TIME,
+ context.getResources().getBoolean(
+ com.android.internal.R.bool.config_ledCanPulse));
+ }
+
+ /**
+ * @param context
+ * @param color
+ * @param onValue
+ * @param offValue
+ */
+ public ApplicationLightPreference(Context context, AttributeSet attrs,
+ int color, int onValue, int offValue) {
+ this(context, attrs, color, onValue, offValue, context.getResources().getBoolean(
+ com.android.internal.R.bool.config_ledCanPulse));
+ }
+
+ /**
+ * @param context
+ * @param color
+ * @param onValue
+ * @param offValue
+ */
+ public ApplicationLightPreference(Context context, AttributeSet attrs,
+ int color, int onValue, int offValue, boolean onOffChangeable) {
+ super(context, attrs);
+ mColorValue = color;
+ mOnValue = onValue;
+ mOffValue = offValue;
+ mOnOffChangeable = onOffChangeable;
+
+ setWidgetLayoutResource(R.layout.preference_application_light);
+ }
+
+ @Override
+ public boolean onLongClick(View view) {
+ if (mLongClickListener != null) {
+ return mLongClickListener.onItemLongClick(getKey());
+ }
+ return false;
+ }
+
+ public void setOnLongClickListener(ItemLongClickListener l) {
+ mLongClickListener = l;
+ }
+
+ @Override
+ public void onBindViewHolder(PreferenceViewHolder holder) {
+ super.onBindViewHolder(holder);
+
+ mLightColorView = (ImageView) holder.findViewById(R.id.light_color);
+ mOnValueView = (TextView) holder.findViewById(R.id.textViewTimeOnValue);
+ mOffValueView = (TextView) holder.findViewById(R.id.textViewTimeOffValue);
+
+ // Hide the summary text - it takes up too much space on a low res device
+ // We use it for storing the package name for the longClickListener
+ TextView tView = (TextView) holder.findViewById(android.R.id.summary);
+ tView.setVisibility(View.GONE);
+
+ if (!getContext().getResources().getBoolean(com.android.internal.R.bool.config_multiColorNotificationLed)) {
+ mLightColorView.setVisibility(View.GONE);
+ }
+
+ updatePreferenceViews();
+ holder.itemView.setOnLongClickListener(this);
+ }
+
+ public void onStop() {
+ if (getDialog() != null) {
+ getDialog().onStop();
+ }
+ }
+
+ public void onStart() {
+ if (getDialog() != null) {
+ getDialog().onStart();
+ }
+ }
+
+ private void updatePreferenceViews() {
+ final int size = (int) getContext().getResources().getDimension(R.dimen.oval_notification_size);
+
+ if (mLightColorView != null) {
+ mLightColorView.setEnabled(true);
+ // adjust if necessary to prevent material whiteout
+ final int imageColor = ((mColorValue & 0xF0F0F0) == 0xF0F0F0) ?
+ (mColorValue - 0x101010) : mColorValue;
+ mLightColorView.setImageDrawable(createOvalShape(size,
+ 0xFF000000 + imageColor));
+ }
+ if (mOnValueView != null) {
+ mOnValueView.setText(mapLengthValue(mOnValue));
+ }
+ if (mOffValueView != null) {
+ if (mOnValue == 1 || !mOnOffChangeable) {
+ mOffValueView.setVisibility(View.GONE);
+ } else {
+ mOffValueView.setVisibility(View.VISIBLE);
+ }
+ mOffValueView.setText(mapSpeedValue(mOffValue));
+ }
+ }
+
+ @NonNull
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ final LightSettingsDialog d = new LightSettingsDialog(getContext(),
+ 0xFF000000 + mColorValue, mOnValue, mOffValue, mOnOffChangeable);
+ d.setAlphaSliderVisible(false);
+
+ d.setButton(AlertDialog.BUTTON_POSITIVE, getContext().getResources().getString(R.string.dlg_ok),
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ mColorValue = d.getColor() - 0xFF000000; // strip alpha, led does not support it
+ mOnValue = d.getPulseSpeedOn();
+ mOffValue = d.getPulseSpeedOff();
+ updatePreferenceViews();
+ }
+ });
+ d.setButton(AlertDialog.BUTTON_NEGATIVE, getContext().getResources().getString(R.string.cancel),
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ }
+ });
+
+ return d;
+ }
+
+
+ /**
+ * Getters and Setters
+ */
+
+ public int getColor() {
+ return mColorValue;
+ }
+
+ public void setColor(int color) {
+ mColorValue = color;
+ updatePreferenceViews();
+ }
+
+ public void setOnValue(int value) {
+ mOnValue = value;
+ updatePreferenceViews();
+ }
+
+ public int getOnValue() {
+ return mOnValue;
+ }
+
+ public void setOffValue(int value) {
+ mOffValue = value;
+ updatePreferenceViews();
+ }
+
+ public int getOffValue() {
+ return mOffValue;
+ }
+
+ public void setAllValues(int color, int onValue, int offValue) {
+ mColorValue = color;
+ mOnValue = onValue;
+ mOffValue = offValue;
+ updatePreferenceViews();
+ }
+
+ public void setAllValues(int color, int onValue, int offValue, boolean onOffChangeable) {
+ mColorValue = color;
+ mOnValue = onValue;
+ mOffValue = offValue;
+ mOnOffChangeable = onOffChangeable;
+ updatePreferenceViews();
+ }
+
+ public void setOnOffValue(int onValue, int offValue) {
+ mOnValue = onValue;
+ mOffValue = offValue;
+ updatePreferenceViews();
+ }
+
+ public void setOnOffChangeable(boolean value) {
+ mOnOffChangeable = value;
+ }
+
+ /**
+ * Utility methods
+ */
+ private static ShapeDrawable createOvalShape(int size, int color) {
+ ShapeDrawable shape = new ShapeDrawable(new OvalShape());
+ shape.setIntrinsicHeight(size);
+ shape.setIntrinsicWidth(size);
+ shape.getPaint().setColor(color);
+ return shape;
+ }
+
+ private String mapLengthValue(Integer time) {
+ if (!mOnOffChangeable) {
+ return getContext().getResources().getString(R.string.pulse_length_always_on);
+ }
+ if (time == DEFAULT_TIME) {
+ return getContext().getResources().getString(R.string.default_time);
+ }
+
+ String[] timeNames = getContext().getResources().getStringArray(R.array.notification_pulse_length_entries);
+ String[] timeValues = getContext().getResources().getStringArray(R.array.notification_pulse_length_values);
+
+ for (int i = 0; i < timeValues.length; i++) {
+ if (Integer.decode(timeValues[i]).equals(time)) {
+ return timeNames[i];
+ }
+ }
+
+ return getContext().getResources().getString(R.string.custom_time);
+ }
+
+ private String mapSpeedValue(Integer time) {
+ if (time == DEFAULT_TIME) {
+ return getContext().getResources().getString(R.string.default_time);
+ }
+
+ String[] timeNames = getContext().getResources().getStringArray(R.array.notification_pulse_speed_entries);
+ String[] timeValues = getContext().getResources().getStringArray(R.array.notification_pulse_speed_values);
+
+ for (int i = 0; i < timeValues.length; i++) {
+ if (Integer.decode(timeValues[i]).equals(time)) {
+ return timeNames[i];
+ }
+ }
+
+ return getContext().getResources().getString(R.string.custom_time);
+ }
+}
diff --git a/src/org/cyanogenmod/cmparts/notificationlight/BatteryLightSettings.java b/src/org/cyanogenmod/cmparts/notificationlight/BatteryLightSettings.java
new file mode 100644
index 0000000..efd5582
--- /dev/null
+++ b/src/org/cyanogenmod/cmparts/notificationlight/BatteryLightSettings.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2012 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.cmparts.notificationlight;
+
+import android.content.ContentResolver;
+import android.content.res.Resources;
+import android.os.Bundle;
+import android.provider.Settings;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.PreferenceGroup;
+import android.support.v7.preference.PreferenceScreen;
+import android.support.v14.preference.PreferenceFragment;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+
+import org.cyanogenmod.cmparts.R;
+
+import org.cyanogenmod.cmparts.SettingsPreferenceFragment;
+
+import cyanogenmod.preference.CMSystemSettingSwitchPreference;
+import cyanogenmod.providers.CMSettings;
+
+public class BatteryLightSettings extends SettingsPreferenceFragment implements
+ Preference.OnPreferenceChangeListener {
+ private static final String TAG = "BatteryLightSettings";
+
+ private static final String LOW_COLOR_PREF = "low_color";
+ private static final String MEDIUM_COLOR_PREF = "medium_color";
+ private static final String FULL_COLOR_PREF = "full_color";
+ private static final String LIGHT_ENABLED_PREF = "battery_light_enabled";
+ private static final String PULSE_ENABLED_PREF = "battery_light_pulse";
+
+ private PreferenceGroup mColorPrefs;
+ private ApplicationLightPreference mLowColorPref;
+ private ApplicationLightPreference mMediumColorPref;
+ private ApplicationLightPreference mFullColorPref;
+ private CMSystemSettingSwitchPreference mLightEnabledPref;
+ private CMSystemSettingSwitchPreference mPulseEnabledPref;
+
+ private static final int MENU_RESET = Menu.FIRST;
+
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+
+
+ addPreferencesFromResource(R.xml.battery_light_settings);
+ getActivity().getActionBar().setTitle(
+ org.cyanogenmod.platform.internal.R.string.battery_light_title);
+
+ PreferenceScreen prefSet = getPreferenceScreen();
+
+ PreferenceGroup mGeneralPrefs = (PreferenceGroup) prefSet.findPreference("general_section");
+
+ mLightEnabledPref = (CMSystemSettingSwitchPreference) prefSet.findPreference(LIGHT_ENABLED_PREF);
+ mPulseEnabledPref = (CMSystemSettingSwitchPreference) prefSet.findPreference(PULSE_ENABLED_PREF);
+
+ if (!getResources().getBoolean(com.android.internal.R.bool.config_ledCanPulse) ||
+ getResources().getBoolean(org.cyanogenmod.platform.internal.R.bool.config_useSegmentedBatteryLed)) {
+ mGeneralPrefs.removePreference(mPulseEnabledPref);
+ }
+
+ // Does the Device support changing battery LED colors?
+ if (getResources().getBoolean(com.android.internal.R.bool.config_multiColorBatteryLed)) {
+ setHasOptionsMenu(true);
+
+ // Low, Medium and full color preferences
+ mLowColorPref = (ApplicationLightPreference) prefSet.findPreference(LOW_COLOR_PREF);
+ mLowColorPref.setOnPreferenceChangeListener(this);
+
+ mMediumColorPref = (ApplicationLightPreference) prefSet.findPreference(MEDIUM_COLOR_PREF);
+ mMediumColorPref.setOnPreferenceChangeListener(this);
+
+ mFullColorPref = (ApplicationLightPreference) prefSet.findPreference(FULL_COLOR_PREF);
+ mFullColorPref.setOnPreferenceChangeListener(this);
+ } else {
+ prefSet.removePreference(prefSet.findPreference("colors_list"));
+ resetColors();
+ }
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ refreshDefault();
+ }
+
+ private void refreshDefault() {
+ ContentResolver resolver = getActivity().getContentResolver();
+ Resources res = getResources();
+
+ if (mLowColorPref != null) {
+ int lowColor = CMSettings.System.getInt(resolver, CMSettings.System.BATTERY_LIGHT_LOW_COLOR,
+ res.getInteger(com.android.internal.R.integer.config_notificationsBatteryLowARGB));
+ mLowColorPref.setAllValues(lowColor, 0, 0, false);
+ }
+
+ if (mMediumColorPref != null) {
+ int mediumColor = CMSettings.System.getInt(resolver, CMSettings.System.BATTERY_LIGHT_MEDIUM_COLOR,
+ res.getInteger(com.android.internal.R.integer.config_notificationsBatteryMediumARGB));
+ mMediumColorPref.setAllValues(mediumColor, 0, 0, false);
+ }
+
+ if (mFullColorPref != null) {
+ int fullColor = CMSettings.System.getInt(resolver, CMSettings.System.BATTERY_LIGHT_FULL_COLOR,
+ res.getInteger(com.android.internal.R.integer.config_notificationsBatteryFullARGB));
+ mFullColorPref.setAllValues(fullColor, 0, 0, false);
+ }
+ }
+
+ /**
+ * Updates the default or application specific notification settings.
+ *
+ * @param key of the specific setting to update
+ * @param color
+ */
+ protected void updateValues(String key, Integer color) {
+ ContentResolver resolver = getActivity().getContentResolver();
+
+ if (key.equals(LOW_COLOR_PREF)) {
+ CMSettings.System.putInt(resolver, CMSettings.System.BATTERY_LIGHT_LOW_COLOR, color);
+ } else if (key.equals(MEDIUM_COLOR_PREF)) {
+ CMSettings.System.putInt(resolver, CMSettings.System.BATTERY_LIGHT_MEDIUM_COLOR, color);
+ } else if (key.equals(FULL_COLOR_PREF)) {
+ CMSettings.System.putInt(resolver, CMSettings.System.BATTERY_LIGHT_FULL_COLOR, color);
+ }
+ }
+
+ @Override
+ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+ menu.add(0, MENU_RESET, 0, R.string.reset)
+ .setIcon(R.drawable.ic_settings_backup_restore)
+ .setAlphabeticShortcut('r')
+ .setShowAsActionFlags(MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case MENU_RESET:
+ resetToDefaults();
+ return true;
+ }
+ return false;
+ }
+
+ protected void resetColors() {
+ ContentResolver resolver = getActivity().getContentResolver();
+ Resources res = getResources();
+
+ // Reset to the framework default colors
+ CMSettings.System.putInt(resolver, CMSettings.System.BATTERY_LIGHT_LOW_COLOR,
+ res.getInteger(com.android.internal.R.integer.config_notificationsBatteryLowARGB));
+ CMSettings.System.putInt(resolver, CMSettings.System.BATTERY_LIGHT_MEDIUM_COLOR,
+ res.getInteger(com.android.internal.R.integer.config_notificationsBatteryMediumARGB));
+ CMSettings.System.putInt(resolver, CMSettings.System.BATTERY_LIGHT_FULL_COLOR,
+ res.getInteger(com.android.internal.R.integer.config_notificationsBatteryFullARGB));
+ refreshDefault();
+ }
+
+ protected void resetToDefaults() {
+ final Resources res = getResources();
+ final boolean batteryLightEnabled = res.getBoolean(R.bool.def_battery_light_enabled);
+ final boolean batteryLightPulseEnabled = res.getBoolean(R.bool.def_battery_light_pulse);
+
+ if (mLightEnabledPref != null) mLightEnabledPref.setChecked(batteryLightEnabled);
+ if (mPulseEnabledPref != null) mPulseEnabledPref.setChecked(batteryLightPulseEnabled);
+
+ resetColors();
+ }
+
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object objValue) {
+ ApplicationLightPreference lightPref = (ApplicationLightPreference) preference;
+ updateValues(lightPref.getKey(), lightPref.getColor());
+
+ return true;
+ }
+}
diff --git a/src/org/cyanogenmod/cmparts/notificationlight/ColorPanelView.java b/src/org/cyanogenmod/cmparts/notificationlight/ColorPanelView.java
new file mode 100644
index 0000000..b2a24d5
--- /dev/null
+++ b/src/org/cyanogenmod/cmparts/notificationlight/ColorPanelView.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2010 Daniel Nilsson
+ * Copyright (C) 2012 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.cmparts.notificationlight;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.RectF;
+import android.util.AttributeSet;
+import android.view.View;
+
+/**
+ * This class draws a panel which which will be filled with a color which can be
+ * set. It can be used to show the currently selected color which you will get
+ * from the {@link ColorPickerView}.
+ *
+ * @author Daniel Nilsson
+ */
+public class ColorPanelView extends View {
+
+ /**
+ * The width in pixels of the border surrounding the color panel.
+ */
+ private final static float BORDER_WIDTH_PX = 1;
+
+ private static float mDensity = 1f;
+
+ private int mBorderColor = 0xff6E6E6E;
+ private int mColor = 0xff000000;
+
+ private Paint mBorderPaint;
+ private Paint mColorPaint;
+
+ private RectF mDrawingRect;
+ private RectF mColorRect;
+
+ private AlphaPatternDrawable mAlphaPattern;
+
+ public ColorPanelView(Context context) {
+ this(context, null);
+ }
+
+ public ColorPanelView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public ColorPanelView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+
+ init();
+ }
+
+ private void init() {
+ mBorderPaint = new Paint();
+ mColorPaint = new Paint();
+ mDensity = getContext().getResources().getDisplayMetrics().density;
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+
+ final RectF rect = mColorRect;
+
+ if (BORDER_WIDTH_PX > 0) {
+ mBorderPaint.setColor(mBorderColor);
+ canvas.drawRect(mDrawingRect, mBorderPaint);
+ }
+
+ if (mAlphaPattern != null) {
+ mAlphaPattern.draw(canvas);
+ }
+
+ mColorPaint.setColor(mColor);
+
+ canvas.drawRect(rect, mColorPaint);
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+
+ int width = MeasureSpec.getSize(widthMeasureSpec);
+ int height = MeasureSpec.getSize(heightMeasureSpec);
+
+ setMeasuredDimension(width, height);
+ }
+
+ @Override
+ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+ super.onSizeChanged(w, h, oldw, oldh);
+
+ mDrawingRect = new RectF();
+ mDrawingRect.left = getPaddingLeft();
+ mDrawingRect.right = w - getPaddingRight();
+ mDrawingRect.top = getPaddingTop();
+ mDrawingRect.bottom = h - getPaddingBottom();
+
+ setUpColorRect();
+
+ }
+
+ private void setUpColorRect() {
+ final RectF dRect = mDrawingRect;
+
+ float left = dRect.left + BORDER_WIDTH_PX;
+ float top = dRect.top + BORDER_WIDTH_PX;
+ float bottom = dRect.bottom - BORDER_WIDTH_PX;
+ float right = dRect.right - BORDER_WIDTH_PX;
+
+ mColorRect = new RectF(left, top, right, bottom);
+
+ mAlphaPattern = new AlphaPatternDrawable((int) (5 * mDensity));
+
+ mAlphaPattern.setBounds(Math.round(mColorRect.left),
+ Math.round(mColorRect.top),
+ Math.round(mColorRect.right),
+ Math.round(mColorRect.bottom));
+
+ }
+
+ /**
+ * Set the color that should be shown by this view.
+ *
+ * @param color
+ */
+ public void setColor(int color) {
+ mColor = color;
+ invalidate();
+ }
+
+ /**
+ * Get the color currently show by this view.
+ *
+ * @return
+ */
+ public int getColor() {
+ return mColor;
+ }
+
+ /**
+ * Set the color of the border surrounding the panel.
+ *
+ * @param color
+ */
+ public void setBorderColor(int color) {
+ mBorderColor = color;
+ invalidate();
+ }
+
+ /**
+ * Get the color of the border surrounding the panel.
+ */
+ public int getBorderColor() {
+ return mBorderColor;
+ }
+
+}
diff --git a/src/org/cyanogenmod/cmparts/notificationlight/ColorPickerView.java b/src/org/cyanogenmod/cmparts/notificationlight/ColorPickerView.java
new file mode 100644
index 0000000..a3a06e3
--- /dev/null
+++ b/src/org/cyanogenmod/cmparts/notificationlight/ColorPickerView.java
@@ -0,0 +1,841 @@
+/*
+ * Copyright (C) 2010 Daniel Nilsson
+ * Copyright (C) 2012 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.cmparts.notificationlight;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.ComposeShader;
+import android.graphics.LinearGradient;
+import android.graphics.Paint;
+import android.graphics.Point;
+import android.graphics.PorterDuff.Mode;
+import android.graphics.RectF;
+import android.graphics.Shader;
+import android.graphics.Paint.Align;
+import android.graphics.Paint.Style;
+import android.graphics.Shader.TileMode;
+import android.os.Build;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+
+/**
+ * Displays a color picker to the user and allow them to select a color. A
+ * slider for the alpha channel is also available. Enable it by setting
+ * setAlphaSliderVisible(boolean) to true.
+ *
+ * @author Daniel Nilsson
+ */
+public class ColorPickerView extends View {
+
+ public interface OnColorChangedListener {
+ public void onColorChanged(int color);
+ }
+
+ private final static int PANEL_SAT_VAL = 0;
+ private final static int PANEL_HUE = 1;
+ private final static int PANEL_ALPHA = 2;
+
+ /**
+ * The width in pixels of the border surrounding all color panels.
+ */
+ private final static float BORDER_WIDTH_PX = 1;
+
+ /**
+ * The width in dp of the hue panel.
+ */
+ private float HUE_PANEL_WIDTH = 30f;
+ /**
+ * The height in dp of the alpha panel
+ */
+ private float ALPHA_PANEL_HEIGHT = 20f;
+ /**
+ * The distance in dp between the different color panels.
+ */
+ private float PANEL_SPACING = 10f;
+ /**
+ * The radius in dp of the color palette tracker circle.
+ */
+ private float PALETTE_CIRCLE_TRACKER_RADIUS = 5f;
+ /**
+ * The dp which the tracker of the hue or alpha panel will extend outside of
+ * its bounds.
+ */
+ private float RECTANGLE_TRACKER_OFFSET = 2f;
+
+ private static float mDensity = 1f;
+
+ private OnColorChangedListener mListener;
+
+ private Paint mSatValPaint;
+ private Paint mSatValTrackerPaint;
+
+ private Paint mHuePaint;
+ private Paint mHueTrackerPaint;
+
+ private Paint mAlphaPaint;
+ private Paint mAlphaTextPaint;
+
+ private Paint mBorderPaint;
+
+ private Shader mValShader;
+ private Shader mSatShader;
+ private Shader mHueShader;
+ private Shader mAlphaShader;
+
+ private int mAlpha = 0xff;
+ private float mHue = 360f;
+ private float mSat = 0f;
+ private float mVal = 0f;
+
+ private String mAlphaSliderText = "Alpha";
+ private int mSliderTrackerColor = 0xff1c1c1c;
+ private int mBorderColor = 0xff6E6E6E;
+ private boolean mShowAlphaPanel = false;
+
+ /*
+ * To remember which panel that has the "focus" when processing hardware
+ * button data.
+ */
+ private int mLastTouchedPanel = PANEL_SAT_VAL;
+
+ /**
+ * Offset from the edge we must have or else the finger tracker will get
+ * clipped when it is drawn outside of the view.
+ */
+ private float mDrawingOffset;
+
+ /*
+ * Distance form the edges of the view of where we are allowed to draw.
+ */
+ private RectF mDrawingRect;
+
+ private RectF mSatValRect;
+ private RectF mHueRect;
+ private RectF mAlphaRect;
+
+ private AlphaPatternDrawable mAlphaPattern;
+
+ private Point mStartTouchPoint = null;
+
+ public ColorPickerView(Context context) {
+ this(context, null);
+ }
+
+ public ColorPickerView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public ColorPickerView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ init();
+ }
+
+ private void init() {
+ mDensity = getContext().getResources().getDisplayMetrics().density;
+ PALETTE_CIRCLE_TRACKER_RADIUS *= mDensity;
+ RECTANGLE_TRACKER_OFFSET *= mDensity;
+ HUE_PANEL_WIDTH *= mDensity;
+ ALPHA_PANEL_HEIGHT *= mDensity;
+ PANEL_SPACING = PANEL_SPACING * mDensity;
+
+ mDrawingOffset = calculateRequiredOffset();
+ initPaintTools();
+
+ // Needed for receiving track ball motion events.
+ setFocusableInTouchMode(true);
+ setFocusable(true);
+ setClickable(true);
+ }
+
+ private void initPaintTools() {
+ mSatValPaint = new Paint();
+ mSatValTrackerPaint = new Paint();
+ mHuePaint = new Paint();
+ mHueTrackerPaint = new Paint();
+ mAlphaPaint = new Paint();
+ mAlphaTextPaint = new Paint();
+ mBorderPaint = new Paint();
+
+ mSatValTrackerPaint.setStyle(Style.STROKE);
+ mSatValTrackerPaint.setStrokeWidth(2f * mDensity);
+ mSatValTrackerPaint.setAntiAlias(true);
+
+ mHueTrackerPaint.setColor(mSliderTrackerColor);
+ mHueTrackerPaint.setStyle(Style.STROKE);
+ mHueTrackerPaint.setStrokeWidth(2f * mDensity);
+ mHueTrackerPaint.setAntiAlias(true);
+
+ mAlphaTextPaint.setColor(0xff1c1c1c);
+ mAlphaTextPaint.setTextSize(14f * mDensity);
+ mAlphaTextPaint.setAntiAlias(true);
+ mAlphaTextPaint.setTextAlign(Align.CENTER);
+ mAlphaTextPaint.setFakeBoldText(true);
+ }
+
+ private float calculateRequiredOffset() {
+ float offset = Math.max(PALETTE_CIRCLE_TRACKER_RADIUS, RECTANGLE_TRACKER_OFFSET);
+ offset = Math.max(offset, BORDER_WIDTH_PX * mDensity);
+
+ return offset * 1.5f;
+ }
+
+ private int[] buildHueColorArray() {
+ int[] hue = new int[361];
+
+ int count = 0;
+ for (int i = hue.length - 1; i >= 0; i--, count++) {
+ hue[count] = Color.HSVToColor(new float[] {
+ i, 1f, 1f
+ });
+ }
+ return hue;
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ if (mDrawingRect.width() <= 0 || mDrawingRect.height() <= 0) {
+ return;
+ }
+ drawSatValPanel(canvas);
+ drawHuePanel(canvas);
+ drawAlphaPanel(canvas);
+ }
+
+ private void drawSatValPanel(Canvas canvas) {
+ final RectF rect = mSatValRect;
+ int rgb = Color.HSVToColor(new float[] {
+ mHue, 1f, 1f
+ });
+
+ if (BORDER_WIDTH_PX > 0) {
+ mBorderPaint.setColor(mBorderColor);
+ canvas.drawRect(mDrawingRect.left, mDrawingRect.top, rect.right + BORDER_WIDTH_PX,
+ rect.bottom + BORDER_WIDTH_PX, mBorderPaint);
+ }
+
+ // On Honeycomb+ we need to use software rendering to create the shader properly
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
+ setLayerType(View.LAYER_TYPE_SOFTWARE, null);
+ }
+
+ // Get the overlaying gradients ready and create the ComposeShader
+ if (mValShader == null) {
+ mValShader = new LinearGradient(rect.left, rect.top, rect.left, rect.bottom,
+ 0xffffffff, 0xff000000, TileMode.CLAMP);
+ }
+ mSatShader = new LinearGradient(rect.left, rect.top, rect.right, rect.top,
+ 0xffffffff, rgb, TileMode.CLAMP);
+ ComposeShader mShader = new ComposeShader(mValShader, mSatShader, Mode.MULTIPLY);
+ mSatValPaint.setShader(mShader);
+ canvas.drawRect(rect, mSatValPaint);
+
+ Point p = satValToPoint(mSat, mVal);
+ mSatValTrackerPaint.setColor(0xff000000);
+ canvas.drawCircle(p.x, p.y, PALETTE_CIRCLE_TRACKER_RADIUS - 1f * mDensity,
+ mSatValTrackerPaint);
+
+ mSatValTrackerPaint.setColor(0xffdddddd);
+ canvas.drawCircle(p.x, p.y, PALETTE_CIRCLE_TRACKER_RADIUS, mSatValTrackerPaint);
+ }
+
+ private void drawHuePanel(Canvas canvas) {
+ final RectF rect = mHueRect;
+
+ if (BORDER_WIDTH_PX > 0) {
+ mBorderPaint.setColor(mBorderColor);
+ canvas.drawRect(rect.left - BORDER_WIDTH_PX,
+ rect.top - BORDER_WIDTH_PX,
+ rect.right + BORDER_WIDTH_PX,
+ rect.bottom + BORDER_WIDTH_PX,
+ mBorderPaint);
+ }
+
+ if (mHueShader == null) {
+ mHueShader = new LinearGradient(rect.left, rect.top, rect.left, rect.bottom,
+ buildHueColorArray(), null, TileMode.CLAMP);
+ mHuePaint.setShader(mHueShader);
+ }
+
+ canvas.drawRect(rect, mHuePaint);
+
+ float rectHeight = 4 * mDensity / 2;
+
+ Point p = hueToPoint(mHue);
+
+ RectF r = new RectF();
+ r.left = rect.left - RECTANGLE_TRACKER_OFFSET;
+ r.right = rect.right + RECTANGLE_TRACKER_OFFSET;
+ r.top = p.y - rectHeight;
+ r.bottom = p.y + rectHeight;
+
+ canvas.drawRoundRect(r, 2, 2, mHueTrackerPaint);
+
+ }
+
+ private void drawAlphaPanel(Canvas canvas) {
+ if (!mShowAlphaPanel || mAlphaRect == null || mAlphaPattern == null) {
+ return;
+ }
+
+ final RectF rect = mAlphaRect;
+
+ if (BORDER_WIDTH_PX > 0) {
+ mBorderPaint.setColor(mBorderColor);
+ canvas.drawRect(rect.left - BORDER_WIDTH_PX,
+ rect.top - BORDER_WIDTH_PX,
+ rect.right + BORDER_WIDTH_PX,
+ rect.bottom + BORDER_WIDTH_PX,
+ mBorderPaint);
+ }
+
+ mAlphaPattern.draw(canvas);
+
+ float[] hsv = new float[] {
+ mHue, mSat, mVal
+ };
+ int color = Color.HSVToColor(hsv);
+ int acolor = Color.HSVToColor(0, hsv);
+
+ mAlphaShader = new LinearGradient(rect.left, rect.top, rect.right, rect.top,
+ color, acolor, TileMode.CLAMP);
+
+ mAlphaPaint.setShader(mAlphaShader);
+
+ canvas.drawRect(rect, mAlphaPaint);
+
+ if (mAlphaSliderText != null && mAlphaSliderText != "") {
+ canvas.drawText(mAlphaSliderText, rect.centerX(), rect.centerY() + 4 * mDensity,
+ mAlphaTextPaint);
+ }
+
+ float rectWidth = 4 * mDensity / 2;
+ Point p = alphaToPoint(mAlpha);
+
+ RectF r = new RectF();
+ r.left = p.x - rectWidth;
+ r.right = p.x + rectWidth;
+ r.top = rect.top - RECTANGLE_TRACKER_OFFSET;
+ r.bottom = rect.bottom + RECTANGLE_TRACKER_OFFSET;
+
+ canvas.drawRoundRect(r, 2, 2, mHueTrackerPaint);
+ }
+
+ private Point hueToPoint(float hue) {
+ final RectF rect = mHueRect;
+ final float height = rect.height();
+
+ Point p = new Point();
+ p.y = (int) (height - (hue * height / 360f) + rect.top);
+ p.x = (int) rect.left;
+ return p;
+ }
+
+ private Point satValToPoint(float sat, float val) {
+
+ final RectF rect = mSatValRect;
+ final float height = rect.height();
+ final float width = rect.width();
+
+ Point p = new Point();
+
+ p.x = (int) (sat * width + rect.left);
+ p.y = (int) ((1f - val) * height + rect.top);
+
+ return p;
+ }
+
+ private Point alphaToPoint(int alpha) {
+ final RectF rect = mAlphaRect;
+ final float width = rect.width();
+
+ Point p = new Point();
+ p.x = (int) (width - (alpha * width / 0xff) + rect.left);
+ p.y = (int) rect.top;
+ return p;
+ }
+
+ private float[] pointToSatVal(float x, float y) {
+ final RectF rect = mSatValRect;
+ float[] result = new float[2];
+ float width = rect.width();
+ float height = rect.height();
+
+ if (x < rect.left) {
+ x = 0f;
+ } else if (x > rect.right) {
+ x = width;
+ } else {
+ x = x - rect.left;
+ }
+
+ if (y < rect.top) {
+ y = 0f;
+ } else if (y > rect.bottom) {
+ y = height;
+ } else {
+ y = y - rect.top;
+ }
+
+ result[0] = 1.f / width * x;
+ result[1] = 1.f - (1.f / height * y);
+ return result;
+ }
+
+ private float pointToHue(float y) {
+ final RectF rect = mHueRect;
+ float height = rect.height();
+
+ if (y < rect.top) {
+ y = 0f;
+ } else if (y > rect.bottom) {
+ y = height;
+ } else {
+ y = y - rect.top;
+ }
+ return 360f - (y * 360f / height);
+ }
+
+ private int pointToAlpha(int x) {
+ final RectF rect = mAlphaRect;
+ final int width = (int) rect.width();
+
+ if (x < rect.left) {
+ x = 0;
+ } else if (x > rect.right) {
+ x = width;
+ } else {
+ x = x - (int) rect.left;
+ }
+ return 0xff - (x * 0xff / width);
+ }
+
+ @Override
+ public boolean onTrackballEvent(MotionEvent event) {
+ float x = event.getX();
+ float y = event.getY();
+ boolean update = false;
+
+ if (event.getAction() == MotionEvent.ACTION_MOVE) {
+ switch (mLastTouchedPanel) {
+ case PANEL_SAT_VAL:
+ float sat,
+ val;
+ sat = mSat + x / 50f;
+ val = mVal - y / 50f;
+ if (sat < 0f) {
+ sat = 0f;
+ } else if (sat > 1f) {
+ sat = 1f;
+ }
+
+ if (val < 0f) {
+ val = 0f;
+ } else if (val > 1f) {
+ val = 1f;
+ }
+ mSat = sat;
+ mVal = val;
+ update = true;
+ break;
+ case PANEL_HUE:
+ float hue = mHue - y * 10f;
+ if (hue < 0f) {
+ hue = 0f;
+ } else if (hue > 360f) {
+ hue = 360f;
+ }
+ mHue = hue;
+ update = true;
+ break;
+ case PANEL_ALPHA:
+ if (!mShowAlphaPanel || mAlphaRect == null) {
+ update = false;
+ } else {
+ int alpha = (int) (mAlpha - x * 10);
+ if (alpha < 0) {
+ alpha = 0;
+ } else if (alpha > 0xff) {
+ alpha = 0xff;
+ }
+ mAlpha = alpha;
+ update = true;
+ }
+ break;
+ }
+ }
+
+ if (update) {
+ if (mListener != null) {
+ mListener.onColorChanged(Color.HSVToColor(mAlpha, new float[] {
+ mHue, mSat, mVal
+ }));
+ }
+ invalidate();
+ return true;
+ }
+ return super.onTrackballEvent(event);
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ boolean update = false;
+
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ mStartTouchPoint = new Point((int) event.getX(), (int) event.getY());
+ update = moveTrackersIfNeeded(event);
+ break;
+ case MotionEvent.ACTION_MOVE:
+ update = moveTrackersIfNeeded(event);
+ break;
+ case MotionEvent.ACTION_UP:
+ mStartTouchPoint = null;
+ update = moveTrackersIfNeeded(event);
+ break;
+ }
+
+ if (update) {
+ requestFocus();
+ if (mListener != null) {
+ mListener.onColorChanged(Color.HSVToColor(mAlpha, new float[] {
+ mHue, mSat, mVal
+ }));
+ }
+ invalidate();
+ return true;
+ }
+
+ return super.onTouchEvent(event);
+ }
+
+ private boolean moveTrackersIfNeeded(MotionEvent event) {
+
+ if (mStartTouchPoint == null)
+ return false;
+
+ boolean update = false;
+ int startX = mStartTouchPoint.x;
+ int startY = mStartTouchPoint.y;
+
+ if (mHueRect.contains(startX, startY)) {
+ mLastTouchedPanel = PANEL_HUE;
+ mHue = pointToHue(event.getY());
+ update = true;
+ } else if (mSatValRect.contains(startX, startY)) {
+ mLastTouchedPanel = PANEL_SAT_VAL;
+ float[] result = pointToSatVal(event.getX(), event.getY());
+ mSat = result[0];
+ mVal = result[1];
+ update = true;
+ } else if (mAlphaRect != null && mAlphaRect.contains(startX, startY)) {
+ mLastTouchedPanel = PANEL_ALPHA;
+ mAlpha = pointToAlpha((int) event.getX());
+ update = true;
+ }
+
+ return update;
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ int width = 0;
+ int height = 0;
+
+ int widthMode = MeasureSpec.getMode(widthMeasureSpec);
+ int heightMode = MeasureSpec.getMode(heightMeasureSpec);
+
+ int widthAllowed = MeasureSpec.getSize(widthMeasureSpec);
+ int heightAllowed = MeasureSpec.getSize(heightMeasureSpec);
+
+ widthAllowed = chooseWidth(widthMode, widthAllowed);
+ heightAllowed = chooseHeight(heightMode, heightAllowed);
+
+ if (!mShowAlphaPanel) {
+ height = (int) (widthAllowed - PANEL_SPACING - HUE_PANEL_WIDTH);
+
+ // If calculated height (based on the width) is more than the
+ // allowed height.
+ if (height > heightAllowed && heightMode != MeasureSpec.UNSPECIFIED) {
+ height = heightAllowed;
+ width = (int) (height + PANEL_SPACING + HUE_PANEL_WIDTH);
+ } else {
+ width = widthAllowed;
+ }
+ } else {
+
+ width = (int) (heightAllowed - ALPHA_PANEL_HEIGHT + HUE_PANEL_WIDTH);
+
+ if (width > widthAllowed && widthMode != MeasureSpec.UNSPECIFIED) {
+ width = widthAllowed;
+ height = (int) (widthAllowed - HUE_PANEL_WIDTH + ALPHA_PANEL_HEIGHT);
+ } else {
+ height = heightAllowed;
+ }
+ }
+ setMeasuredDimension(width, height);
+ }
+
+ private int chooseWidth(int mode, int size) {
+ if (mode == MeasureSpec.AT_MOST || mode == MeasureSpec.EXACTLY) {
+ return size;
+ } else { // (mode == MeasureSpec.UNSPECIFIED)
+ return getPrefferedWidth();
+ }
+ }
+
+ private int chooseHeight(int mode, int size) {
+ if (mode == MeasureSpec.AT_MOST || mode == MeasureSpec.EXACTLY) {
+ return size;
+ } else { // (mode == MeasureSpec.UNSPECIFIED)
+ return getPrefferedHeight();
+ }
+ }
+
+ private int getPrefferedWidth() {
+ int width = getPrefferedHeight();
+ if (mShowAlphaPanel) {
+ width -= (PANEL_SPACING + ALPHA_PANEL_HEIGHT);
+ }
+ return (int) (width + HUE_PANEL_WIDTH + PANEL_SPACING);
+ }
+
+ private int getPrefferedHeight() {
+ int height = (int) (200 * mDensity);
+ if (mShowAlphaPanel) {
+ height += PANEL_SPACING + ALPHA_PANEL_HEIGHT;
+ }
+ return height;
+ }
+
+ @Override
+ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+ super.onSizeChanged(w, h, oldw, oldh);
+
+ mDrawingRect = new RectF();
+ mDrawingRect.left = mDrawingOffset + getPaddingLeft();
+ mDrawingRect.right = w - mDrawingOffset - getPaddingRight();
+ mDrawingRect.top = mDrawingOffset + getPaddingTop();
+ mDrawingRect.bottom = h - mDrawingOffset - getPaddingBottom();
+
+ setUpSatValRect();
+ setUpHueRect();
+ setUpAlphaRect();
+ }
+
+ private void setUpSatValRect() {
+ final RectF dRect = mDrawingRect;
+ float panelSide = dRect.height() - BORDER_WIDTH_PX * 2;
+
+ if (mShowAlphaPanel) {
+ panelSide -= PANEL_SPACING + ALPHA_PANEL_HEIGHT;
+ }
+
+ float left = dRect.left + BORDER_WIDTH_PX;
+ float top = dRect.top + BORDER_WIDTH_PX;
+ float bottom = top + panelSide;
+ float right = left + panelSide;
+ mSatValRect = new RectF(left, top, right, bottom);
+ }
+
+ private void setUpHueRect() {
+ final RectF dRect = mDrawingRect;
+
+ float left = dRect.right - HUE_PANEL_WIDTH + BORDER_WIDTH_PX;
+ float top = dRect.top + BORDER_WIDTH_PX;
+ float bottom = dRect.bottom - BORDER_WIDTH_PX
+ - (mShowAlphaPanel ? (PANEL_SPACING + ALPHA_PANEL_HEIGHT) : 0);
+ float right = dRect.right - BORDER_WIDTH_PX;
+
+ mHueRect = new RectF(left, top, right, bottom);
+ }
+
+ private void setUpAlphaRect() {
+ if (!mShowAlphaPanel) {
+ return;
+ }
+
+ final RectF dRect = mDrawingRect;
+ float left = dRect.left + BORDER_WIDTH_PX;
+ float top = dRect.bottom - ALPHA_PANEL_HEIGHT + BORDER_WIDTH_PX;
+ float bottom = dRect.bottom - BORDER_WIDTH_PX;
+ float right = dRect.right - BORDER_WIDTH_PX;
+
+ mAlphaRect = new RectF(left, top, right, bottom);
+ mAlphaPattern = new AlphaPatternDrawable((int) (5 * mDensity));
+ mAlphaPattern.setBounds(Math.round(mAlphaRect.left), Math
+ .round(mAlphaRect.top), Math.round(mAlphaRect.right), Math
+ .round(mAlphaRect.bottom));
+ }
+
+ /**
+ * Set a OnColorChangedListener to get notified when the color selected by
+ * the user has changed.
+ *
+ * @param listener
+ */
+ public void setOnColorChangedListener(OnColorChangedListener listener) {
+ mListener = listener;
+ }
+
+ /**
+ * Set the color of the border surrounding all panels.
+ *
+ * @param color
+ */
+ public void setBorderColor(int color) {
+ mBorderColor = color;
+ invalidate();
+ }
+
+ /**
+ * Get the color of the border surrounding all panels.
+ */
+ public int getBorderColor() {
+ return mBorderColor;
+ }
+
+ /**
+ * Get the current color this view is showing.
+ *
+ * @return the current color.
+ */
+ public int getColor() {
+ return Color.HSVToColor(mAlpha, new float[] {
+ mHue, mSat, mVal
+ });
+ }
+
+ /**
+ * Set the color the view should show.
+ *
+ * @param color The color that should be selected.
+ */
+ public void setColor(int color) {
+ setColor(color, false);
+ }
+
+ /**
+ * Set the color this view should show.
+ *
+ * @param color The color that should be selected.
+ * @param callback If you want to get a callback to your
+ * OnColorChangedListener.
+ */
+ public void setColor(int color, boolean callback) {
+ int alpha = Color.alpha(color);
+ int red = Color.red(color);
+ int blue = Color.blue(color);
+ int green = Color.green(color);
+ float[] hsv = new float[3];
+
+ Color.RGBToHSV(red, green, blue, hsv);
+ mAlpha = alpha;
+ mHue = hsv[0];
+ mSat = hsv[1];
+ mVal = hsv[2];
+
+ if (callback && mListener != null) {
+ mListener.onColorChanged(Color.HSVToColor(mAlpha, new float[] {
+ mHue, mSat, mVal
+ }));
+ }
+ invalidate();
+ }
+
+ /**
+ * Get the drawing offset of the color picker view. The drawing offset is
+ * the distance from the side of a panel to the side of the view minus the
+ * padding. Useful if you want to have your own panel below showing the
+ * currently selected color and want to align it perfectly.
+ *
+ * @return The offset in pixels.
+ */
+ public float getDrawingOffset() {
+ return mDrawingOffset;
+ }
+
+ /**
+ * Set if the user is allowed to adjust the alpha panel. Default is false.
+ * If it is set to false no alpha will be set.
+ *
+ * @param visible
+ */
+ public void setAlphaSliderVisible(boolean visible) {
+ if (mShowAlphaPanel != visible) {
+ mShowAlphaPanel = visible;
+
+ /*
+ * Reset all shader to force a recreation. Otherwise they will not
+ * look right after the size of the view has changed.
+ */
+ mValShader = null;
+ mSatShader = null;
+ mHueShader = null;
+ mAlphaShader = null;
+ requestLayout();
+ }
+
+ }
+
+ public boolean isAlphaSliderVisible() {
+ return mShowAlphaPanel;
+ }
+
+ public void setSliderTrackerColor(int color) {
+ mSliderTrackerColor = color;
+ mHueTrackerPaint.setColor(mSliderTrackerColor);
+ invalidate();
+ }
+
+ public int getSliderTrackerColor() {
+ return mSliderTrackerColor;
+ }
+
+ /**
+ * Set the text that should be shown in the alpha slider. Set to null to
+ * disable text.
+ *
+ * @param res string resource id.
+ */
+ public void setAlphaSliderText(int res) {
+ String text = getContext().getString(res);
+ setAlphaSliderText(text);
+ }
+
+ /**
+ * Set the text that should be shown in the alpha slider. Set to null to
+ * disable text.
+ *
+ * @param text Text that should be shown.
+ */
+ public void setAlphaSliderText(String text) {
+ mAlphaSliderText = text;
+ invalidate();
+ }
+
+ /**
+ * Get the current value of the text that will be shown in the alpha slider.
+ *
+ * @return
+ */
+ public String getAlphaSliderText() {
+ return mAlphaSliderText;
+ }
+}
diff --git a/src/org/cyanogenmod/cmparts/notificationlight/LightSettingsDialog.java b/src/org/cyanogenmod/cmparts/notificationlight/LightSettingsDialog.java
new file mode 100644
index 0000000..7b08124
--- /dev/null
+++ b/src/org/cyanogenmod/cmparts/notificationlight/LightSettingsDialog.java
@@ -0,0 +1,444 @@
+/*
+ * Copyright (C) 2010 Daniel Nilsson
+ * Copyright (C) 2012 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.cmparts.notificationlight;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.content.Context;
+import android.graphics.Color;
+import android.graphics.PixelFormat;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.text.InputFilter;
+import android.text.InputFilter.LengthFilter;
+import android.util.Pair;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnFocusChangeListener;
+import android.view.ViewGroup;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.AdapterView;
+import android.widget.BaseAdapter;
+import android.widget.EditText;
+import android.widget.LinearLayout;
+import android.widget.Spinner;
+import android.widget.SpinnerAdapter;
+import android.widget.TextView;
+
+import org.cyanogenmod.cmparts.R;
+import org.cyanogenmod.cmparts.notificationlight.ColorPickerView.OnColorChangedListener;
+
+import java.util.ArrayList;
+import java.util.IllegalFormatException;
+import java.util.Locale;
+
+public class LightSettingsDialog extends AlertDialog implements
+ ColorPickerView.OnColorChangedListener, TextWatcher, OnFocusChangeListener {
+
+ private final static String STATE_KEY_COLOR = "LightSettingsDialog:color";
+ // Minimum delay between LED notification updates
+ private final static long LED_UPDATE_DELAY_MS = 250;
+
+ private ColorPickerView mColorPicker;
+ private LinearLayout mColorPanel;
+ private View mLightsDialogDivider;
+
+ private EditText mHexColorInput;
+ private ColorPanelView mNewColor;
+ private Spinner mPulseSpeedOn;
+ private Spinner mPulseSpeedOff;
+ private LayoutInflater mInflater;
+
+ private OnColorChangedListener mListener;
+
+ private NotificationManager mNotificationManager;
+
+ private boolean mReadyForLed;
+ private int mLedLastColor;
+ private int mLedLastSpeedOn;
+ private int mLedLastSpeedOff;
+
+ /**
+ * @param context
+ * @param initialColor
+ * @param initialSpeedOn
+ * @param initialSpeedOff
+ */
+ protected LightSettingsDialog(Context context, int initialColor, int initialSpeedOn,
+ int initialSpeedOff) {
+ super(context);
+
+ init(context, initialColor, initialSpeedOn, initialSpeedOff, true);
+ }
+
+ /**
+ * @param context
+ * @param initialColor
+ * @param initialSpeedOn
+ * @param initialSpeedOff
+ * @param onOffChangeable
+ */
+ protected LightSettingsDialog(Context context, int initialColor, int initialSpeedOn,
+ int initialSpeedOff, boolean onOffChangeable) {
+ super(context);
+
+ init(context, initialColor, initialSpeedOn, initialSpeedOff, onOffChangeable);
+ }
+
+ private void init(Context context, int color, int speedOn, int speedOff,
+ boolean onOffChangeable) {
+ mNotificationManager =
+ (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
+
+ mReadyForLed = false;
+ mLedLastColor = 0;
+
+ // To fight color banding.
+ getWindow().setFormat(PixelFormat.RGBA_8888);
+ setUp(color, speedOn, speedOff, onOffChangeable);
+ }
+
+ /**
+ * This function sets up the dialog with the proper values. If the speedOff parameters
+ * has a -1 value disable both spinners
+ *
+ * @param color - the color to set
+ * @param speedOn - the flash time in ms
+ * @param speedOff - the flash length in ms
+ */
+ private void setUp(int color, int speedOn, int speedOff, boolean onOffChangeable) {
+ mInflater = (LayoutInflater) getContext()
+ .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ View layout = mInflater.inflate(R.layout.dialog_light_settings, null);
+
+ mColorPicker = (ColorPickerView) layout.findViewById(R.id.color_picker_view);
+ mColorPanel = (LinearLayout) layout.findViewById(R.id.color_panel_view);
+ mHexColorInput = (EditText) layout.findViewById(R.id.hex_color_input);
+ mNewColor = (ColorPanelView) layout.findViewById(R.id.color_panel);
+ mLightsDialogDivider = (View) layout.findViewById(R.id.lights_dialog_divider);
+ mPulseSpeedOn = (Spinner) layout.findViewById(R.id.on_spinner);
+ mPulseSpeedOff = (Spinner) layout.findViewById(R.id.off_spinner);
+
+ mColorPicker.setOnColorChangedListener(this);
+ mColorPicker.setColor(color, true);
+
+ mHexColorInput.setOnFocusChangeListener(this);
+
+ if (onOffChangeable) {
+ PulseSpeedAdapter pulseSpeedAdapter = new PulseSpeedAdapter(
+ R.array.notification_pulse_length_entries,
+ R.array.notification_pulse_length_values,
+ speedOn);
+ mPulseSpeedOn.setAdapter(pulseSpeedAdapter);
+ mPulseSpeedOn.setSelection(pulseSpeedAdapter.getTimePosition(speedOn));
+ mPulseSpeedOn.setOnItemSelectedListener(mPulseSelectionListener);
+
+ pulseSpeedAdapter = new PulseSpeedAdapter(R.array.notification_pulse_speed_entries,
+ R.array.notification_pulse_speed_values,
+ speedOff);
+ mPulseSpeedOff.setAdapter(pulseSpeedAdapter);
+ mPulseSpeedOff.setSelection(pulseSpeedAdapter.getTimePosition(speedOff));
+ mPulseSpeedOff.setOnItemSelectedListener(mPulseSelectionListener);
+ } else {
+ View speedSettingsGroup = layout.findViewById(R.id.speed_title_view);
+ speedSettingsGroup.setVisibility(View.GONE);
+ }
+
+ mPulseSpeedOn.setEnabled(onOffChangeable);
+ mPulseSpeedOff.setEnabled((speedOn != 1) && onOffChangeable);
+
+ setView(layout);
+ setTitle(R.string.edit_light_settings);
+
+ if (!getContext().getResources().getBoolean(
+ com.android.internal.R.bool.config_multiColorNotificationLed)) {
+ mColorPicker.setVisibility(View.GONE);
+ mColorPanel.setVisibility(View.GONE);
+ mLightsDialogDivider.setVisibility(View.GONE);
+ }
+
+ mReadyForLed = true;
+ updateLed();
+ }
+
+ private AdapterView.OnItemSelectedListener mPulseSelectionListener =
+ new AdapterView.OnItemSelectedListener() {
+ @Override
+ public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
+ if (parent == mPulseSpeedOn) {
+ mPulseSpeedOff.setEnabled(mPulseSpeedOn.isEnabled() && getPulseSpeedOn() != 1);
+ }
+ updateLed();
+ }
+
+ @Override
+ public void onNothingSelected(AdapterView<?> parent) {
+ }
+ };
+
+ @Override
+ public Bundle onSaveInstanceState() {
+ Bundle state = super.onSaveInstanceState();
+ state.putInt(STATE_KEY_COLOR, getColor());
+ return state;
+ }
+
+ @Override
+ public void onRestoreInstanceState(Bundle state) {
+ super.onRestoreInstanceState(state);
+ mColorPicker.setColor(state.getInt(STATE_KEY_COLOR), true);
+ }
+
+ @Override
+ public void onStop() {
+ super.onStop();
+ dismissLed();
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ updateLed();
+ }
+
+ @Override
+ public void onColorChanged(int color) {
+ final boolean hasAlpha = mColorPicker.isAlphaSliderVisible();
+ final String format = hasAlpha ? "%08x" : "%06x";
+ final int mask = hasAlpha ? 0xFFFFFFFF : 0x00FFFFFF;
+
+ mNewColor.setColor(color);
+ mHexColorInput.setText(String.format(Locale.US, format, color & mask));
+
+ if (mListener != null) {
+ mListener.onColorChanged(color);
+ }
+
+ updateLed();
+ }
+
+ public void setAlphaSliderVisible(boolean visible) {
+ mHexColorInput.setFilters(new InputFilter[] { new InputFilter.LengthFilter(visible ? 8 : 6) } );
+ mColorPicker.setAlphaSliderVisible(visible);
+ }
+
+ public int getColor() {
+ return mColorPicker.getColor();
+ }
+
+ @SuppressWarnings("unchecked")
+ public int getPulseSpeedOn() {
+ if (mPulseSpeedOn.isEnabled()) {
+ return ((Pair<String, Integer>) mPulseSpeedOn.getSelectedItem()).second;
+ } else {
+ return 1;
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ public int getPulseSpeedOff() {
+ // return 0 if 'Always on' is selected
+ return getPulseSpeedOn() == 1 ? 0 : ((Pair<String, Integer>) mPulseSpeedOff.getSelectedItem()).second;
+ }
+
+ private Handler mLedHandler = new Handler() {
+ public void handleMessage(Message msg) {
+ updateLed();
+ }
+ };
+
+ private void updateLed() {
+ if (!mReadyForLed) {
+ return;
+ }
+
+ final int color = getColor() & 0xFFFFFF;
+ final int speedOn, speedOff;
+ if (mPulseSpeedOn.isEnabled()) {
+ speedOn = getPulseSpeedOn();
+ speedOff = getPulseSpeedOff();
+ } else {
+ speedOn = 1;
+ speedOff = 0;
+ }
+
+ if (mLedLastColor == color && mLedLastSpeedOn == speedOn
+ && mLedLastSpeedOff == speedOff) {
+ return;
+ }
+
+ // Dampen rate of consecutive LED changes
+ if (mLedHandler.hasMessages(0)) {
+ return;
+ }
+ mLedHandler.sendEmptyMessageDelayed(0, LED_UPDATE_DELAY_MS);
+
+ mLedLastColor = color;
+ mLedLastSpeedOn = speedOn;
+ mLedLastSpeedOff = speedOff;
+
+ final Bundle b = new Bundle();
+ b.putBoolean(Notification.EXTRA_FORCE_SHOW_LIGHTS, true);
+
+ final Notification.Builder builder = new Notification.Builder(getContext());
+ builder.setLights(color, speedOn, speedOff);
+ builder.setExtras(b);
+
+ // Set a notification
+ builder.setSmallIcon(R.drawable.ic_settings_24dp);
+ builder.setContentTitle(getContext().getString(R.string.led_notification_title));
+ builder.setContentText(getContext().getString(R.string.led_notification_text));
+ builder.setOngoing(true);
+
+ mNotificationManager.notify(1, builder.build());
+ }
+
+ public void dismissLed() {
+ mNotificationManager.cancel(1);
+ // ensure we later reset LED if dialog is
+ // hidden and then made visible
+ mLedLastColor = 0;
+ }
+
+ class PulseSpeedAdapter extends BaseAdapter implements SpinnerAdapter {
+ private ArrayList<Pair<String, Integer>> times;
+
+ public PulseSpeedAdapter(int timeNamesResource, int timeValuesResource) {
+ times = new ArrayList<Pair<String, Integer>>();
+
+ String[] time_names = getContext().getResources().getStringArray(timeNamesResource);
+ String[] time_values = getContext().getResources().getStringArray(timeValuesResource);
+
+ for(int i = 0; i < time_values.length; ++i) {
+ times.add(new Pair<String, Integer>(time_names[i], Integer.decode(time_values[i])));
+ }
+
+ }
+
+ /**
+ * This constructor apart from taking a usual time entry array takes the
+ * currently configured time value which might cause the addition of a
+ * "Custom" time entry in the spinner in case this time value does not
+ * match any of the predefined ones in the array.
+ *
+ * @param timeNamesResource The time entry names array
+ * @param timeValuesResource The time entry values array
+ * @param customTime Current time value that might be one of the
+ * predefined values or a totally custom value
+ */
+ public PulseSpeedAdapter(int timeNamesResource, int timeValuesResource, Integer customTime) {
+ this(timeNamesResource, timeValuesResource);
+
+ // Check if we also need to add the custom value entry
+ if (getTimePosition(customTime) == -1) {
+ times.add(new Pair<String, Integer>(getContext().getResources()
+ .getString(R.string.custom_time), customTime));
+ }
+ }
+
+ /**
+ * Will return the position of the spinner entry with the specified
+ * time. Returns -1 if there is no such entry.
+ *
+ * @param time Time in ms
+ * @return Position of entry with given time or -1 if not found.
+ */
+ public int getTimePosition(Integer time) {
+ for (int position = 0; position < getCount(); ++position) {
+ if (getItem(position).second.equals(time)) {
+ return position;
+ }
+ }
+
+ return -1;
+ }
+
+ @Override
+ public int getCount() {
+ return times.size();
+ }
+
+ @Override
+ public Pair<String, Integer> getItem(int position) {
+ return times.get(position);
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return position;
+ }
+
+ @Override
+ public View getView(int position, View view, ViewGroup parent) {
+ if (view == null) {
+ view = mInflater.inflate(R.layout.pulse_time_item, parent, false);
+ }
+
+ Pair<String, Integer> entry = getItem(position);
+ ((TextView) view.findViewById(R.id.textViewName)).setText(entry.first);
+
+ return view;
+ }
+ }
+
+ @Override
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+ }
+
+ @Override
+ public void onTextChanged(CharSequence s, int start, int before, int count) {
+ }
+
+ @Override
+ public void afterTextChanged(Editable s) {
+ String hexColor = mHexColorInput.getText().toString();
+ if (!hexColor.isEmpty()) {
+ try {
+ int color = Color.parseColor('#' + hexColor);
+ if (!mColorPicker.isAlphaSliderVisible()) {
+ color |= 0xFF000000; // set opaque
+ }
+ mColorPicker.setColor(color);
+ mNewColor.setColor(color);
+ updateLed();
+ if (mListener != null) {
+ mListener.onColorChanged(color);
+ }
+ } catch (IllegalArgumentException ex) {
+ // Number format is incorrect, ignore
+ }
+ }
+ }
+
+ @Override
+ public void onFocusChange(View v, boolean hasFocus) {
+ if (!hasFocus) {
+ mHexColorInput.removeTextChangedListener(this);
+ InputMethodManager inputMethodManager = (InputMethodManager) getContext()
+ .getSystemService(Activity.INPUT_METHOD_SERVICE);
+ inputMethodManager.hideSoftInputFromWindow(v.getWindowToken(), 0);
+ } else {
+ mHexColorInput.addTextChangedListener(this);
+ }
+ }
+}
diff --git a/src/org/cyanogenmod/cmparts/notificationlight/NotificationLightSettings.java b/src/org/cyanogenmod/cmparts/notificationlight/NotificationLightSettings.java
new file mode 100644
index 0000000..eddfea7
--- /dev/null
+++ b/src/org/cyanogenmod/cmparts/notificationlight/NotificationLightSettings.java
@@ -0,0 +1,580 @@
+/*
+ * Copyright (C) 2012 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.cmparts.notificationlight;
+
+import android.app.AlertDialog;
+import android.app.Application;
+import android.app.Dialog;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.provider.Settings;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.PreferenceGroup;
+import android.support.v7.preference.PreferenceScreen;
+import android.support.v7.preference.PreferenceViewHolder;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.ListView;
+
+import org.cyanogenmod.cmparts.PackageListAdapter;
+import org.cyanogenmod.cmparts.PackageListAdapter.PackageItem;
+import org.cyanogenmod.cmparts.R;
+import org.cyanogenmod.cmparts.SettingsPreferenceFragment;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import cyanogenmod.preference.CMSystemSettingSwitchPreference;
+import cyanogenmod.preference.SystemSettingSwitchPreference;
+import cyanogenmod.providers.CMSettings;
+import cyanogenmod.util.ColorUtils;
+
+public class NotificationLightSettings extends SettingsPreferenceFragment implements
+ Preference.OnPreferenceChangeListener, ApplicationLightPreference.ItemLongClickListener {
+ private static final String TAG = "NotificationLightSettings";
+ private static final String DEFAULT_PREF = "default";
+ private static final String MISSED_CALL_PREF = "missed_call";
+ private static final String VOICEMAIL_PREF = "voicemail";
+ public static final int ACTION_TEST = 0;
+ public static final int ACTION_DELETE = 1;
+ private static final int MENU_ADD = 0;
+ private static final int DIALOG_APPS = 0;
+
+ private int mDefaultColor;
+ private int mDefaultLedOn;
+ private int mDefaultLedOff;
+ private PackageManager mPackageManager;
+ private PreferenceGroup mApplicationPrefList;
+ private PreferenceScreen mNotificationLedBrightnessPref;
+ private SystemSettingSwitchPreference mEnabledPref;
+ private CMSystemSettingSwitchPreference mCustomEnabledPref;
+ private CMSystemSettingSwitchPreference mMultipleLedsEnabledPref;
+ private CMSystemSettingSwitchPreference mScreenOnLightsPref;
+ private CMSystemSettingSwitchPreference mAutoGenerateColors;
+ private ApplicationLightPreference mDefaultPref;
+ private ApplicationLightPreference mCallPref;
+ private ApplicationLightPreference mVoicemailPref;
+ private Menu mMenu;
+ private PackageListAdapter mPackageAdapter;
+ private String mPackageList;
+ private Map<String, Package> mPackages;
+ private boolean mMultiColorLed;
+
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+
+ addPreferencesFromResource(R.xml.notification_light_settings);
+ getActivity().getActionBar().setTitle(
+ org.cyanogenmod.platform.internal.R.string.notification_light_title);
+
+ PreferenceScreen prefSet = getPreferenceScreen();
+ Resources resources = getResources();
+
+ PreferenceGroup mAdvancedPrefs = (PreferenceGroup) prefSet.findPreference("advanced_section");
+
+ // Get the system defined default notification color
+ mDefaultColor =
+ resources.getColor(com.android.internal.R.color.config_defaultNotificationColor, null);
+
+ mDefaultLedOn = resources.getInteger(
+ com.android.internal.R.integer.config_defaultNotificationLedOn);
+ mDefaultLedOff = resources.getInteger(
+ com.android.internal.R.integer.config_defaultNotificationLedOff);
+
+ mEnabledPref = (SystemSettingSwitchPreference)
+ findPreference(Settings.System.NOTIFICATION_LIGHT_PULSE);
+ mEnabledPref.setOnPreferenceChangeListener(this);
+
+ mDefaultPref = (ApplicationLightPreference) findPreference(DEFAULT_PREF);
+ mDefaultPref.setOnPreferenceChangeListener(this);
+
+ mAutoGenerateColors = (CMSystemSettingSwitchPreference)
+ findPreference(CMSettings.System.NOTIFICATION_LIGHT_COLOR_AUTO);
+
+ // Advanced light settings
+ mNotificationLedBrightnessPref = (PreferenceScreen)
+ findPreference(CMSettings.System.NOTIFICATION_LIGHT_BRIGHTNESS_LEVEL);
+ mMultipleLedsEnabledPref = (CMSystemSettingSwitchPreference)
+ findPreference(CMSettings.System.NOTIFICATION_LIGHT_MULTIPLE_LEDS_ENABLE);
+ mScreenOnLightsPref = (CMSystemSettingSwitchPreference)
+ findPreference(CMSettings.System.NOTIFICATION_LIGHT_SCREEN_ON);
+ mScreenOnLightsPref.setOnPreferenceChangeListener(this);
+ mCustomEnabledPref = (CMSystemSettingSwitchPreference)
+ findPreference(CMSettings.System.NOTIFICATION_LIGHT_PULSE_CUSTOM_ENABLE);
+ mCustomEnabledPref.setOnPreferenceChangeListener(this);
+ if (!resources.getBoolean(
+ org.cyanogenmod.platform.internal.R.bool.config_adjustableNotificationLedBrightness)) {
+ mAdvancedPrefs.removePreference(mNotificationLedBrightnessPref);
+ } else {
+ mNotificationLedBrightnessPref.setOnPreferenceChangeListener(this);
+ }
+ if (!resources.getBoolean(
+ org.cyanogenmod.platform.internal.R.bool.config_multipleNotificationLeds)) {
+ mAdvancedPrefs.removePreference(mMultipleLedsEnabledPref);
+ } else {
+ mMultipleLedsEnabledPref.setOnPreferenceChangeListener(this);
+ }
+
+ // Missed call and Voicemail preferences should only show on devices with a voice capabilities
+ TelephonyManager tm = (TelephonyManager) getActivity().getSystemService(Context.TELEPHONY_SERVICE);
+ if (tm.getPhoneType() == TelephonyManager.PHONE_TYPE_NONE) {
+ removePreference("phone_list");
+ } else {
+ mCallPref = (ApplicationLightPreference) findPreference(MISSED_CALL_PREF);
+ mCallPref.setOnPreferenceChangeListener(this);
+
+ mVoicemailPref = (ApplicationLightPreference) findPreference(VOICEMAIL_PREF);
+ mVoicemailPref.setOnPreferenceChangeListener(this);
+ }
+
+ mApplicationPrefList = (PreferenceGroup) findPreference("applications_list");
+ mApplicationPrefList.setOrderingAsAdded(false);
+
+ // Get launch-able applications
+ mPackageManager = getActivity().getPackageManager();
+ mPackageAdapter = new PackageListAdapter(getActivity());
+
+ mPackages = new HashMap<String, Package>();
+ setHasOptionsMenu(true);
+
+ mMultiColorLed = resources.getBoolean(com.android.internal.R.bool.config_multiColorNotificationLed);
+ if (!mMultiColorLed) {
+ resetColors();
+ PreferenceGroup mGeneralPrefs = (PreferenceGroup) prefSet.findPreference("general_section");
+ mGeneralPrefs.removePreference(mAutoGenerateColors);
+ } else {
+ mAutoGenerateColors.setOnPreferenceChangeListener(this);
+ }
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ refreshDefault();
+ refreshCustomApplicationPrefs();
+ getActivity().invalidateOptionsMenu();
+ }
+
+ private void refreshDefault() {
+ ContentResolver resolver = getActivity().getContentResolver();
+ int color = CMSettings.System.getInt(resolver,
+ CMSettings.System.NOTIFICATION_LIGHT_PULSE_DEFAULT_COLOR, mDefaultColor);
+ int timeOn = CMSettings.System.getInt(resolver,
+ CMSettings.System.NOTIFICATION_LIGHT_PULSE_DEFAULT_LED_ON, mDefaultLedOn);
+ int timeOff = CMSettings.System.getInt(resolver,
+ CMSettings.System.NOTIFICATION_LIGHT_PULSE_DEFAULT_LED_OFF, mDefaultLedOff);
+
+ mDefaultPref.setAllValues(color, timeOn, timeOff);
+
+ // Get Missed call and Voicemail values
+ if (mCallPref != null) {
+ int callColor = CMSettings.System.getInt(resolver,
+ CMSettings.System.NOTIFICATION_LIGHT_PULSE_CALL_COLOR, mDefaultColor);
+ int callTimeOn = CMSettings.System.getInt(resolver,
+ CMSettings.System.NOTIFICATION_LIGHT_PULSE_CALL_LED_ON, mDefaultLedOn);
+ int callTimeOff = CMSettings.System.getInt(resolver,
+ CMSettings.System.NOTIFICATION_LIGHT_PULSE_CALL_LED_OFF, mDefaultLedOff);
+
+ mCallPref.setAllValues(callColor, callTimeOn, callTimeOff);
+ }
+
+ if (mVoicemailPref != null) {
+ int vmailColor = CMSettings.System.getInt(resolver,
+ CMSettings.System.NOTIFICATION_LIGHT_PULSE_VMAIL_COLOR, mDefaultColor);
+ int vmailTimeOn = CMSettings.System.getInt(resolver,
+ CMSettings.System.NOTIFICATION_LIGHT_PULSE_VMAIL_LED_ON, mDefaultLedOn);
+ int vmailTimeOff = CMSettings.System.getInt(resolver,
+ CMSettings.System.NOTIFICATION_LIGHT_PULSE_VMAIL_LED_OFF, mDefaultLedOff);
+
+ mVoicemailPref.setAllValues(vmailColor, vmailTimeOn, vmailTimeOff);
+ }
+
+ mApplicationPrefList = (PreferenceGroup) findPreference("applications_list");
+ mApplicationPrefList.setOrderingAsAdded(false);
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ setChildrenStarted(getPreferenceScreen(), true);
+ }
+
+ @Override
+ public void onStop() {
+ super.onStop();
+ setChildrenStarted(getPreferenceScreen(), false);
+ }
+
+ private void setChildrenStarted(PreferenceGroup group, boolean started) {
+ final int count = group.getPreferenceCount();
+ for (int i = 0; i < count; i++) {
+ Preference pref = group.getPreference(i);
+ if (pref instanceof ApplicationLightPreference) {
+ ApplicationLightPreference ap = (ApplicationLightPreference) pref;
+ if (started) {
+ ap.onStart();
+ } else {
+ ap.onStop();
+ }
+ } else if (pref instanceof PreferenceGroup) {
+ setChildrenStarted((PreferenceGroup) pref, started);
+ }
+ }
+ }
+
+ private void refreshCustomApplicationPrefs() {
+ Context context = getActivity();
+
+ if (!parsePackageList()) {
+ return;
+ }
+
+ // Add the Application Preferences
+ if (mApplicationPrefList != null) {
+ mApplicationPrefList.removeAll();
+
+ for (Package pkg : mPackages.values()) {
+ try {
+ PackageInfo info = mPackageManager.getPackageInfo(pkg.name,
+ PackageManager.GET_META_DATA);
+ ApplicationLightPreference pref =
+ new ApplicationLightPreference(context, null,
+ pkg.color, pkg.timeon, pkg.timeoff);
+
+ pref.setKey(pkg.name);
+ pref.setTitle(info.applicationInfo.loadLabel(mPackageManager));
+ pref.setIcon(info.applicationInfo.loadIcon(mPackageManager));
+ pref.setPersistent(false);
+ pref.setOnPreferenceChangeListener(this);
+ pref.setOnLongClickListener(this);
+ mApplicationPrefList.addPreference(pref);
+ } catch (NameNotFoundException e) {
+ // Do nothing
+ }
+ }
+
+ /* Display a pref explaining how to add apps */
+ if (mPackages.size() == 0) {
+ String summary = getResources().getString(
+ R.string.notification_light_no_apps_summary);
+ String useCustom = getResources().getString(
+ R.string.notification_light_use_custom);
+ Preference pref = new Preference(context);
+ pref.setSummary(String.format(summary, useCustom));
+ pref.setEnabled(false);
+ mApplicationPrefList.addPreference(pref);
+ }
+ }
+ }
+
+ private int getInitialColorForPackage(String packageName) {
+ boolean autoColor = CMSettings.System.getInt(getActivity().getContentResolver(),
+ CMSettings.System.NOTIFICATION_LIGHT_COLOR_AUTO, mMultiColorLed ? 1 : 0) == 1;
+ int color = mDefaultColor;
+ if (autoColor) {
+ try {
+ Drawable icon = mPackageManager.getApplicationIcon(packageName);
+ color = ColorUtils.generateAlertColorFromDrawable(icon);
+ } catch (NameNotFoundException e) {
+ // shouldn't happen, but just return default
+ }
+ }
+ return color;
+ }
+
+ private void addCustomApplicationPref(String packageName) {
+ Package pkg = mPackages.get(packageName);
+ if (pkg == null) {
+ int color = getInitialColorForPackage(packageName);
+ pkg = new Package(packageName, color, mDefaultLedOn, mDefaultLedOff);
+ mPackages.put(packageName, pkg);
+ savePackageList(false);
+ refreshCustomApplicationPrefs();
+ }
+ }
+
+ private void removeCustomApplicationPref(String packageName) {
+ if (mPackages.remove(packageName) != null) {
+ savePackageList(false);
+ refreshCustomApplicationPrefs();
+ }
+ }
+
+ private boolean parsePackageList() {
+ final String baseString = CMSettings.System.getString(getActivity().getContentResolver(),
+ CMSettings.System.NOTIFICATION_LIGHT_PULSE_CUSTOM_VALUES);
+
+ if (TextUtils.equals(mPackageList, baseString)) {
+ return false;
+ }
+
+ mPackageList = baseString;
+ mPackages.clear();
+
+ if (baseString != null) {
+ final String[] array = TextUtils.split(baseString, "\\|");
+ for (String item : array) {
+ if (TextUtils.isEmpty(item)) {
+ continue;
+ }
+ Package pkg = Package.fromString(item);
+ if (pkg != null) {
+ mPackages.put(pkg.name, pkg);
+ }
+ }
+ }
+
+ return true;
+ }
+
+ private void savePackageList(boolean preferencesUpdated) {
+ List<String> settings = new ArrayList<String>();
+ for (Package app : mPackages.values()) {
+ settings.add(app.toString());
+ }
+ final String value = TextUtils.join("|", settings);
+ if (preferencesUpdated) {
+ mPackageList = value;
+ }
+ CMSettings.System.putString(getActivity().getContentResolver(),
+ CMSettings.System.NOTIFICATION_LIGHT_PULSE_CUSTOM_VALUES, value);
+ }
+
+ /**
+ * Updates the default or package specific notification settings.
+ *
+ * @param packageName Package name of application specific settings to update
+ * @param color
+ * @param timeon
+ * @param timeoff
+ */
+ protected void updateValues(String packageName, Integer color, Integer timeon, Integer timeoff) {
+ ContentResolver resolver = getActivity().getContentResolver();
+
+ if (packageName.equals(DEFAULT_PREF)) {
+ CMSettings.System.putInt(resolver, CMSettings.System.NOTIFICATION_LIGHT_PULSE_DEFAULT_COLOR, color);
+ CMSettings.System.putInt(resolver, CMSettings.System.NOTIFICATION_LIGHT_PULSE_DEFAULT_LED_ON, timeon);
+ CMSettings.System.putInt(resolver, CMSettings.System.NOTIFICATION_LIGHT_PULSE_DEFAULT_LED_OFF, timeoff);
+ refreshDefault();
+ return;
+ } else if (packageName.equals(MISSED_CALL_PREF)) {
+ CMSettings.System.putInt(resolver, CMSettings.System.NOTIFICATION_LIGHT_PULSE_CALL_COLOR, color);
+ CMSettings.System.putInt(resolver, CMSettings.System.NOTIFICATION_LIGHT_PULSE_CALL_LED_ON, timeon);
+ CMSettings.System.putInt(resolver, CMSettings.System.NOTIFICATION_LIGHT_PULSE_CALL_LED_OFF, timeoff);
+ refreshDefault();
+ return;
+ } else if (packageName.equals(VOICEMAIL_PREF)) {
+ CMSettings.System.putInt(resolver, CMSettings.System.NOTIFICATION_LIGHT_PULSE_VMAIL_COLOR, color);
+ CMSettings.System.putInt(resolver, CMSettings.System.NOTIFICATION_LIGHT_PULSE_VMAIL_LED_ON, timeon);
+ CMSettings.System.putInt(resolver, CMSettings.System.NOTIFICATION_LIGHT_PULSE_VMAIL_LED_OFF, timeoff);
+ refreshDefault();
+ return;
+ }
+
+ // Find the custom package and sets its new values
+ Package app = mPackages.get(packageName);
+ if (app != null) {
+ app.color = color;
+ app.timeon = timeon;
+ app.timeoff = timeoff;
+ savePackageList(true);
+ }
+ }
+
+ protected void resetColors() {
+ ContentResolver resolver = getActivity().getContentResolver();
+
+ // Reset to the framework default colors
+ CMSettings.System.putInt(resolver, CMSettings.System.NOTIFICATION_LIGHT_PULSE_DEFAULT_COLOR, mDefaultColor);
+ CMSettings.System.putInt(resolver, CMSettings.System.NOTIFICATION_LIGHT_PULSE_CALL_COLOR, mDefaultColor);
+ CMSettings.System.putInt(resolver, CMSettings.System.NOTIFICATION_LIGHT_PULSE_VMAIL_COLOR, mDefaultColor);
+
+ refreshDefault();
+ }
+
+ public boolean onItemLongClick(final String key) {
+ final ApplicationLightPreference pref =
+ (ApplicationLightPreference) getPreferenceScreen().findPreference(key);
+
+ if (mApplicationPrefList.findPreference(key) == null) {
+ return false;
+ }
+
+ AlertDialog.Builder builder = new AlertDialog.Builder(getActivity())
+ .setTitle(R.string.dialog_delete_title)
+ .setMessage(R.string.dialog_delete_message)
+ .setIconAttribute(android.R.attr.alertDialogIcon)
+ .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ removeCustomApplicationPref(key);
+ }
+ })
+ .setNegativeButton(android.R.string.cancel, null);
+
+ builder.show();
+ return true;
+ }
+
+ public boolean onPreferenceChange(Preference preference, Object objValue) {
+ if (preference == mEnabledPref || preference == mCustomEnabledPref ||
+ preference == mMultipleLedsEnabledPref ||
+ preference == mNotificationLedBrightnessPref ||
+ preference == mScreenOnLightsPref ||
+ preference == mAutoGenerateColors) {
+ getActivity().invalidateOptionsMenu();
+ } else {
+ ApplicationLightPreference lightPref = (ApplicationLightPreference) preference;
+ updateValues(lightPref.getKey(), lightPref.getColor(),
+ lightPref.getOnValue(), lightPref.getOffValue());
+ }
+
+ return true;
+ }
+
+ @Override
+ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+ mMenu = menu;
+ mMenu.add(0, MENU_ADD, 0, R.string.add)
+ .setIcon(R.drawable.ic_menu_add_white)
+ .setShowAsActionFlags(MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
+ }
+
+ @Override
+ public void onPrepareOptionsMenu(Menu menu) {
+ boolean enableAddButton = mEnabledPref.isChecked() && mCustomEnabledPref.isChecked();
+ menu.findItem(MENU_ADD).setVisible(enableAddButton);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case MENU_ADD:
+ showDialog(DIALOG_APPS);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Utility classes and supporting methods
+ */
+ @Override
+ public Dialog onCreateDialog(int id) {
+ AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+ final Dialog dialog;
+ switch (id) {
+ case DIALOG_APPS:
+ final ListView list = new ListView(getActivity());
+ list.setAdapter(mPackageAdapter);
+
+ builder.setTitle(R.string.choose_app);
+ builder.setView(list);
+ dialog = builder.create();
+
+ list.setOnItemClickListener(new OnItemClickListener() {
+ @Override
+ public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+ // Add empty application definition, the user will be able to edit it later
+ PackageItem info = (PackageItem) parent.getItemAtPosition(position);
+ addCustomApplicationPref(info.packageName);
+ dialog.cancel();
+ }
+ });
+ break;
+ default:
+ dialog = null;
+ }
+ return dialog;
+ }
+
+ /**
+ * Application class
+ */
+ private static class Package {
+ public String name;
+ public Integer color;
+ public Integer timeon;
+ public Integer timeoff;
+
+ /**
+ * Stores all the application values in one call
+ * @param name
+ * @param color
+ * @param timeon
+ * @param timeoff
+ */
+ public Package(String name, Integer color, Integer timeon, Integer timeoff) {
+ this.name = name;
+ this.color = color;
+ this.timeon = timeon;
+ this.timeoff = timeoff;
+ }
+
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append(name);
+ builder.append("=");
+ builder.append(color);
+ builder.append(";");
+ builder.append(timeon);
+ builder.append(";");
+ builder.append(timeoff);
+ return builder.toString();
+ }
+
+ public static Package fromString(String value) {
+ if (TextUtils.isEmpty(value)) {
+ return null;
+ }
+ String[] app = value.split("=", -1);
+ if (app.length != 2)
+ return null;
+
+ String[] values = app[1].split(";", -1);
+ if (values.length != 3)
+ return null;
+
+ try {
+ Package item = new Package(app[0], Integer.parseInt(values[0]), Integer
+ .parseInt(values[1]), Integer.parseInt(values[2]));
+ return item;
+ } catch (NumberFormatException e) {
+ return null;
+ }
+ }
+
+ }
+}