Controls UI - Basic impl

The start of basic controls implementation. Support for lights, locks,
and basic thermostat information. Token support is needed only for
mocking at the moment and will be removed.

Bug: 148207527
Test: SystemUiTests

Change-Id: If25c57735ebd314a978d3969934d781eafc4f89c
diff --git a/packages/SystemUI/res/color/light_background.xml b/packages/SystemUI/res/color/light_background.xml
new file mode 100644
index 0000000..2effd99
--- /dev/null
+++ b/packages/SystemUI/res/color/light_background.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+  <item android:state_enabled="false"
+        android:color="@color/control_default_background" />
+  <item android:color="@color/GM2_yellow_50" />
+</selector>
diff --git a/packages/SystemUI/res/color/light_foreground.xml b/packages/SystemUI/res/color/light_foreground.xml
new file mode 100644
index 0000000..8143028
--- /dev/null
+++ b/packages/SystemUI/res/color/light_foreground.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+  <item android:state_enabled="false"
+        android:color="@color/control_default_foreground" />
+  <item android:color="@color/GM2_orange_900" />
+</selector>
diff --git a/packages/SystemUI/res/color/lock_background.xml b/packages/SystemUI/res/color/lock_background.xml
new file mode 100644
index 0000000..646fe5d
--- /dev/null
+++ b/packages/SystemUI/res/color/lock_background.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+  <item android:state_enabled="false"
+        android:color="@color/control_default_background" />
+  <item android:color="@color/GM2_blue_50" />
+</selector>
diff --git a/packages/SystemUI/res/color/lock_foreground.xml b/packages/SystemUI/res/color/lock_foreground.xml
new file mode 100644
index 0000000..3e05653
--- /dev/null
+++ b/packages/SystemUI/res/color/lock_foreground.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+  <item android:state_enabled="false"
+        android:color="@color/control_default_foreground" />
+  <item android:color="@color/GM2_blue_700" />
+</selector>
diff --git a/packages/SystemUI/res/color/unknown_foreground.xml b/packages/SystemUI/res/color/unknown_foreground.xml
new file mode 100644
index 0000000..bf028f1
--- /dev/null
+++ b/packages/SystemUI/res/color/unknown_foreground.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+   <item android:state_enabled="false"
+         android:color="@color/control_default_foreground" />
+   <item android:color="@color/GM2_blue_700" />
+ </selector>
diff --git a/packages/SystemUI/res/drawable/control_background.xml b/packages/SystemUI/res/drawable/control_background.xml
new file mode 100644
index 0000000..b246ea0
--- /dev/null
+++ b/packages/SystemUI/res/drawable/control_background.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+* Copyright 2020, 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.
+*/
+-->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+  <item>
+    <shape>
+      <solid android:color="?android:attr/colorBackgroundFloating"/>
+      <corners android:radius="@dimen/control_corner_radius" />
+    </shape>
+  </item>
+  <item
+      android:id="@+id/clip_layer">
+    <clip
+        android:clipOrientation="horizontal"
+        android:drawable="@drawable/control_layer"/>
+  </item>
+</layer-list>
diff --git a/packages/SystemUI/res/drawable/control_layer.xml b/packages/SystemUI/res/drawable/control_layer.xml
new file mode 100644
index 0000000..fe8c4a4
--- /dev/null
+++ b/packages/SystemUI/res/drawable/control_layer.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+* Copyright 2020, 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">
+  <solid android:color="@android:color/transparent"/>
+  <corners android:radius="@dimen/control_corner_radius" />
+</shape>
diff --git a/packages/SystemUI/res/drawable/control_no_favorites_background.xml b/packages/SystemUI/res/drawable/control_no_favorites_background.xml
new file mode 100644
index 0000000..1e282ad
--- /dev/null
+++ b/packages/SystemUI/res/drawable/control_no_favorites_background.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+* Copyright 2020, 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">
+  <stroke android:width="1dp" android:color="?android:attr/colorBackgroundFloating"/>
+  <corners android:radius="@dimen/control_corner_radius" />
+</shape>
diff --git a/packages/SystemUI/res/drawable/ic_device_thermostat_gm2_24px.xml b/packages/SystemUI/res/drawable/ic_device_thermostat_gm2_24px.xml
new file mode 100644
index 0000000..45a658f
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_device_thermostat_gm2_24px.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+  <path
+      android:fillColor="#FF000000"
+      android:pathData="M18,9h-5v2h5m3,-6h-8v2h8m-9,11.97c0.62,-0.83 1,-1.85 1,-2.97 0,-1.63 -0.79,-3.09 -2,-4V6c0,-1.66 -1.34,-3 -3,-3S5,4.34 5,6v6c-1.21,0.91 -2,2.37 -2,4 0,1.12 0.38,2.14 1,2.97V19h0.02c0.91,1.21 2.35,2 3.98,2s3.06,-0.79 3.98,-2H12v-0.03zM6.2,13.6L7,13V6c0,-0.55 0.45,-1 1,-1s1,0.45 1,1v7l0.8,0.6c0.75,0.57 1.2,1.46 1.2,2.4H5c0,-0.94 0.45,-1.84 1.2,-2.4z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_light_off_gm2_24px.xml b/packages/SystemUI/res/drawable/ic_light_off_gm2_24px.xml
new file mode 100644
index 0000000..78c3cc5
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_light_off_gm2_24px.xml
@@ -0,0 +1,19 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+  <path
+      android:fillColor="#FF000000"
+      android:pathData="M9,21v-1h6v1c0,0.55 -0.45,1 -1,1h-4c-0.55,0 -1,-0.45 -1,-1z"/>
+  <group>
+    <clip-path android:pathData="M0,0h24v24H0z M 0,0"/>
+  </group>
+  <path
+      android:fillColor="#FF000000"
+      android:pathData="M12,2c-1.89,0 -3.6,0.75 -4.86,1.97l1.41,1.41C9.45,4.53 10.67,4 12,4c2.76,0 5,2.24 5,5 0,1.28 -0.5,2.5 -1.36,3.42l-0.02,0.02 1.41,1.41C18.25,12.6 19,10.89 19,9c0,-3.86 -3.14,-7 -7,-7z"
+      android:fillType="evenOdd"/>
+  <path
+      android:fillColor="#FF000000"
+      android:pathData="M2.92,2.29L1.65,3.57l3.59,3.59C5.09,7.75 5,8.36 5,9c0,2.38 1.19,4.47 3,5.74V17c0,0.55 0.45,1 1,1h6c0.3,0 0.57,-0.13 0.75,-0.34L20.09,22l1.27,-1.27L2.92,2.29zM10,16v-2.3l-0.85,-0.6C7.8,12.16 7,10.63 7,9v-0.08L14.09,16H10z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_lock_gm2_24px.xml b/packages/SystemUI/res/drawable/ic_lock_gm2_24px.xml
new file mode 100644
index 0000000..f4299e6
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_lock_gm2_24px.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+  <path
+      android:fillColor="#FF000000"
+      android:pathData="M18,8h-1L17,6c0,-2.76 -2.24,-5 -5,-5S7,3.24 7,6v2L6,8c-1.1,0 -2,0.9 -2,2v10c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2L20,10c0,-1.1 -0.9,-2 -2,-2zM9,6c0,-1.66 1.34,-3 3,-3s3,1.34 3,3v2L9,8L9,6zM18,20L6,20L6,10h12v10zM12,17c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_lock_open_gm2_24px.xml b/packages/SystemUI/res/drawable/ic_lock_open_gm2_24px.xml
new file mode 100644
index 0000000..59fe0a9
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_lock_open_gm2_24px.xml
@@ -0,0 +1,12 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+  <path
+      android:fillColor="#FF000000"
+      android:pathData="M18,8h-1L17,6c0,-2.76 -2.24,-5 -5,-5S7,3.24 7,6h2c0,-1.66 1.34,-3 3,-3s3,1.34 3,3v2L6,8c-1.1,0 -2,0.9 -2,2v10c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2L20,10c0,-1.1 -0.9,-2 -2,-2zM18,20L6,20L6,10h12v10z"/>
+  <path
+      android:fillColor="#FF000000"
+      android:pathData="M12,15m-2,0a2,2 0,1 1,4 0a2,2 0,1 1,-4 0"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_more_vert.xml b/packages/SystemUI/res/drawable/ic_more_vert.xml
new file mode 100644
index 0000000..1309fa8
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_more_vert.xml
@@ -0,0 +1,24 @@
+<!--
+    Copyright (C) 2020 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+  <path
+      android:fillColor="#FF000000"
+      android:pathData="M12,8c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2zM12,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM12,16c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_power_gm2_24px.xml b/packages/SystemUI/res/drawable/ic_power_gm2_24px.xml
new file mode 100644
index 0000000..cd95719
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_power_gm2_24px.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+  <path
+      android:fillColor="#FF000000"
+      android:pathData="M16,9v4.66l-3.5,3.51V19h-1v-1.83L8,13.65V9h8m0,-6h-2v4h-4V3H8v4h-0.01C6.9,6.99 6,7.89 6,8.98v5.52L9.5,18v3h5v-3l3.5,-3.51V9c0,-1.1 -0.9,-2 -2,-2V3z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_power_off_gm2_24px.xml b/packages/SystemUI/res/drawable/ic_power_off_gm2_24px.xml
new file mode 100644
index 0000000..3eb7dd6
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_power_off_gm2_24px.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+  <path
+      android:fillColor="#FF000000"
+      android:pathData="M21.19,21.19L2.81,2.81 1.39,4.22l4.63,4.63L6,14.5 9.5,18v3h5v-3l0.34,-0.34 4.94,4.94 1.41,-1.41zM12.5,17.17L12.5,19h-1v-1.83L8,13.65v-2.83l5.42,5.42 -0.92,0.93zM11.83,9L8,5.17L8,3h2v4h4L14,3h2v4c1.1,0 2,0.9 2,2v5.49l-0.34,0.34L16,13.17L16,9h-4.17z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_power_settings_new_gm2_24px.xml b/packages/SystemUI/res/drawable/ic_power_settings_new_gm2_24px.xml
new file mode 100644
index 0000000..f4edd87
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_power_settings_new_gm2_24px.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+  <path
+      android:fillColor="#FF000000"
+      android:pathData="M11,2h2v10h-2zM18.37,5.64l-1.41,1.41c2.73,2.73 2.72,7.16 -0.01,9.89 -2.73,2.73 -7.17,2.73 -9.89,0.01 -2.73,-2.73 -2.74,-7.18 -0.01,-9.91l-1.41,-1.4c-3.51,3.51 -3.51,9.21 0.01,12.73 3.51,3.51 9.21,3.51 12.72,-0.01 3.51,-3.51 3.51,-9.2 0,-12.72z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_switches_gm2_24px.xml b/packages/SystemUI/res/drawable/ic_switches_gm2_24px.xml
new file mode 100644
index 0000000..bb535ce
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_switches_gm2_24px.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+  <path
+      android:fillColor="#FF000000"
+      android:pathData="M19,9h-8.02C10.06,7.79 8.63,7 7,7c-2.76,0 -5,2.24 -5,5s2.24,5 5,5c1.63,0 3.06,-0.79 3.98,-2H19c1.66,0 3,-1.34 3,-3S20.66,9 19,9zM19,13h-7.1c0.07,-0.32 0.1,-0.66 0.1,-1s-0.04,-0.68 -0.1,-1H19c0.55,0 1,0.45 1,1S19.55,13 19,13z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_vacuum_gm2_24px.xml b/packages/SystemUI/res/drawable/ic_vacuum_gm2_24px.xml
new file mode 100644
index 0000000..86b9591
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_vacuum_gm2_24px.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+  <path
+      android:fillColor="#FF000000"
+      android:pathData="M4,16c-1.66,0 -3,1.34 -3,3s1.34,3 3,3 3,-1.34 3,-3 -1.34,-3 -3,-3zM4,20c-0.55,0 -1,-0.45 -1,-1s0.45,-1 1,-1 1,0.45 1,1 -0.45,1 -1,1zM23,20v2h-7v-2h2.49L12.01,4.59C11.6,3.63 10.66,3 9.61,3 8.17,3 7,4.17 7,5.61L7,9h2c2.21,0 4,1.79 4,4v9L7.99,22c0.44,-0.58 0.76,-1.26 0.91,-2L11,20v-7c0,-1.1 -0.9,-2 -2,-2L4,11v3c-0.71,0 -1.39,0.15 -2,0.42L2,9h3L5,5.61C5,3.07 7.07,1 9.61,1c1.86,0 3.53,1.11 4.25,2.82L20.66,20L23,20z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_videocam_gm2_24px.xml b/packages/SystemUI/res/drawable/ic_videocam_gm2_24px.xml
new file mode 100644
index 0000000..687c9c4
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_videocam_gm2_24px.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+  <path
+      android:fillColor="#FF000000"
+      android:pathData="M18,10.48L18,6c0,-1.1 -0.9,-2 -2,-2L4,4c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2v-4.48l4,3.98v-11l-4,3.98zM16,9.69L16,18L4,18L4,6h12v3.69z"/>
+</vector>
diff --git a/packages/SystemUI/res/layout/controls_base_item.xml b/packages/SystemUI/res/layout/controls_base_item.xml
new file mode 100644
index 0000000..3c4c61e
--- /dev/null
+++ b/packages/SystemUI/res/layout/controls_base_item.xml
@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2020 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.
+-->
+<androidx.constraintlayout.widget.ConstraintLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="0dp"
+    android:layout_weight="1"
+    android:layout_height="@dimen/control_height"
+    android:padding="@dimen/control_padding"
+    android:clickable="true"
+    android:focusable="true"
+    android:layout_marginLeft="3dp"
+    android:layout_marginRight="3dp"
+    android:background="@drawable/control_background">
+
+    <ImageView
+        android:id="@+id/icon"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+
+    <TextView
+        android:id="@+id/status"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:textSize="@dimen/control_status_normal"
+        android:textColor="?android:attr/textColorPrimary"
+        android:fontFamily="@*android:string/config_bodyFontFamily"
+        android:paddingLeft="3dp"
+        app:layout_constraintBottom_toBottomOf="@+id/icon"
+        app:layout_constraintStart_toEndOf="@+id/icon" />
+
+    <TextView
+        android:id="@+id/status_extra"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:textSize="@dimen/control_status_normal"
+        android:textColor="?android:attr/textColorPrimary"
+        android:fontFamily="@*android:string/config_bodyFontFamily"
+        android:paddingLeft="3dp"
+        app:layout_constraintBottom_toBottomOf="@+id/icon"
+        app:layout_constraintStart_toEndOf="@+id/status" />
+
+    <TextView
+        android:id="@+id/title"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:textSize="18sp"
+        android:textColor="?android:attr/textColorPrimary"
+        android:fontFamily="@*android:string/config_headlineFontFamily"
+        app:layout_constraintBottom_toTopOf="@+id/subtitle"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/icon" />
+
+    <TextView
+        android:id="@+id/subtitle"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:textSize="16sp"
+        android:textColor="?android:attr/textColorSecondary"
+        android:fontFamily="@*android:string/config_headlineFontFamily"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintStart_toStartOf="parent" />
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/packages/SystemUI/res/layout/controls_no_favorites.xml b/packages/SystemUI/res/layout/controls_no_favorites.xml
new file mode 100644
index 0000000..79672ca
--- /dev/null
+++ b/packages/SystemUI/res/layout/controls_no_favorites.xml
@@ -0,0 +1,18 @@
+<merge
+    xmlns:android="http://schemas.android.com/apk/res/android">
+  <TextView
+      android:id="@+id/controls_title"
+      android:text="@string/quick_controls_title"
+      android:layout_width="match_parent"
+      android:layout_height="wrap_content"
+      android:singleLine="true"
+      android:gravity="center"
+      android:textSize="25dp"
+      android:paddingTop="40dp"
+      android:paddingBottom="40dp"
+      android:layout_marginLeft="10dp"
+      android:layout_marginRight="10dp"
+      android:textColor="?android:attr/textColorPrimary"
+      android:fontFamily="@*android:string/config_headlineFontFamily"
+      android:background="@drawable/control_no_favorites_background"/>
+</merge>
diff --git a/packages/SystemUI/res/layout/controls_row.xml b/packages/SystemUI/res/layout/controls_row.xml
new file mode 100644
index 0000000..13a6b36
--- /dev/null
+++ b/packages/SystemUI/res/layout/controls_row.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2020 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.
+-->
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    orientation="horizontal"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:layout_marginBottom="@dimen/control_spacing" />
diff --git a/packages/SystemUI/res/layout/controls_with_favorites.xml b/packages/SystemUI/res/layout/controls_with_favorites.xml
new file mode 100644
index 0000000..7804fe6
--- /dev/null
+++ b/packages/SystemUI/res/layout/controls_with_favorites.xml
@@ -0,0 +1,35 @@
+<merge
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+
+  <androidx.constraintlayout.widget.ConstraintLayout
+      android:layout_width="match_parent"
+      android:layout_height="wrap_content">
+
+    <TextView
+        android:text="@string/quick_controls_title"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:singleLine="true"
+        android:gravity="center"
+        android:textSize="25dp"
+        android:textColor="?android:attr/textColorPrimary"
+        android:fontFamily="@*android:string/config_headlineFontFamily"
+        app:layout_constraintLeft_toLeftOf="parent"
+        app:layout_constraintRight_toRightOf="parent"/>
+    <ImageView
+        android:id="@+id/controls_more"
+        android:src="@drawable/ic_more_vert"
+        android:layout_width="34dp"
+        android:layout_height="24dp" 
+        android:layout_marginEnd="10dp"
+        app:layout_constraintEnd_toEndOf="parent"/>
+
+  </androidx.constraintlayout.widget.ConstraintLayout>
+
+  <LinearLayout
+      android:id="@+id/global_actions_controls_list"
+      android:layout_width="match_parent"
+      android:layout_height="wrap_content"
+      android:orientation="vertical" />
+</merge>
diff --git a/packages/SystemUI/res/layout/global_actions_grid_v2.xml b/packages/SystemUI/res/layout/global_actions_grid_v2.xml
index 4cfb47e..6741484 100644
--- a/packages/SystemUI/res/layout/global_actions_grid_v2.xml
+++ b/packages/SystemUI/res/layout/global_actions_grid_v2.xml
@@ -111,20 +111,7 @@
         app:layout_constraintLeft_toLeftOf="parent"
         app:layout_constraintRight_toRightOf="parent"
         app:layout_constraintTop_toBottomOf="@id/global_actions_panel">
