OmniControl: use FabricatedOverlay for custom accent color

Change-Id: I489b27e6350e1b2100fcc1f02d5d23ed993e61ce
diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml
index c08862a..0577e1d 100644
--- a/.idea/codeStyles/Project.xml
+++ b/.idea/codeStyles/Project.xml
@@ -24,9 +24,6 @@
         </value>
       </option>
     </JavaCodeStyleSettings>
-    <JetCodeStyleSettings>
-      <option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
-    </JetCodeStyleSettings>
     <codeStyleSettings language="XML">
       <indentOptions>
         <option name="CONTINUATION_INDENT_SIZE" value="4" />
@@ -139,8 +136,5 @@
         </rules>
       </arrangement>
     </codeStyleSettings>
-    <codeStyleSettings language="kotlin">
-      <option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
-    </codeStyleSettings>
   </code_scheme>
 </component>
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
index fbd59d8..4a39a00 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -6,16 +6,22 @@
         <entry key="../../../../../../../layout/custom_preview.xml" value="0.5130208333333334" />
         <entry key="../../../../layout/custom_preview.xml" value="0.2526041666666667" />
         <entry key="app/src/main/res/drawable-v24/ic_launcher_foreground.xml" value="0.3067708333333333" />
+        <entry key="app/src/main/res/drawable/accent_color_item_background.xml" value="0.4306306306306306" />
+        <entry key="app/src/main/res/drawable/add_accent_color_item_background.xml" value="0.4436936936936937" />
         <entry key="app/src/main/res/drawable/applist_icon.xml" value="0.30885416666666665" />
         <entry key="app/src/main/res/drawable/bottom_navigation_selector.xml" value="0.3067708333333333" />
         <entry key="app/src/main/res/drawable/check_circle.xml" value="0.5130208333333334" />
         <entry key="app/src/main/res/drawable/check_circle_outline.xml" value="0.5130208333333334" />
         <entry key="app/src/main/res/drawable/check_circle_shape.xml" value="0.30885416666666665" />
+        <entry key="app/src/main/res/drawable/edit_accent_color_item_background.xml" value="0.4418918918918919" />
         <entry key="app/src/main/res/drawable/enabled_selector.xml" value="0.5130208333333334" />
         <entry key="app/src/main/res/drawable/grid_item_background.xml" value="0.30833333333333335" />
         <entry key="app/src/main/res/drawable/grid_item_background_shape.xml" value="0.3104166666666667" />
+        <entry key="app/src/main/res/drawable/ic_add.xml" value="0.26756756756756755" />
         <entry key="app/src/main/res/drawable/ic_bars_tile.xml" value="0.30885416666666665" />
+        <entry key="app/src/main/res/drawable/ic_baseline_check_circle_24.xml" value="0.4436936936936937" />
         <entry key="app/src/main/res/drawable/ic_baseline_circle_24.xml" value="0.30885416666666665" />
+        <entry key="app/src/main/res/drawable/ic_edit_color.xml" value="0.4436936936936937" />
         <entry key="app/src/main/res/drawable/ic_google.xml" value="0.5051282051282051" />
         <entry key="app/src/main/res/drawable/ic_homepage_bars.xml" value="0.5109375" />
         <entry key="app/src/main/res/drawable/ic_info_outline.xml" value="0.5130208333333334" />
@@ -32,13 +38,23 @@
         <entry key="app/src/main/res/drawable/overlay_item_background.xml" value="0.5109375" />
         <entry key="app/src/main/res/drawable/overlay_item_background_shape.xml" value="0.5130208333333334" />
         <entry key="app/src/main/res/drawable/settings_icon.xml" value="0.30885416666666665" />
+        <entry key="app/src/main/res/layout-land/color_select_dialog.xml" value="0.42151675485008816" />
+        <entry key="app/src/main/res/layout/accent_colors_item.xml" value="0.4306306306306306" />
+        <entry key="app/src/main/res/layout/add_accent_colors_item.xml" value="0.4306306306306306" />
         <entry key="app/src/main/res/layout/color_item.xml" value="0.3677536231884058" />
+        <entry key="app/src/main/res/layout/color_preset_item.xml" value="0.4519927536231884" />
+        <entry key="app/src/main/res/layout/color_select_dialog.xml" value="0.4519927536231884" />
         <entry key="app/src/main/res/layout/colors_item.xml" value="0.75" />
+        <entry key="app/src/main/res/layout/dialog_battery_settings.xml" value="0.2942708333333333" />
+        <entry key="app/src/main/res/layout/edit_accent_colors_item.xml" value="0.4306306306306306" />
+        <entry key="app/src/main/res/layout/edit_dark_accent_colors_item.xml" value="0.1" />
+        <entry key="app/src/main/res/layout/edit_light_accent_colors_item.xml" value="0.4306306306306306" />
         <entry key="app/src/main/res/layout/grid_fragment.xml" value="0.3020833333333333" />
         <entry key="app/src/main/res/layout/grid_item.xml" value="0.29270833333333335" />
         <entry key="app/src/main/res/layout/icon_shape_item.xml" value="0.9" />
         <entry key="app/src/main/res/layout/overlays_fragment.xml" value="0.29270833333333335" />
         <entry key="app/src/main/res/layout/overlays_item.xml" value="0.3423913043478261" />
+        <entry key="app/src/main/res/layout/primary_colors_item.xml" value="0.4306306306306306" />
         <entry key="app/src/main/res/layout/settings_activity.xml" value="0.259375" />
         <entry key="app/src/main/res/menu/bottom_navbar.xml" value="0.23541666666666666" />
         <entry key="app/src/main/res/menu/bottom_navigation.xml" value="0.2375" />
diff --git a/app/src/main/java/org/omnirom/control/OverlaysFragment.kt b/app/src/main/java/org/omnirom/control/OverlaysFragment.kt
index bf91a74..c5c8fd2 100644
--- a/app/src/main/java/org/omnirom/control/OverlaysFragment.kt
+++ b/app/src/main/java/org/omnirom/control/OverlaysFragment.kt
@@ -17,11 +17,9 @@
  */
 package org.omnirom.control
 
-import android.content.BroadcastReceiver
 import android.content.Context
-import android.content.Intent
-import android.content.IntentFilter
 import android.content.res.Resources.NotFoundException
+import android.graphics.Color
 import android.graphics.Path
 import android.graphics.drawable.AdaptiveIconDrawable
 import android.graphics.drawable.ColorDrawable
@@ -29,18 +27,19 @@
 import android.graphics.drawable.ShapeDrawable
 import android.graphics.drawable.shapes.PathShape
 import android.os.Bundle
-import android.os.PatternMatcher
-import android.util.Log
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
+import android.view.*
 import android.widget.ImageView
 import android.widget.LinearLayout
+import androidx.appcompat.app.AlertDialog
 import androidx.appcompat.app.AppCompatActivity
 import androidx.core.graphics.PathParser
 import androidx.fragment.app.Fragment
 import androidx.recyclerview.widget.LinearLayoutManager
 import androidx.recyclerview.widget.RecyclerView
+import com.google.android.material.snackbar.Snackbar
+import org.json.JSONArray
+import org.json.JSONObject
+import org.omnirom.control.widget.ColorSelectDialog
 import org.omnirom.control.widget.DynamicAdaptiveIconDrawable
 
 
@@ -53,34 +52,46 @@
 
     private val iconShapeGridItems = ArrayList<IconShapeGridItem>()
     private val primaryColorsGridItems = ArrayList<ColorsGridItem>()
-    private val accentColorsGridItems = ArrayList<ColorsGridItem>()
+    private val accentColorsGridItems = ArrayList<AccentColorsGridItem>()
     private val overlayItemList = ArrayList<View>()
+    private val customAccentColors = ArrayList<CustomAccentColor>()
 
     private val TAG = "OverlaysFragment"
 
     val COLORS_NEUTRAL1 = arrayOf(
-        "system_neutral1_50",
-        "system_neutral1_100",
-        "system_neutral1_200",
-        "system_neutral1_300",
-        "system_neutral1_400",
-        "system_neutral1_500",
-        "system_neutral1_600",
-        "system_neutral1_700",
-        "system_neutral1_800",
-        "system_neutral1_900"
+            "system_neutral1_50",
+            "system_neutral1_100",
+            "system_neutral1_200",
+            "system_neutral1_300",
+            "system_neutral1_400",
+            "system_neutral1_500",
+            "system_neutral1_600",
+            "system_neutral1_700",
+            "system_neutral1_800",
+            "system_neutral1_900"
     )
 
     val COLORS_ACCENT1 = arrayOf(
-        "system_accent1_100",
-        "system_accent1_600"
+            "system_accent1_100",
+            "system_accent1_600"
+    )
+
+    val CUSTOM_ACCENT_COLORS = arrayOf(
+            Color.parseColor("#a1c729"),
+            Color.parseColor("#ff8000"),
+            Color.parseColor("#a020f0"),
+            Color.parseColor("#ff005a"),
+            Color.parseColor("#e5141b"),
+            Color.parseColor("#f0b50b"),
+            Color.parseColor("#009ed8"),
+            Color.parseColor("#00897b"),
     )
 
     abstract class GridItem(
-        packageName: String,
-        isEnabled: Boolean,
-        isSystem: Boolean,
-        category: String
+            packageName: String,
+            isEnabled: Boolean,
+            isSystem: Boolean,
+            category: String
     ) {
         val packageName: String = packageName
         val isEnabled: Boolean = isEnabled
@@ -89,41 +100,70 @@
     }
 
     class IconShapeGridItem(
-        packageName: String,
-        iconShape: String,
-        isEnabled: Boolean,
-        isSystem: Boolean,
-        category: String
+            packageName: String,
+            iconShape: String,
+            isEnabled: Boolean,
+            isSystem: Boolean,
+            category: String
     ) : GridItem(packageName, isEnabled, isSystem, category) {
         val iconShape: String = iconShape
     }
 
