diff options
-rw-r--r-- | Android.mk | 2 | ||||
-rw-r--r-- | res/layout/dialog_notification_lights.xml | 116 | ||||
-rw-r--r-- | res/layout/dialog_notification_lights_title.xml | 49 | ||||
-rw-r--r-- | res/layout/notification_pulse_time_item.xml | 25 | ||||
-rw-r--r-- | res/layout/preference_folder_notification_light.xml | 114 | ||||
-rw-r--r-- | res/values/cm_arrays.xml | 32 | ||||
-rw-r--r-- | res/values/cm_dimens.xml | 20 | ||||
-rw-r--r-- | res/values/cm_strings.xml | 22 | ||||
-rw-r--r-- | src/com/android/mail/preferences/FolderPreferences.java | 47 | ||||
-rw-r--r-- | src/com/android/mail/preferences/notifications/AlphaPatternDrawable.java | 125 | ||||
-rw-r--r-- | src/com/android/mail/preferences/notifications/ColorPanelView.java | 171 | ||||
-rw-r--r-- | src/com/android/mail/preferences/notifications/ColorPickerView.java | 860 | ||||
-rw-r--r-- | src/com/android/mail/preferences/notifications/FolderNotificationLightPreference.java | 307 | ||||
-rw-r--r-- | src/com/android/mail/preferences/notifications/LightSettingsDialog.java | 448 | ||||
-rw-r--r-- | src/com/android/mail/utils/NotificationUtils.java | 38 |
15 files changed, 2358 insertions, 18 deletions
diff --git a/Android.mk b/Android.mk index 2f81831f0..8e8a21238 100644 --- a/Android.mk +++ b/Android.mk @@ -45,8 +45,6 @@ LOCAL_STATIC_JAVA_LIBRARIES += android-opt-datetimepicker LOCAL_STATIC_JAVA_LIBRARIES += owasp-html-sanitizer LOCAL_STATIC_JAVA_LIBRARIES += com.android.emailcommon -LOCAL_SDK_VERSION := current - LOCAL_SRC_FILES := $(call all-java-files-under, $(src_dirs)) \ $(call all-logtags-files-under, $(src_dirs)) LOCAL_RESOURCE_DIR := $(addprefix $(LOCAL_PATH)/, $(res_dirs)) diff --git a/res/layout/dialog_notification_lights.xml b/res/layout/dialog_notification_lights.xml new file mode 100644 index 000000000..343cc41b4 --- /dev/null +++ b/res/layout/dialog_notification_lights.xml @@ -0,0 +1,116 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2010 Daniel Nilsson + 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. +--> +<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" > + + <com.android.mail.preferences.notifications.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" /> + + <com.android.mail.preferences.notifications.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/dialog_notification_lights_title.xml b/res/layout/dialog_notification_lights_title.xml new file mode 100644 index 000000000..b03518a79 --- /dev/null +++ b/res/layout/dialog_notification_lights_title.xml @@ -0,0 +1,49 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2010 Daniel Nilsson + 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. +--> + +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="wrap_content"> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal" + android:gravity="center_vertical" + android:layout_marginTop="10dip" + android:layout_marginBottom="10dip" + android:layout_marginStart="16dip" + android:layout_marginEnd="16dip"> + + <com.android.internal.widget.DialogTitle android:id="@android:id/title" + style="@android:style/TextAppearance.DialogWindowTitle" + android:singleLine="true" + android:ellipsize="end" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1" + android:textStyle="bold" + android:textAlignment="viewStart" /> + + <Switch + android:id="@android:id/toggle" + android:layout_width="wrap_content" + android:layout_height="wrap_content" /> + + </LinearLayout> +</FrameLayout> diff --git a/res/layout/notification_pulse_time_item.xml b/res/layout/notification_pulse_time_item.xml new file mode 100644 index 000000000..49076736a --- /dev/null +++ b/res/layout/notification_pulse_time_item.xml @@ -0,0 +1,25 @@ +<?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. +--> +<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" />
\ No newline at end of file diff --git a/res/layout/preference_folder_notification_light.xml b/res/layout/preference_folder_notification_light.xml new file mode 100644 index 000000000..952a8074e --- /dev/null +++ b/res/layout/preference_folder_notification_light.xml @@ -0,0 +1,114 @@ +<?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. +--> + +<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> + + <FrameLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content"> + + <LinearLayout + android:id="@+id/lights_status" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="horizontal"> + + <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> + + <TextView + android:id="@+id/lights_default" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center_vertical|end" + android:textAppearance="@android:style/TextAppearance.Material.Notification.Line2" + android:text="@string/pref_lights_default" /> + </FrameLayout> + +</LinearLayout> diff --git a/res/values/cm_arrays.xml b/res/values/cm_arrays.xml index 3177784d5..6a2b33ee5 100644 --- a/res/values/cm_arrays.xml +++ b/res/values/cm_arrays.xml @@ -31,4 +31,36 @@ <item>recents</item> <item>all</item> </string-array> + + <!-- Values for the notification lights 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/cm_dimens.xml b/res/values/cm_dimens.xml new file mode 100644 index 000000000..3e46f1358 --- /dev/null +++ b/res/values/cm_dimens.xml @@ -0,0 +1,20 @@ +<?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> + <dimen name="notification_lights_button_width">16dip</dimen> + <dimen name="notification_lights_button_height">32dip</dimen> +</resources> diff --git a/res/values/cm_strings.xml b/res/values/cm_strings.xml index 7e0c4a48e..7a7fd112f 100644 --- a/res/values/cm_strings.xml +++ b/res/values/cm_strings.xml @@ -30,4 +30,26 @@ <string name="suggested_contacts_cleared">Suggested contacts cleared.</string> <string name="clear_suggested_contacts_dialog_title">Clear suggested contacts?</string> <string name="clear_suggested_contacts_dialog_message">All the suggested contacts previously stored will be removed.</string> + + <!-- Notification lights dialogs --> + <string name="edit_light_settings">Folder 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="pref_lights_default">Default</string> + + <!-- Values for the notification lights 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> </resources> diff --git a/src/com/android/mail/preferences/FolderPreferences.java b/src/com/android/mail/preferences/FolderPreferences.java index fe007d609..a34ba940a 100644 --- a/src/com/android/mail/preferences/FolderPreferences.java +++ b/src/com/android/mail/preferences/FolderPreferences.java @@ -21,7 +21,9 @@ import android.database.Cursor; import android.media.RingtoneManager; import android.net.Uri; import android.provider.Settings; +import android.text.TextUtils; +import com.android.mail.preferences.notifications.FolderNotificationLightPreference; import com.android.mail.providers.Account; import com.android.mail.providers.Folder; import com.android.mail.providers.UIProvider.AccountCapabilities; @@ -48,6 +50,8 @@ public class FolderPreferences extends VersionedPrefs { public static final String NOTIFICATION_RINGTONE = "notification-ringtone"; /** Boolean value indicating whether we should explicitly vibrate */ public static final String NOTIFICATION_VIBRATE = "notification-vibrate"; + /** Boolean value indicating whether the notification has a custom notificaition light */ + public static final String NOTIFICATION_LIGHTS = "notification-lights"; /** * Boolean value indicating whether we notify for every message (<code>true</code>), or just * once for the folder (<code>false</code>) @@ -60,10 +64,43 @@ public class FolderPreferences extends VersionedPrefs { .add(NOTIFICATIONS_ENABLED) .add(NOTIFICATION_RINGTONE) .add(NOTIFICATION_VIBRATE) + .add(NOTIFICATION_LIGHTS) .add(NOTIFICATION_NOTIFY_EVERY_MESSAGE) .build(); } + public static final class NotificationLight { + public final boolean mOn; + public final int mColor; + public final int mTimeOn; + public final int mTimeOff; + + private NotificationLight(boolean on, int color, int timeOn, int timeOff) { + mOn = on; + mColor = color; + mTimeOn = timeOn; + mTimeOff = timeOff; + } + + public String toStringPref() { + if (!mOn) { + return ""; + } + return TextUtils.join("|", new Integer[]{mColor, mTimeOn, mTimeOff}); + } + + public static NotificationLight fromStringPref(String pref) { + if (TextUtils.isEmpty(pref)) { + return new NotificationLight(false, FolderNotificationLightPreference.DEFAULT_COLOR, + FolderNotificationLightPreference.DEFAULT_TIME, + FolderNotificationLightPreference.DEFAULT_TIME); + } + String[] data = pref.split("\\|"); + return new NotificationLight(true, Integer.parseInt(data[0]), + Integer.parseInt(data[1]), Integer.parseInt(data[2])); + } + } + private final Folder mFolder; /** An id that is constant across app installations. */ private final String mPersistentId; @@ -236,6 +273,16 @@ public class FolderPreferences extends VersionedPrefs { notifyBackupPreferenceChanged(); } + public NotificationLight getNotificationLight() { + String pref = getSharedPreferences().getString(PreferenceKeys.NOTIFICATION_LIGHTS, ""); + return NotificationLight.fromStringPref(pref); + } + + public void setNotificationLights(final NotificationLight lights) { + getEditor().putString(PreferenceKeys.NOTIFICATION_LIGHTS, lights.toStringPref()).apply(); + notifyBackupPreferenceChanged(); + } + public boolean isEveryMessageNotificationEnabled() { return getSharedPreferences() .getBoolean(PreferenceKeys.NOTIFICATION_NOTIFY_EVERY_MESSAGE, false); diff --git a/src/com/android/mail/preferences/notifications/AlphaPatternDrawable.java b/src/com/android/mail/preferences/notifications/AlphaPatternDrawable.java new file mode 100644 index 000000000..70daf6cbb --- /dev/null +++ b/src/com/android/mail/preferences/notifications/AlphaPatternDrawable.java @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2010 Daniel Nilsson + * Copyright (C) 2015 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.mail.preferences.notifications; + +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/com/android/mail/preferences/notifications/ColorPanelView.java b/src/com/android/mail/preferences/notifications/ColorPanelView.java new file mode 100644 index 000000000..604bf0fcc --- /dev/null +++ b/src/com/android/mail/preferences/notifications/ColorPanelView.java @@ -0,0 +1,171 @@ +/* + * Copyright (C) 2010 Daniel Nilsson + * Copyright (C) 2015 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.mail.preferences.notifications; + +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/com/android/mail/preferences/notifications/ColorPickerView.java b/src/com/android/mail/preferences/notifications/ColorPickerView.java new file mode 100644 index 000000000..87bdb8a05 --- /dev/null +++ b/src/com/android/mail/preferences/notifications/ColorPickerView.java @@ -0,0 +1,860 @@ +/* + * Copyright (C) 2010 Daniel Nilsson + * Copyright (C) 2015 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.mail.preferences.notifications; + +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 + public void setEnabled(boolean enabled) { + super.setEnabled(enabled); + postInvalidate(); + } + + @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); + + if (isEnabled()) { + 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); + + if (isEnabled()) { + 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 (isEnabled()) { + 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) { + if (!isEnabled()) { + return super.onTrackballEvent(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) { + if (!isEnabled()) { + return super.onTouchEvent(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/com/android/mail/preferences/notifications/FolderNotificationLightPreference.java b/src/com/android/mail/preferences/notifications/FolderNotificationLightPreference.java new file mode 100644 index 000000000..a224d413b --- /dev/null +++ b/src/com/android/mail/preferences/notifications/FolderNotificationLightPreference.java @@ -0,0 +1,307 @@ +/* + * Copyright (C) 2015 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.mail.preferences.notifications; + +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.preference.DialogPreference; +import android.text.TextUtils; +import android.util.AttributeSet; +import android.view.View; +import android.widget.ImageView; +import android.widget.TextView; + +import com.android.mail.R; + +public class FolderNotificationLightPreference extends DialogPreference { + + public static final int DEFAULT_TIME = 1000; + public static final int DEFAULT_COLOR = 0xffffff; + + private View mLightsStatusView; + private View mLightsDefaultView; + private ImageView mLightColorView; + private TextView mOnValueView; + private TextView mOffValueView; + + private boolean mOn; + private int mColorValue; + private int mOnValue; + private int mOffValue; + private boolean mOnOffChangeable; + + private Resources mResources; + + /** + * @param context + * @param attrs + */ + public FolderNotificationLightPreference(Context context, AttributeSet attrs) { + super(context, attrs); + mOn = false; + mColorValue = DEFAULT_COLOR; + mOnValue = DEFAULT_TIME; + mOffValue = DEFAULT_TIME; + mOnOffChangeable = true; + init(); + } + + /** + * @param context + * @param color + * @param onValue + * @param offValue + */ + public FolderNotificationLightPreference(Context context, int color, + int onValue, int offValue) { + super(context, null); + mOn = false; + mColorValue = color; + mOnValue = onValue; + mOffValue = offValue; + mOnOffChangeable = true; + init(); + } + + /** + * @param context + * @param color + * @param onValue + * @param offValue + */ + public FolderNotificationLightPreference(Context context, int color, int onValue, + int offValue, boolean onOffChangeable) { + super(context, null); + mOn = false; + mColorValue = color; + mOnValue = onValue; + mOffValue = offValue; + mOnOffChangeable = onOffChangeable; + init(); + } + + private void init() { + setLayoutResource(R.layout.preference_folder_notification_light); + mResources = getContext().getResources(); + } + + public void onStart() { + LightSettingsDialog d = (LightSettingsDialog) getDialog(); + if (d != null) { + d.onStart(); + } + } + + public void onStop() { + LightSettingsDialog d = (LightSettingsDialog) getDialog(); + if (d != null) { + d.onStop(); + } + } + + @Override + protected void onBindView(View view) { + super.onBindView(view); + + mLightsStatusView = view.findViewById(R.id.lights_status); + mLightsDefaultView = view.findViewById(R.id.lights_default); + mLightColorView = (ImageView) view.findViewById(R.id.light_color); + mOnValueView = (TextView) view.findViewById(R.id.textViewTimeOnValue); + mOffValueView = (TextView) view.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) view.findViewById(android.R.id.summary); + tView.setVisibility(View.GONE); + + updatePreferenceViews(); + } + + private void updatePreferenceViews() { + final int width = (int) mResources.getDimension(R.dimen.notification_lights_button_width); + final int height = (int) mResources.getDimension(R.dimen.notification_lights_button_height); + + if (mLightColorView != null) { + mLightColorView.setEnabled(true); + // adjust if necessary to prevent material whiteout + final int imageColor = ((mColorValue & 0xF0F0F0) == 0xF0F0F0) ? + (mColorValue - 0x101010) : mColorValue; + mLightColorView.setImageDrawable(createRectShape(width, height, + 0xFF000000 + imageColor)); + } + if (mOnValueView != null) { + mOnValueView.setText(mapLengthValue(mOnValue)); + } + if (mOffValueView != null) { + if (mOnValue == 1) { + mOffValueView.setVisibility(View.GONE); + } else { + mOffValueView.setVisibility(View.VISIBLE); + } + mOffValueView.setText(mapSpeedValue(mOffValue)); + } + + if (mLightsStatusView != null) { + mLightsStatusView.setVisibility(mOn ? View.VISIBLE : View.GONE); + } + if (mLightsDefaultView != null) { + mLightsDefaultView.setVisibility(!mOn ? View.VISIBLE : View.GONE); + } + } + + @Override + protected Dialog createDialog() { + final LightSettingsDialog d = new LightSettingsDialog(getContext(), mOn, + 0xFF000000 + mColorValue, mOnValue, mOffValue, mOnOffChangeable); + d.setAlphaSliderVisible(false); + + d.setButton(AlertDialog.BUTTON_POSITIVE, mResources.getString(android.R.string.ok), + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + mOn = d.getEnabled(); + mColorValue = d.getColor() - 0xFF000000; // strip alpha, led does not support it + mOnValue = d.getPulseSpeedOn(); + mOffValue = d.getPulseSpeedOff(); + updatePreferenceViews(); + callChangeListener(mOn + ? TextUtils.join("|", new Integer[]{mColorValue, mOnValue, mOffValue}) + : ""); + } + }); + d.setButton(AlertDialog.BUTTON_NEGATIVE, mResources.getString(R.string.cancel), + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + } + }); + + return d; + } + + public boolean getOn() { + return mOn; + } + + public void setOn(boolean on) { + mOn = on; + updatePreferenceViews(); + } + + 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; + mOnOffChangeable = true; + 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 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; + } + + private String mapLengthValue(Integer time) { + if (time == DEFAULT_TIME) { + return getContext().getString(R.string.default_time); + } + + String[] timeNames = mResources.getStringArray(R.array.notification_pulse_length_entries); + String[] timeValues = mResources.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().getString(R.string.custom_time); + } + + private String mapSpeedValue(Integer time) { + if (time == DEFAULT_TIME) { + return getContext().getString(R.string.default_time); + } + + String[] timeNames = mResources.getStringArray(R.array.notification_pulse_speed_entries); + String[] timeValues = mResources.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().getString(R.string.custom_time); + } +} diff --git a/src/com/android/mail/preferences/notifications/LightSettingsDialog.java b/src/com/android/mail/preferences/notifications/LightSettingsDialog.java new file mode 100644 index 000000000..4abfe353a --- /dev/null +++ b/src/com/android/mail/preferences/notifications/LightSettingsDialog.java @@ -0,0 +1,448 @@ +/* + * Copyright (C) 2010 Daniel Nilsson + * Copyright (C) 2015 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.mail.preferences.notifications; + +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.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.CompoundButton; +import android.widget.CompoundButton.OnCheckedChangeListener; +import android.widget.EditText; +import android.widget.Spinner; +import android.widget.SpinnerAdapter; +import android.widget.Switch; +import android.widget.TextView; + +import com.android.mail.R; + +import java.util.ArrayList; +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 Switch mSwitch; + + private ColorPickerView mColorPicker; + + private EditText mHexColorInput; + private ColorPanelView mNewColor; + private Spinner mPulseSpeedOn; + private Spinner mPulseSpeedOff; + private LayoutInflater mInflater; + + private NotificationManager mNotificationManager; + + private boolean mReadyForLed; + private boolean mLedLastEnabled; + private int mLedLastColor; + private int mLedLastSpeedOn; + private int mLedLastSpeedOff; + + private boolean mFromResume; + + protected LightSettingsDialog(Context context, boolean enabled, int initialColor, + int initialSpeedOn, int initialSpeedOff) { + super(context); + + init(context, enabled, initialColor, initialSpeedOn, initialSpeedOff, true); + } + + protected LightSettingsDialog(Context context, boolean enabled, int initialColor, + int initialSpeedOn, int initialSpeedOff, boolean onOffChangeable) { + super(context); + + init(context, enabled, initialColor, initialSpeedOn, initialSpeedOff, onOffChangeable); + } + + private void init(Context context, boolean enabled, int color, int speedOn, int speedOff, + boolean onOffChangeable) { + mNotificationManager = + (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); + + mReadyForLed = false; + mLedLastColor = 0; + mLedLastEnabled = enabled; + + // To fight color banding. + getWindow().setFormat(PixelFormat.RGBA_8888); + setUp(enabled, 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 enabled - if the dialog is enabled + * @param color - the color to set + * @param speedOn - the flash time in ms + * @param speedOff - the flash length in ms + */ + private void setUp(boolean enabled, int color, int speedOn, + int speedOff, boolean onOffChangeable) { + mInflater = (LayoutInflater) getContext() + .getSystemService(Context.LAYOUT_INFLATER_SERVICE); + View layout = mInflater.inflate(R.layout.dialog_notification_lights, null); + View title = mInflater.inflate(R.layout.dialog_notification_lights_title, 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); + + mColorPicker.setOnColorChangedListener(this); + mColorPicker.setColor(color, true); + + mHexColorInput.setOnFocusChangeListener(this); + mPulseSpeedOn = (Spinner) layout.findViewById(R.id.on_spinner); + 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); + + mPulseSpeedOff = (Spinner) layout.findViewById(R.id.off_spinner); + 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); + + mPulseSpeedOn.setEnabled(onOffChangeable); + mPulseSpeedOff.setEnabled((speedOn != 1) && onOffChangeable); + + setView(layout); + + TextView titleView = (TextView) title.findViewById(android.R.id.title); + mSwitch = (Switch) title.findViewById(android.R.id.toggle); + titleView.setText(R.string.edit_light_settings); + setCustomTitle(title); + + mSwitch.setChecked(enabled); + updateDialogEnableState(enabled); + mSwitch.setOnCheckedChangeListener(new OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + updateDialogEnableState(isChecked); + } + }); + + mReadyForLed = true; + updateLed(); + } + + private void updateDialogEnableState(boolean enabled) { + mColorPicker.setEnabled(enabled); + mHexColorInput.setEnabled(enabled); + mNewColor.setEnabled(enabled); + mPulseSpeedOn.setEnabled(enabled); + mPulseSpeedOff.setEnabled(enabled); + if (enabled) { + updateLed(); + } else { + dismissLed(); + } + mLedLastEnabled = enabled; + } + + 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() { + dismissLed(); + mFromResume = true; + + Bundle state = super.onSaveInstanceState(); + state.putInt(STATE_KEY_COLOR, getColor()); + return state; + } + + @Override + public void onWindowFocusChanged(boolean hasFocus) { + if (hasFocus && mFromResume) { + updateLed(); + } + mFromResume = false; + } + + @Override + public void onRestoreInstanceState(Bundle state) { + updateLed(); + 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)); + + updateLed(); + } + + public void setAlphaSliderVisible(boolean visible) { + mHexColorInput.setFilters(new InputFilter[] { new InputFilter.LengthFilter(visible ? 8 : 6) } ); + mColorPicker.setAlphaSliderVisible(visible); + } + + public boolean getEnabled() { + return mSwitch.isChecked(); + } + + public int getColor() { + return mColorPicker.getColor(); + } + + @SuppressWarnings("unchecked") + public int getPulseSpeedOn() { + return ((Pair<String, Integer>) mPulseSpeedOn.getSelectedItem()).second; + } + + @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 || !mSwitch.isChecked()) { + return; + } + + final boolean enabled = mSwitch.isChecked(); + final int color = getColor() & 0xFFFFFF; + final int speedOn, speedOff; + if (mPulseSpeedOn.isEnabled()) { + speedOn = getPulseSpeedOn(); + speedOff = getPulseSpeedOff(); + } else { + speedOn = 1; + speedOff = 0; + } + + if (mLedLastEnabled == enabled && 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); + + mLedLastEnabled = enabled; + 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); + + mNotificationManager.notify(R.layout.notification_pulse_time_item, builder.build()); + } + + public void dismissLed() { + mNotificationManager.cancel(R.layout.notification_pulse_time_item); + // 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.notification_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(); + } 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/com/android/mail/utils/NotificationUtils.java b/src/com/android/mail/utils/NotificationUtils.java index 910870248..d990d2d2e 100644 --- a/src/com/android/mail/utils/NotificationUtils.java +++ b/src/com/android/mail/utils/NotificationUtils.java @@ -56,6 +56,7 @@ import com.android.mail.photomanager.LetterTileProvider; import com.android.mail.preferences.AccountPreferences; import com.android.mail.preferences.FolderPreferences; import com.android.mail.preferences.MailPrefs; +import com.android.mail.preferences.FolderPreferences.NotificationLight; import com.android.mail.providers.Account; import com.android.mail.providers.Conversation; import com.android.mail.providers.Folder; @@ -547,6 +548,8 @@ public class NotificationUtils { final Account account, boolean getAttention, boolean ignoreUnobtrusiveSetting, NotificationKey key, final ContactFetcher contactFetcher) { + final Resources res = context.getResources(); + // Check that the folder supports notifications, prior to create all the // NotificationManager stuff final boolean isInbox = folder.folderUri.equals(account.settings.defaultInbox); @@ -648,8 +651,7 @@ public class NotificationUtils { new ArrayMap<Integer, NotificationBuilders>(); if (com.android.mail.utils.Utils.isRunningLOrLater()) { - notification.setColor( - context.getResources().getColor(R.color.notification_icon_color)); + notification.setColor(res.getColor(R.color.notification_icon_color)); } if(unseenCount > 1) { @@ -772,25 +774,29 @@ public class NotificationUtils { * oldWhen check. */ if (getAttention && oldWhen == 0 && hasNewConversationNotification) { - final AccountPreferences accountPreferences = - new AccountPreferences(context, account.getAccountId()); - if (accountPreferences.areNotificationsEnabled()) { - if (vibrate) { - defaults |= Notification.DEFAULT_VIBRATE; - } - - notification.setSound(TextUtils.isEmpty(ringtoneUri) ? null - : Uri.parse(ringtoneUri)); - LogUtils.i(LOG_TAG, "New email in %s vibrateWhen: %s, playing notification: %s", - LogUtils.sanitizeName(LOG_TAG, account.getEmailAddress()), vibrate, - ringtoneUri); + if (vibrate) { + defaults |= Notification.DEFAULT_VIBRATE; } + + notification.setSound(TextUtils.isEmpty(ringtoneUri) ? null + : Uri.parse(ringtoneUri)); + LogUtils.i(LOG_TAG, "New email in %s vibrateWhen: %s, playing notification: %s", + LogUtils.sanitizeName(LOG_TAG, account.getEmailAddress()), vibrate, + ringtoneUri); } // TODO(skennedy) Why do we do any of the above if we're just going to bail here? if (eventInfoConfigured) { - defaults |= Notification.DEFAULT_LIGHTS; - notification.setDefaults(defaults); + boolean isArgbNotifColorSupported = res.getBoolean( + com.android.internal.R.bool.config_multiColorNotificationLed); + NotificationLight notificationLight = folderPreferences.getNotificationLight(); + if (isArgbNotifColorSupported && notificationLight.mOn) { + notification.setLights(notificationLight.mColor, + notificationLight.mTimeOn, notificationLight.mTimeOff); + } else { + defaults |= Notification.DEFAULT_LIGHTS; + notification.setDefaults(defaults); + } if (oldWhen != 0) { // We do not want to display the ticker again if we are re-displaying this |