-      <TextView
-          android:text="Home"
-          android:layout_width="match_parent"
-          android:layout_height="wrap_content"
-          android:singleLine="true"
-          android:gravity="center"
-          android:textSize="25dp"
-          android:textColor="?android:attr/textColorPrimary"
-          android:fontFamily="@*android:string/config_headlineFontFamily" />
-    <LinearLayout
-        android:id="@+id/global_actions_controls_list"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:orientation="vertical" />
+
     </LinearLayout>
   </androidx.constraintlayout.widget.ConstraintLayout>
 </ScrollView>
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 8dd2a8b..09058f2 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -200,18 +200,28 @@
     <color name="GM2_grey_800">#3C4043</color>
     <color name="GM2_grey_900">#202124</color>
 
+    <color name="GM2_red_50">#FCE8E6</color>
     <color name="GM2_red_300">#F28B82</color>
     <color name="GM2_red_500">#B71C1C</color>
     <color name="GM2_red_700">#C5221F</color>
 
+    <color name="GM2_blue_50">#E8F0FE</color>
     <color name="GM2_blue_200">#AECBFA</color>
     <color name="GM2_blue_300">#8AB4F8</color>
+    <color name="GM2_blue_500">#FF4285F4</color>
     <color name="GM2_blue_600">#1A73E8</color>
     <color name="GM2_blue_700">#1967D2</color>
 