-    class ColorsGridItem(
-        packageName: String,
-        colorList: ArrayList<Int>,
-        isEnabled: Boolean,
-        isSystem: Boolean,
-        category: String
+    open class ColorsGridItem(
+            packageName: String,
+            colorList: ArrayList<Int>,
+            isEnabled: Boolean,
+            isSystem: Boolean,
+            category: String
     ) : GridItem(packageName, isEnabled, isSystem, category) {
         val colorList: ArrayList<Int> = colorList
     }
 
+    open class AccentColorsGridItem(
+            packageName: String,
+            colorList: ArrayList<Int>,
+            isEnabled: Boolean,
+            isSystem: Boolean,
+            category: String
+    ) : ColorsGridItem(packageName, colorList, isEnabled, isSystem, category) {
+    }
+
+    class FabricatedAccentColorsGridItem(packageName: String,
+                                         colorList: ArrayList<Int>,
+                                         isEnabled: Boolean,
+                                         isSystem: Boolean,
+                                         category: String,
+                                         customAccentColor: CustomAccentColor
+    ) : AccentColorsGridItem(packageName, colorList, isEnabled, isSystem, category) {
+        val customAccentColor = customAccentColor
+    }
+
+    class AddAccentColorsGridItem(
+    ) : AccentColorsGridItem("", ArrayList(), false, false, "") {
+    }
+
+    class CustomAccentColor(var identifier: String, var darkColor: Int, var lightColor: Int) {
+        fun toJSONObject(): JSONObject {
+            val color = JSONObject()
+            color.put("identifier", identifier)
+            color.put("system_accent1_100", darkColor)
+            color.put("system_accent1_600", lightColor)
+            return color
+        }
+    }
+
     inner class IconShapeListViewAdapter(
-        val context: Context,
-        val gridItems: ArrayList<IconShapeGridItem>
+            val context: Context,
+            val gridItems: ArrayList<IconShapeGridItem>
     ) :
-        RecyclerView.Adapter<RecyclerView.ViewHolder>() {
+            RecyclerView.Adapter<RecyclerView.ViewHolder>() {
         private val iconShapeSize = context.resources.getDimensionPixelSize(R.dimen.icon_shape_size)
         private val PATH_SIZE = 100f
 
         inner class IconShapeListItem(view: View) : RecyclerView.ViewHolder(view) {
-            val shapeImage: ImageView
-            val enabledImage: ImageView
+            val shapeImage: ImageView = view.findViewById(R.id.icon_shape_icon)
+            val enabledImage: ImageView = view.findViewById(R.id.icon_shape_icon_enabled)
 
-            init {
-                shapeImage = view.findViewById(R.id.icon_shape_icon)
-                enabledImage = view.findViewById(R.id.icon_shape_icon_enabled)
-            }
         }
 
         override fun getItemCount(): Int {
@@ -132,7 +172,7 @@
 
         override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
             return IconShapeListItem(
-                LayoutInflater.from(context).inflate(R.layout.icon_shape_item, parent, false)
+                    LayoutInflater.from(context).inflate(R.layout.icon_shape_item, parent, false)
             )
         }
 
@@ -140,18 +180,18 @@
             if (holder is IconShapeListItem) {
                 val gridItem: IconShapeGridItem = gridItems[position]
                 holder.shapeImage.setImageDrawable(
-                    createShapeIconDrawable(
-                        PathParser.createPathFromPathData(
-                            gridItem.iconShape
-                        ), context.resources.getDrawable(R.mipmap.ic_launcher, null)
-                    )
+                        createShapeIconDrawable(
+                                PathParser.createPathFromPathData(
+                                        gridItem.iconShape
+                                ), context.resources.getDrawable(R.mipmap.ic_launcher, null)
+                        )
                 )
                 if (gridItem.isEnabled || (gridItem.isSystem && !isOverlayEnabled())) {
                     holder.enabledImage.visibility =
-                        View.VISIBLE
+                            View.VISIBLE
                 } else {
                     holder.enabledImage.visibility =
-                        View.GONE
+                            View.GONE
                 }
                 holder.itemView.setOnClickListener {
                     if (gridItem.isSystem) {
@@ -176,8 +216,8 @@
         private fun createShapeIconDrawable(path: Path, appIcon: Drawable): Drawable {
             if (appIcon is AdaptiveIconDrawable) {
                 return DynamicAdaptiveIconDrawable(
-                    appIcon.background,
-                    appIcon.foreground, path
+                        appIcon.background,
+                        appIcon.foreground, path
                 )
             }
             return createShapeDrawable(path)
@@ -188,12 +228,11 @@
         }
     }
 
-    inner class ColorsListViewAdapter(
-        val context: Context,
-        val gridItems: ArrayList<ColorsGridItem>,
-        val isPrimary: Boolean
+    inner class PrimaryColorsListViewAdapter(
+            val context: Context,
+            private val gridItems: ArrayList<ColorsGridItem>
     ) :
-        RecyclerView.Adapter<RecyclerView.ViewHolder>() {
+            RecyclerView.Adapter<RecyclerView.ViewHolder>() {
 
         inner class ColorsListItem(view: View) : RecyclerView.ViewHolder(view) {
             val colorList: LinearLayout = view.findViewById(R.id.colors_item_list)
@@ -206,7 +245,7 @@
 
         override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
             return ColorsListItem(
-                LayoutInflater.from(context).inflate(R.layout.colors_item, parent, false)
+                    LayoutInflater.from(context).inflate(R.layout.primary_colors_item, parent, false)
             )
         }
 
@@ -217,34 +256,29 @@
 
                 for (color in gridItem.colorList) {
                     val colorItem: ImageView =
-                        LayoutInflater.from(context).inflate(R.layout.color_item, null) as ImageView
+                            LayoutInflater.from(context).inflate(R.layout.color_item, null) as ImageView
                     colorItem.setImageDrawable(createColorDrawable(color))
                     holder.colorList.addView(
-                        colorItem,
-                        context.resources.getDimensionPixelSize(R.dimen.color_item_width),
-                        context.resources.getDimensionPixelSize(R.dimen.color_item_height)
+                            colorItem,
+                            context.resources.getDimensionPixelSize(R.dimen.color_item_width),
+                            context.resources.getDimensionPixelSize(R.dimen.color_item_height)
                     )
                 }
 
                 if (gridItem.isEnabled || (gridItem.isSystem && !isOverlayEnabled())) {
                     holder.enabledImage.visibility =
-                        View.VISIBLE
+                            View.VISIBLE
                 } else {
                     holder.enabledImage.visibility =
-                        View.GONE
+                            View.GONE
                 }
-                holder.itemView.setOnClickListener {
-                    val gridItem: ColorsGridItem = gridItems.get(position)
+                holder.colorList.setOnClickListener {
                     if (gridItem.isSystem) {
                         overlayProvider.disableAllInCategory(gridItem.category)
                     } else if (!gridItem.isEnabled) {
                         overlayProvider.enableOverlayExclusive(gridItem.packageName)
                     }
-                    if (isPrimary) {
-                        loadPrimaryColorsOverlays()
-                    } else {
-                        loadAccentColorsOverlays()
-                    }
+                    loadPrimaryColorsOverlays()
                     notifyDataSetChanged()
                 }
             }
@@ -259,11 +293,150 @@
         }
     }
 
-    inner class OverlayItemListViewAdapter(
-        val context: Context,
-        val overlayItems: ArrayList<View>
+    inner class AccentColorsListViewAdapter(
+            val context: Context,
+            private val gridItems: ArrayList<AccentColorsGridItem>
     ) :
-        RecyclerView.Adapter<RecyclerView.ViewHolder>() {
+            RecyclerView.Adapter<RecyclerView.ViewHolder>() {
+
+        open inner class AccentColorsListItem(view: View) : RecyclerView.ViewHolder(view) {
+            val colorList: LinearLayout = view.findViewById(R.id.colors_item_list)
+            val enabledImage: ImageView = view.findViewById(R.id.colors_enabled)
+        }
+
+        inner class FabricatedAccentColorsListItem(view: View) : AccentColorsListItem(view) {
+            var accentColorDarkBackground: ImageView? = null
+            var accentColorLightBackground: ImageView? = null
+        }
+
+        inner class AddAccentColorsListItem(view: View) : RecyclerView.ViewHolder(view) {
+        }
+
+        override fun getItemCount(): Int {
+            return gridItems.size
+        }
+
+        override fun getItemViewType(position: Int): Int {
+            if (position == gridItems.size - 1)
+                return 0
+            else if (gridItems[position] is FabricatedAccentColorsGridItem)
+                return 1
+            return 2
+        }
+
+        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
+            return if (viewType == 0) {
+                AddAccentColorsListItem(
+                        LayoutInflater.from(context).inflate(R.layout.add_accent_colors_item, parent, false)
+                )
+            } else if (viewType == 1) {
+                FabricatedAccentColorsListItem(
+                        LayoutInflater.from(context).inflate(R.layout.accent_colors_item, parent, false)
+                )
+            } else {
+                AccentColorsListItem(
+                        LayoutInflater.from(context).inflate(R.layout.accent_colors_item, parent, false)
+                )
+            }
+        }
+
+        override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
+            val gridItem: AccentColorsGridItem = gridItems[position]
+
+            if (holder is AddAccentColorsListItem) {
+                holder.itemView.setOnClickListener {
+                    addNewCustomAccentColor()
+                    loadAccentColorsOverlays()
+                    notifyDataSetChanged()
+                }
+            } else if (holder is AccentColorsListItem) {
+                holder.colorList.removeAllViews()
+                if (holder is FabricatedAccentColorsListItem) {
+                    val v = LayoutInflater.from(context).inflate(R.layout.edit_dark_accent_colors_item, null) as ImageView
+                    holder.colorList.addView(
+                            v,
+                            context.resources.getDimensionPixelSize(R.dimen.color_item_width),
+                            context.resources.getDimensionPixelSize(R.dimen.edit_accent_color_item_height)
+                    )
+                    holder.accentColorDarkBackground = v
+                }
+                for (color in gridItem.colorList) {
+                    val colorItem: ImageView =
+                            LayoutInflater.from(context).inflate(R.layout.color_item, null) as ImageView
+                    colorItem.setImageDrawable(createColorDrawable(color))
+                    holder.colorList.addView(
+                            colorItem,
+                            context.resources.getDimensionPixelSize(R.dimen.color_item_width),
+                            context.resources.getDimensionPixelSize(R.dimen.accent_color_item_height)
+                    )
+                }
+                if (holder is FabricatedAccentColorsListItem) {
+                    val v = LayoutInflater.from(context).inflate(R.layout.edit_light_accent_colors_item, null) as ImageView
+                    holder.colorList.addView(
+                            v,
+                            context.resources.getDimensionPixelSize(R.dimen.color_item_width),
+                            context.resources.getDimensionPixelSize(R.dimen.edit_accent_color_item_height)
+                    )
+                    holder.accentColorLightBackground = v
+                }
+
+                if (gridItem.isEnabled || (gridItem.isSystem && !isOverlayEnabled())) {
+                    holder.enabledImage.visibility =
+                            View.VISIBLE
+                } else {
+                    holder.enabledImage.visibility =
+                            View.GONE
+                }
+
+                if (holder is FabricatedAccentColorsListItem) {
+                    holder.colorList.setOnClickListener {
+                        if (!gridItem.isEnabled) {
+                            overlayProvider.enableFabricatedAccentOverlayTransaction((gridItem as FabricatedAccentColorsGridItem).customAccentColor)
+                        }
+                    }
+                    //registerForContextMenu(holder.colorList)
+                    holder.colorList.setOnLongClickListener {
+                        doDeleteCustomAccentColor(gridItem as FabricatedAccentColorsGridItem)
+                        true
+                    }
+                    if (holder.accentColorDarkBackground != null) {
+                        holder.accentColorDarkBackground!!.setOnClickListener {
+                            setCustomAccentColor(gridItem as FabricatedAccentColorsGridItem, true)
+                        }
+                    }
+                    if (holder.accentColorLightBackground != null) {
+                        holder.accentColorLightBackground!!.setOnClickListener {
+                            setCustomAccentColor(gridItem as FabricatedAccentColorsGridItem, false)
+                        }
+                    }
+                } else {
+                    holder.colorList.setOnClickListener {
+                        if (gridItem.isSystem) {
+                            overlayProvider.enableAccentOverlayTransaction(null)
+                        } else if (!gridItem.isEnabled) {
+                            overlayProvider.enableAccentOverlayTransaction(gridItem.packageName)
+                        }
+                        loadAccentColorsOverlays()
+                        notifyDataSetChanged()
+                    }
+                }
+            }
+        }
+
+        private fun createColorDrawable(color: Int): ColorDrawable {
+            return ColorDrawable(color)
+        }
+
+        private fun isOverlayEnabled(): Boolean {
+            return gridItems.any { it.isEnabled }
+        }
+    }
+
+    inner class OverlayItemListViewAdapter(
+            val context: Context,
+            val overlayItems: ArrayList<View>
+    ) :
+            RecyclerView.Adapter<RecyclerView.ViewHolder>() {
 
         inner class OverlayListItem(view: View) : RecyclerView.ViewHolder(view) {
         }
@@ -296,16 +469,16 @@
         super.onResume()
         (activity as? AppCompatActivity)?.supportActionBar?.setDisplayHomeAsUpEnabled(true)
         (activity as? SettingsActivity)?.updateFragmentTitle(
-            getFragmentTitle(),
-            getFragmentSummary(),
-            getFragmentIcon()
+                getFragmentTitle(),
+                getFragmentSummary(),
+                getFragmentIcon()
         )
     }
 
     override fun onCreateView(
-        inflater: LayoutInflater,
-        container: ViewGroup?,
-        savedInstanceState: Bundle?
+            inflater: LayoutInflater,
+            container: ViewGroup?,
+            savedInstanceState: Bundle?
     ): View? {
         overlayProvider = OverlaysProvider(requireContext())
         overlayProvider.init()
@@ -315,26 +488,26 @@
     private fun loadIconShapeOverlays() {
         iconShapeGridItems.clear();
         iconShapeGridItems.add(
-            IconShapeGridItem(
-                "android",
-                overlayProvider.getSystemIconMask(),
-                false,
-                true,
-                overlayProvider.getIconShapeCategory()
-            )
+                IconShapeGridItem(
+                        "android",
+                        overlayProvider.getSystemIconMask(),
+                        false,
+                        true,
+                        overlayProvider.getIconShapeCategory()
+                )
         )
-        for (packageName in overlayProvider.getIconShapeOverlays()) {
+        for (packageName in overlayProvider.getIconShapeOverlays().sorted()) {
             try {
                 val iconMask = overlayProvider.loadString("config_icon_mask", packageName)
                 iconShapeGridItems.add(
-                    IconShapeGridItem(
-                        packageName,
-                        iconMask,
-                        overlayProvider.isOverlayEnabled(packageName),
-                        false,
-                        overlayProvider.getIconShapeCategory()
+                        IconShapeGridItem(
+                                packageName,
+                                iconMask,
+                                overlayProvider.isOverlayEnabled(packageName),
+                                false,
+                                overlayProvider.getIconShapeCategory()
 
-                    )
+                        )
                 )
             } catch (e: NotFoundException) {
             }
@@ -348,29 +521,29 @@
             colorList.add(overlayProvider.getSystemColor(color))
         }
         primaryColorsGridItems.add(
-            ColorsGridItem(
-                "android",
-                colorList,
-                false,
-                true,
-                overlayProvider.getPrimaryColorCategory()
-            )
+                ColorsGridItem(
+                        "android",
+                        colorList,
+                        false,
+                        true,
+                        overlayProvider.getPrimaryColorCategory()
+                )
         )
-        for (packageName in overlayProvider.getPrimaryColorOverlays()) {
+        for (packageName in overlayProvider.getPrimaryColorOverlays().sorted()) {
             try {
-                val colorList = ArrayList<Int>()
+                val overlayColorList = ArrayList<Int>()
                 for (color in COLORS_NEUTRAL1) {
-                    colorList.add(overlayProvider.loadColor(color, packageName))
+                    overlayColorList.add(overlayProvider.loadColor(color, packageName))
                 }
 
                 primaryColorsGridItems.add(
-                    ColorsGridItem(
-                        packageName,
-                        colorList,
-                        overlayProvider.isOverlayEnabled(packageName),
-                        false,
-                        overlayProvider.getPrimaryColorCategory()
-                    )
+                        ColorsGridItem(
+                                packageName,
+                                overlayColorList,
+                                overlayProvider.isOverlayEnabled(packageName),
+                                false,
+                                overlayProvider.getPrimaryColorCategory()
+                        )
                 )
             } catch (e: NotFoundException) {
             }
@@ -384,67 +557,219 @@
             colorList.add(overlayProvider.getSystemColor(color))
         }
         accentColorsGridItems.add(
-            ColorsGridItem(
-                "android",
-                colorList,
-                false,
-                true,
-                overlayProvider.getAccentColorCategory()
-            )
+                AccentColorsGridItem(
+                        "android",
+                        colorList,
+                        false,
+                        true,
+                        overlayProvider.getAccentColorCategory()
+                )
         )
-        for (packageName in overlayProvider.getAccentColorOverlays()) {
+        for (packageName in overlayProvider.getAccentColorOverlays().sorted()) {
             try {
-                val colorList = ArrayList<Int>()
+                val overlayColorList = ArrayList<Int>()
                 for (color in COLORS_ACCENT1) {
-                    colorList.add(overlayProvider.loadColor(color, packageName))
+                    overlayColorList.add(overlayProvider.loadColor(color, packageName))
                 }
 
                 accentColorsGridItems.add(
-                    ColorsGridItem(
-                        packageName,
-                        colorList,
-                        overlayProvider.isOverlayEnabled(packageName),
-                        false,
-                        overlayProvider.getAccentColorCategory()
-                    )
+                        AccentColorsGridItem(
+                                packageName,
+                                overlayColorList,
+                                overlayProvider.isOverlayEnabled(packageName),
+                                false,
+                                overlayProvider.getAccentColorCategory()
+                        )
                 )
             } catch (e: NotFoundException) {
             }
         }
+        // add all custom colors
+        for (customColor in customAccentColors) {
+            val customColorList = ArrayList<Int>()
+            customColorList.add(customColor.darkColor)
+            customColorList.add(customColor.lightColor)
+            accentColorsGridItems.add(
+                    FabricatedAccentColorsGridItem(
+                            overlayProvider.getFabricatedOverlayIdentifier(customColor.identifier),
+                            customColorList,
+                            overlayProvider.isOverlayEnabled(overlayProvider.getFabricatedOverlayIdentifier(customColor.identifier)),
+                            false,
+                            overlayProvider.getAccentColorCategory(),
+                            customColor
+                    )
+            )
+        }
+        accentColorsGridItems.add(AddAccentColorsGridItem())
     }
 
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
         super.onViewCreated(view, savedInstanceState)
 
+        loadCustomAccentColors()
         loadIconShapeOverlays()
         loadPrimaryColorsOverlays()
         loadAccentColorsOverlays()
 
         overlayItemList.clear()
         val overlayItemView =
-            LayoutInflater.from(context).inflate(R.layout.overlays_item, null, false)
+                LayoutInflater.from(context).inflate(R.layout.overlays_item, null, false)
         overlayItemList.add(overlayItemView)
 
         iconShapeListView = overlayItemView.findViewById(R.id.icon_shape_list_view)
         iconShapeListView.layoutManager =
-            LinearLayoutManager(requireContext(), LinearLayoutManager.HORIZONTAL, false)
+                LinearLayoutManager(requireContext(), LinearLayoutManager.HORIZONTAL, false)
         iconShapeListView.adapter = IconShapeListViewAdapter(requireContext(), iconShapeGridItems)
 
         primaryColorsListView = overlayItemView.findViewById(R.id.primary_color_list_view)
         primaryColorsListView.layoutManager =
-            LinearLayoutManager(requireContext(), LinearLayoutManager.HORIZONTAL, false)
+                LinearLayoutManager(requireContext(), LinearLayoutManager.HORIZONTAL, false)
         primaryColorsListView.adapter =
-            ColorsListViewAdapter(requireContext(), primaryColorsGridItems, true)
+                PrimaryColorsListViewAdapter(requireContext(), primaryColorsGridItems)
 
         accentColorsListView = overlayItemView.findViewById(R.id.accent_color_list_view)
         accentColorsListView.layoutManager =
-            LinearLayoutManager(requireContext(), LinearLayoutManager.HORIZONTAL, false)
+                LinearLayoutManager(requireContext(), LinearLayoutManager.HORIZONTAL, false)
         accentColorsListView.adapter =
-            ColorsListViewAdapter(requireContext(), accentColorsGridItems, false)
+                AccentColorsListViewAdapter(requireContext(), accentColorsGridItems)
 
         overlayItemListView = view.findViewById(R.id.overlay_item_list)
         overlayItemListView.adapter = OverlayItemListViewAdapter(requireContext(), overlayItemList)
         overlayItemListView.layoutManager =
-            LinearLayoutManager(requireContext(), LinearLayoutManager.VERTICAL, false)
+                LinearLayoutManager(requireContext(), LinearLayoutManager.VERTICAL, false)
+    }
+
+    private fun setCustomAccentColor(accentColor: FabricatedAccentColorsGridItem, changeDark: Boolean) {
+        val color = accentColor.customAccentColor
+        val oldColor = if (changeDark) color.darkColor else color.lightColor
+        val dialog = ColorSelectDialog(
+                requireContext(),
+                oldColor, false, CUSTOM_ACCENT_COLORS
+        )
+        dialog.setButton(
+                AlertDialog.BUTTON_POSITIVE, resources.getString(android.R.string.ok)
+        ) { _, _ ->
+            if (changeDark) color.darkColor = dialog.color
+            else color.lightColor = dialog.color
+            saveCustomAccentColors()
+
+            if (accentColor.isEnabled) {
+                overlayProvider.enableFabricatedAccentOverlayTransaction(color)
+            } else {
+                loadAccentColorsOverlays()
+                accentColorsListView.adapter!!.notifyDataSetChanged()
+            }
+        }
+        dialog.setButton(
+                AlertDialog.BUTTON_NEGATIVE, resources.getString(android.R.string.cancel)
+        ) { _, _ ->
+            dialog.dismiss()
+        }
+        dialog.show()
+    }
+
+    private fun doDeleteCustomAccentColor(accentColor: FabricatedAccentColorsGridItem) {
+        val dialog = AlertDialog.Builder(requireContext())
+        dialog.setMessage(getString(R.string.dialog_delete_accent_color_message))
+        dialog.setPositiveButton(resources.getString(android.R.string.ok)
+        ) { _, _ ->
+            deleteCustomAccentColor(accentColor)
+            loadAccentColorsOverlays()
+            accentColorsListView.adapter!!.notifyDataSetChanged()
+        }
+        dialog.setNegativeButton(resources.getString(android.R.string.cancel), null)
+
+        dialog.show()
+    }
+
+    private fun loadCustomAccentColors() {
+        val prefs = requireContext().getSharedPreferences("overlays", Context.MODE_PRIVATE)
+        val colorsString = prefs.getString("colors", "")
+        val deletedColor = prefs.getString("deletedAccentColor", "")
+
+        if (colorsString != null && colorsString.isNotEmpty()) {
+            val colorArray = JSONArray(colorsString)
+            for (i in 0..colorArray.length()) {
+                try {
+                    val color = colorArray[i] as JSONObject
+                    if (!deletedColor.isNullOrEmpty() && color["identifier"] == deletedColor) {
+                        prefs.edit().remove("deletedAccentColor").commit()
+                        continue
+                    }
+                    customAccentColors.add(
+                            CustomAccentColor(
+                                    color["identifier"] as String,
+                                    color["system_accent1_100"] as Int,
+                                    color["system_accent1_600"] as Int
+                            )
+                    )
+                } catch (e: Exception) {
+                }
+            }
+        }
+    }
+
+    private fun saveCustomAccentColors() {
+        val prefs = requireContext().getSharedPreferences("overlays", Context.MODE_PRIVATE)
+        val colorArray = JSONArray()
+        for (color in customAccentColors) {
+            colorArray.put(color.toJSONObject())
+        }
+        prefs.edit().putString("colors", colorArray.toString()).commit()
+    }
+
+    private fun addNewCustomAccentColor() {
+        val customColor = getDefaultCustomAccentColor()
+        customAccentColors.add(customColor)
+        saveCustomAccentColors()
+        loadAccentColorsOverlays()
+        accentColorsListView.adapter!!.notifyDataSetChanged()
+
+        val prefs = requireContext().getSharedPreferences("overlays", Context.MODE_PRIVATE)
+        val firstTime = prefs.getBoolean("firstTimeAdd", true)
+        if (firstTime) {
+            Snackbar.make(requireView(), getString(R.string.delete_accent_color_info_message), Snackbar.LENGTH_LONG).setAction(android.R.string.ok, { prefs.edit().putBoolean("firstTimeAdd", false).commit() }).show()
+        }
+    }
+
+    private fun getDefaultCustomAccentColor(): CustomAccentColor {
+        val customColor = CustomAccentColor(
+                "",
+                resources.getColor(android.R.color.system_accent1_100, null),
+                resources.getColor(android.R.color.system_accent1_600, null)
+        )
+        customColor.identifier = "accent_color_" + customColor.hashCode()
+        return customColor
+    }
+
+    private fun deleteCustomAccentColor(accentColor: AccentColorsGridItem) {
+        if (accentColor !is FabricatedAccentColorsGridItem) return
+
+        if (accentColor.isEnabled) {
+            val prefs = requireContext().getSharedPreferences("overlays", Context.MODE_PRIVATE)
+            prefs.edit().putString("deletedAccentColor", accentColor.customAccentColor.identifier).commit()
+            // set to system default this will trigger a complete reload anyway
+            overlayProvider.enableAccentOverlayTransaction(null)
+        } else {
+            customAccentColors.remove(accentColor.customAccentColor)
+            saveCustomAccentColors()
+            loadAccentColorsOverlays()
+            accentColorsListView.adapter!!.notifyDataSetChanged()
+        }
+    }
+
+    override fun onCreateContextMenu(menu: ContextMenu, v: View, menuInfo: ContextMenu.ContextMenuInfo?) {
+        super.onCreateContextMenu(menu, v, menuInfo)
+        menu.setHeaderTitle(resources.getString(R.string.accent_color_title));
+        menu.add(0, 0, 0, "Delete");
+    }
+
+    override fun onContextItemSelected(item: MenuItem): Boolean {
+        if (item.itemId == 0) {
+            /*deleteCustomAccentColor(gridItem)
+            loadAccentColorsOverlays()
+            accentColorsListView.adapter!!.notifyDataSetChanged()*/
+        }
+        return super.onContextItemSelected(item)
     }
 }
diff --git a/app/src/main/java/org/omnirom/control/OverlaysProvider.kt b/app/src/main/java/org/omnirom/control/OverlaysProvider.kt
index 2e46d03..f483595 100644
--- a/app/src/main/java/org/omnirom/control/OverlaysProvider.kt
+++ b/app/src/main/java/org/omnirom/control/OverlaysProvider.kt
@@ -18,11 +18,14 @@
 package org.omnirom.control
 
 import android.content.Context
+import android.content.om.FabricatedOverlay
 import android.content.om.OverlayInfo
+import android.content.om.OverlayIdentifier
 import android.content.om.OverlayManager
+import android.content.om.OverlayManagerTransaction
 import android.content.res.Resources
 import android.os.UserHandle
-import android.util.Log
+import android.util.TypedValue
 
 class OverlaysProvider(context: Context) {
     private val overlaysMap = HashMap<String, OverlayInfo>()
@@ -40,9 +43,8 @@
     private fun loadOverlayInfo() {
         if (om != null) {
             overlaysMap.clear()
-            val user: UserHandle = UserHandle.of(UserHandle.myUserId())
             om!!.getOverlayInfosForTarget("android", user).forEach { overlayInfo ->
-                overlaysMap.put(overlayInfo.getPackageName(), overlayInfo)
+                overlaysMap.put(overlayInfo.getOverlayIdentifier().toString(), overlayInfo)
             }
         }
     }
@@ -71,13 +73,20 @@
         return "android.theme.customization.system_palette"
     }
 
-    fun getCategoryOverlays(category: String): ArrayList<String> {
+    private fun getCategoryOverlays(category: String): ArrayList<String> {
         val packageList = ArrayList<String>()
         overlaysMap.values.filter { it.getCategory() == category }
             .forEach { packageList.add(it.getPackageName()) }
         return packageList
     }
 
+    private fun getCategoryOverlaysRaw(category: String): ArrayList<OverlayInfo> {
+        val overlayList = ArrayList<OverlayInfo>()
+        overlaysMap.values.filter { it.getCategory() == category }
+            .forEach { overlayList.add(it) }
+        return overlayList
+    }
+
     @Throws(Resources.NotFoundException::class)
     fun loadString(stringName: String, packageName: String): String {
         val overlayRes: Resources = context.packageManager.getResourcesForApplication(
@@ -116,8 +125,8 @@
         )
     }
 
-    fun isOverlayEnabled(packageName: String): Boolean {
-        val overlayInfo = overlaysMap.get(packageName)
+    fun isOverlayEnabled(identifier: String): Boolean {
+        val overlayInfo = overlaysMap.get(OverlayIdentifier.fromString(identifier).toString())
         if (overlayInfo != null) {
             return overlayInfo.isEnabled()
         }
@@ -130,14 +139,7 @@
         }
     }
 
-    fun enableOverlay(packageName: String, reload: Boolean) {
-        if (om != null) {
-            om!!.setEnabled(packageName, true, user)
-            if (reload) loadOverlayInfo()
-        }
-    }
-
-    fun disableOverlay(packageName: String, reload: Boolean) {
+    private fun disableOverlay(packageName: String, reload: Boolean) {
         if (om != null) {
             om!!.setEnabled(packageName, false, user)
             if (reload) loadOverlayInfo()
@@ -148,4 +150,118 @@
         getCategoryOverlays(category).forEach { disableOverlay(it, false) }
         loadOverlayInfo()
     }
+
+    fun enableAccentOverlayTransaction(
+        overlay: String?
+    ) {
+        if (om != null) {
+            val transaction = OverlayManagerTransaction.Builder()
+            // disable farbricated
+            om!!.getOverlayInfosForTarget("android", user)
+                .forEach { info ->
+                    if (info.isFabricated() && info.getPackageName() == context.packageName) {
+                        transaction.unregisterFabricatedOverlay(info.getOverlayIdentifier())
+                    }
+                }
+            // disable enabled
+            getCategoryOverlaysRaw(getAccentColorCategory()).filter { it.isEnabled() }
+                .forEach {
+                    transaction.setEnabled(
+                        it.getOverlayIdentifier(),
+                        false,
+                        UserHandle.myUserId()
+                    )
+                }
+            // enable new if provided
+            if (overlay != null && overlaysMap.containsKey(OverlayIdentifier.fromString(overlay).toString())) {
+                transaction.setEnabled(
+                    OverlayIdentifier.fromString(overlay),
+                    true,
+                    UserHandle.myUserId()
+                )
+            }
+            om!!.commit(transaction.build())
+        }
+    }
+
+    fun enableFabricatedAccentOverlayTransaction(
+            accentColor: OverlaysFragment.CustomAccentColor
+    ) {
+        if (om != null) {
+            val transaction = OverlayManagerTransaction.Builder()
+            // disable farbricated
+            om!!.getOverlayInfosForTarget("android", user)
+                    .forEach { info ->
+                        if (info.isFabricated() && info.getPackageName() == context.packageName) {
+                            transaction.unregisterFabricatedOverlay(info.getOverlayIdentifier())
+                        }
+                    }
+            // disable enabled
+            getCategoryOverlaysRaw(getAccentColorCategory()).filter { it.isEnabled() }
+                    .forEach {
+                        transaction.setEnabled(
+                                it.getOverlayIdentifier(),
+                                false,
+                                UserHandle.myUserId()
+                        )
+                    }
+            // enable fabricated
+            val accentColorOverlay = FabricatedOverlay.Builder(
+                    context.packageName, accentColor.identifier, "android"
+            )
+                    .setResourceValue(
+                            "@android:color/system_accent1_100",
+                            TypedValue.TYPE_INT_COLOR_ARGB8,
+                            accentColor.darkColor
+                    )
+                    .setResourceValue(
+                            "@android:color/system_accent1_600",
+                            TypedValue.TYPE_INT_COLOR_ARGB8,
+                            accentColor.lightColor
+                    )
+                    .build()
+
+
+            transaction.registerFabricatedOverlay(accentColorOverlay)
+                    .setEnabled(accentColorOverlay!!.getIdentifier(), true, UserHandle.myUserId())
+            om!!.commit(transaction.build())
+        }
+    }
+
+    fun createFabricatedAccentOverlay(color: Int, name: String) {
+        if (om != null) {
+            val accentColorOverlay = FabricatedOverlay.Builder(
+                context.packageName, name, "android"
+            )
+                .setResourceValue(
+                    "@android:color/system_accent1_100",
+                    TypedValue.TYPE_INT_COLOR_ARGB8,
+                    color
+                )
+                .setResourceValue(
+                    "@android:color/system_accent1_600",
+                    TypedValue.TYPE_INT_COLOR_ARGB8,
+                    color
+                )
+                .build()
+
+            // disable all others
+            val transaction = OverlayManagerTransaction.Builder()
+            getCategoryOverlaysRaw(getAccentColorCategory()).filter { it.isEnabled() }
+                .forEach {
+                    transaction.setEnabled(
+                        it.getOverlayIdentifier(),
+                        false,
+                        UserHandle.myUserId()
+                    )
+                }
+            transaction.registerFabricatedOverlay(accentColorOverlay)
+                .setEnabled(accentColorOverlay!!.getIdentifier(), true, UserHandle.myUserId())
+            om!!.commit(transaction.build())
+        }
+    }
+
+    fun getFabricatedOverlayIdentifier(overlayName: String) : String {
+        return OverlayIdentifier(context.packageName, overlayName).toString()
+    }
 }
\ No newline at end of file
diff --git a/app/src/main/java/org/omnirom/control/widget/AlphaPatternDrawable.java b/app/src/main/java/org/omnirom/control/widget/AlphaPatternDrawable.java
new file mode 100644
index 0000000..91be1e3
--- /dev/null
+++ b/app/src/main/java/org/omnirom/control/widget/AlphaPatternDrawable.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2010 Daniel Nilsson
+ * Copyright (C) 2012 The CyanogenMod Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.omnirom.control.widget;
+
+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/app/src/main/java/org/omnirom/control/widget/ColorPanelView.java b/app/src/main/java/org/omnirom/control/widget/ColorPanelView.java
new file mode 100644
index 0000000..9cd42cf
--- /dev/null
+++ b/app/src/main/java/org/omnirom/control/widget/ColorPanelView.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2010 Daniel Nilsson
+ * Copyright (C) 2012 The CyanogenMod Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.omnirom.control.widget;
+
+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/app/src/main/java/org/omnirom/control/widget/ColorPickerView.java b/app/src/main/java/org/omnirom/control/widget/ColorPickerView.java
new file mode 100644
index 0000000..2465f3b
--- /dev/null
+++ b/app/src/main/java/org/omnirom/control/widget/ColorPickerView.java
@@ -0,0 +1,841 @@
+/*
+ * Copyright (C) 2010 Daniel Nilsson
+ * Copyright (C) 2012 The CyanogenMod Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.omnirom.control.widget;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.ComposeShader;
+import android.graphics.LinearGradient;
+import android.graphics.Paint;
+import android.graphics.Point;
+import android.graphics.PorterDuff.Mode;
+import android.graphics.RectF;
+import android.graphics.Shader;
+import android.graphics.Paint.Align;
+import android.graphics.Paint.Style;
+import android.graphics.Shader.TileMode;
+import android.os.Build;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+
+/**
+ * Displays a color picker to the user and allow them to select a color. A
+ * slider for the alpha channel is also available. Enable it by setting
+ * setAlphaSliderVisible(boolean) to true.
+ *
+ * @author Daniel Nilsson
+ */
+public class ColorPickerView extends View {
+
+    public interface OnColorChangedListener {
+        public void onColorChanged(int color);
+    }
+
+    private final static int PANEL_SAT_VAL = 0;
+    private final static int PANEL_HUE = 1;
+    private final static int PANEL_ALPHA = 2;
+
+    /**
+     * The width in pixels of the border surrounding all color panels.
+     */
+    private final static float BORDER_WIDTH_PX = 1;
+
+    /**
+     * The width in dp of the hue panel.
+     */
+    private float HUE_PANEL_WIDTH = 30f;
+    /**
+     * The height in dp of the alpha panel
+     */
+    private float ALPHA_PANEL_HEIGHT = 20f;
+    /**
+     * The distance in dp between the different color panels.
+     */
+    private float PANEL_SPACING = 10f;
+    /**
+     * The radius in dp of the color palette tracker circle.
+     */
+    private float PALETTE_CIRCLE_TRACKER_RADIUS = 5f;
+    /**
+     * The dp which the tracker of the hue or alpha panel will extend outside of
+     * its bounds.
+     */
+    private float RECTANGLE_TRACKER_OFFSET = 2f;
+
+    private static float mDensity = 1f;
+
+    private OnColorChangedListener mListener;
+
+    private Paint mSatValPaint;
+    private Paint mSatValTrackerPaint;
+
+    private Paint mHuePaint;
+    private Paint mHueTrackerPaint;
+
+    private Paint mAlphaPaint;
+    private Paint mAlphaTextPaint;
+
+    private Paint mBorderPaint;
+
+    private Shader mValShader;
+    private Shader mSatShader;
+    private Shader mHueShader;
+    private Shader mAlphaShader;
+
+    private int mAlpha = 0xff;
+    private float mHue = 360f;
+    private float mSat = 0f;
+    private float mVal = 0f;
+
+    private String mAlphaSliderText = "Alpha";
+    private int mSliderTrackerColor = 0xff1c1c1c;
+    private int mBorderColor = 0xff6E6E6E;
+    private boolean mShowAlphaPanel = false;
+
+    /*
+     * To remember which panel that has the "focus" when processing hardware
+     * button data.
+     */
+    private int mLastTouchedPanel = PANEL_SAT_VAL;
+
+    /**
+     * Offset from the edge we must have or else the finger tracker will get
+     * clipped when it is drawn outside of the view.
+     */
+    private float mDrawingOffset;
+
+    /*
+     * Distance form the edges of the view of where we are allowed to draw.
+     */
+    private RectF mDrawingRect;
+
+    private RectF mSatValRect;
+    private RectF mHueRect;
+    private RectF mAlphaRect;
+
+    private AlphaPatternDrawable mAlphaPattern;
+
+    private Point mStartTouchPoint = null;
+
+    public ColorPickerView(Context context) {
+        this(context, null);
+    }
+
+    public ColorPickerView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public ColorPickerView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+        init();
+    }
+
+    private void init() {
+        mDensity = getContext().getResources().getDisplayMetrics().density;
+        PALETTE_CIRCLE_TRACKER_RADIUS *= mDensity;
+        RECTANGLE_TRACKER_OFFSET *= mDensity;
+        HUE_PANEL_WIDTH *= mDensity;
+        ALPHA_PANEL_HEIGHT *= mDensity;
+        PANEL_SPACING = PANEL_SPACING * mDensity;
+
+        mDrawingOffset = calculateRequiredOffset();
+        initPaintTools();
+
+        // Needed for receiving track ball motion events.
+        setFocusableInTouchMode(true);
+        setFocusable(true);
+        setClickable(true);
+    }
+
+    private void initPaintTools() {
+        mSatValPaint = new Paint();
+        mSatValTrackerPaint = new Paint();
+        mHuePaint = new Paint();
+        mHueTrackerPaint = new Paint();
+        mAlphaPaint = new Paint();
+        mAlphaTextPaint = new Paint();
+        mBorderPaint = new Paint();
+
+        mSatValTrackerPaint.setStyle(Style.STROKE);
+        mSatValTrackerPaint.setStrokeWidth(2f * mDensity);
+        mSatValTrackerPaint.setAntiAlias(true);
+
+        mHueTrackerPaint.setColor(mSliderTrackerColor);
+        mHueTrackerPaint.setStyle(Style.STROKE);
+        mHueTrackerPaint.setStrokeWidth(2f * mDensity);
+        mHueTrackerPaint.setAntiAlias(true);
+
+        mAlphaTextPaint.setColor(0xff1c1c1c);
+        mAlphaTextPaint.setTextSize(14f * mDensity);
+        mAlphaTextPaint.setAntiAlias(true);
+        mAlphaTextPaint.setTextAlign(Align.CENTER);
+        mAlphaTextPaint.setFakeBoldText(true);
+    }
+
+    private float calculateRequiredOffset() {
+        float offset = Math.max(PALETTE_CIRCLE_TRACKER_RADIUS, RECTANGLE_TRACKER_OFFSET);
+        offset = Math.max(offset, BORDER_WIDTH_PX * mDensity);
+
+        return offset * 1.5f;
+    }
+
+    private int[] buildHueColorArray() {
+        int[] hue = new int[361];
+
+        int count = 0;
+        for (int i = hue.length - 1; i >= 0; i--, count++) {
+            hue[count] = Color.HSVToColor(new float[] {
+                    i, 1f, 1f
+            });
+        }
+        return hue;
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        if (mDrawingRect.width() <= 0 || mDrawingRect.height() <= 0) {
+            return;
+        }
+        drawSatValPanel(canvas);
+        drawHuePanel(canvas);
+        drawAlphaPanel(canvas);
+    }
+
+    private void drawSatValPanel(Canvas canvas) {
+        final RectF rect = mSatValRect;
+        int rgb = Color.HSVToColor(new float[] {
+                mHue, 1f, 1f
+        });
+
+        if (BORDER_WIDTH_PX > 0) {
+            mBorderPaint.setColor(mBorderColor);
+            canvas.drawRect(mDrawingRect.left, mDrawingRect.top, rect.right + BORDER_WIDTH_PX,
+                    rect.bottom + BORDER_WIDTH_PX, mBorderPaint);
+        }
+
+        // On Honeycomb+ we need to use software rendering to create the shader properly
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
+            setLayerType(View.LAYER_TYPE_SOFTWARE, null);
+        }
+
+        // Get the overlaying gradients ready and create the ComposeShader
+        if (mValShader == null) {
+            mValShader = new LinearGradient(rect.left, rect.top, rect.left, rect.bottom,
+                    0xffffffff, 0xff000000, TileMode.CLAMP);
+        }
+        mSatShader = new LinearGradient(rect.left, rect.top, rect.right, rect.top,
+                0xffffffff, rgb, TileMode.CLAMP);
+        ComposeShader mShader = new ComposeShader(mValShader, mSatShader, Mode.MULTIPLY);
+        mSatValPaint.setShader(mShader);
+        canvas.drawRect(rect, mSatValPaint);
+
+        Point p = satValToPoint(mSat, mVal);
+        mSatValTrackerPaint.setColor(0xff000000);
+        canvas.drawCircle(p.x, p.y, PALETTE_CIRCLE_TRACKER_RADIUS - 1f * mDensity,
+                mSatValTrackerPaint);
+
+        mSatValTrackerPaint.setColor(0xffdddddd);
+        canvas.drawCircle(p.x, p.y, PALETTE_CIRCLE_TRACKER_RADIUS, mSatValTrackerPaint);
+    }
+
+    private void drawHuePanel(Canvas canvas) {
+        final RectF rect = mHueRect;
+
+        if (BORDER_WIDTH_PX > 0) {
+            mBorderPaint.setColor(mBorderColor);
+            canvas.drawRect(rect.left - BORDER_WIDTH_PX,
+                    rect.top - BORDER_WIDTH_PX,
+                    rect.right + BORDER_WIDTH_PX,
+                    rect.bottom + BORDER_WIDTH_PX,
+                    mBorderPaint);
+        }
+
+        if (mHueShader == null) {
+            mHueShader = new LinearGradient(rect.left, rect.top, rect.left, rect.bottom,
+                    buildHueColorArray(), null, TileMode.CLAMP);
+            mHuePaint.setShader(mHueShader);
+        }
+
+        canvas.drawRect(rect, mHuePaint);
+
+        float rectHeight = 4 * mDensity / 2;
+
+        Point p = hueToPoint(mHue);
+
+        RectF r = new RectF();
+        r.left = rect.left - RECTANGLE_TRACKER_OFFSET;
+        r.right = rect.right + RECTANGLE_TRACKER_OFFSET;
+        r.top = p.y - rectHeight;
+        r.bottom = p.y + rectHeight;
+
+        canvas.drawRoundRect(r, 2, 2, mHueTrackerPaint);
+
+    }
+
+    private void drawAlphaPanel(Canvas canvas) {
+        if (!mShowAlphaPanel || mAlphaRect == null || mAlphaPattern == null) {
+            return;
+        }
+
+        final RectF rect = mAlphaRect;
+
+        if (BORDER_WIDTH_PX > 0) {
+            mBorderPaint.setColor(mBorderColor);
+            canvas.drawRect(rect.left - BORDER_WIDTH_PX,
+                    rect.top - BORDER_WIDTH_PX,
+                    rect.right + BORDER_WIDTH_PX,
+                    rect.bottom + BORDER_WIDTH_PX,
+                    mBorderPaint);
+        }
+
+        mAlphaPattern.draw(canvas);
+
+        float[] hsv = new float[] {
+                mHue, mSat, mVal
+        };
+        int color = Color.HSVToColor(hsv);
+        int acolor = Color.HSVToColor(0, hsv);
+
+        mAlphaShader = new LinearGradient(rect.left, rect.top, rect.right, rect.top,
+                color, acolor, TileMode.CLAMP);
+
+        mAlphaPaint.setShader(mAlphaShader);
+
+        canvas.drawRect(rect, mAlphaPaint);
+
+        if (mAlphaSliderText != null && mAlphaSliderText != "") {
+            canvas.drawText(mAlphaSliderText, rect.centerX(), rect.centerY() + 4 * mDensity,
+                    mAlphaTextPaint);
+        }
+
+        float rectWidth = 4 * mDensity / 2;
+        Point p = alphaToPoint(mAlpha);
+
+        RectF r = new RectF();
+        r.left = p.x - rectWidth;
+        r.right = p.x + rectWidth;
+        r.top = rect.top - RECTANGLE_TRACKER_OFFSET;
+        r.bottom = rect.bottom + RECTANGLE_TRACKER_OFFSET;
+
+        canvas.drawRoundRect(r, 2, 2, mHueTrackerPaint);
+    }
+
+    private Point hueToPoint(float hue) {
+        final RectF rect = mHueRect;
+        final float height = rect.height();
+
+        Point p = new Point();
+        p.y = (int) (height - (hue * height / 360f) + rect.top);
+        p.x = (int) rect.left;
+        return p;
+    }
+
+    private Point satValToPoint(float sat, float val) {
+
+        final RectF rect = mSatValRect;
+        final float height = rect.height();
+        final float width = rect.width();
+
+        Point p = new Point();
+
+        p.x = (int) (sat * width + rect.left);
+        p.y = (int) ((1f - val) * height + rect.top);
+
+        return p;
+    }
+
+    private Point alphaToPoint(int alpha) {
+        final RectF rect = mAlphaRect;
+        final float width = rect.width();
+
+        Point p = new Point();
+        p.x = (int) (width - (alpha * width / 0xff) + rect.left);
+        p.y = (int) rect.top;
+        return p;
+    }
+
+    private float[] pointToSatVal(float x, float y) {
+        final RectF rect = mSatValRect;
+        float[] result = new float[2];
+        float width = rect.width();
+        float height = rect.height();
+
+        if (x < rect.left) {
+            x = 0f;
+        } else if (x > rect.right) {
+            x = width;
+        } else {
+            x = x - rect.left;
+        }
+
+        if (y < rect.top) {
+            y = 0f;
+        } else if (y > rect.bottom) {
+            y = height;
+        } else {
+            y = y - rect.top;
+        }
+
+        result[0] = 1.f / width * x;
+        result[1] = 1.f - (1.f / height * y);
+        return result;
+    }
+
+    private float pointToHue(float y) {
+        final RectF rect = mHueRect;
+        float height = rect.height();
+
+        if (y < rect.top) {
+            y = 0f;
+        } else if (y > rect.bottom) {
+            y = height;
+        } else {
+            y = y - rect.top;
+        }
+        return 360f - (y * 360f / height);
+    }
+
+    private int pointToAlpha(int x) {
+        final RectF rect = mAlphaRect;
+        final int width = (int) rect.width();
+
+        if (x < rect.left) {
+            x = 0;
+        } else if (x > rect.right) {
+            x = width;
+        } else {
+            x = x - (int) rect.left;
+        }
+        return 0xff - (x * 0xff / width);
+    }
+
+    @Override
+    public boolean onTrackballEvent(MotionEvent event) {
+        float x = event.getX();
+        float y = event.getY();
+        boolean update = false;
+
+        if (event.getAction() == MotionEvent.ACTION_MOVE) {
+            switch (mLastTouchedPanel) {
+                case PANEL_SAT_VAL:
+                    float sat,
+                    val;
+                    sat = mSat + x / 50f;
+                    val = mVal - y / 50f;
+                    if (sat < 0f) {
+                        sat = 0f;
+                    } else if (sat > 1f) {
+                        sat = 1f;
+                    }
+
+                    if (val < 0f) {
+                        val = 0f;
+                    } else if (val > 1f) {
+                        val = 1f;
+                    }
+                    mSat = sat;
+                    mVal = val;
+                    update = true;
+                    break;
+                case PANEL_HUE:
+                    float hue = mHue - y * 10f;
+                    if (hue < 0f) {
+                        hue = 0f;
+                    } else if (hue > 360f) {
+                        hue = 360f;
+                    }
+                    mHue = hue;
+                    update = true;
+                    break;
+                case PANEL_ALPHA:
+                    if (!mShowAlphaPanel || mAlphaRect == null) {
+                        update = false;
+                    } else {
+                        int alpha = (int) (mAlpha - x * 10);
+                        if (alpha < 0) {
+                            alpha = 0;
+                        } else if (alpha > 0xff) {
+                            alpha = 0xff;
+                        }
+                        mAlpha = alpha;
+                        update = true;
+                    }
+                    break;
+            }
+        }
+
+        if (update) {
+            if (mListener != null) {
+                mListener.onColorChanged(Color.HSVToColor(mAlpha, new float[] {
+                        mHue, mSat, mVal
+                }));
+            }
+            invalidate();
+            return true;
+        }
+        return super.onTrackballEvent(event);
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+        boolean update = false;
+
+        switch (event.getAction()) {
+            case MotionEvent.ACTION_DOWN:
+                mStartTouchPoint = new Point((int) event.getX(), (int) event.getY());
+                update = moveTrackersIfNeeded(event);
+                break;
+            case MotionEvent.ACTION_MOVE:
+                update = moveTrackersIfNeeded(event);
+                break;
+            case MotionEvent.ACTION_UP:
+                mStartTouchPoint = null;
+                update = moveTrackersIfNeeded(event);
+                break;
+        }
+
+        if (update) {
+            requestFocus();
+            if (mListener != null) {
+                mListener.onColorChanged(Color.HSVToColor(mAlpha, new float[] {
+                        mHue, mSat, mVal
+                }));
+            }
+            invalidate();
+            return true;
+        }
+
+        return super.onTouchEvent(event);
+    }
+
+    private boolean moveTrackersIfNeeded(MotionEvent event) {
+
+        if (mStartTouchPoint == null)
+            return false;
+
+        boolean update = false;
+        int startX = mStartTouchPoint.x;
+        int startY = mStartTouchPoint.y;
+
+        if (mHueRect.contains(startX, startY)) {
+            mLastTouchedPanel = PANEL_HUE;
+            mHue = pointToHue(event.getY());
+            update = true;
+        } else if (mSatValRect.contains(startX, startY)) {
+            mLastTouchedPanel = PANEL_SAT_VAL;
+            float[] result = pointToSatVal(event.getX(), event.getY());
+            mSat = result[0];
+            mVal = result[1];
+            update = true;
+        } else if (mAlphaRect != null && mAlphaRect.contains(startX, startY)) {
+            mLastTouchedPanel = PANEL_ALPHA;
+            mAlpha = pointToAlpha((int) event.getX());
+            update = true;
+        }
+
+        return update;
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        int width = 0;
+        int height = 0;
+
+        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
+        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
+
+        int widthAllowed = MeasureSpec.getSize(widthMeasureSpec);
+        int heightAllowed = MeasureSpec.getSize(heightMeasureSpec);
+
+        widthAllowed = chooseWidth(widthMode, widthAllowed);
+        heightAllowed = chooseHeight(heightMode, heightAllowed);
+
+        if (!mShowAlphaPanel) {
+            height = (int) (widthAllowed - PANEL_SPACING - HUE_PANEL_WIDTH);
+
+            // If calculated height (based on the width) is more than the
+            // allowed height.
+            if (height > heightAllowed && heightMode != MeasureSpec.UNSPECIFIED) {
+                height = heightAllowed;
+                width = (int) (height + PANEL_SPACING + HUE_PANEL_WIDTH);
+            } else {
+                width = widthAllowed;
+            }
+        } else {
+
+            width = (int) (heightAllowed - ALPHA_PANEL_HEIGHT + HUE_PANEL_WIDTH);
+
+            if (width > widthAllowed && widthMode != MeasureSpec.UNSPECIFIED) {
+                width = widthAllowed;
+                height = (int) (widthAllowed - HUE_PANEL_WIDTH + ALPHA_PANEL_HEIGHT);
+            } else {
+                height = heightAllowed;
+            }
+        }
+        setMeasuredDimension(width, height);
+    }
+
+    private int chooseWidth(int mode, int size) {
+        if (mode == MeasureSpec.AT_MOST || mode == MeasureSpec.EXACTLY) {
+            return size;
+        } else { // (mode == MeasureSpec.UNSPECIFIED)
+            return getPrefferedWidth();
+        }
+    }
+
+    private int chooseHeight(int mode, int size) {
+        if (mode == MeasureSpec.AT_MOST || mode == MeasureSpec.EXACTLY) {
+            return size;
+        } else { // (mode == MeasureSpec.UNSPECIFIED)
+            return getPrefferedHeight();
+        }
+    }
+
+    private int getPrefferedWidth() {
+        int width = getPrefferedHeight();
+        if (mShowAlphaPanel) {
+            width -= (PANEL_SPACING + ALPHA_PANEL_HEIGHT);
+        }
+        return (int) (width + HUE_PANEL_WIDTH + PANEL_SPACING);
+    }
+
+    private int getPrefferedHeight() {
+        int height = (int) (200 * mDensity);
+        if (mShowAlphaPanel) {
+            height += PANEL_SPACING + ALPHA_PANEL_HEIGHT;
+        }
+        return height;
+    }
+
+    @Override
+    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+        super.onSizeChanged(w, h, oldw, oldh);
+
+        mDrawingRect = new RectF();
+        mDrawingRect.left = mDrawingOffset + getPaddingLeft();
+        mDrawingRect.right = w - mDrawingOffset - getPaddingRight();
+        mDrawingRect.top = mDrawingOffset + getPaddingTop();
+        mDrawingRect.bottom = h - mDrawingOffset - getPaddingBottom();
+
+        setUpSatValRect();
+        setUpHueRect();
+        setUpAlphaRect();
+    }
+
+    private void setUpSatValRect() {
+        final RectF dRect = mDrawingRect;
+        float panelSide = dRect.height() - BORDER_WIDTH_PX * 2;
+
+        if (mShowAlphaPanel) {
+            panelSide -= PANEL_SPACING + ALPHA_PANEL_HEIGHT;
+        }
+
+        float left = dRect.left + BORDER_WIDTH_PX;
+        float top = dRect.top + BORDER_WIDTH_PX;
+        float bottom = top + panelSide;
+        float right = left + panelSide;
+        mSatValRect = new RectF(left, top, right, bottom);
+    }
+
+    private void setUpHueRect() {
+        final RectF dRect = mDrawingRect;
+
+        float left = dRect.right - HUE_PANEL_WIDTH + BORDER_WIDTH_PX;
+        float top = dRect.top + BORDER_WIDTH_PX;
+        float bottom = dRect.bottom - BORDER_WIDTH_PX
+                - (mShowAlphaPanel ? (PANEL_SPACING + ALPHA_PANEL_HEIGHT) : 0);
+        float right = dRect.right - BORDER_WIDTH_PX;
+
+        mHueRect = new RectF(left, top, right, bottom);
+    }
+
+    private void setUpAlphaRect() {
+        if (!mShowAlphaPanel) {
+            return;
+        }
+
+        final RectF dRect = mDrawingRect;
+        float left = dRect.left + BORDER_WIDTH_PX;
+        float top = dRect.bottom - ALPHA_PANEL_HEIGHT + BORDER_WIDTH_PX;
+        float bottom = dRect.bottom - BORDER_WIDTH_PX;
+        float right = dRect.right - BORDER_WIDTH_PX;
+
+        mAlphaRect = new RectF(left, top, right, bottom);
+        mAlphaPattern = new AlphaPatternDrawable((int) (5 * mDensity));
+        mAlphaPattern.setBounds(Math.round(mAlphaRect.left), Math
+                .round(mAlphaRect.top), Math.round(mAlphaRect.right), Math
+                .round(mAlphaRect.bottom));
+    }
+
+    /**
+     * Set a OnColorChangedListener to get notified when the color selected by
+     * the user has changed.
+     *
+     * @param listener
+     */
+    public void setOnColorChangedListener(OnColorChangedListener listener) {
+        mListener = listener;
+    }
+
+    /**
+     * Set the color of the border surrounding all panels.
+     *
+     * @param color
+     */
+    public void setBorderColor(int color) {
+        mBorderColor = color;
+        invalidate();
+    }
+
+    /**
+     * Get the color of the border surrounding all panels.
+     */
+    public int getBorderColor() {
+        return mBorderColor;
+    }
+
+    /**
+     * Get the current color this view is showing.
+     *
+     * @return the current color.
+     */
+    public int getColor() {
+        return Color.HSVToColor(mAlpha, new float[] {
+                mHue, mSat, mVal
+        });
+    }
+
+    /**
+     * Set the color the view should show.
+     *
+     * @param color The color that should be selected.
+     */
+    public void setColor(int color) {
+        setColor(color, false);
+    }
+
+    /**
+     * Set the color this view should show.
+     *
+     * @param color The color that should be selected.
+     * @param callback If you want to get a callback to your
+     *            OnColorChangedListener.
+     */
+    public void setColor(int color, boolean callback) {
+        int alpha = Color.alpha(color);
+        int red = Color.red(color);
+        int blue = Color.blue(color);
+        int green = Color.green(color);
+        float[] hsv = new float[3];
+
+        Color.RGBToHSV(red, green, blue, hsv);
+        mAlpha = alpha;
+        mHue = hsv[0];
+        mSat = hsv[1];
+        mVal = hsv[2];
+
+        if (callback && mListener != null) {
+            mListener.onColorChanged(Color.HSVToColor(mAlpha, new float[] {
+                    mHue, mSat, mVal
+            }));
+        }
+        invalidate();
+    }
+
+    /**
+     * Get the drawing offset of the color picker view. The drawing offset is
+     * the distance from the side of a panel to the side of the view minus the
+     * padding. Useful if you want to have your own panel below showing the
+     * currently selected color and want to align it perfectly.
+     *
+     * @return The offset in pixels.
+     */
+    public float getDrawingOffset() {
+        return mDrawingOffset;
+    }
+
+    /**
+     * Set if the user is allowed to adjust the alpha panel. Default is false.
+     * If it is set to false no alpha will be set.
+     *
+     * @param visible
+     */
+    public void setAlphaSliderVisible(boolean visible) {
+        if (mShowAlphaPanel != visible) {
+            mShowAlphaPanel = visible;
+
+            /*
+             * Reset all shader to force a recreation. Otherwise they will not
+             * look right after the size of the view has changed.
+             */
+            mValShader = null;
+            mSatShader = null;
+            mHueShader = null;
+            mAlphaShader = null;
+            requestLayout();
+        }
+
+    }
+
+    public boolean isAlphaSliderVisible() {
+        return mShowAlphaPanel;
+    }
+
+    public void setSliderTrackerColor(int color) {
+        mSliderTrackerColor = color;
+        mHueTrackerPaint.setColor(mSliderTrackerColor);
+        invalidate();
+    }
+
+    public int getSliderTrackerColor() {
+        return mSliderTrackerColor;
+    }
+
+    /**
+     * Set the text that should be shown in the alpha slider. Set to null to
+     * disable text.
+     *
+     * @param res string resource id.
+     */
+    public void setAlphaSliderText(int res) {
+        String text = getContext().getString(res);
+        setAlphaSliderText(text);
+    }
+
+    /**
+     * Set the text that should be shown in the alpha slider. Set to null to
+     * disable text.
+     *
+     * @param text Text that should be shown.
+     */
+    public void setAlphaSliderText(String text) {
+        mAlphaSliderText = text;
+        invalidate();
+    }
+
+    /**
+     * Get the current value of the text that will be shown in the alpha slider.
+     *
+     * @return
+     */
+    public String getAlphaSliderText() {
+        return mAlphaSliderText;
+    }
+}
diff --git a/app/src/main/java/org/omnirom/control/widget/ColorSelectDialog.java b/app/src/main/java/org/omnirom/control/widget/ColorSelectDialog.java
new file mode 100644
index 0000000..ff5eba8
--- /dev/null
+++ b/app/src/main/java/org/omnirom/control/widget/ColorSelectDialog.java
@@ -0,0 +1,189 @@
+/*
+ * 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.control.widget;
+
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Color;
+import android.graphics.PixelFormat;
+import android.graphics.drawable.ColorDrawable;
+import android.os.Bundle;
+import android.text.Editable;
+import android.text.InputFilter;
+import android.text.TextWatcher;
+import android.util.Log;
+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.EditText;
+import android.widget.GridLayout;
+import android.widget.LinearLayout;
+
+import org.omnirom.control.R;
+
+import java.util.Locale;
+
+import androidx.appcompat.app.AlertDialog;
+
+public class ColorSelectDialog extends AlertDialog implements
+        ColorPickerView.OnColorChangedListener, TextWatcher, OnFocusChangeListener {
+
+    private static final String TAG = "ColorSelectDialog";
+    private final static String STATE_KEY_COLOR = "color";
+
+    private ColorPickerView mColorPicker;
+
+    private EditText mHexColorInput;
+    private ColorPanelView mNewColor;
+    private LayoutInflater mInflater;
+    private LinearLayout mColorPanelView;
+    private boolean mWithAlpha;
+    private Context mContext;
+    private ViewGroup mColorPresetView;
+    private Integer[] mPresetColors;
+
+    public ColorSelectDialog(Context context, int initialColor, boolean withAlpha, Integer[] presetColors) {
+        super(context);
+        mContext = context;
+        mWithAlpha = withAlpha;
+        mPresetColors = presetColors;
+        init(initialColor);
+    }
+
+    private void init(int color) {
+        // To fight color banding.
+        getWindow().setFormat(PixelFormat.RGBA_8888);
+        setUp(color);
+    }
+
+    /**
+     * 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
+     */
+    private void setUp(int color) {
+        mInflater = (LayoutInflater) getContext()
+                .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+        View layout = mInflater.inflate(R.layout.color_select_dialog, 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);
+        mColorPresetView = layout.findViewById(R.id.color_preset_view);
+        if (mPresetColors != null && mPresetColors.length != 0) {
+            mColorPresetView.setVisibility(View.VISIBLE);
+            for (Integer presetColor : mPresetColors) {
+                Log.d(TAG, "presetColor = " + presetColor);
+                View colorPresetItem = mInflater.inflate(R.layout.color_preset_item, null);
+                colorPresetItem.findViewById(R.id.color_preset_item_view).setBackground(new ColorDrawable(presetColor));
+                int colorPresetItemSize = getContext().getResources().getDimensionPixelSize(R.dimen.color_preset_item_size);
+                mColorPresetView.addView(colorPresetItem, colorPresetItemSize, colorPresetItemSize);
+                colorPresetItem.setOnClickListener(new View.OnClickListener() {
+                    @Override
+                    public void onClick(View v) {
+                        int presetColor = ((ColorDrawable) v.findViewById(R.id.color_preset_item_view).getBackground()).getColor();
+                        mColorPicker.setColor(presetColor, true);
+                    }
+                });
+            }
+        }
+
+        mColorPicker.setOnColorChangedListener(this);
+        mHexColorInput.setOnFocusChangeListener(this);
+        setAlphaSliderVisible(mWithAlpha);
+        mColorPicker.setColor(color, true);
+
+        setView(layout);
+
+        mColorPicker.setVisibility(View.VISIBLE);
+        mColorPanelView.setVisibility(View.VISIBLE);
+    }
+
+    @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 onColorChanged(int color) {
+        final boolean hasAlpha = mWithAlpha;
+        final String format = hasAlpha ? "%08x" : "%06x";
+        final int mask = hasAlpha ? 0xFFFFFFFF : 0x00FFFFFF;
+
+        mNewColor.setColor(color);
+        mHexColorInput.setText(String.format(Locale.US, format, color & mask));
+    }
+
+    public void setAlphaSliderVisible(boolean visible) {
+        mHexColorInput.setFilters(new InputFilter[]{new InputFilter.LengthFilter(visible ? 8 : 6)});
+        mColorPicker.setAlphaSliderVisible(visible);
+    }
+
+    public int getColor() {
+        return mColorPicker.getColor();
+    }
+
+    @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 (!mWithAlpha) {
+                    color |= 0xFF000000; // set opaque
+                }
+                mColorPicker.setColor(color);
+                mNewColor.setColor(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/app/src/main/res/drawable/add_accent_color_item_background.xml b/app/src/main/res/drawable/add_accent_color_item_background.xml
new file mode 100644
index 0000000..a3a0177
--- /dev/null
+++ b/app/src/main/res/drawable/add_accent_color_item_background.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Copyright (C) 2021 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.
+  -->
+
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+    android:shape="rectangle">
+    <stroke
+        android:width="2dp"
+        android:color="?attr/colorControlNormal"
+        android:dashWidth="8dp"
+        android:dashGap="4dp"/>
+
+</shape>
\ No newline at end of file
diff --git a/app/src/main/res/drawable/check_circle.xml b/app/src/main/res/drawable/check_circle.xml
index e1e3f25..df8097a 100644
--- a/app/src/main/res/drawable/check_circle.xml
+++ b/app/src/main/res/drawable/check_circle.xml
@@ -1,10 +1,10 @@
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
     android:width="24dp"
     android:height="24dp"
-    android:tint="?android:textColorPrimary"
+    android:tint="@color/colorAccent"
     android:viewportWidth="24"
     android:viewportHeight="24">
     <path
         android:fillColor="@android:color/white"
-        android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8zM16.59,7.58L10,14.17l-2.59,-2.58L6,13l4,4 8,-8z" />
+        android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM10,17l-5,-5 1.41,-1.41L10,14.17l7.59,-7.59L19,8l-9,9z"/>
 </vector>
diff --git a/app/src/main/res/drawable/check_circle_shape.xml b/app/src/main/res/drawable/check_circle_shape.xml
index 2e6add0..fe96cf0 100644
--- a/app/src/main/res/drawable/check_circle_shape.xml
+++ b/app/src/main/res/drawable/check_circle_shape.xml
@@ -1,7 +1,7 @@
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
     android:width="24dp"
     android:height="24dp"
-    android:tint="@color/colorAccent"
+    android:tint="@android:color/white"
     android:viewportWidth="24"
     android:viewportHeight="24">
     <path
diff --git a/app/src/main/res/drawable/ic_add.xml b/app/src/main/res/drawable/ic_add.xml
new file mode 100644
index 0000000..eb23254
--- /dev/null
+++ b/app/src/main/res/drawable/ic_add.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
+    android:tint="?attr/colorControlNormal">
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_delete.xml b/app/src/main/res/drawable/ic_delete.xml
new file mode 100644
index 0000000..5fb767e
--- /dev/null
+++ b/app/src/main/res/drawable/ic_delete.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
+    android:tint="?attr/colorControlNormal">
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M6,19c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2L18,7L6,7v12zM8,9h8v10L8,19L8,9zM15.5,4l-1,-1h-5l-1,1L5,4v2h14L19,4z"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_edit_color.xml b/app/src/main/res/drawable/ic_edit_color.xml
new file mode 100644
index 0000000..8adfae3
--- /dev/null
+++ b/app/src/main/res/drawable/ic_edit_color.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
+    android:tint="?attr/colorControlNormal">
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M20.71,5.63l-2.34,-2.34c-0.39,-0.39 -1.02,-0.39 -1.41,0l-3.12,3.12 -1.93,-1.91 -1.41,1.41 1.42,1.42L3,16.25V21h4.75l8.92,-8.92 1.42,1.42 1.41,-1.41 -1.92,-1.92 3.12,-3.12c0.4,-0.4 0.4,-1.03 0.01,-1.42zM6.92,19L5,17.08l8.06,-8.06 1.92,1.92L6.92,19z"/>
+</vector>
diff --git a/app/src/main/res/layout-land/color_select_dialog.xml b/app/src/main/res/layout-land/color_select_dialog.xml
new file mode 100644
index 0000000..09f76e7
--- /dev/null
+++ b/app/src/main/res/layout-land/color_select_dialog.xml
@@ -0,0 +1,58 @@
+<?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="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="horizontal"
+    android:padding="@dimen/alert_dialog_padding_material">
+
+    <org.omnirom.control.widget.ColorPickerView
+        android:id="@+id/color_picker_view"
+        android:layout_width="280dp"
+        android:layout_height="280dp" />
+
+    <GridLayout
+        android:id="@+id/color_preset_view"
+        android:layout_width="wrap_content"
+        android:layout_height="match_parent"
+        android:layout_gravity="top"
+        android:layout_margin="3dp"
+        android:columnCount="2"
+        android:rowCount="4"
+        android:visibility="gone" />
+
+    <LinearLayout
+        android:id="@+id/color_panel_view"
+        android:layout_width="wrap_content"
+        android:layout_height="match_parent"
+        android:layout_gravity="top"
+        android:layout_margin="3dp"
+        android:orientation="vertical">
+
+        <EditText
+            android:id="@+id/hex_color_input"
+            android:layout_width="150dp"
+            android:layout_height="40dp"
+            android:digits="0123456789ABCDEFabcdef"
+            android:inputType="textNoSuggestions"
+            android:maxLength="6" />
+
+        <org.omnirom.control.widget.ColorPanelView
+            android:id="@+id/color_panel"
+            android:layout_width="150dp"
+            android:layout_height="40dp" />
+    </LinearLayout>
+</LinearLayout>
diff --git a/app/src/main/res/layout/accent_colors_item.xml b/app/src/main/res/layout/accent_colors_item.xml
new file mode 100644
index 0000000..71068c4
--- /dev/null
+++ b/app/src/main/res/layout/accent_colors_item.xml
@@ -0,0 +1,54 @@
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="@dimen/color_item_column_width"
+    android:layout_height="@dimen/color_item_column_height"
+    android:layout_margin="2dp"
+    android:background="@drawable/overlay_item_background">
+
+    <FrameLayout
+        android:layout_width="@dimen/color_item_container_width"
+        android:layout_height="@dimen/color_item_container_height"
+        android:layout_gravity="center">
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:orientation="vertical">
+
+            <ImageView
+                android:layout_width="match_parent"
+                android:layout_height="0dp"
+                android:layout_gravity="center_vertical"
+                android:layout_weight="1"
+                android:src="@android:color/system_neutral1_900" />
+
+            <ImageView
+                android:layout_width="match_parent"
+                android:layout_height="0dp"
+                android:layout_gravity="center_vertical"
+                android:layout_weight="1"
+                android:src="@android:color/system_neutral1_50" />
+        </LinearLayout>
+
+        <LinearLayout
+            android:id="@+id/colors_item_list"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center_vertical"
+            android:orientation="vertical" />
+
+        <ImageView
+            android:layout_width="match_parent"
+            android:layout_height="1dp"
+            android:layout_gravity="center_vertical"
+            android:src="@android:color/system_neutral1_100" />
+
+        <ImageView
+            android:id="@+id/colors_enabled"
+            android:layout_width="@dimen/check_circle_size"
+            android:layout_height="@dimen/check_circle_size"
+            android:layout_gravity="bottom|end"
+            android:background="@drawable/check_circle_shape"
+            android:src="@drawable/check_circle"
+            android:visibility="gone" />
+    </FrameLayout>
+</FrameLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/add_accent_colors_item.xml b/app/src/main/res/layout/add_accent_colors_item.xml
new file mode 100644
index 0000000..5f5ceab
--- /dev/null
+++ b/app/src/main/res/layout/add_accent_colors_item.xml
@@ -0,0 +1,20 @@
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="@dimen/color_item_column_width"
+    android:layout_height="@dimen/color_item_column_height"
+    android:layout_margin="5dp"
+    android:background="@drawable/overlay_item_background">
+
+    <FrameLayout
+        android:layout_width="@dimen/color_item_container_width"
+        android:layout_height="@dimen/color_item_container_height"
+        android:layout_gravity="center"
+        android:background="@drawable/add_accent_color_item_background">
+
+        <ImageView
+            android:layout_width="32dp"
+            android:layout_height="32dp"
+            android:layout_gravity="center"
+            android:src="@drawable/ic_add"/>
+
+    </FrameLayout>
+</FrameLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/color_preset_item.xml b/app/src/main/res/layout/color_preset_item.xml
new file mode 100644
index 0000000..9885f1e
--- /dev/null
+++ b/app/src/main/res/layout/color_preset_item.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="@dimen/color_preset_item_size"
+    android:layout_height="@dimen/color_preset_item_size"
+    android:background="?android:attr/selectableItemBackground">
+
+    <View
+        android:id="@+id/color_preset_item_view"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_margin="5dp"/>
+
+</LinearLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/color_select_dialog.xml b/app/src/main/res/layout/color_select_dialog.xml
new file mode 100644
index 0000000..c527437
--- /dev/null
+++ b/app/src/main/res/layout/color_select_dialog.xml
@@ -0,0 +1,61 @@
+<?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="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical"
+    android:padding="@dimen/alert_dialog_padding_material">
+
+    <org.omnirom.control.widget.ColorPickerView
+        android:id="@+id/color_picker_view"
+        android:layout_width="330dp"
+        android:layout_height="330dp" />
+
+    <GridLayout
+        android:id="@+id/color_preset_view"
+        android:layout_width="wrap_content"
+        android:layout_height="40dp"
+        android:layout_marginStart="3dp"
+        android:layout_marginTop="10dp"
+        android:layout_marginBottom="10dp"
+        android:columnCount="8"
+        android:rowCount="1"
+        android:visibility="gone" />
+
+    <LinearLayout
+        android:id="@+id/color_panel_view"
+        android:layout_width="wrap_content"
+        android:layout_height="40dp"
+        android:layout_marginStart="3dp"
+        android:layout_marginTop="10dp"
+        android:layout_marginBottom="10dp"
+        android:orientation="horizontal">
+
+        <EditText
+            android:id="@+id/hex_color_input"
+            android:layout_width="155dp"
+            android:layout_height="match_parent"
+            android:digits="0123456789ABCDEFabcdef"
+            android:inputType="textNoSuggestions"
+            android:maxLength="6" />
+
+        <org.omnirom.control.widget.ColorPanelView
+            android:id="@+id/color_panel"
+            android:layout_width="155dp"
+            android:layout_height="match_parent"
+            android:layout_marginStart="10dp" />
+    </LinearLayout>
+</LinearLayout>
diff --git a/app/src/main/res/layout/edit_dark_accent_colors_item.xml b/app/src/main/res/layout/edit_dark_accent_colors_item.xml
new file mode 100644
index 0000000..58fbca0
--- /dev/null
+++ b/app/src/main/res/layout/edit_dark_accent_colors_item.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/accent_color_dark_background"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:background="@android:color/system_neutral1_900"
+    android:foreground="?android:attr/selectableItemBackground"
+    android:scaleType="center"
+    app:tint="@android:color/white"
+    android:src="@drawable/ic_edit_color" />
diff --git a/app/src/main/res/layout/edit_light_accent_colors_item.xml b/app/src/main/res/layout/edit_light_accent_colors_item.xml
new file mode 100644
index 0000000..48556f4
--- /dev/null
+++ b/app/src/main/res/layout/edit_light_accent_colors_item.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/accent_color_light_background"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:background="@android:color/system_neutral1_50"
+    android:foreground="?android:attr/selectableItemBackground"
+    android:scaleType="center"
+    app:tint="@android:color/black"
+    android:src="@drawable/ic_edit_color" />
diff --git a/app/src/main/res/layout/icon_shape_item.xml b/app/src/main/res/layout/icon_shape_item.xml
index 9b2f9c6..08f1c58 100644
--- a/app/src/main/res/layout/icon_shape_item.xml
+++ b/app/src/main/res/layout/icon_shape_item.xml
@@ -1,7 +1,7 @@
 <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="@dimen/icon_shape_column_size"
     android:layout_height="@dimen/icon_shape_column_size"
-    android:layout_margin="5dp"
+    android:layout_margin="2dp"
     android:background="@drawable/overlay_item_background">
 
     <FrameLayout
diff --git a/app/src/main/res/layout/colors_item.xml b/app/src/main/res/layout/primary_colors_item.xml
similarity index 97%
rename from app/src/main/res/layout/colors_item.xml
rename to app/src/main/res/layout/primary_colors_item.xml
index a449869..b3d0a7b 100644
--- a/app/src/main/res/layout/colors_item.xml
+++ b/app/src/main/res/layout/primary_colors_item.xml
@@ -1,7 +1,7 @@
 <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="@dimen/color_item_column_width"
     android:layout_height="@dimen/color_item_column_height"
-    android:layout_margin="5dp"
+    android:layout_margin="2dp"
     android:background="@drawable/overlay_item_background">
 
     <FrameLayout
diff --git a/app/src/main/res/values-sw600dp-land/dimens.xml b/app/src/main/res/values-sw600dp-land/dimens.xml
new file mode 100644
index 0000000..7e1a5d7
--- /dev/null
+++ b/app/src/main/res/values-sw600dp-land/dimens.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <dimen name="color_item_column_width">@dimen/color_item_column_height</dimen>
+    <dimen name="color_item_container_width">@dimen/color_item_container_height</dimen>
+</resources>
\ No newline at end of file
diff --git a/app/src/main/res/values-sw600dp/dimens.xml b/app/src/main/res/values-sw600dp/dimens.xml
new file mode 100644
index 0000000..cecab27
--- /dev/null
+++ b/app/src/main/res/values-sw600dp/dimens.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <dimen name="icon_shape_size">64dp</dimen>
+    <dimen name="icon_shape_container_size">72dp</dimen>
+    <dimen name="icon_shape_column_size">80dp</dimen>
+    <dimen name="color_item_height">16dp</dimen>
+    <dimen name="color_item_container_width">130dp</dimen>
+    <dimen name="color_item_container_height">160dp</dimen>
+    <dimen name="accent_color_item_height">30dp</dimen>
+    <!-- color_item_container_height / 2 - accent_color_item_height -->
+    <dimen name="edit_accent_color_item_height">50dp</dimen>
+    <dimen name="color_item_column_width">150dp</dimen>
+    <dimen name="color_item_column_height">180dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
index e7dea2f..d06af94 100644
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -5,4 +5,6 @@
     <color name="colorAccent">@android:color/system_accent1_600</color>
     <color name="grid_shape_background">@color/colorPrimary</color>
     <color name="overlay_item_background">@android:color/system_neutral1_0</color>
+    <color name="overlay_item_system_background">@color/colorAccent</color>
+
 </resources>
\ No newline at end of file
diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml
index 4c7126d..5fabdfe 100644
--- a/app/src/main/res/values/dimens.xml
+++ b/app/src/main/res/values/dimens.xml
@@ -7,15 +7,20 @@
     <dimen name="fragment_icon_margin_start">10dp</dimen>
     <dimen name="grid_item_icon_size">32dp</dimen>
     <dimen name="grid_icon_margin_start">0dp</dimen>
-    <dimen name="icon_shape_size">64dp</dimen>
+    <dimen name="icon_shape_size">48dp</dimen>
     <dimen name="check_circle_size">24dp</dimen>
-    <dimen name="icon_shape_container_size">72dp</dimen>
-    <dimen name="icon_shape_column_size">80dp</dimen>
-    <dimen name="color_item_height">16dp</dimen>
+    <dimen name="icon_shape_container_size">56dp</dimen>
+    <dimen name="icon_shape_column_size">64dp</dimen>
+    <dimen name="color_item_height">12dp</dimen>
     <dimen name="color_item_width">@dimen/color_item_container_width</dimen>
-    <dimen name="color_item_container_width">120dp</dimen>
-    <dimen name="color_item_container_height">160dp</dimen>
-    <dimen name="color_item_column_width">140dp</dimen>
-    <dimen name="color_item_column_height">180dp</dimen>
+    <dimen name="color_item_container_width">90dp</dimen>
+    <dimen name="color_item_container_height">120dp</dimen>
+    <dimen name="accent_color_item_height">20dp</dimen>
+    <!-- color_item_container_height / 2 - accent_color_item_height -->
+    <dimen name="edit_accent_color_item_height">40dp</dimen>
+    <dimen name="color_item_column_width">110dp</dimen>
+    <dimen name="color_item_column_height">140dp</dimen>
     <dimen name="overlay_fragment_side_margin">10dp</dimen>
+    <dimen name="alert_dialog_padding_material">20dp</dimen>
+    <dimen name="color_preset_item_size">40dp</dimen>
 </resources>
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index d754c64..7810bf8 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -70,6 +70,9 @@
 
     <string name="device_registration_title">Google device registration</string>
     <string name="device_registration_summary">Register device for Play Protect certification</string>
+    <string name="dialog_delete_accent_color_title">Delete</string>
+    <string name="dialog_delete_accent_color_message">Delete accent color?</string>
+    <string name="delete_accent_color_info_message">Long press to delete accent color</string>
 
     <string name="navigation_bar_menu_arrow_keys_title">Arrow keys while typing</string>
     <string name="navigation_bar_menu_arrow_keys_summary">Display left and right cursor buttons while typing</string>