Bluetooth configuration for SetupWizard.

This bluetooth activity will ask the user to pair a bluetooth device.
Upon completion of a successful pairing, the screen will move onto the
next action. This means during setup, only a single bluetooth item will
be set up.

Test: manually tested
Bug: 34378933
Change-Id: I4469c274cf0ba063a871b8772d33496a99de4d5b
diff --git a/SetupWizard/Android.mk b/SetupWizard/Android.mk
new file mode 100644
index 0000000..3a151f2
--- /dev/null
+++ b/SetupWizard/Android.mk
@@ -0,0 +1,45 @@
+#
+# Copyright (C) 2017 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.
+#
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
+
+LOCAL_PACKAGE_NAME := CarSetupWizard
+
+LOCAL_CERTIFICATE := platform
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_PRIVILEGED_MODULE := true
+
+# For setup wizard common lib
+LOCAL_STATIC_JAVA_LIBRARIES += android-setup-wizard-common
+LOCAL_AAPT_FLAGS += --auto-add-overlay --extra-packages com.google.android.setupwizard.common
+LOCAL_RESOURCE_DIR += vendor/google/apps/SetupWizard/libs/base/res
+
+LOCAL_PROGUARD_ENABLED := disabled
+
+LOCAL_DEX_PREOPT := false
+
+include frameworks/opt/setupwizard/library/common-gingerbread.mk
+include frameworks/base/packages/SettingsLib/common.mk
+
+include $(BUILD_PACKAGE)
diff --git a/SetupWizard/AndroidManifest.xml b/SetupWizard/AndroidManifest.xml
new file mode 100644
index 0000000..0e7d0ca
--- /dev/null
+++ b/SetupWizard/AndroidManifest.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2017 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.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.car.setupwizard">
+    <uses-sdk
+        android:minSdkVersion="23"
+        android:targetSdkVersion="23" />
+
+    <uses-permission android:name="android.permission.BLUETOOTH"/>
+    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
+
+    <application
+        android:allowBackup="false"
+        android:supportsRtl="true"
+        android:allowClearUserData="false"
+        android:hardwareAccelerated="true"
+        android:label="@string/app_name"
+        android:theme="@style/SuwThemeGlif.Light">
+
+        <activity android:name=".bluetooth.BluetoothActivity"
+            android:enabled="true"
+            android:exported="true"
+            android:immersive="true"
+            android:label="@string/bluetooth_title"
+            android:windowSoftInputMode="adjustNothing">
+            <intent-filter>
+                <action android:name="com.android.car.setupwizard.BLUETOOTH_SETTINGS" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </activity>
+    </application>
+</manifest>
diff --git a/SetupWizard/res/drawable/ic_bluetooth.xml b/SetupWizard/res/drawable/ic_bluetooth.xml
new file mode 100644
index 0000000..3e96002
--- /dev/null
+++ b/SetupWizard/res/drawable/ic_bluetooth.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+    Copyright (C) 2017 Google Inc.
+
+    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="@dimen/glif_icon_size"
+        android:height="@dimen/glif_icon_size"
+        android:viewportWidth="48.0"
+        android:viewportHeight="48.0"
+        android:autoMirrored="true">
+    <path
+        android:fillColor="?android:attr/colorPrimary"
+        android:pathData="M35.41,15.41L24,4h-2v15.17L12.83,10 10,12.83 21.17,24 10,35.17 12.83,38 22,28.83L22,44h2l11.41,-11.41L26.83,24l8.58,-8.59zM26,11.66l3.76,3.76L26,19.17v-7.51zM29.76,32.59L26,36.34v-7.52l3.76,3.77z" />
+</vector>
diff --git a/SetupWizard/res/drawable/ic_bluetooth_connected.xml b/SetupWizard/res/drawable/ic_bluetooth_connected.xml
new file mode 100644
index 0000000..7e08c89
--- /dev/null
+++ b/SetupWizard/res/drawable/ic_bluetooth_connected.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+    Copyright (c) 2017 Google Inc.
+
+    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="48.0"
+        android:viewportHeight="48.0"
+        android:autoMirrored="true">
+    <path
+        android:fillColor="?attr/suwListItemIconColor"
+        android:pathData="M7,12l-2,-2 -2,2 2,2 2,-2zM17.71,7.71L12,2h-1v7.59L6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 11,14.41L11,22h1l5.71,-5.71 -4.3,-4.29 4.3,-4.29zM13,5.83l1.88,1.88L13,9.59L13,5.83zM14.88,16.29L13,18.17v-3.76l1.88,1.88zM19,10l-2,2 2,2 2,-2 -2,-2z" />
+</vector>
diff --git a/SetupWizard/res/drawable/ic_bluetooth_item.xml b/SetupWizard/res/drawable/ic_bluetooth_item.xml
new file mode 100644
index 0000000..a1841c0
--- /dev/null
+++ b/SetupWizard/res/drawable/ic_bluetooth_item.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+    Copyright (C) 2017 Google Inc.
+
+    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="48.0"
+        android:viewportHeight="48.0"
+        android:autoMirrored="true">
+    <path
+        android:fillColor="?android:attr/colorPrimary"
+        android:pathData="M35.41,15.41L24,4h-2v15.17L12.83,10 10,12.83 21.17,24 10,35.17 12.83,38 22,28.83L22,44h2l11.41,-11.41L26.83,24l8.58,-8.59zM26,11.66l3.76,3.76L26,19.17v-7.51zM29.76,32.59L26,36.34v-7.52l3.76,3.77z" />
+</vector>
diff --git a/SetupWizard/res/drawable/ic_bluetooth_scanning.xml b/SetupWizard/res/drawable/ic_bluetooth_scanning.xml
new file mode 100644
index 0000000..0a8d064
--- /dev/null
+++ b/SetupWizard/res/drawable/ic_bluetooth_scanning.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+    Copyright (C) 2017 Google Inc.
+
+    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="48.0"
+        android:viewportHeight="48.0">
+    <path
+        android:fillAlpha=".3"
+        android:fillColor="?attr/suwListItemIconColor"
+        android:pathData="M28.48,24.02l4.64,4.64c0.56,-1.45 0.88,-3.02 0.88,-4.66 0,-1.63 -0.31,-3.19 -0.86,-4.63l-4.66,4.65zM39.06,13.43l-2.53,2.53c1.25,2.41 1.97,5.14 1.97,8.05s-0.72,5.63 -1.97,8.05l2.4,2.4c1.93,-3.1 3.07,-6.73 3.07,-10.63 0,-3.82 -1.09,-7.37 -2.94,-10.4zM31.41,15.41L20,4h-2v15.17L8.83,10 6,12.83 17.17,24 6,35.17 8.83,38 18,28.83L18,44h2l11.41,-11.41L22.83,24l8.58,-8.59zM22,11.66l3.76,3.76L22,19.17v-7.51zM25.76,32.59L22,36.34v-7.52l3.76,3.77z" />
+</vector>
diff --git a/SetupWizard/res/drawable/ic_computer.xml b/SetupWizard/res/drawable/ic_computer.xml
new file mode 100644
index 0000000..e7020ee
--- /dev/null
+++ b/SetupWizard/res/drawable/ic_computer.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+    Copyright (C) 2017 Google Inc.
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+        http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:pathData="M20,18c1.1,0 1.99,-0.9 1.99,-2L22,6c0,-1.1 -0.9,-2 -2,-2H4c-1.1,0 -2,0.9 -2,2v10c0,1.1 0.9,2 2,2H0v2h24v-2h-4zM4,6h16v10H4V6z"
+        android:fillColor="#000000"/>
+</vector>
diff --git a/SetupWizard/res/drawable/ic_headset.xml b/SetupWizard/res/drawable/ic_headset.xml
new file mode 100644
index 0000000..ef805c1
--- /dev/null
+++ b/SetupWizard/res/drawable/ic_headset.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+    Copyright (C) 2017 Google Inc.
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+        http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:pathData="M12,1c-4.97,0 -9,4.03 -9,9v7c0,1.66 1.34,3 3,3h3v-8H5v-2c0,-3.87 3.13,-7 7,-7s7,3.13 7,7v2h-4v8h3c1.66,0 3,-1.34 3,-3v-7c0,-4.97 -4.03,-9 -9,-9z"
+        android:fillColor="#000000"/>
+</vector>
diff --git a/SetupWizard/res/drawable/ic_refresh.xml b/SetupWizard/res/drawable/ic_refresh.xml
new file mode 100644
index 0000000..5c39440
--- /dev/null
+++ b/SetupWizard/res/drawable/ic_refresh.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+    Copyright (C) 2017 Google Inc.
+
+    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="48.0"
+        android:viewportHeight="48.0"
+        android:autoMirrored="true">
+    <path
+        android:fillColor="?attr/suwListItemIconColor"
+        android:pathData="M35.3,12.7C32.41,9.8 28.42,8 24,8 15.16,8 8.02,15.16 8.02,24S15.16,40 24,40c7.45,0 13.69,-5.1 15.46,-12H35.3c-1.65,4.66 -6.07,8 -11.3,8 -6.63,0 -12,-5.37 -12,-12s5.37,-12 12,-12c3.31,0 6.28,1.38 8.45,3.55L26,22h14V8l-4.7,4.7z" />
+</vector>
diff --git a/SetupWizard/res/drawable/ic_skip.xml b/SetupWizard/res/drawable/ic_skip.xml
new file mode 100644
index 0000000..8707990
--- /dev/null
+++ b/SetupWizard/res/drawable/ic_skip.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+    Copyright (C) 2017 Google Inc.
+
+    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"
+    android:autoMirrored="true">
+    <path
+        android:fillColor="?attr/suwListItemIconColor"
+        android:pathData="M18.4,10.6C16.55,8.99 14.15,8 11.5,8c-4.65,0 -8.58,3.03 -9.96,7.22L3.9,16c1.05,-3.19 4.05,-5.5 7.6,-5.5 1.95,0 3.73,0.72 5.12,1.88L13,16h9V7l-3.6,3.6z"/>
+</vector>
diff --git a/SetupWizard/res/drawable/ic_smartphone.xml b/SetupWizard/res/drawable/ic_smartphone.xml
new file mode 100644
index 0000000..050d73c
--- /dev/null
+++ b/SetupWizard/res/drawable/ic_smartphone.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+    Copyright (C) 2017 Google Inc.
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+        http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:pathData="M17,1.01L7,1c-1.1,0 -2,0.9 -2,2v18c0,1.1 0.9,2 2,2h10c1.1,0 2,-0.9 2,-2V3c0,-1.1 -0.9,-1.99 -2,-1.99zM17,19H7V5h10v14z"
+        android:fillColor="#000000"/>
+</vector>
diff --git a/SetupWizard/res/drawable/ic_watch.xml b/SetupWizard/res/drawable/ic_watch.xml
new file mode 100644
index 0000000..1346e5b
--- /dev/null
+++ b/SetupWizard/res/drawable/ic_watch.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+    Copyright (C) 2017 Google Inc.
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+        http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:pathData="M20,12c0,-2.54 -1.19,-4.81 -3.04,-6.27L16,0H8l-0.95,5.73C5.19,7.19 4,9.45 4,12s1.19,4.81 3.05,6.27L8,24h8l0.96,-5.73C18.81,16.81 20,14.54 20,12zM6,12c0,-3.31 2.69,-6 6,-6s6,2.69 6,6 -2.69,6 -6,6 -6,-2.69 -6,-6z"
+        android:fillColor="#000000"/>
+</vector>
diff --git a/SetupWizard/res/layout/bluetooth_activity.xml b/SetupWizard/res/layout/bluetooth_activity.xml
new file mode 100644
index 0000000..9e32b4d
--- /dev/null
+++ b/SetupWizard/res/layout/bluetooth_activity.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+    Copyright (C) 2017 Google Inc.
+
+    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.
+-->
+<com.android.setupwizardlib.GlifRecyclerLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/setup_wizard_layout"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:entries="@xml/items_bluetooth"
+    android:icon="@drawable/ic_bluetooth"
+    app:suwDividerInset="@dimen/suw_items_glif_icon_divider_inset"
+    app:suwHeaderText="@string/bluetooth_title" />
diff --git a/SetupWizard/res/layout/items_greyed_out.xml b/SetupWizard/res/layout/items_greyed_out.xml
new file mode 100644
index 0000000..0c190c5
--- /dev/null
+++ b/SetupWizard/res/layout/items_greyed_out.xml
@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+    Copyright (C) 2017 Google Inc.
+
+    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"
+    xmlns:tools="http://schemas.android.com/tools"
+    style="@style/SuwItemContainer"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="horizontal">
+
+    <FrameLayout
+        android:id="@+id/suw_items_icon_container"
+        android:layout_width="@dimen/suw_items_icon_container_width"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center_vertical"
+        android:gravity="start">
+
+        <ImageView
+            android:id="@+id/suw_items_icon"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            tools:ignore="ContentDescription" />
+
+    </FrameLayout>
+
+    <LinearLayout
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_weight="1"
+        android:gravity="center_vertical"
+        android:orientation="vertical">
+
+        <TextView
+            android:id="@+id/suw_items_title"
+            style="@style/SuwItemTitle"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:gravity="start"
+            android:textAlignment="viewStart"
+            android:textColor="?android:attr/textColorSecondary"
+            tools:ignore="UnusedAttribute" />
+
+        <TextView
+            android:id="@+id/suw_items_summary"
+            style="@style/SuwItemSummary"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginBottom="@dimen/suw_items_padding_bottom_extra"
+            android:gravity="start"
+            android:textAlignment="viewStart"
+            android:visibility="gone"
+            tools:ignore="UnusedAttribute" />
+
+    </LinearLayout>
+
+</LinearLayout>
diff --git a/SetupWizard/res/values/dimens.xml b/SetupWizard/res/values/dimens.xml
new file mode 100644
index 0000000..6ce560c
--- /dev/null
+++ b/SetupWizard/res/values/dimens.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright (c) 2017 Google Inc.
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+        http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<resources>
+    <dimen name="glif_icon_size">32dp</dimen>
+</resources>
diff --git a/SetupWizard/res/values/strings.xml b/SetupWizard/res/values/strings.xml
new file mode 100644
index 0000000..8b55140
--- /dev/null
+++ b/SetupWizard/res/values/strings.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+    Copyright (C) 2017 Google Inc.
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+        http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- Title of the application. Usually not shown. Used as a fallback for TalkBack when activity label is not specified (an accessibility service). [CHAR LIMIT=NONE] -->
+    <string name="app_name">Setup Wizard</string>
+
+    <!-- Title of bluetooth screen, allowing users to a device to pair to. [CHAR_LIMIT=40] -->
+    <string name="bluetooth_title">Pair a device</string>
+
+    <!-- Message shown while the device is scanning for nearby bluetooth devices. [CHAR LIMIT=NONE] -->
+    <string name="bluetooth_scanning">Searching for Bluetooth devices\u2026</string>
+
+    <!-- Skip connecting to any bluetooth devices. [CHAR LIMIT=80] -->
+    <string name="bluetooth_dont_connect">Don\u2019t pair to any bluetooth device</string>
+
+    <!-- Initiate a re-scan for any nearby bluetooth devices. [CHAR LIMIT=80] -->
+    <string name="bluetooth_rescan">Refresh</string>
+
+    <!-- Message displayed to convey to the user that a specific bluetooth device is being paired to. [CHAR LIMIT=80] -->
+    <string name="bluetooth_device_connecting">Pairing\u2026</string>
+
+    <!-- Message when attempting to disconnect from a paired bluetooth device. [CHAR LIMIT=80] -->
+    <string name="bluetooth_device_disconnecting">Unpairing\u2026</string>
+
+    <!-- Message when an in-progress connection to a bluetooth device is being cancelled. [CHAR LIMIT=80] -->
+    <string name="bluetooth_device_cancelling">Cancelling\u2026</string>
+
+    <!-- Message displayed to convey to the user that a specific bluetooth device has been paired to. [CHAR LIMIT=80] -->
+    <string name="bluetooth_device_connected">Connected</string>
+
+    <!-- Description of the bluetooth screen, telling the user to select a device to pair to it via bluetooth. [CHAR LIMIT=NONE] -->
+    <string name="bluetooth_description">Select a device from the list to pair to it via bluetooth.</string>
+</resources>
diff --git a/SetupWizard/res/xml/items_bluetooth.xml b/SetupWizard/res/xml/items_bluetooth.xml
new file mode 100644
index 0000000..ed538c8
--- /dev/null
+++ b/SetupWizard/res/xml/items_bluetooth.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+    Copyright (c) 2017 Google Inc.
+
+    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.
+-->
+<ItemGroup xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <Item
+        android:id="@+id/bluetooth_description"
+        android:enabled="false"
+        android:layout="@layout/suw_items_description" />
+
+    <Item
+        android:id="@+id/bluetooth_dont_connect"
+        android:icon="@drawable/ic_skip"
+        android:title="@string/bluetooth_dont_connect" />
+
+    <Item
+        android:id="@+id/bluetooth_scanning"
+        android:icon="@drawable/ic_bluetooth_scanning"
+        android:layout="@layout/items_greyed_out"
+        android:title="@string/bluetooth_scanning" />
+
+    <Item
+        android:id="@+id/bluetooth_rescan"
+        android:icon="@drawable/ic_refresh"
+        android:title="@string/bluetooth_rescan"
+        android:visible="false"  />
+
+    <com.android.car.setupwizard.bluetooth.BluetoothDeviceHierarchy
+        android:id="@+id/bluetooth_device_list" />
+
+</ItemGroup>
diff --git a/SetupWizard/res/xml/wizard_script_setup_as_new_flow.xml b/SetupWizard/res/xml/wizard_script_setup_as_new_flow.xml
new file mode 100644
index 0000000..c55bebc
--- /dev/null
+++ b/SetupWizard/res/xml/wizard_script_setup_as_new_flow.xml
@@ -0,0 +1,191 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+    Copyright (C) 2017 Google Inc.
+
+    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.
+-->
+
+<!--
+    The wizard:uris recorded here have the inconvenience of being generated by hand, but they allow
+    for the full spread of launch flags (we need FLAG_ACTIVITY_NEW_TASK [0x10000000]), where the
+    <intent> tag processed by Intent.parseIntent() does not.
+
+    adb shell am to-intent-uri -a com.android.setupwizard.WELCOME -f 0x10000000 \-\-ez firstRun true
+-->
+<WizardScript xmlns:wizard="http://schemas.android.com/apk/res/com.android.car.setupwizard"
+    wizard:firstAction="device_owner_warning">
+
+    <!-- Security warning [RECOMMENDED] -->
+    <WizardAction id="device_owner_warning"
+        wizard:uri="intent:#Intent;action=com.google.android.setupwizard.DEVICE_OWNER_WARNING;end">
+        <result wizard:name="skip"
+            wizard:resultCode="1"
+            wizard:action="check_frp" />
+        <result wizard:action="factory_reset" />
+    </WizardAction>
+
+    <WizardAction id="factory_reset"
+        wizard:uri="intent:#Intent;action=com.google.android.setupwizard.FACTORY_RESET;end">
+        <!-- Factory reset should cause a reboot, but if it returns unexpectedly,
+             continue on to check_frp -->
+        <result wizard:action="check_frp" />
+    </WizardAction>
+
+
+    <!-- Wait to check factory reset protection status [RECOMMENDED] -->
+    <WizardAction id="check_frp"
+        wizard:uri="intent:#Intent;action=com.google.android.setupwizard.CHECK_FRP;end">
+        <result wizard:action="bluetooth_settings" />
+    </WizardAction>
+
+    <!-- Bluetooth selection [REQUIRED, CUSTOMIZABLE] -->
+    <WizardAction id="bluetooth_settings"
+        wizard:uri="intent:#Intent;action=com.android.setupwizard.BLUETOOTH_SETTINGS;end">
+        <result wizard:action="network_settings" />
+    </WizardAction>
+
+
+    <!-- Users must be given the opportunity to set up an internet connection, using the given
+         screens or a custom flow. -->
+    <!-- Network selection [REQUIRED, CUSTOMIZABLE] -->
+    <WizardAction id="network_settings"
+        wizard:uri="intent:#Intent;action=com.android.setupwizard.NETWORK_SETTINGS;end">
+        <result wizard:name="see_all_wifi"
+            wizard:resultCode="101"
+            wizard:action="wifi_settings" />
+        <result wizard:name="use_cellular"
+            wizard:resultCode="102"
+            wizard:action="activate_mobile_data" />
+        <result wizard:name="skip"
+            wizard:resultCode="1"
+            wizard:action="no_network_flow" />
+        <result wizard:action="captive_portal" />
+    </WizardAction>
+
+    <!-- Mobile data activation -->
+    <WizardAction id="activate_mobile_data"
+        wizard:uri="intent:#Intent;action=com.android.setupwizard.ACTIVATE_MOBILE_DATA;end">
+        <result wizard:action="captive_portal" />
+    </WizardAction>
+
+    <!-- Wi-Fi setup -->
+    <WizardAction id="wifi_settings"
+        wizard:uri="intent:#Intent;action=com.android.setupwizard.WIFI_SETTINGS;end">
+        <result wizard:action="captive_portal" />
+    </WizardAction>
+
+
+    <!-- Resolve captive portal access, and wait for check-in [REQUIRED] -->
+    <WizardAction id="captive_portal"
+        wizard:uri="intent:#Intent;action=com.android.setupwizard.CAPTIVE_PORTAL;end">
+        <result wizard:action="gms_checkin" />
+    </WizardAction>
+    <WizardAction id="gms_checkin"
+        wizard:uri="intent:#Intent;action=com.google.android.setupwizard.GMS_CHECKIN;end">
+        <result wizard:action="ota_update" />
+    </WizardAction>
+
+
+    <!-- Update system packages [REQUIRED] -->
+    <WizardAction id="ota_update"
+        wizard:uri="intent:#Intent;action=com.google.android.setupwizard.OTA_UPDATE;end">
+        <result wizard:name="skip"
+            wizard:resultCode="1"
+            wizard:action="early_update" />
+        <result wizard:action="system_update" />
+    </WizardAction>
+
+    <WizardAction id="system_update"
+        wizard:uri="intent:#Intent;action=android.settings.SYSTEM_UPDATE_SETTINGS;end">
+        <!-- System update should cause a reboot, but if it returns unexpectedly, continue on to
+            early update -->
+        <result wizard:action="early_update" />
+    </WizardAction>
+
+
+    <!-- Update other important packages [REQUIRED] -->
+    <WizardAction id="early_update"
+        wizard:uri="intent:#Intent;action=com.google.android.setupwizard.EARLY_UPDATE;end">
+        <result wizard:action="zero_touch" />
+    </WizardAction>
+
+
+    <!-- Zero touch provisioning (for enterprise) [RECOMMENDED] -->
+    <WizardAction id="zero_touch"
+        wizard:script="android.resource://com.google.android.setupwizard/xml/wizard_script_zero_touch_flow" >
+        <result wizard:name="dpm_user_complete"
+            wizard:resultCode="111" />
+        <result wizard:action="load_account_intent" />
+    </WizardAction>
+
+
+    <!-- Add an account [REQUIRED] -->
+    <WizardAction id="load_account_intent"
+        wizard:uri="intent:#Intent;action=com.google.android.setupwizard.LOAD_ADD_ACCOUNT_INTENT;B.showTapAndGo=false;end">
+        <result wizard:action="account_setup" />
+    </WizardAction>
+
+    <WizardAction id="account_setup"
+        wizard:uri="intent:#Intent;action=com.google.android.setupwizard.ACCOUNT_SETUP;end">
+        <result wizard:name="skip"
+            wizard:resultCode="1"
+            wizard:action="no_account_flow" />
+        <!-- Alternate flow if managed provisioning already set the user up (for enterprise) [RECOMMENDED] -->
+        <result wizard:name="dpm_user_complete"
+            wizard:resultCode="111" />
+        <result wizard:action="gms_account_checkin" />
+    </WizardAction>
+
+
+    <!-- Checkin with Gservices using account. If it fails, VPA will not be available. [REQUIRED] -->
+    <WizardAction id="gms_account_checkin"
+        wizard:uri="intent:#Intent;action=com.google.android.setupwizard.GMS_ACCOUNT_CHECKIN;end">
+        <result wizard:name="skip"
+            wizard:resultCode="1"
+            wizard:action="mfm_check" />
+        <result wizard:action="start_vpa" />
+    </WizardAction>
+
+
+    <!-- Initiate VPA (required for Play Auto-Installs) [RECOMMENDED] -->
+    <WizardAction id="start_vpa"
+        wizard:uri="intent:#Intent;action=com.google.android.setupwizard.START_VPA;end">
+        <result wizard:action="mfm_check" />
+    </WizardAction>
+
+
+    <!-- Branch to script for setting up with or without an account [REQUIRED] -->
+    <WizardAction id="mfm_check"
+        wizard:uri="intent:#Intent;action=com.google.android.setupwizard.ACCOUNT_CHECK;end">
+        <result wizard:name="skip"
+            wizard:resultCode="1"
+            wizard:action="no_account_flow" />
+        <result wizard:action="account_flow" />
+    </WizardAction>
+
+
+    <!-- Set up with an account [REQUIRED] -->
+    <WizardAction id="account_flow"
+        wizard:script="android.resource://com.google.android.setupwizard/xml/wizard_script_new_device_account_flow" />
+
+
+    <!-- Set up without an account [REQUIRED] -->
+    <WizardAction id="no_account_flow"
+        wizard:script="android.resource://com.google.android.setupwizard/xml/wizard_script_no_account_flow" />
+
+
+    <!-- Set up without a network connection [RECOMMENDED] -->
+    <WizardAction id="no_network_flow"
+                  wizard:script="android.resource://com.google.android.setupwizard/xml/wizard_script_no_network_flow" />
+
+</WizardScript>
diff --git a/SetupWizard/res/xml/wizard_script_user.xml b/SetupWizard/res/xml/wizard_script_user.xml
new file mode 100644
index 0000000..10c91a7
--- /dev/null
+++ b/SetupWizard/res/xml/wizard_script_user.xml
@@ -0,0 +1,163 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+    Copyright (c) 2014 Google Inc.
+
+    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.
+-->
+
+<!--
+    The wizard:uris recorded here have the inconvenience of being generated by hand, but they allow
+    for the full spread of launch flags (we need FLAG_ACTIVITY_NEW_TASK [0x10000000]), where the
+    <intent> tag processed by Intent.parseIntent() does not.
+
+    adb shell am to-intent-uri -a com.android.setupwizard.WELCOME -f 0x10000000 \-\-ez firstRun true
+-->
+<WizardScript xmlns:wizard="http://schemas.android.com/apk/res/com.android.car.setupwizard"
+    wizard:firstAction="oem_pre_setup">
+
+    <!-- Preliminary setup for OEMs [CUSTOMIZABLE] -->
+    <WizardAction id="oem_pre_setup"
+        wizard:uri="intent:#Intent;action=com.android.setupwizard.OEM_PRE_SETUP;end">
+        <result wizard:action="secondary_user_warning" />
+    </WizardAction>
+
+
+    <!-- Secondary user warning [RECOMMENDED, CUSTOMIZABLE] -->
+    <WizardAction id="secondary_user_warning"
+        wizard:uri="intent:#Intent;action=com.android.setupwizard.USER_WARNING;end">
+        <result wizard:name="dpm_user_complete"
+            wizard:resultCode="111"
+            wizard:action="check_user_unlock_dpm_user_complete" />
+        <result wizard:action="check_user_unlock" />
+    </WizardAction>
+
+    <WizardAction id="check_user_unlock_dpm_user_complete"
+        wizard:uri="intent:#Intent;action=com.android.setupwizard.CHECK_USER_UNLOCK;end">
+        <result wizard:action="oem_post_setup" />
+    </WizardAction>
+
+    <WizardAction id="check_user_unlock"
+        wizard:uri="intent:#Intent;action=com.android.setupwizard.CHECK_USER_UNLOCK;end">
+        <result wizard:action="bluetooth_settings" />
+    </WizardAction>
+
+    <!-- Bluetooth selection [REQUIRED, CUSTOMIZABLE] -->
+    <WizardAction id="bluetooth_settings"
+        wizard:uri="intent:#Intent;action=com.android.setupwizard.BLUETOOTH_SETTINGS;end">
+        <result wizard:action="network_settings" />
+    </WizardAction>
+
+    <!-- Network selection [REQUIRED, CUSTOMIZABLE] -->
+    <WizardAction id="network_settings"
+        wizard:uri="intent:#Intent;action=com.android.setupwizard.NETWORK_SETTINGS;end">
+        <result wizard:name="see_all_wifi"
+            wizard:resultCode="101"
+            wizard:action="wifi_settings" />
+        <result wizard:name="skip"
+            wizard:resultCode="1"
+            wizard:action="no_account_flow" />
+        <result wizard:action="captive_portal" />
+    </WizardAction>
+
+
+    <!-- Wi-Fi setup [REQUIRED, CUSTOMIZABLE] -->
+    <WizardAction id="wifi_settings"
+        wizard:uri="intent:#Intent;action=com.android.setupwizard.WIFI_SETTINGS;end">
+        <result wizard:action="captive_portal" />
+    </WizardAction>
+
+
+    <!-- Resolve captive portal access, and wait for check-in [REQUIRED] -->
+    <WizardAction id="captive_portal"
+        wizard:uri="intent:#Intent;action=com.android.setupwizard.CAPTIVE_PORTAL;end">
+        <result wizard:action="gms_checkin" />
+    </WizardAction>
+
+    <WizardAction id="gms_checkin"
+        wizard:uri="intent:#Intent;action=com.google.android.setupwizard.GMS_CHECKIN;end">
+        <result wizard:action="load_account_intent" />
+    </WizardAction>
+
+
+    <!-- Add an account [REQUIRED] -->
+    <WizardAction id="load_account_intent"
+        wizard:uri="intent:#Intent;action=com.google.android.setupwizard.LOAD_ADD_ACCOUNT_INTENT;end">
+        <result wizard:action="account_setup" />
+    </WizardAction>
+
+    <WizardAction id="account_setup"
+        wizard:uri="intent:#Intent;action=com.google.android.setupwizard.ACCOUNT_SETUP;end">
+        <result wizard:name="skip"
+            wizard:resultCode="1"
+            wizard:action="no_account_flow" />
+        <!-- Alternate flow if managed provisioning already set the user up (for enterprise) [RECOMMENDED] -->
+        <result wizard:name="dpm_user_complete"
+            wizard:resultCode="111"
+            wizard:action="oem_post_setup" />
+        <result wizard:action="gms_account_checkin" />
+    </WizardAction>
+
+
+    <!-- Checkin with Gservices using account. If it fails, VPA will not be available. [REQUIRED] -->
+    <WizardAction id="gms_account_checkin"
+        wizard:uri="intent:#Intent;action=com.google.android.setupwizard.GMS_ACCOUNT_CHECKIN;end">
+        <result wizard:name="skip"
+            wizard:resultCode="1"
+            wizard:action="mfm_check" />
+        <result wizard:action="start_vpa" />
+    </WizardAction>
+
+
+    <!-- Initiate VPA (required for Play Auto-Installs) [RECOMMENDED] -->
+    <WizardAction id="start_vpa"
+        wizard:uri="intent:#Intent;action=com.google.android.setupwizard.START_VPA;end">
+        <result wizard:action="mfm_check" />
+    </WizardAction>
+
+
+    <!-- Branch to script for setting up with or without an account [REQUIRED] -->
+    <WizardAction id="mfm_check"
+        wizard:uri="intent:#Intent;action=com.google.android.setupwizard.ACCOUNT_CHECK;end">
+        <result wizard:name="skip"
+            wizard:resultCode="1"
+            wizard:action="no_account_flow" />
+        <result wizard:action="account_flow" />
+    </WizardAction>
+
+
+    <!-- Set up with an account [REQUIRED] -->
+    <WizardAction id="account_flow"
+        wizard:script="android.resource://com.google.android.setupwizard/xml/wizard_script_user_account_flow">
+        <result wizard:action="oem_post_setup" />
+    </WizardAction>
+
+
+    <!-- Set up without an account [REQUIRED] -->
+    <WizardAction id="no_account_flow"
+        wizard:script="android.resource://com.google.android.setupwizard/xml/wizard_script_user_no_account_flow">
+        <result wizard:action="oem_post_setup" />
+    </WizardAction>
+
+
+    <!-- OEM completion [CUSTOMIZABLE] -->
+    <WizardAction id="oem_post_setup"
+        wizard:uri="intent:#Intent;action=com.android.setupwizard.OEM_POST_SETUP;end">
+        <result wizard:action="exit" />
+    </WizardAction>
+
+
+    <!-- Leave Setup Wizard [REQUIRED] -->
+    <WizardAction id="exit"
+        wizard:uri="intent:#Intent;action=com.android.setupwizard.EXIT;end" />
+
+</WizardScript>
diff --git a/SetupWizard/src/com/android/car/setupwizard/bluetooth/BluetoothActivity.java b/SetupWizard/src/com/android/car/setupwizard/bluetooth/BluetoothActivity.java
new file mode 100644
index 0000000..734e1ee
--- /dev/null
+++ b/SetupWizard/src/com/android/car/setupwizard/bluetooth/BluetoothActivity.java
@@ -0,0 +1,427 @@
+/*
+ * Copyright (C) 2017 Google Inc.
+ *
+ * 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.car.setupwizard.bluetooth;
+
+import static com.android.setupwizardlib.util.ResultCodes.RESULT_SKIP;
+
+import android.app.Activity;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.os.Handler;
+import android.util.Log;
+import android.view.View;
+
+import com.android.car.setupwizard.R;
+import com.android.car.setupwizard.bluetooth.BluetoothDeviceHierarchy.BluetoothItem;
+
+import com.android.setupwizardlib.GlifRecyclerLayout;
+import com.android.setupwizardlib.items.IItem;
+import com.android.setupwizardlib.items.Item;
+import com.android.setupwizardlib.items.ItemGroup;
+import com.android.setupwizardlib.items.RecyclerItemAdapter;
+import com.android.setupwizardlib.util.ResultCodes;
+import com.android.setupwizardlib.util.WizardManagerHelper;
+
+/**
+ * An Activity that presents the option for the user to pair the current device to a nearby
+ * bluetooth device. This screen will list the devices in the order that they are discovered
+ * as well as an option to not pair at all.
+ */
+public class BluetoothActivity extends Activity
+        implements RecyclerItemAdapter.OnItemSelectedListener {
+    private static final String TAG = "BluetoothActivity";
+
+    /**
+     * This value is copied from {@code com.google.android.setupwizard.BaseActivity}. Wizard
+     * Manager does not actually return an activity result, but if we invoke Wizard Manager without
+     * requesting a result, the framework will choose not to issue a call to onActivityResult with
+     * RESULT_CANCELED when navigating backward.
+     */
+    private static final int REQUEST_CODE_NEXT = 10000;
+
+    private static final int BLUETOOTH_SCAN_RETRY_DELAY = 1000;
+    private static final int MAX_BLUETOOTH_SCAN_RETRIES = 3;
+
+    private final Handler mHandler = new Handler();
+    private int mScanRetryCount;
+
+    private BluetoothScanReceiver mScanReceiver;
+    private BluetoothAdapterReceiver mAdapterReceiver;
+    private BluetoothAdapter mAdapter;
+    private BluetoothDeviceHierarchy mBluetoothDeviceHierarchy;
+
+    private GlifRecyclerLayout mLayout;
+    private Item mScanningIndicator;
+    private Item mRescanIndicator;
+
+    /**
+     * The current {@link BluetoothDevice} that is being paired to.
+     */
+    private BluetoothDevice mCurrentBondingDevice;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        mAdapter = BluetoothAdapter.getDefaultAdapter();
+
+        if (mAdapter == null) {
+            Log.w(TAG, "No bluetooth adapter found on the device. Skipping to next action.");
+            nextAction(RESULT_SKIP);
+            return;
+        }
+
+        setContentView(R.layout.bluetooth_activity);
+
+        mLayout = (GlifRecyclerLayout) findViewById(R.id.setup_wizard_layout);
+
+        RecyclerItemAdapter adapter = (RecyclerItemAdapter) mLayout.getAdapter();
+        adapter.setOnItemSelectedListener(this);
+
+        ItemGroup hierarchy = (ItemGroup) adapter.getRootItemHierarchy();
+        mBluetoothDeviceHierarchy =
+                (BluetoothDeviceHierarchy) hierarchy.findItemById(R.id.bluetooth_device_list);
+        mScanningIndicator = (Item) hierarchy.findItemById(R.id.bluetooth_scanning);
+        mRescanIndicator = (Item) hierarchy.findItemById(R.id.bluetooth_rescan);
+
+        Item descriptionItem = (Item) hierarchy.findItemById(R.id.bluetooth_description);
+        descriptionItem.setTitle(getText(R.string.bluetooth_description));
+
+        // Assume that a search will be started, so display the progress bar to let the user
+        // know that something is going on.
+        mLayout.setProgressBarShown(true);
+
+        if (mAdapter.isEnabled()) {
+            setUpAndStartScan();
+        } else {
+            mAdapterReceiver = new BluetoothAdapterReceiver();
+            maybeRegisterAdapterReceiver();
+            mAdapter.enable();
+        }
+    }
+
+    @Override
+    protected void onStart() {
+        super.onStart();
+
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, "onStart()");
+        }
+
+        if (mAdapter == null) {
+            Log.w(TAG, "No bluetooth adapter found on the device. Skipping to next action.");
+            nextAction(RESULT_SKIP);
+            return;
+        }
+
+        maybeRegisterAdapterReceiver();
+        registerScanReceiver();
+    }
+
+    @Override
+    protected void onStop() {
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, "onStop()");
+        }
+
+        stopScanning();
+
+        if (mScanReceiver != null) {
+            unregisterReceiver(mScanReceiver);
+        }
+
+        if (mAdapterReceiver != null) {
+            unregisterReceiver(mAdapterReceiver);
+        }
+
+        super.onStop();
+    }
+
+    /**
+     * Sets up an Intent filter to listen for bluetooth state changes and initiates a scan for
+     * nearby bluetooth devices.
+     */
+    private void setUpAndStartScan() {
+        mBluetoothDeviceHierarchy.clearAllDevices();
+        registerScanReceiver();
+        startScanning();
+    }
+
+    /**
+     * Registers a receiver to be listen on changes to the {@link BluetoothAdapter}. This method
+     * will only register the receiver if {@link #mAdapterReceiver} is not {@code null}.
+     */
+    private void maybeRegisterAdapterReceiver() {
+        if (mAdapterReceiver == null) {
+            return;
+        }
+
+        IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
+        registerReceiver(mAdapterReceiver, filter);
+    }
+
+    /**
+     * Registers an Intent filter to listen for the results of a bluetooth discovery scan as well as
+     * changes to individual bluetooth devices.
+     */
+    private void registerScanReceiver() {
+        if (mScanReceiver == null) {
+            mScanReceiver = new BluetoothScanReceiver();
+        }
+
+        IntentFilter intentFilter = new IntentFilter();
+        intentFilter.addAction(BluetoothAdapter.ACTION_DISCOVERY_STARTED);
+        intentFilter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
+        intentFilter.addAction(BluetoothDevice.ACTION_FOUND);
+        intentFilter.addAction(BluetoothDevice.ACTION_NAME_CHANGED);
+        intentFilter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
+        registerReceiver(mScanReceiver, intentFilter);
+    }
+
+    /**
+     * Start a scan for nearby bluetooth devices. If the call to
+     * {@link BluetoothAdapter#startDiscovery()} fails, then this method will retry the call after
+     * an exponential backoff period based on {@link #BLUETOOTH_SCAN_RETRY_DELAY}.
+     *
+     * <p>If there is already a bluetooth scan in progress when this function is called, then this
+     * function will do nothing.
+     */
+    private void startScanning() {
+        if (mAdapter.isDiscovering()) {
+            return;
+        }
+
+        boolean success = mAdapter.startDiscovery();
+
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, "startDiscovery() success: " + success);
+        }
+
+        // If a scan fails, attempt to try again up to MAX_BLUETOOTH_SCAN_RETRIES tries.
+        if (success) {
+            mScanRetryCount = 0;
+        } else if (mScanRetryCount >= MAX_BLUETOOTH_SCAN_RETRIES) {
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "Reached max retries to initiate a bluetooth scan. Moving onto next "
+                        + "action");
+            }
+
+            nextAction(RESULT_SKIP);
+        } else {
+            mHandler.postDelayed(this::startScanning,
+                    BLUETOOTH_SCAN_RETRY_DELAY * ++mScanRetryCount);
+        }
+    }
+
+    /**
+     * Stops any scan in that is currently in progress for nearby bluetooth devices.
+     */
+    private void stopScanning() {
+        if (mAdapter != null && mAdapter.isDiscovering()) {
+            mAdapter.cancelDiscovery();
+        }
+
+        mScanRetryCount = 0;
+    }
+
+    @Override
+    public void onItemSelected(IItem item) {
+        if (item instanceof BluetoothItem) {
+            pairOrUnpairDevice((BluetoothItem) item);
+            return;
+        }
+
+        if (!(item instanceof Item)) {
+            return;
+        }
+
+        switch (((Item) item).getId()) {
+            case R.id.bluetooth_dont_connect:
+                nextAction(RESULT_SKIP);
+                break;
+
+            case R.id.bluetooth_rescan:
+                stopScanning();
+                startScanning();
+                break;
+
+            default:
+                Log.w(TAG, "Unknown item clicked: " + item);
+        }
+    }
+
+    /**
+     * Starts a pairing or unpairing session with the given device based on its current bonded
+     * state. For example, if the current item is already paired, it is unpaired and vice versa.
+     */
+    private void pairOrUnpairDevice(BluetoothItem item) {
+        // Pairing is unreliable while scanning, so cancel discovery.
+        stopScanning();
+
+        BluetoothDevice device = item.getBluetoothDevice();
+
+        boolean success;
+        switch (device.getBondState()) {
+            case BluetoothDevice.BOND_BONDED:
+                mCurrentBondingDevice = null;
+                success = device.removeBond();
+
+                if (Log.isLoggable(TAG, Log.DEBUG)) {
+                    Log.d(TAG, "removeBond() to device (" + device + ") successful: " + success);
+                }
+
+                // Immediately update the UI so that the user has feedback on their actions.
+                item.updateConnectionState(this /* context */,
+                        BluetoothItem.CONNECTION_STATE_DISCONNECTING);
+                break;
+
+            case BluetoothDevice.BOND_BONDING:
+                mCurrentBondingDevice = null;
+                success = device.cancelBondProcess();
+
+                if (Log.isLoggable(TAG, Log.DEBUG)) {
+                    Log.d(TAG, "cancelBondProcess() to device (" + device + ") successful: "
+                            + success);
+                }
+
+                // Immediately update the UI so that the user has feedback on their actions.
+                item.updateConnectionState(this /* context */,
+                        BluetoothItem.CONNECTION_STATE_CANCELLING);
+                break;
+
+            case BluetoothDevice.BOND_NONE:
+                mCurrentBondingDevice = device;
+                success = device.createBond();
+
+                if (Log.isLoggable(TAG, Log.DEBUG)) {
+                    Log.d(TAG, "createBond() to device (" + device + ") successful: " + success);
+                }
+
+                // Immediately update the UI so that the user has feedback on their actions.
+                item.updateConnectionState(this /* context */,
+                        BluetoothItem.CONNECTION_STATE_CONNECTING);
+
+            default:
+                Log.w(TAG, "Encountered unknown bond state: " + device.getBondState());
+        }
+    }
+
+    private void nextAction(int resultCode) {
+        setResult(resultCode);
+        Intent nextIntent = WizardManagerHelper.getNextIntent(getIntent(), resultCode);
+        startActivityForResult(nextIntent, REQUEST_CODE_NEXT);
+    }
+
+    /**
+     * A {@link BroadReceiver} that listens for when the bluetooth adapter has been turned on.
+     */
+    private class BluetoothAdapterReceiver extends BroadcastReceiver {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+
+            if (action != null && action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
+                int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE,
+                        BluetoothAdapter.ERROR);
+
+                if (state == BluetoothAdapter.STATE_ON) {
+                    setUpAndStartScan();
+                }
+            }
+        }
+    }
+
+    /**
+     * Handles bluetooth scan responses and other indicators.
+     **/
+    private class BluetoothScanReceiver extends BroadcastReceiver {
+       @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+
+            if (action == null) {
+                return;
+            }
+
+            BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+
+            if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                Log.v(TAG, "Received device: " + device);
+            }
+
+            switch (action) {
+                case BluetoothDevice.ACTION_FOUND:
+                    if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                        Log.v(TAG, "Bluetooth device found");
+                    }
+
+                    mLayout.setProgressBarShown(false);
+                    mScanningIndicator.setVisible(false);
+                    mRescanIndicator.setVisible(true);
+                    mBluetoothDeviceHierarchy.addOrUpdateDevice(context, device);
+                    break;
+
+                case BluetoothAdapter.ACTION_DISCOVERY_STARTED:
+                    if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                        Log.v(TAG, "Bluetooth discovery started");
+                    }
+
+                    mLayout.setProgressBarShown(true);
+                    mScanningIndicator.setVisible(true);
+                    mRescanIndicator.setVisible(false);
+                    mBluetoothDeviceHierarchy.clearAllDevices();
+                    break;
+
+                case BluetoothAdapter.ACTION_DISCOVERY_FINISHED:
+                    if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                        Log.v(TAG, "Bluetooth discovery finished");
+                    }
+                    break;
+
+                case BluetoothDevice.ACTION_BOND_STATE_CHANGED:
+                    if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                        Log.v(TAG, "Bluetooth bond state changed");
+                    }
+
+                    mBluetoothDeviceHierarchy.addOrUpdateDevice(context, device);
+
+                    // When a bluetooth device has been paired, then move onto the next action so
+                    // the user is not stuck on this screen for too long.
+                    if (device.equals(mCurrentBondingDevice)
+                            && device.getBondState() == BluetoothDevice.BOND_BONDED) {
+                        nextAction(RESULT_OK);
+                    }
+                    break;
+
+                case BluetoothDevice.ACTION_NAME_CHANGED:
+                    if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                        Log.v(TAG, "Bluetooth device name chaged");
+                    }
+                    mBluetoothDeviceHierarchy.addOrUpdateDevice(context, device);
+                    break;
+
+                default:
+                    Log.w(TAG, "Unknown action received: " + action);
+            }
+        }
+    }
+}
diff --git a/SetupWizard/src/com/android/car/setupwizard/bluetooth/BluetoothDeviceHierarchy.java b/SetupWizard/src/com/android/car/setupwizard/bluetooth/BluetoothDeviceHierarchy.java
new file mode 100644
index 0000000..02f76bf
--- /dev/null
+++ b/SetupWizard/src/com/android/car/setupwizard/bluetooth/BluetoothDeviceHierarchy.java
@@ -0,0 +1,277 @@
+/*
+ * Copyright (C) 2017 Google Inc.
+ *
+ * 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.car.setupwizard.bluetooth;
+
+import android.bluetooth.BluetoothClass.Device;
+import android.bluetooth.BluetoothDevice;
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.content.res.TypedArray;
+import android.graphics.drawable.Drawable;
+import android.support.annotation.DrawableRes;
+import android.support.annotation.IntDef;
+import android.support.annotation.Nullable;
+import android.support.annotation.VisibleForTesting;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+
+import com.android.car.setupwizard.R;
+
+import com.android.setupwizardlib.items.AbstractItemHierarchy;
+import com.android.setupwizardlib.items.IItem;
+import com.android.setupwizardlib.items.Item;
+import com.android.setupwizardlib.items.ItemHierarchy;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * An item hierarchy that represents a list of Bluetooth devices.
+ */
+public class BluetoothDeviceHierarchy extends AbstractItemHierarchy {
+    private static final String TAG = "BtDeviceHierarchy";
+
+    /**
+     * A set of all discovered bluetooth devices. The key of this map is the device's MAC address.
+     */
+    private final HashMap<String, BluetoothItem> mItems = new HashMap<>();
+
+    /**
+     * A list of all discovered bluetooth devices' MAC addresses. This list is sorted in the order
+     * that the devices were discovered in.
+     */
+    private final List<String> mAddresses = new ArrayList<>();
+
+    public BluetoothDeviceHierarchy(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    /**
+     * Clears the current list of all bluetooth devices.
+     */
+    public void clearAllDevices() {
+        mItems.clear();
+        mAddresses.clear();
+        notifyChanged();
+    }
+
+    /**
+     * Adds the given {@link BluetoothDevice} to be displayed. If the device has already been
+     * added before, its information is updated based on the given {@code BluetoothDevice}.
+     */
+    public void addOrUpdateDevice(Context context, @Nullable BluetoothDevice device) {
+        if (device == null) {
+            return;
+        }
+
+        String address = device.getAddress();
+        BluetoothItem item;
+
+        if (mItems.containsKey(address)) {
+            item = mItems.get(address);
+        } else {
+            // First time encountering this address, so keep track of it.
+            mAddresses.add(address);
+
+            int id = View.generateViewId();
+            if (id >= 0x00ffffff) {
+                // View.generateViewId returns an incrementing number from 1 to 0x00ffffff.
+                // Since we are generating view IDs without recycling, it is theoretically possible
+                // for the ID space to run out if the user encounters enough bluetooth devices.
+                // Just log if this happens.
+                Log.e(TAG, "Ran out of IDs to use for bluetooth item IDs");
+            }
+            item = new BluetoothItem(id);
+        }
+
+        item.update(context, device);
+        mItems.put(address, item);
+
+        notifyChanged();
+    }
+
+    @Override
+    public ItemHierarchy findItemById(int id) {
+        if (id == getId()) {
+            return this;
+        }
+
+        // Child items have generated hash code IDs. Don't try to find those.
+        return null;
+    }
+
+    @Override
+    public int getCount() {
+        return mItems.size();
+    }
+
+    @Override
+    public IItem getItemAt(int position) {
+        return mItems.get(mAddresses.get(position));
+    }
+
+    /**
+     * A {@link Item} that is linked to a particular {@link BluetoothDevice} and is responsible
+     * for binding this information to a View to be displayed.
+     */
+    public static class BluetoothItem extends Item {
+        private BluetoothDevice mDevice;
+
+        /**
+         * Whether or not the icon for this particular BluetoothDevice has been updated to reflect
+         * the type of Bluetooth device this is.
+         */
+        private boolean mIconUpdated;
+
+        public static final int CONNECTION_STATE_DISCONNECTING = 0;
+        public static final int CONNECTION_STATE_CONNECTING = 1;
+        public static final int CONNECTION_STATE_CANCELLING = 2;
+
+        @IntDef({
+            CONNECTION_STATE_DISCONNECTING,
+            CONNECTION_STATE_CONNECTING,
+            CONNECTION_STATE_CANCELLING })
+        public @interface ConnectionState {}
+
+        public BluetoothItem(int id) {
+            setId(id);
+        }
+
+        /**
+         * Immediately updates the connection state of the device that is represented by this
+         * {@link BluetoothItem}. This state is not necessarily tied to the bonded state that
+         * will be returned by the {@link BluetoothDevice} associated with this item.
+         */
+        public void updateConnectionState(Context context, @ConnectionState int state) {
+            if (mDevice == null) {
+                return;
+            }
+
+            switch (state) {
+                case CONNECTION_STATE_DISCONNECTING:
+                    setSummary(context.getString(R.string.bluetooth_device_disconnecting));
+                    break;
+
+                case CONNECTION_STATE_CONNECTING:
+                    setSummary(context.getString(R.string.bluetooth_device_connecting));
+                    break;
+
+                case CONNECTION_STATE_CANCELLING:
+                    setSummary(context.getString(R.string.bluetooth_device_cancelling));
+                    break;
+
+                default:
+                    // Do nothing.
+            }
+        }
+
+        /**
+         * Associate a {@link BluetoothDevice} with this {@link BluetoothItem}.
+         */
+        public void update(Context context, BluetoothDevice device) {
+            mIconUpdated = false;
+            mDevice = device;
+
+            String name = mDevice.getName();
+            setTitle(TextUtils.isEmpty(name) ? mDevice.getAddress() : name);
+
+            switch (mDevice.getBondState()) {
+                case BluetoothDevice.BOND_BONDED:
+                    setSummary(context.getString(R.string.bluetooth_device_connected));
+                    break;
+
+                case BluetoothDevice.BOND_BONDING:
+                    setSummary(context.getString(R.string.bluetooth_device_connecting));
+                    break;
+
+                default:
+                    setSummary(null);
+            }
+        }
+
+        /**
+         * Returns the {@link BluetoothDevice} set via {@link #update(Context, BluetoothDevice)}.
+         */
+        public BluetoothDevice getBluetoothDevice() {
+            return mDevice;
+        }
+
+        @Override
+        public void onBindView(View view) {
+            if (mIconUpdated && getIcon() != null) {
+                super.onBindView(view);
+                return;
+            }
+
+            Context context = view.getContext();
+            TypedArray a = context.obtainStyledAttributes(
+                    new int[] { R.attr.suwListItemIconColor });
+
+            try {
+                ColorStateList bluetoothIconColor = a.getColorStateList(0);
+                Drawable bluetoothIcon = getDeviceIcon(context).mutate();
+                bluetoothIcon.setTintList(bluetoothIconColor);
+                setIcon(bluetoothIcon);
+            } finally {
+                a.recycle();
+            }
+
+            mIconUpdated = true;
+
+            super.onBindView(view);
+        }
+
+        /**
+         * Returns an appropriate {@link Drawable} to use as the icon for the bluetooth device
+         * associated with this {@link BluetoothItem}.
+         */
+        private Drawable getDeviceIcon(Context context) {
+            if (mDevice == null) {
+                return context.getDrawable(R.drawable.ic_bluetooth_item);
+            }
+
+            @DrawableRes int deviceIcon;
+            switch (mDevice.getBluetoothClass().getDeviceClass()) {
+                case Device.AUDIO_VIDEO_HEADPHONES:
+                case Device.AUDIO_VIDEO_WEARABLE_HEADSET:
+                    deviceIcon = R.drawable.ic_headset;
+                    break;
+
+                case Device.COMPUTER_DESKTOP:
+                case Device.COMPUTER_LAPTOP:
+                    deviceIcon = R.drawable.ic_computer;
+                    break;
+
+                case Device.PHONE_SMART:
+                    deviceIcon = R.drawable.ic_smartphone;
+                    break;
+
+                case Device.WEARABLE_WRIST_WATCH:
+                    deviceIcon = R.drawable.ic_watch;
+                    break;
+
+                default:
+                    deviceIcon = R.drawable.ic_bluetooth_item;
+            }
+
+            return context.getDrawable(deviceIcon);
+        }
+    }
+}