+    <color name="GM2_yellow_50">#FEF7E0</color>
     <color name="GM2_yellow_500">#FFFBBC04</color>
+
     <color name="GM2_green_500">#FF34A853</color>
-    <color name="GM2_blue_500">#FF4285F4</color>
+
+    <color name="GM2_orange_900">#B06000</color>
 
     <color name="magnification_border_color">#FF9900</color>
+
+    <!-- controls -->
+    <color name="control_default_foreground">?android:attr/textColorPrimary</color>
+    <color name="control_default_background">?android:attr/colorBackgroundFloating</color>
 </resources>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index b40c5c0..cc58b20 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1181,5 +1181,12 @@
     <dimen name="magnifier_up_down_controls_width">45dp</dimen>
     <dimen name="magnifier_up_down_controls_height">40dp</dimen>
 
+    <!-- Home Controls -->
+    <dimen name="control_spacing">5dp</dimen>
+    <dimen name="control_corner_radius">15dp</dimen>
+    <dimen name="control_height">100dp</dimen>
+    <dimen name="control_padding">15dp</dimen>
+    <dimen name="control_status_normal">12dp</dimen>
+    <dimen name="control_status_expanded">18dp</dimen>
     <dimen name="app_icon_size">32dp</dimen>
 </resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 44a7fda..1f13f8d 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2546,4 +2546,7 @@
     <string name="magnification_window_title">Magnification Window</string>
     <!-- Title for Magnification Controls Window [CHAR LIMIT=NONE] -->
     <string name="magnification_controls_title">Magnification Window Controls</string>
