Omnigears: [2/3] Notification light settings
Change-Id: I1bfd3d1ae2013f41c4ceb4acbbb38f926694c9cb
diff --git a/res/layout/dialog_notification_settings.xml b/res/layout/dialog_notification_settings.xml
new file mode 100644
index 0000000..0abde26
--- /dev/null
+++ b/res/layout/dialog_notification_settings.xml
@@ -0,0 +1,99 @@
+<?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.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:paddingTop="@dimen/alert_dialog_padding_material"
+ android:paddingStart="@dimen/alert_dialog_padding_material"
+ android:paddingEnd="@dimen/alert_dialog_padding_material" >
+ <org.omnirom.omnigears.ui.ColorPickerView
+ android:id="@+id/color_picker_view"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginEnd="10dp"
+ android:layout_marginStart="10dp" />
+ <LinearLayout
+ android:id="@+id/color_panel_view"
+ android:layout_width="match_parent"
+ android:layout_height="40dp"
+ android:layout_marginEnd="10dp"
+ android:layout_marginStart="10dp"
+ android:layout_marginTop="4dp"
+ android:orientation="horizontal" >
+ <EditText
+ android:id="@+id/hex_color_input"
+ android:layout_width="0px"
+ android:layout_height="match_parent"
+ android:layout_weight="0.5"
+ android:digits="0123456789ABCDEFabcdef"
+ android:inputType="textNoSuggestions"
+ android:maxLength="6" />
+ <org.omnirom.omnigears.ui.ColorPanelView
+ android:id="@+id/color_panel"
+ android:layout_width="0px"
+ android:layout_height="match_parent"
+ android:layout_marginStart="10dp"
+ 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>
+</LinearLayout>
diff --git a/res/layout/preference_icon.xml b/res/layout/preference_icon.xml
new file mode 100644
index 0000000..687de3d
--- /dev/null
+++ b/res/layout/preference_icon.xml
@@ -0,0 +1,69 @@
+<?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:attr/textAppearanceListItem"
+ 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:attr/textAppearanceListItemSecondary"
+ android:textColor="?android:attr/textColorSecondary"
+ android:maxLines="2" />
+
+ </RelativeLayout>
+
+</LinearLayout>
diff --git a/res/layout/preference_notification_light.xml b/res/layout/preference_notification_light.xml
new file mode 100644
index 0000000..9cc98af
--- /dev/null
+++ b/res/layout/preference_notification_light.xml
@@ -0,0 +1,84 @@
+<?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">
+
+ <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:attr/textAppearanceListItem"
+ 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:attr/textAppearanceListItemSecondary"
+ 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:layout_marginEnd="6dp"
+ android:textColor="?android:attr/textColorSecondary"
+ android:textAppearance="?android:attr/textAppearanceListItemSecondary" />
+
+ <TextView
+ android:id="@+id/textViewTimeOffValue"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="end"
+ android:layout_marginEnd="6dp"
+ android:textColor="?android:attr/textColorSecondary"
+ android:textAppearance="?android:attr/textAppearanceListItemSecondary" />
+ </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/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/custom_arrays.xml b/res/values/custom_arrays.xml
index 44d447b..6514aab 100644
--- a/res/values/custom_arrays.xml
+++ b/res/values/custom_arrays.xml
@@ -260,4 +260,38 @@
<item>1</item>
<item>2</item>
</string-array>
+ <!-- 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>2000</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>2000</item>
+ <item>5000</item>
+ </string-array>
</resources>
diff --git a/res/values/custom_strings.xml b/res/values/custom_strings.xml
index 353c8e2..3b429f4 100644
--- a/res/values/custom_strings.xml
+++ b/res/values/custom_strings.xml
@@ -20,6 +20,10 @@
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="ok">OK</string>
+ <string name="advanced">Advanced</string>
+ <string name="profiles_add">Add...</string>
+ <string name="profile_choose_app">Choose app</string>
+ <string name="reset">Reset</string>
<!-- Hardware keys -->
<string name="button_keys_title">Keys</string>
<string name="keys_bindings_title">Key actions</string>
@@ -353,4 +357,41 @@
<string name="power_menu_animation_enter">Omni (default)</string>
<string name="power_menu_animation_bottom">Bottom</string>
<string name="power_menu_animation_top">Top</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>
+ <!-- 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>
+ <!-- Lights settings screen, notification light settings -->
+ <string name="notification_light_title">Notification light</string>
+ <string name="notificationlight_title">Notification LED settings</string>
+ <string name="notification_light_general_title">General</string>
+ <string name="notification_light_applist_title">Apps</string>
+ <string name="notification_light_use_custom">Use custom values</string>
+ <string name="notification_light_default_value">Default</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/notification_light_settings.xml b/res/xml/notification_light_settings.xml
new file mode 100644
index 0000000..e93dc73
--- /dev/null
+++ b/res/xml/notification_light_settings.xml
@@ -0,0 +1,64 @@
+<?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">
+
+ <PreferenceCategory
+ android:key="general_section"
+ android:title="@string/notification_light_general_title">
+
+ <org.omnirom.omnigears.preference.SystemSettingSwitchPreference
+ android:key="notification_light_pulse"
+ android:title="@string/notification_light_title" />
+
+ <org.omnirom.omnigears.batterylight.NotificationLightPreference
+ 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">
+
+ <org.omnirom.omnigears.preference.SystemSettingSwitchPreference
+ android:key="notification_light_screen_on_enable"
+ android:title="@string/notification_light_screen_on"
+ android:dependency="notification_light_pulse" />
+
+ <org.omnirom.omnigears.preference.SystemSettingSwitchPreference
+ android:key="allow_lights"
+ android:title="@string/notification_light_zen_mode"
+ android:dependency="notification_light_pulse"
+ android:defaultValue="true" />
+
+ <org.omnirom.omnigears.preference.SystemSettingSwitchPreference
+ android:key="notification_light_pulse_custom_enable"
+ android:title="@string/notification_light_use_custom"
+ android:dependency="notification_light_pulse" />
+
+ </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/omnirom/omnigears/batterylight/NotificationLightDialog.java b/src/org/omnirom/omnigears/batterylight/NotificationLightDialog.java
new file mode 100644
index 0000000..6075695
--- /dev/null
+++ b/src/org/omnirom/omnigears/batterylight/NotificationLightDialog.java
@@ -0,0 +1,527 @@
+/*
+ * 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.omnirom.omnigears.batterylight;
+
+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 com.android.settings.R;
+import org.omnirom.omnigears.ui.ColorPanelView;
+import org.omnirom.omnigears.ui.ColorPickerView;
+import org.omnirom.omnigears.ui.ColorPickerView.OnColorChangedListener;
+
+import java.util.ArrayList;
+import java.util.Locale;
+
+public class NotificationLightDialog extends AlertDialog implements
+ ColorPickerView.OnColorChangedListener, TextWatcher, OnFocusChangeListener {
+
+ private static final String TAG = "NotificationLightDialog";
+ private final static String STATE_KEY_COLOR = "NotificationLightDialog:color";
+ private final static long LED_UPDATE_DELAY_MS = 250;
+
+ private ColorPickerView mColorPicker;
+ private View mLightsDialogDivider;
+ private EditText mHexColorInput;
+ private Spinner mColorList;
+ private ColorPanelView mNewColor;
+ private LinearLayout mColorListView;
+ private Spinner mPulseSpeedOn;
+ private Spinner mPulseSpeedOff;
+ private LayoutInflater mInflater;
+ private LinearLayout mColorPanelView;
+ private ColorPanelView mNewListColor;
+ private LedColorAdapter mLedColorAdapter;
+ private boolean mWithAlpha;
+
+ 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 NotificationLightDialog(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 NotificationLightDialog(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_notification_settings, null);
+
+ mColorPicker = (ColorPickerView) layout.findViewById(R.id.color_picker_view);
+ mHexColorInput = (EditText) layout.findViewById(R.id.hex_color_input);
+ mNewColor = (ColorPanelView) layout.findViewById(R.id.color_panel);
+ mColorPanelView = (LinearLayout) layout.findViewById(R.id.color_panel_view);
+
+ mColorPicker.setOnColorChangedListener(this);
+ mHexColorInput.setOnFocusChangeListener(this);
+ setAlphaSliderVisible(mWithAlpha);
+ mColorPicker.setColor(color, true);
+
+ 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);
+
+ 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);
+
+ mColorPicker.setVisibility(View.VISIBLE);
+ mColorPanelView.setVisibility(View.VISIBLE);
+
+ if (!getContext().getResources().getBoolean(
+ com.android.internal.R.bool.config_multiColorNotificationLed)) {
+ mColorPicker.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();
+ }
+
+ class LedColorAdapter extends BaseAdapter implements SpinnerAdapter {
+ private ArrayList<Pair<String, Integer>> mColors;
+
+ public LedColorAdapter(int ledColorResource, int ledValueResource) {
+ mColors = new ArrayList<Pair<String, Integer>>();
+
+ String[] color_names = getContext().getResources().getStringArray(ledColorResource);
+ String[] color_values = getContext().getResources().getStringArray(ledValueResource);
+
+ for(int i = 0; i < color_values.length; ++i) {
+ try {
+ int color = Color.parseColor(color_values[i]);
+ mColors.add(new Pair<String, Integer>(color_names[i], color));
+ } catch (IllegalArgumentException ex) {
+ // Number format is incorrect, ignore entry
+ }
+ }
+ }
+
+ /**
+ * Will return the position of the spinner entry with the specified
+ * color. Returns 0 if there is no such entry.
+ */
+ public int getColorPosition(int color) {
+ for (int position = 0; position < getCount(); ++position) {
+ if (getItem(position).second.equals(color)) {
+ return position;
+ }
+ }
+
+ return 0;
+ }
+
+ public int getColor(int position) {
+ Pair<String, Integer> item = getItem(position);
+ if (item != null){
+ return item.second;
+ }
+
+ // -1 is white
+ return -1;
+ }
+
+ @Override
+ public int getCount() {
+ return mColors.size();
+ }
+
+ @Override
+ public Pair<String, Integer> getItem(int position) {
+ return mColors.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.led_color_item, null);
+ }
+
+ Pair<String, Integer> entry = getItem(position);
+ ((TextView) view.findViewById(R.id.textViewName)).setText(entry.first);
+
+ return view;
+ }
+ }
+
+ @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_leds);
+ 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/omnirom/omnigears/batterylight/NotificationLightPreference.java b/src/org/omnirom/omnigears/batterylight/NotificationLightPreference.java
new file mode 100644
index 0000000..9fc752a
--- /dev/null
+++ b/src/org/omnirom/omnigears/batterylight/NotificationLightPreference.java
@@ -0,0 +1,294 @@
+/*
+ * 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.omnirom.omnigears.batterylight;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.res.Resources;
+import android.graphics.drawable.ShapeDrawable;
+import android.graphics.drawable.shapes.RectShape;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.PreferenceViewHolder;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.android.settings.R;
+
+public class NotificationLightPreference extends Preference implements DialogInterface.OnDismissListener,
+ View.OnLongClickListener {
+
+ private static String TAG = "NotificationLightPreference";
+ public static final int DEFAULT_TIME = 1000;
+ public static final int DEFAULT_COLOR = 0xFFFFFF; //White
+
+ private ImageView mLightColorView;
+ private TextView mOnValueView;
+ private TextView mOffValueView;
+ private Resources mResources;
+ private int mColorValue;
+ private Dialog mDialog;
+
+ private int mOnValue;
+ private int mOffValue;
+ private boolean mOnOffChangeable;
+
+ public interface ItemLongClickListener {
+ public boolean onItemLongClick(String key);
+ }
+
+ private ItemLongClickListener mLongClickListener;
+
+ /**
+ * @param context
+ * @param attrs
+ */
+ public NotificationLightPreference(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ mColorValue = DEFAULT_COLOR;
+ mOnValue = DEFAULT_TIME;
+ mOffValue = DEFAULT_TIME;
+ mOnOffChangeable = context.getResources().getBoolean(
+ com.android.internal.R.bool.config_ledCanPulse);
+ init();
+ }
+
+ /**
+ * @param context
+ * @param color
+ * @param onValue
+ * @param offValue
+ */
+ public NotificationLightPreference(Context context, int color, int onValue, int offValue) {
+ super(context, null);
+ mColorValue = color;
+ mOnValue = onValue;
+ mOffValue = offValue;
+ mOnOffChangeable = context.getResources().getBoolean(
+ com.android.internal.R.bool.config_ledCanPulse);
+ init();
+ }
+
+ /**
+ * @param context
+ * @param color
+ * @param onValue
+ * @param offValue
+ */
+ public NotificationLightPreference(Context context, int color, int onValue, int offValue, boolean onOffChangeable) {
+ super(context, null);
+ mColorValue = color;
+ mOnValue = onValue;
+ mOffValue = offValue;
+ mOnOffChangeable = onOffChangeable;
+ init();
+ }
+
+ private void init() {
+ setLayoutResource(R.layout.preference_notification_light);
+ mResources = getContext().getResources();
+ }
+
+ public void setColor(int color) {
+ mColorValue = color;
+ updatePreferenceViews();
+ }
+
+ public int getColor() {
+ return mColorValue;
+ }
+
+ public void onStart() {
+ NotificationLightDialog d = (NotificationLightDialog) getDialog();
+ if (d != null) {
+ d.onStart();
+ }
+ }
+
+ public void onStop() {
+ NotificationLightDialog d = (NotificationLightDialog) getDialog();
+ if (d != null) {
+ d.onStop();
+ }
+ }
+
+ @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);
+ }
+
+ private void updatePreferenceViews() {
+ final int width = (int) mResources.getDimension(R.dimen.color_preference_width);
+ final int height = (int) mResources.getDimension(R.dimen.color_preference_height);
+
+ if (mLightColorView != null) {
+ mLightColorView.setEnabled(true);
+ mLightColorView.setImageDrawable(createRectShape(width, height, 0xFF000000 | mColorValue));
+ }
+ if (mOnValueView != null) {
+ mOnValueView.setText(mapLengthValue(mOnValue));
+ }
+ if (mOffValueView != null) {
+ if (mOnValue == 1 || !mOnOffChangeable) {
+ mOffValueView.setEnabled(false);
+ } else {
+ mOffValueView.setEnabled(true);
+ }
+ mOffValueView.setText(mapSpeedValue(mOffValue));
+ }
+ }
+
+ @Override
+ protected void onClick() {
+ if (mDialog != null && mDialog.isShowing()) return;
+ mDialog = getDialog();
+ mDialog.setOnDismissListener(this);
+ mDialog.show();
+ }
+
+ public Dialog getDialog() {
+ final NotificationLightDialog d = new NotificationLightDialog(getContext(),
+ 0xFF000000 + mColorValue, mOnValue, mOffValue, mOnOffChangeable);
+
+ d.setButton(AlertDialog.BUTTON_POSITIVE, mResources.getString(R.string.ok),
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ mColorValue = d.getColor() & 0x00FFFFFF; // strip alpha, led does not support it
+ mOnValue = d.getPulseSpeedOn();
+ mOffValue = d.getPulseSpeedOff();
+ updatePreferenceViews();
+ callChangeListener(this);
+ }
+ });
+ d.setButton(AlertDialog.BUTTON_NEGATIVE, mResources.getString(R.string.cancel),
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ }
+ });
+
+ return d;
+ }
+
+ private static ShapeDrawable createRectShape(int width, int height, int color) {
+ ShapeDrawable shape = new ShapeDrawable(new RectShape());
+ shape.setIntrinsicHeight(height);
+ shape.setIntrinsicWidth(width);
+ shape.getPaint().setColor(color);
+ return shape;
+ }
+
+ @Override
+ public void onDismiss(DialogInterface dialog) {
+ mDialog = null;
+ }
+
+ public void setAllValues(int color, int onValue, int offValue) {
+ mColorValue = color;
+ mOnValue = onValue;
+ mOffValue = offValue;
+ 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;
+ }
+
+ 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/omnirom/omnigears/batterylight/NotificationLightSettings.java b/src/org/omnirom/omnigears/batterylight/NotificationLightSettings.java
new file mode 100644
index 0000000..708feb6
--- /dev/null
+++ b/src/org/omnirom/omnigears/batterylight/NotificationLightSettings.java
@@ -0,0 +1,500 @@
+/*
+ * 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.omnirom.omnigears.batterylight;
+
+import android.app.AlertDialog;
+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.support.v7.preference.Preference;
+import android.support.v7.preference.PreferenceGroup;
+import android.support.v7.preference.PreferenceScreen;
+import android.provider.SearchIndexableResource;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.ListView;
+
+import com.android.internal.logging.MetricsProto.MetricsEvent;
+import com.android.internal.util.omni.ColorUtils;
+import com.android.settings.R;
+import com.android.settings.SettingsPreferenceFragment;
+import com.android.settings.search.BaseSearchIndexProvider;
+import com.android.settings.search.Indexable;
+
+import org.omnirom.omnigears.preference.SystemSettingSwitchPreference;
+import org.omnirom.omnigears.preference.AppSelectListPreference;
+import org.omnirom.omnigears.preference.AppSelectListPreference.PackageItem;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class NotificationLightSettings extends SettingsPreferenceFragment implements
+ Preference.OnPreferenceChangeListener, NotificationLightPreference.ItemLongClickListener, Indexable {
+ private static final String TAG = "NotificationLightSettings";
+
+ private static final String NOTIFICATION_LIGHT_PULSE_DEFAULT_COLOR = "notification_light_pulse_default_color";
+ private static final String NOTIFICATION_LIGHT_PULSE_DEFAULT_LED_ON = "notification_light_pulse_default_led_on";
+ private static final String NOTIFICATION_LIGHT_PULSE_DEFAULT_LED_OFF = "notification_light_pulse_default_led_off";
+ private static final String DEFAULT_PREF = "default";
+ 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 SystemSettingSwitchPreference mEnabledPref;
+ private SystemSettingSwitchPreference mScreenOnLightsPref;
+ private SystemSettingSwitchPreference mCustomEnabledPref;
+ private NotificationLightPreference mDefaultPref;
+ private Menu mMenu;
+ private AppSelectListPreference mPackageAdapter;
+ private String mPackageList;
+ private Map<String, Package> mPackages;
+ private boolean mMultiColorLed;
+
+ @Override
+ protected int getMetricsCategory() {
+ return MetricsEvent.OMNI_SETTINGS;
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ addPreferencesFromResource(R.xml.notification_light_settings);
+
+ 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 = (NotificationLightPreference) findPreference(DEFAULT_PREF);
+ mDefaultPref.setOnPreferenceChangeListener(this);
+
+ mScreenOnLightsPref = (SystemSettingSwitchPreference)
+ findPreference(Settings.System.NOTIFICATION_LIGHT_SCREEN_ON);
+ mScreenOnLightsPref.setOnPreferenceChangeListener(this);
+
+ // Advanced light settings
+ mCustomEnabledPref = (SystemSettingSwitchPreference)
+ findPreference(Settings.System.NOTIFICATION_LIGHT_PULSE_CUSTOM_ENABLE);
+ mCustomEnabledPref.setOnPreferenceChangeListener(this);
+
+ mApplicationPrefList = (PreferenceGroup) findPreference("applications_list");
+ mApplicationPrefList.setOrderingAsAdded(false);
+
+ // Get launch-able applications
+ mPackageManager = getPackageManager();
+ mPackageAdapter = new AppSelectListPreference(getActivity());
+
+ mPackages = new HashMap<String, Package>();
+ setHasOptionsMenu(true);
+
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ refreshDefault();
+ refreshCustomApplicationPrefs();
+ getActivity().invalidateOptionsMenu();
+ }
+
+ private void refreshDefault() {
+ ContentResolver resolver = getContentResolver();
+ int color = Settings.System.getInt(resolver,
+ Settings.System.NOTIFICATION_LIGHT_PULSE_DEFAULT_COLOR, mDefaultColor);
+ int timeOn = Settings.System.getInt(resolver,
+ Settings.System.NOTIFICATION_LIGHT_PULSE_DEFAULT_LED_ON, mDefaultLedOn);
+ int timeOff = Settings.System.getInt(resolver,
+ Settings.System.NOTIFICATION_LIGHT_PULSE_DEFAULT_LED_OFF, mDefaultLedOff);
+
+ mDefaultPref.setAllValues(color, timeOn, timeOff);
+
+ mApplicationPrefList = (PreferenceGroup) findPreference("applications_list");
+ mApplicationPrefList.setOrderingAsAdded(false);
+ }
+
+ 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);
+ NotificationLightPreference pref =
+ new NotificationLightPreference(context, 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 (mApplicationPrefList.getPreferenceCount() == 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 = true;
+ int color = mDefaultColor;
+ if (autoColor) {
+ try {
+ Drawable icon = mPackageManager.getApplicationIcon(packageName);
+ color = ColorUtils.getIconColorFromDrawable(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 = Settings.System.getString(getContentResolver(),
+ Settings.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;
+ }
+ Settings.System.putString(getContentResolver(),
+ Settings.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 = getContentResolver();
+
+ if (packageName.equals(DEFAULT_PREF)) {
+ Settings.System.putInt(resolver, Settings.System.NOTIFICATION_LIGHT_PULSE_DEFAULT_COLOR, color);
+ Settings.System.putInt(resolver, Settings.System.NOTIFICATION_LIGHT_PULSE_DEFAULT_LED_ON, timeon);
+ Settings.System.putInt(resolver, Settings.System.NOTIFICATION_LIGHT_PULSE_DEFAULT_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 = getContentResolver();
+
+ // Reset to the framework default colors
+ Settings.System.putInt(resolver, Settings.System.NOTIFICATION_LIGHT_PULSE_DEFAULT_COLOR, mDefaultColor);
+
+ refreshDefault();
+ }
+
+ public boolean onItemLongClick(final String key) {
+ final NotificationLightPreference pref =
+ (NotificationLightPreference) 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;
+ }
+
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object objValue) {
+ if (preference == mEnabledPref || preference == mCustomEnabledPref ||
+ preference == mScreenOnLightsPref) {
+ getActivity().invalidateOptionsMenu();
+ } else {
+ NotificationLightPreference lightPref = (NotificationLightPreference) 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.profiles_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.profile_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;
+ }
+ }
+
+ }
+
+/* public static final Indexable.SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
+ new BaseSearchIndexProvider() {
+ @Override
+ public List<SearchIndexableResource> getXmlResourcesToIndex(Context context,
+ boolean enabled) {
+ ArrayList<SearchIndexableResource> result =
+ new ArrayList<SearchIndexableResource>();
+
+ SearchIndexableResource sir = new SearchIndexableResource(context);
+ sir.xmlResId = R.xml.battery_light_settings;
+ result.add(sir);
+ return result;
+ }
+
+ @Override
+ public List<String> getNonIndexableKeys(Context context) {
+ ArrayList<String> result = new ArrayList<String>();
+ final Resources res = context.getResources();
+ if (!res.getBoolean(com.android.internal.R.bool.config_intrusiveBatteryLed)) {
+ result.add(BATTERY_LIGHT_PREF);
+ result.add(BATTERY_PULSE_PREF);
+ }
+ if (!res.getBoolean(com.android.internal.R.bool.config_multiColorBatteryLed)) {
+ result.add(LOW_COLOR_PREF);
+ result.add(MEDIUM_COLOR_PREF);
+ result.add(FULL_COLOR_PREF);
+ result.add(REALLY_FULL_COLOR_PREF);
+ }
+ return result;
+ }
+ }; */
+}
diff --git a/src/org/omnirom/omnigears/preference/AppSelectListPreference.java b/src/org/omnirom/omnigears/preference/AppSelectListPreference.java
index ff2ce2f..59f9a34 100644
--- a/src/org/omnirom/omnigears/preference/AppSelectListPreference.java
+++ b/src/org/omnirom/omnigears/preference/AppSelectListPreference.java
@@ -1,213 +1,186 @@
/*
- * Copyright (C) 2016 The OmniROM Project
+ * Copyright (C) 2012-2014 The CyanogenMod Project
*
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 2 of the License, or
- * (at your option) any later version.
+ * 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
*
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
+ * http://www.apache.org/licenses/LICENSE-2.0
*
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * 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.omnirom.omnigears.preference;
-import android.app.AlertDialog;
-import android.app.AlertDialog.Builder;
-import android.app.Dialog;
import android.content.Context;
-import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
-import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
-import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
-import android.os.Bundle;
-import android.preference.DialogPreference;
-import android.util.AttributeSet;
-import android.util.Log;
+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.AdapterView;
-import android.widget.ArrayAdapter;
-import android.widget.CheckBox;
+import android.widget.BaseAdapter;
import android.widget.ImageView;
-import android.widget.ListView;
import android.widget.TextView;
import com.android.settings.R;
-import java.text.Collator;
-import java.util.ArrayList;
import java.util.Collections;
-import java.util.Comparator;
-import java.util.HashSet;
+import java.util.LinkedList;
import java.util.List;
-import java.util.Set;
+import java.util.TreeSet;
-public class AppSelectListPreference extends DialogPreference {
- private final List<MyApplicationInfo> mPackageInfoList = new ArrayList<MyApplicationInfo>();
- private AppListAdapter mAdapter;
- private String mReturnValue;
+public class AppSelectListPreference extends BaseAdapter implements Runnable {
+ private PackageManager mPm;
+ private LayoutInflater mInflater;
+ private List<PackageItem> mInstalledPackages = new LinkedList<PackageItem>();
- public AppSelectListPreference(Context context) {
- this(context, null);
- }
+ // 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 */
+ };
- public AppSelectListPreference(Context context, AttributeSet attrs) {
- super(context, attrs);
-
- setDialogLayoutResource(R.layout.preference_app_list);
-
- final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
- mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
- List<ResolveInfo> installedAppsInfo = getContext().getPackageManager().queryIntentActivities(
- mainIntent, 0);
-
- for (ResolveInfo info : installedAppsInfo) {
- MyApplicationInfo myInfo = new MyApplicationInfo();
- myInfo.resolveInfo = info;
- myInfo.label = getResolveInfoTitle(info);
- mPackageInfoList.add(myInfo);
- }
- Collections.sort(mPackageInfoList, sDisplayNameComparator);
- }
-
- public String getValue() {
- return mReturnValue;
- }
-
- @Override
- protected void onBindDialogView(View view) {
- super.onBindDialogView(view);
-
- mAdapter = new AppListAdapter(getContext());
- final ListView listView = (ListView) view.findViewById(R.id.app_list);
- listView.setAdapter(mAdapter);
- listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
- @Override
- public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
- final AppViewHolder holder = (AppViewHolder) view.getTag();
-
- MyApplicationInfo myInfo = mAdapter.getItem(position);
- ResolveInfo info = myInfo.resolveInfo;
- Intent intent = getIntentForResolveInfo(info, Intent.ACTION_MAIN);
- intent.addCategory(Intent.CATEGORY_LAUNCHER);
-
- mReturnValue = intent.toUri(0).toString();
- AppSelectListPreference.this.onClick(getDialog(), DialogInterface.BUTTON_POSITIVE);
- getDialog().dismiss();
- }
- });
- }
-
- @Override
- protected void onPrepareDialogBuilder(Builder builder) {
- super.onPrepareDialogBuilder(builder);
- builder.setPositiveButton(null, null);
- }
-
- @Override
- protected void onDialogClosed(boolean positiveResult) {
- super.onDialogClosed(positiveResult);
- callChangeListener(mReturnValue);
- }
-
- private String getResolveInfoTitle(ResolveInfo info) {
- CharSequence label = info.loadLabel(getContext().getPackageManager());
- if (label == null) label = info.activityInfo.name;
- return label != null ? label.toString() : null;
- }
-
- private Intent getIntentForResolveInfo(ResolveInfo info, String action) {
- Intent intent = new Intent(action);
- ActivityInfo ai = info.activityInfo;
- intent.setClassName(ai.packageName, ai.name);
- return intent;
- }
-
- class MyApplicationInfo {
- ApplicationInfo info;
- CharSequence label;
- ResolveInfo resolveInfo;
- }
-
- public class AppListAdapter extends ArrayAdapter<MyApplicationInfo> {
- private final LayoutInflater mInflater;
-
- public AppListAdapter(Context context) {
- super(context, 0);
- mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- addAll(mPackageInfoList);
- }
-
+ private final Handler mHandler = new Handler() {
@Override
- public View getView(int position, View convertView, ViewGroup parent) {
- // A ViewHolder keeps references to children views to avoid unnecessary calls
- // to findViewById() on each row.
- AppViewHolder holder = AppViewHolder.createOrRecycle(mInflater, convertView);
- convertView = holder.rootView;
- MyApplicationInfo info = getItem(position);
- holder.appName.setText(info.label);
- Drawable icon = info.resolveInfo.loadIcon(getContext().getPackageManager());
- if (icon != null) {
- holder.appIcon.setImageDrawable(icon);
+ 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 {
- holder.appIcon.setImageDrawable(null);
+ mInstalledPackages.get(index).activityTitles.addAll(item.activityTitles);
}
- return convertView;
- }
-
- @Override
- public MyApplicationInfo getItem(int position) {
- return mPackageInfoList.get(position);
- }
- }
-
- public static class AppViewHolder {
- public View rootView;
- public TextView appName;
- public ImageView appIcon;
- public CheckBox checkBox;
-
- public static AppViewHolder createOrRecycle(LayoutInflater inflater, View convertView) {
- if (convertView == null) {
- convertView = inflater.inflate(R.layout.app_select_item, null);
-
- // Creates a ViewHolder and store references to the two children views
- // we want to bind data to.
- AppViewHolder holder = new AppViewHolder();
- holder.rootView = convertView;
- holder.appName = (TextView) convertView.findViewById(R.id.app_name);
- holder.appIcon = (ImageView) convertView.findViewById(R.id.app_icon);
- holder.checkBox = (CheckBox) convertView.findViewById(android.R.id.checkbox);
- holder.checkBox.setVisibility(View.GONE);
- convertView.setTag(holder);
- return holder;
- } else {
- // Get the ViewHolder back to get fast access to the TextView
- // and the ImageView.
- return (AppViewHolder)convertView.getTag();
- }
- }
- }
-
- private final static Comparator<MyApplicationInfo> sDisplayNameComparator
- = new Comparator<MyApplicationInfo>() {
-
- private final Collator collator = Collator.getInstance();
-
- public final int compare(MyApplicationInfo a, MyApplicationInfo b) {
- return collator.compare(a.label, b.label);
+ 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 AppSelectListPreference(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;
+ }
+}