+
+    <!-- Quick Controls strings [CHAR LIMIT=30] -->
+    <string name="quick_controls_title">Quick Controls</string>
 </resources>
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt
new file mode 100644
index 0000000..81b5f36
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt
@@ -0,0 +1,400 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.controls.ui
+
+import android.content.Context
+import android.graphics.drawable.ClipDrawable
+import android.graphics.drawable.Drawable
+import android.graphics.drawable.GradientDrawable
+import android.graphics.drawable.Icon
+import android.graphics.drawable.LayerDrawable
+import android.service.controls.Control
+import android.service.controls.DeviceTypes
+import android.service.controls.actions.BooleanAction
+import android.service.controls.actions.ControlAction
+import android.service.controls.actions.FloatAction
+import android.service.controls.templates.ControlTemplate
+import android.service.controls.templates.RangeTemplate
+import android.service.controls.templates.ToggleRangeTemplate
+import android.service.controls.templates.ToggleTemplate
+import android.util.TypedValue
+import android.view.MotionEvent
+import android.view.View
+import android.view.ViewGroup
+import android.widget.ImageView
+import android.widget.TextView
+
+import com.android.systemui.controls.controller.ControlsController
+import com.android.systemui.R
+
+private const val MIN_LEVEL = 0
+private const val MAX_LEVEL = 10000
+
+class ControlViewHolder(
+    val layout: ViewGroup,
+    val controlsController: ControlsController
+) {
+    val icon: ImageView = layout.requireViewById(R.id.icon)
+    val status: TextView = layout.requireViewById(R.id.status)
+    val statusExtra: TextView = layout.requireViewById(R.id.status_extra)
+    val title: TextView = layout.requireViewById(R.id.title)
+    val subtitle: TextView = layout.requireViewById(R.id.subtitle)
+    val context: Context = layout.getContext()
+    val clipLayer: ClipDrawable
+    val gd: GradientDrawable
+    lateinit var cws: ControlWithState
+
+    init {
+        val ld = layout.getBackground() as LayerDrawable
+        ld.mutate()
+        clipLayer = ld.findDrawableByLayerId(R.id.clip_layer) as ClipDrawable
+        gd = clipLayer.getDrawable() as GradientDrawable
+    }
+
+    fun bindData(cws: ControlWithState) {
+        this.cws = cws
+
+        val (status, template) = cws.control?.let {
+            title.setText(it.getTitle())
+            subtitle.setText(it.getSubtitle())
+            Pair(it.getStatus(), it.getControlTemplate())
+        } ?: run {
+            title.setText(cws.ci.controlTitle)
+            subtitle.setText("")
+            Pair(Control.STATUS_UNKNOWN, ControlTemplate.NO_TEMPLATE)
+        }
+
+        findBehavior(status, template).apply(this, cws)
+    }
+
+    fun action(action: ControlAction) {
+        controlsController.action(cws.ci, action)
+    }
+
+    private fun findBehavior(status: Int, template: ControlTemplate): Behavior {
+        return when {
+            status == Control.STATUS_UNKNOWN -> UnknownBehavior()
+            template is ToggleTemplate -> ToggleTemplateBehavior()
+            template is ToggleRangeTemplate -> ToggleRangeTemplateBehavior()
+            else -> {
+                object : Behavior {
+                    override fun apply(cvh: ControlViewHolder, cws: ControlWithState) {
+                        cvh.status.setText(cws.control?.getStatusText())
+                        cvh.applyRenderInfo(findRenderInfo(cws.ci.deviceType, false))
+                    }
+                }
+            }
+        }
+    }
+
+    internal fun applyRenderInfo(ri: RenderInfo) {
+        val fg = context.getResources().getColorStateList(ri.foreground, context.getTheme())
+        val bg = context.getResources().getColorStateList(ri.background, context.getTheme())
+        status.setTextColor(fg)
+        statusExtra.setTextColor(fg)
+
+        icon.setImageIcon(Icon.createWithResource(context, ri.iconResourceId))
+        icon.setImageTintList(fg)
+
+        gd.setColor(bg)
+    }
+
+    fun setEnabled(enabled: Boolean) {
+        status.setEnabled(enabled)
+        icon.setEnabled(enabled)
+    }
+}
+
+private interface Behavior {
+    fun apply(cvh: ControlViewHolder, cws: ControlWithState)
+
+    fun findRenderInfo(deviceType: Int, isActive: Boolean): RenderInfo =
+        deviceRenderMap.getOrDefault(deviceType, unknownDeviceMap).getValue(isActive)
+}
+
+private class UnknownBehavior : Behavior {
+    override fun apply(cvh: ControlViewHolder, cws: ControlWithState) {
+        cvh.status.setText("Loading...")
+        cvh.applyRenderInfo(findRenderInfo(cws.ci.deviceType, false))
+    }
+}
+
+private class ToggleRangeTemplateBehavior : Behavior {
+    lateinit var clipLayer: Drawable
+    lateinit var template: ToggleRangeTemplate
+    lateinit var control: Control
+    lateinit var cvh: ControlViewHolder
+    lateinit var rangeTemplate: RangeTemplate
+    lateinit var statusExtra: TextView
+    lateinit var status: TextView
+    lateinit var context: Context
+
+    override fun apply(cvh: ControlViewHolder, cws: ControlWithState) {
+        this.control = cws.control!!
+        this.cvh = cvh
+
+        statusExtra = cvh.statusExtra
+        status = cvh.status
+
+        status.setText(control.getStatusText())
+
+        context = status.getContext()
+
+        cvh.layout.setOnTouchListener(ToggleRangeTouchListener())
+
+        val ld = cvh.layout.getBackground() as LayerDrawable
+        clipLayer = ld.findDrawableByLayerId(R.id.clip_layer)
+
+        template = control.getControlTemplate() as ToggleRangeTemplate
+        rangeTemplate = template.getRange()
+
+        val checked = template.isChecked()
+        val deviceType = control.getDeviceType()
+
+        updateRange((rangeTemplate.getCurrentValue() / 100.0f), checked)
+
+        cvh.setEnabled(checked)
+        cvh.applyRenderInfo(findRenderInfo(deviceType, checked))
+    }
+
+    fun toggle() {
+        cvh.action(BooleanAction(template.getTemplateId(), !template.isChecked()))
+
+        val nextLevel = if (template.isChecked()) MIN_LEVEL else MAX_LEVEL
+        clipLayer.setLevel(nextLevel)
+    }
+
+    fun beginUpdateRange() {
+        status.setVisibility(View.GONE)
+        statusExtra.setTextSize(TypedValue.COMPLEX_UNIT_PX, context.getResources()
+                .getDimensionPixelSize(R.dimen.control_status_expanded).toFloat())
+    }
+
+    fun updateRange(f: Float, checked: Boolean) {
+        clipLayer.setLevel(if (checked) (MAX_LEVEL * f).toInt() else MIN_LEVEL)
+
+        if (checked && f < 100.0f && f > 0.0f) {
+            statusExtra.setText("" + (f * 100.0).toInt() + "%")
+            statusExtra.setVisibility(View.VISIBLE)
+        } else {
+            statusExtra.setText("")
+            statusExtra.setVisibility(View.GONE)
+        }
+    }
+
+    fun endUpdateRange(f: Float) {
+        statusExtra.setText(" - " + (f * 100.0).toInt() + "%")
+
+        val newValue = rangeTemplate.getMinValue() +
+            (f * (rangeTemplate.getMaxValue() - rangeTemplate.getMinValue()))
+
+        statusExtra.setTextSize(TypedValue.COMPLEX_UNIT_PX, context.getResources()
+                .getDimensionPixelSize(R.dimen.control_status_normal).toFloat())
+        status.setVisibility(View.VISIBLE)
+
+        cvh.action(FloatAction(rangeTemplate.getTemplateId(), findNearestStep(newValue)))
+    }
+
+    fun findNearestStep(value: Float): Float {
+        var minDiff = 1000f
+
+        var f = rangeTemplate.getMinValue()
+        while (f <= rangeTemplate.getMaxValue()) {
+            val currentDiff = Math.abs(value - f)
+            if (currentDiff < minDiff) {
+                minDiff = currentDiff
+            } else {
+                return f - rangeTemplate.getStepValue()
+            }
+
+            f += rangeTemplate.getStepValue()
+        }
+
+        return rangeTemplate.getMaxValue()
+    }
+
+    inner class ToggleRangeTouchListener() : View.OnTouchListener {
+        private var initialTouchX: Float = 0.0f
+        private var initialTouchY: Float = 0.0f
+        private var isDragging: Boolean = false
+        private val minDragDiff = 20
+
+        override fun onTouch(v: View, e: MotionEvent): Boolean {
+            when (e.getActionMasked()) {
+                MotionEvent.ACTION_DOWN -> setupTouch(e)
+                MotionEvent.ACTION_MOVE -> detectDrag(v, e)
+                MotionEvent.ACTION_UP -> endTouch(v, e)
+            }
+
+            return true
+        }
+
+        private fun setupTouch(e: MotionEvent) {
+            initialTouchX = e.getX()
+            initialTouchY = e.getY()
+        }
+
+        private fun detectDrag(v: View, e: MotionEvent) {
+            val xDiff = Math.abs(e.getX() - initialTouchX)
+            val yDiff = Math.abs(e.getY() - initialTouchY)
+
+            if (xDiff < minDragDiff) {
+                isDragging = false
+            } else {
+                if (!isDragging) {
+                    this@ToggleRangeTemplateBehavior.beginUpdateRange()
+                }
+                v.getParent().requestDisallowInterceptTouchEvent(true)
+                isDragging = true
+                if (yDiff > xDiff) {
+                    endTouch(v, e)
+                } else {
+                    val percent = Math.max(0.0f, Math.min(1.0f, e.getX() / v.getWidth()))
+                    this@ToggleRangeTemplateBehavior.updateRange(percent, true)
+                }
+            }
+        }
+
+        private fun endTouch(v: View, e: MotionEvent) {
+            if (!isDragging) {
+                this@ToggleRangeTemplateBehavior.toggle()
+            } else {
+                val percent = Math.max(0.0f, Math.min(1.0f, e.getX() / v.getWidth()))
+                this@ToggleRangeTemplateBehavior.endUpdateRange(percent)
+            }
+
+            initialTouchX = 0.0f
+            initialTouchY = 0.0f
+            isDragging = false
+        }
+    }
+}
+
+private class ToggleTemplateBehavior : Behavior {
+    lateinit var clipLayer: Drawable
+    lateinit var template: ToggleTemplate
+    lateinit var control: Control
+    lateinit var cvh: ControlViewHolder
+    lateinit var context: Context
+    lateinit var status: TextView
+
+    override fun apply(cvh: ControlViewHolder, cws: ControlWithState) {
+        this.control = cws.control!!
+        this.cvh = cvh
+        status = cvh.status
+
+        status.setText(control.getStatusText())
+
+        cvh.layout.setOnClickListener(View.OnClickListener() { toggle() })
+
+        val ld = cvh.layout.getBackground() as LayerDrawable
+        clipLayer = ld.findDrawableByLayerId(R.id.clip_layer)
+
+        template = control.getControlTemplate() as ToggleTemplate
+
+        val checked = template.isChecked()
+        val deviceType = control.getDeviceType()
+
+        clipLayer.setLevel(if (checked) MAX_LEVEL else MIN_LEVEL)
+        cvh.setEnabled(checked)
+        cvh.applyRenderInfo(findRenderInfo(deviceType, checked))
+    }
+
+    fun toggle() {
+        cvh.action(BooleanAction(template.getTemplateId(), !template.isChecked()))
+
+        val nextLevel = if (template.isChecked()) MIN_LEVEL else MAX_LEVEL
+        clipLayer.setLevel(nextLevel)
+    }
+}
+
+internal data class RenderInfo(val iconResourceId: Int, val foreground: Int, val background: Int)
+
+private val unknownDeviceMap = mapOf(
+    false to RenderInfo(
+        R.drawable.ic_light_off_gm2_24px,
+        R.color.unknown_foreground,
+        R.color.unknown_foreground),
+    true to RenderInfo(
+        R.drawable.ic_lightbulb_outline_gm2_24px,
+        R.color.unknown_foreground,
+        R.color.unknown_foreground)
+)
+
+private val deviceRenderMap = mapOf<Int, Map<Boolean, RenderInfo>>(
+    DeviceTypes.TYPE_UNKNOWN to unknownDeviceMap,
+    DeviceTypes.TYPE_LIGHT to mapOf(
+        false to RenderInfo(
+            R.drawable.ic_light_off_gm2_24px,
+            R.color.light_foreground,
+            R.color.light_background),
+        true to RenderInfo(
+            R.drawable.ic_lightbulb_outline_gm2_24px,
+            R.color.light_foreground,
+            R.color.light_background)
+    ),
+    DeviceTypes.TYPE_THERMOSTAT to mapOf(
+        false to RenderInfo(
+            R.drawable.ic_device_thermostat_gm2_24px,
+            R.color.light_foreground,
+            R.color.light_background),
+        true to RenderInfo(
+            R.drawable.ic_device_thermostat_gm2_24px,
+            R.color.light_foreground,
+            R.color.light_background)
+    ),
+    DeviceTypes.TYPE_CAMERA to mapOf(
+        false to RenderInfo(
+            R.drawable.ic_videocam_gm2_24px,
+            R.color.light_foreground,
+            R.color.light_background),
+        true to RenderInfo(
+            R.drawable.ic_videocam_gm2_24px,
+            R.color.light_foreground,
+            R.color.light_background)
+    ),
+    DeviceTypes.TYPE_LOCK to mapOf(
+        false to RenderInfo(
+            R.drawable.ic_lock_open_gm2_24px,
+            R.color.lock_foreground,
+            R.color.lock_background),
+        true to RenderInfo(
+            R.drawable.ic_lock_gm2_24px,
+            R.color.lock_foreground,
+            R.color.lock_background)
+    ),
+    DeviceTypes.TYPE_SWITCH to mapOf(
+        false to RenderInfo(
+            R.drawable.ic_switches_gm2_24px,
+            R.color.lock_foreground,
+            R.color.lock_background),
+        true to RenderInfo(
+            R.drawable.ic_switches_gm2_24px,
+            R.color.lock_foreground,
+            R.color.lock_background)
+    ),
+    DeviceTypes.TYPE_OUTLET to mapOf(
+        false to RenderInfo(
+            R.drawable.ic_power_off_gm2_24px,
+            R.color.lock_foreground,
+            R.color.lock_background),
+        true to RenderInfo(
+            R.drawable.ic_power_gm2_24px,
+            R.color.lock_foreground,
+            R.color.lock_background)
+    )
+)
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlWithState.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlWithState.kt
new file mode 100644
index 0000000..816f0b2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlWithState.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.controls.ui
+
+import android.service.controls.Control
+
+import com.android.systemui.controls.controller.ControlInfo
+
+/**
+ * A container for:
+ * <ul>
+ *  <li>ControlInfo - Basic cached info about a Control
+ *  <li>Control - Actual Control parcelable received directly from
+ *  the participating application
+ * </ul>
+ */
+data class ControlWithState(val ci: ControlInfo, val control: Control?)
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt
index 0270c2b..b07a75d 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt
@@ -19,12 +19,15 @@
 import android.content.ComponentName
 import android.service.controls.Control
 import android.service.controls.actions.ControlAction
+import android.view.ViewGroup
 
 interface ControlsUiController {
+    fun show(parent: ViewGroup)
+    fun hide()
     fun onRefreshState(componentName: ComponentName, controls: List<Control>)
     fun onActionResponse(
         componentName: ComponentName,
         controlId: String,
         @ControlAction.ResponseResult response: Int
     )
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
index 0ace126..926fb6e 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
@@ -16,19 +16,204 @@
 
 package com.android.systemui.controls.ui
 
+import android.accounts.Account
+import android.accounts.AccountManager
 import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.content.ServiceConnection
+import android.os.IBinder
 import android.service.controls.Control
+import android.service.controls.TokenProvider
+import android.util.Log
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.TextView
+
+import com.android.systemui.controls.controller.ControlsController
+import com.android.systemui.controls.controller.ControlInfo
+import com.android.systemui.controls.management.ControlsProviderSelectorActivity
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.R
+
+import dagger.Lazy
+
+import java.util.concurrent.Executor
 import javax.inject.Inject
 import javax.inject.Singleton
 
+private const val TAG = "ControlsUi"
+
+// TEMP CODE for MOCK
+private const val TOKEN = "https://www.googleapis.com/auth/assistant"
+private const val SCOPE = "oauth2:" + TOKEN
+private var tokenProviderConnection: TokenProviderConnection? = null
+class TokenProviderConnection(val cc: ControlsController, val context: Context)
+    : ServiceConnection {
+    private var mTokenProvider: TokenProvider? = null
+
+    override fun onServiceConnected(cName: ComponentName, binder: IBinder) {
+        Thread({
+            Log.i(TAG, "TokenProviderConnection connected")
+            mTokenProvider = TokenProvider.Stub.asInterface(binder)
+
+            val mLastAccountName = mTokenProvider?.getAccountName()
+
+            if (mLastAccountName == null || mLastAccountName.isEmpty()) {
+                Log.e(TAG, "NO ACCOUNT IS SET. Open HomeMock app")
+            } else {
+                mTokenProvider?.setAuthToken(getAuthToken(mLastAccountName))
+                cc.subscribeToFavorites()
+            }
+        }, "TokenProviderThread").start()
+    }
+
+    override fun onServiceDisconnected(cName: ComponentName) {
+        mTokenProvider = null
+    }
+
+    fun getAuthToken(accountName: String): String? {
+        val am = AccountManager.get(context)
+        val accounts = am.getAccountsByType("com.google")
+        if (accounts == null || accounts.size == 0) {
+            Log.w(TAG, "No com.google accounts found")
+            return null
+        }
+
+        var account: Account? = null
+        for (a in accounts) {
+            if (a.name.equals(accountName)) {
+                account = a
+                break
+            }
+        }
+
+        if (account == null) {
+            account = accounts[0]
+        }
+
+        try {
+            return am.blockingGetAuthToken(account!!, SCOPE, true)
+        } catch (e: Throwable) {
+            Log.e(TAG, "Error getting auth token", e)
+            return null
+        }
+    }
+}
+
 @Singleton
-class ControlsUiControllerImpl @Inject constructor() : ControlsUiController {
+class ControlsUiControllerImpl @Inject constructor (
+    val controlsController: Lazy<ControlsController>,
+    val context: Context,
+    @Main val uiExecutor: Executor
+) : ControlsUiController {
+
+    private lateinit var controlInfos: List<ControlInfo>
+    private val controlsById = mutableMapOf<Pair<ComponentName, String>, ControlWithState>()
+    private val controlViewsById = mutableMapOf<String, ControlViewHolder>()
+    private lateinit var parent: ViewGroup
+
+    override fun show(parent: ViewGroup) {
+        Log.d(TAG, "show()")
+
+        this.parent = parent
+
+        controlInfos = controlsController.get().getFavoriteControls()
+
+        controlInfos.map {
+            ControlWithState(it, null)
+        }.associateByTo(controlsById) { Pair(it.ci.component, it.ci.controlId) }
+
+        if (controlInfos.isEmpty()) {
+            showInitialSetupView()
+        } else {
+            showControlsView()
+        }
+
+        // Temp code to pass auth
+        tokenProviderConnection = TokenProviderConnection(controlsController.get(), context)
+        val serviceIntent = Intent()
+        serviceIntent.setComponent(ComponentName("com.android.systemui.home.mock",
+                "com.android.systemui.home.mock.AuthService"))
+        context.bindService(serviceIntent, tokenProviderConnection!!, Context.BIND_AUTO_CREATE)
+    }
+
+    private fun showInitialSetupView() {
+        val inflater = LayoutInflater.from(context)
+        inflater.inflate(R.layout.controls_no_favorites, parent, true)
+
+        val textView = parent.requireViewById(R.id.controls_title) as TextView
+        textView.setOnClickListener {
+            val i = Intent()
+            i.setComponent(ComponentName(context, ControlsProviderSelectorActivity::class.java))
+            i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
+            context.startActivity(i)
+        }
+    }
+
+    private fun showControlsView() {
+        val inflater = LayoutInflater.from(context)
+        inflater.inflate(R.layout.controls_with_favorites, parent, true)
+
+        val listView = parent.requireViewById(R.id.global_actions_controls_list) as ViewGroup
+        var lastRow: ViewGroup = createRow(inflater, listView)
+        controlInfos.forEach {
+            Log.d(TAG, "favorited control id: " + it.controlId)
+            if (lastRow.getChildCount() == 2) {
+                lastRow = createRow(inflater, listView)
+            }
+            val item = inflater.inflate(
+                R.layout.controls_base_item, lastRow, false) as ViewGroup
+            lastRow.addView(item)
+            val cvh = ControlViewHolder(item, controlsController.get())
+            cvh.bindData(controlsById.get(Pair(it.component, it.controlId))!!)
+            controlViewsById.put(it.controlId, cvh)
+        }
+
+        val moreImageView = parent.requireViewById(R.id.controls_more) as View
+        moreImageView.setOnClickListener {
+            val i = Intent()
+            i.setComponent(ComponentName(context, ControlsProviderSelectorActivity::class.java))
+            i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
+            context.startActivity(i)
+        }
+    }
+
+    override fun hide() {
+        Log.d(TAG, "hide()")
+        controlsController.get().unsubscribe()
+        context.unbindService(tokenProviderConnection)
+        tokenProviderConnection = null
+
+        parent.removeAllViews()
+        controlsById.clear()
+        controlViewsById.clear()
+    }
 
     override fun onRefreshState(componentName: ComponentName, controls: List<Control>) {
-        TODO("not implemented")
+        Log.d(TAG, "onRefreshState()")
+        controls.forEach { c ->
+            controlsById.get(Pair(componentName, c.getControlId()))?.let {
+                Log.d(TAG, "onRefreshState() for id: " + c.getControlId())
+                val cws = ControlWithState(it.ci, c)
+                controlsById.put(Pair(componentName, c.getControlId()), cws)
+
+                uiExecutor.execute {
+                    controlViewsById.get(c.getControlId())?.bindData(cws)
+                }
+            }
+        }
     }
 
     override fun onActionResponse(componentName: ComponentName, controlId: String, response: Int) {
+        Log.d(TAG, "onActionResponse()")
         TODO("not implemented")
     }
-}
\ No newline at end of file
+
+    private fun createRow(inflater: LayoutInflater, parent: ViewGroup): ViewGroup {
+        val row = inflater.inflate(R.layout.controls_row, parent, false) as ViewGroup
+        parent.addView(row)
+        return row
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
index 83f6d45..80d776a 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
@@ -93,12 +93,13 @@
 import com.android.systemui.MultiListLayout.MultiListAdapter;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
+import com.android.systemui.controls.ui.ControlsUiController;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.GlobalActions.GlobalActionsManager;
 import com.android.systemui.plugins.GlobalActionsPanelPlugin;
-import com.android.systemui.statusbar.phone.NotificationShadeWindowController;
 import com.android.systemui.statusbar.BlurUtils;
+import com.android.systemui.statusbar.phone.NotificationShadeWindowController;
 import com.android.systemui.statusbar.phone.ScrimController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -183,6 +184,7 @@
     private final IStatusBarService mStatusBarService;
     private final NotificationShadeWindowController mNotificationShadeWindowController;
     private GlobalActionsPanelPlugin mPanelPlugin;
+    private ControlsUiController mControlsUiController;
 
     /**
      * @param context everything needs a context :(
@@ -200,7 +202,8 @@
             TelecomManager telecomManager, MetricsLogger metricsLogger,
             BlurUtils blurUtils, SysuiColorExtractor colorExtractor,
             IStatusBarService statusBarService,
-            NotificationShadeWindowController notificationShadeWindowController) {
+            NotificationShadeWindowController notificationShadeWindowController,
+            ControlsUiController controlsUiController) {
         mContext = new ContextThemeWrapper(context, com.android.systemui.R.style.qs_theme);
         mWindowManagerFuncs = windowManagerFuncs;
         mAudioManager = audioManager;
@@ -220,6 +223,7 @@
         mSysuiColorExtractor = colorExtractor;
         mStatusBarService = statusBarService;
         mNotificationShadeWindowController = notificationShadeWindowController;
+        mControlsUiController = controlsUiController;
 
         // receive broadcasts
         IntentFilter filter = new IntentFilter();
@@ -455,9 +459,12 @@
                                 mKeyguardManager.isDeviceLocked())
                         : null;
 
+        boolean showControls = !mKeyguardManager.isDeviceLocked() && isControlsEnabled(mContext);
+
         ActionsDialog dialog = new ActionsDialog(mContext, mAdapter, panelViewController,
                 mBlurUtils, mSysuiColorExtractor, mStatusBarService,
-                mNotificationShadeWindowController, isControlsEnabled(mContext));
+                mNotificationShadeWindowController,
+                showControls ? mControlsUiController : null);
         dialog.setCanceledOnTouchOutside(false); // Handled by the custom class.
         dialog.setKeyguardShowing(mKeyguardShowing);
 
@@ -1543,13 +1550,15 @@
         private boolean mHadTopUi;
         private final NotificationShadeWindowController mNotificationShadeWindowController;
         private final BlurUtils mBlurUtils;
-        private final boolean mControlsEnabled;
+
+        private ControlsUiController mControlsUiController;
+        private ViewGroup mControlsView;
 
         ActionsDialog(Context context, MyAdapter adapter,
                 GlobalActionsPanelPlugin.PanelViewController plugin, BlurUtils blurUtils,
                 SysuiColorExtractor sysuiColorExtractor, IStatusBarService statusBarService,
                 NotificationShadeWindowController notificationShadeWindowController,
-                boolean controlsEnabled) {
+                ControlsUiController controlsUiController) {
             super(context, com.android.systemui.R.style.Theme_SystemUI_Dialog_GlobalActions);
             mContext = context;
             mAdapter = adapter;
@@ -1557,7 +1566,7 @@
             mColorExtractor = sysuiColorExtractor;
             mStatusBarService = statusBarService;
             mNotificationShadeWindowController = notificationShadeWindowController;
-            mControlsEnabled = controlsEnabled;
+            mControlsUiController = controlsUiController;
 
             // Window initialization
             Window window = getWindow();
@@ -1639,6 +1648,7 @@
         private void initializeLayout() {
             setContentView(getGlobalActionsLayoutId(mContext));
             fixNavBarClipping();
+            mControlsView = findViewById(com.android.systemui.R.id.global_actions_controls);
             mGlobalActionsLayout = findViewById(com.android.systemui.R.id.global_actions_view);
             mGlobalActionsLayout.setOutsideTouchListener(view -> dismiss());
             ((View) mGlobalActionsLayout.getParent()).setOnClickListener(view -> dismiss());
@@ -1674,7 +1684,7 @@
         }
 
         private int getGlobalActionsLayoutId(Context context) {
-            if (mControlsEnabled) {
+            if (mControlsUiController != null) {
                 return com.android.systemui.R.layout.global_actions_grid_v2;
             }
 
@@ -1758,6 +1768,9 @@
                                 mBlurUtils.radiusForRatio(animatedValue));
                     })
                     .start();
+            if (mControlsUiController != null) {
+                mControlsUiController.show(mControlsView);
+            }
         }
 
         @Override
@@ -1766,6 +1779,7 @@
                 return;
             }
             mShowing = false;
+            if (mControlsUiController != null) mControlsUiController.hide();
             mGlobalActionsLayout.setTranslationX(0);
             mGlobalActionsLayout.setTranslationY(0);
             mGlobalActionsLayout.setAlpha(1);