Merge Android 14 QPR1

Merged-In: I303e802db3b45f0340070484e77d7e4d31cb2480
Bug: 315507370
Change-Id: I795e7061792ddb48fe4287198ada49b938aadf63
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 75a199a..836a663 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -77,10 +77,15 @@
         </intent>
     </queries>
 
+    <!-- The app needs to be directBootAware so that it can reflect the correct call state
+     when the system boots up. -->
+    <!-- Consider "defaultToDeviceProtectedStorage" which prevents app crash before boot
+     complete if access storage, but be careful with any user sensitive data when using. -->
     <application
         android:icon="@drawable/ic_launcher_home"
         android:label="@string/app_title"
         android:theme="@style/Theme.Launcher"
+        android:directBootAware="true"
         android:supportsRtl="true">
         <activity
             android:name=".CarLauncher"
diff --git a/OWNERS b/OWNERS
index d363985..61cc463 100644
--- a/OWNERS
+++ b/OWNERS
@@ -7,4 +7,7 @@
 # Secondary
 nehah@google.com
 babakbo@google.com
-igorr@google.com
\ No newline at end of file
+igorr@google.com
+
+# Recents
+per-file src/com/android/car/carlauncher/recents/* = jainams@google.com
diff --git a/res/drawable/recent_task_hidden_icon.xml b/res/drawable/recent_task_hidden_icon.xml
deleted file mode 100644
index e6c52cc..0000000
--- a/res/drawable/recent_task_hidden_icon.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2023 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
-  -->
-
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
-       android:shape="oval">
-    <solid android:color="@android:color/transparent" />
-    <size
-        android:width="@dimen/recent_task_icon_size"
-        android:height="@dimen/recent_task_icon_size"/>
-</shape>
\ No newline at end of file
diff --git a/res/layout/recent_task_view_hidden.xml b/res/layout/recent_clear_all_view.xml
similarity index 60%
rename from res/layout/recent_task_view_hidden.xml
rename to res/layout/recent_clear_all_view.xml
index 0783001..87220af 100644
--- a/res/layout/recent_task_view_hidden.xml
+++ b/res/layout/recent_clear_all_view.xml
@@ -17,27 +17,17 @@
 <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="@dimen/recent_task_width"
+    android:layout_width="wrap_content"
     android:layout_height="match_parent">
-    <ImageView
-        android:id="@+id/task_icon"
-        android:focusable="false"
-        app:layout_constraintTop_toTopOf="parent"
-        app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintBottom_toTopOf="@id/task_thumbnail"
-        app:layout_constraintEnd_toEndOf="parent"
-        app:layout_constraintHorizontal_bias="0.5"
-        app:layout_constraintVertical_chainStyle="packed"
-        style="@style/RecentTaskIcon"/>
-
-    <com.google.android.material.imageview.ShapeableImageView
-        android:id="@+id/task_thumbnail"
-        android:focusable="false"
-        app:layout_constrainedHeight="true"
-        app:layout_constraintTop_toBottomOf="@id/task_icon"
-        app:layout_constraintStart_toStartOf="parent"
+    <Button
+        android:id="@+id/recents_clear_all_button"
+        android:text="@string/recents_clear_all_text"
         app:layout_constraintBottom_toBottomOf="parent"
         app:layout_constraintEnd_toEndOf="parent"
         app:layout_constraintHorizontal_bias="0.5"
-        style="@style/HiddenRecentTaskThumbnail"/>
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        app:layout_constraintVertical_bias="0.5"
+        style="@style/ClearAllRecentTasksButton"/>
+
 </androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
diff --git a/res/layout/recents_activity.xml b/res/layout/recents_activity.xml
index a818f6d..aecdd04 100644
--- a/res/layout/recents_activity.xml
+++ b/res/layout/recents_activity.xml
@@ -33,7 +33,7 @@
         android:id="@+id/recent_tasks_list_focus_area"
         app:layout_constraintTop_toTopOf="parent"
         app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintBottom_toTopOf="@id/clear_all_button_focus_area"
+        app:layout_constraintBottom_toBottomOf="parent"
         app:layout_constraintEnd_toEndOf="parent"
         style="@style/RecentTasksListFocusArea">
         <com.android.car.carlauncher.recents.view.RecentsRecyclerView
@@ -46,19 +46,6 @@
             style="@style/RecentTasksList"/>
     </com.android.car.ui.FocusArea>
 
-    <com.android.car.ui.FocusArea
-        android:id="@+id/clear_all_button_focus_area"
-        app:layout_constraintTop_toBottomOf="@id/recent_tasks_list_focus_area"
-        app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintBottom_toBottomOf="parent"
-        app:layout_constraintEnd_toEndOf="parent"
-        style="@style/ClearAllRecentTasksButtonFocusArea">
-        <Button
-            android:id="@+id/clear_all_button"
-            android:text="@string/recents_clear_all_text"
-            style="@style/ClearAllRecentTasksButton"/>
-    </com.android.car.ui.FocusArea>
-
     <androidx.constraintlayout.widget.Group
         android:id="@+id/recent_tasks_group"
         android:layout_width="wrap_content"
diff --git a/res/values-af/strings.xml b/res/values-af/strings.xml
index b1d194e..8497941 100644
--- a/res/values-af/strings.xml
+++ b/res/values-af/strings.xml
@@ -23,7 +23,6 @@
     <string name="app_launcher_title_all_apps" msgid="3215107396036838217">"Alle programme"</string>
     <string name="app_launcher_title_media_only" msgid="4400886555907422816">"Mediaprogramme"</string>
     <string name="app_launcher_stop_app_action" msgid="1081680842410279692">"Stop program"</string>
-    <string name="app_launcher_app_info_action" msgid="6388181061002827660">"Appinligting"</string>
     <string name="app_launcher_stop_app_success_toast_text" msgid="2117271772351005349">"<xliff:g id="APP_NAME">%1$s</xliff:g> is gestop."</string>
     <string name="app_launcher_stop_app_dialog_title" msgid="8276889322638001144">"Stop program?"</string>
     <string name="app_launcher_stop_app_dialog_text" msgid="3264059639436254372">"As jy ’n program dwing om te stop, kan dit sleg optree."</string>
diff --git a/res/values-am/strings.xml b/res/values-am/strings.xml
index d2d3df0..d2ecb6c 100644
--- a/res/values-am/strings.xml
+++ b/res/values-am/strings.xml
@@ -23,7 +23,6 @@
     <string name="app_launcher_title_all_apps" msgid="3215107396036838217">"ሁሉም መተግበሪያዎች"</string>
     <string name="app_launcher_title_media_only" msgid="4400886555907422816">"የሚዲያ መተግበሪያዎች"</string>
     <string name="app_launcher_stop_app_action" msgid="1081680842410279692">"መተግበሪያን አቁም"</string>
-    <string name="app_launcher_app_info_action" msgid="6388181061002827660">"የመተግበሪያ መረጃ"</string>
     <string name="app_launcher_stop_app_success_toast_text" msgid="2117271772351005349">"<xliff:g id="APP_NAME">%1$s</xliff:g> እንዲያቆም ተደርጓል።"</string>
     <string name="app_launcher_stop_app_dialog_title" msgid="8276889322638001144">"መተግበሪያ ይቁም?"</string>
     <string name="app_launcher_stop_app_dialog_text" msgid="3264059639436254372">"አንድ መተግበሪያን በኃይል እንዲቆም ካደረጉት በትክክል ላይሰራ ይችላል።"</string>
@@ -40,7 +39,7 @@
     <string name="projection_devices" msgid="916314090031116598">"{count,plural, =1{# መሣሪያ}one{# መሣሪያዎች}other{# መሣሪያዎች}}"</string>
     <string name="weather_app_name" msgid="8048146303519139993">"የአየር ሁኔታ"</string>
     <string name="fake_weather_main_text" msgid="8117240999340284429">"--° በአብዛኛው ጸሐያማ"</string>
-    <string name="fake_weather_footer_text" msgid="4570172025869749926">"የተራራ ዕይታ • ከ፦ --° ዝ፦ --°"</string>
+    <string name="fake_weather_footer_text" msgid="4570172025869749926">"የተራራ እይታ • ከ፦ --° ዝ፦ --°"</string>
     <string name="hide_debug_apps" msgid="6128313544379622475">"የአራሚ መተግበሪያዎችን ደብቅ"</string>
     <string name="show_debug_apps" msgid="4521073121286325786">"የአራሚ መተግበሪያዎችን አሳይ"</string>
     <string name="times_separator" msgid="6180368193321715816">"/"</string>
diff --git a/res/values-ar/strings.xml b/res/values-ar/strings.xml
index cadd3e0..b2ecb13 100644
--- a/res/values-ar/strings.xml
+++ b/res/values-ar/strings.xml
@@ -23,7 +23,6 @@
     <string name="app_launcher_title_all_apps" msgid="3215107396036838217">"جميع التطبيقات"</string>
     <string name="app_launcher_title_media_only" msgid="4400886555907422816">"تطبيقات الوسائط"</string>
     <string name="app_launcher_stop_app_action" msgid="1081680842410279692">"إيقاف التطبيق"</string>
-    <string name="app_launcher_app_info_action" msgid="6388181061002827660">"معلومات عن التطبيقات"</string>
     <string name="app_launcher_stop_app_success_toast_text" msgid="2117271772351005349">"تم إيقاف <xliff:g id="APP_NAME">%1$s</xliff:g>."</string>
     <string name="app_launcher_stop_app_dialog_title" msgid="8276889322638001144">"هل تريد إيقاف التطبيق؟"</string>
     <string name="app_launcher_stop_app_dialog_text" msgid="3264059639436254372">"في حال فرض إيقاف التطبيق، قد لا يعمل بشكل صحيح."</string>
diff --git a/res/values-as/strings.xml b/res/values-as/strings.xml
index 7ece385..7bcd915 100644
--- a/res/values-as/strings.xml
+++ b/res/values-as/strings.xml
@@ -23,7 +23,6 @@
     <string name="app_launcher_title_all_apps" msgid="3215107396036838217">"আটাইবোৰ এপ্‌"</string>
     <string name="app_launcher_title_media_only" msgid="4400886555907422816">"মিডিয়া এপ্‌"</string>
     <string name="app_launcher_stop_app_action" msgid="1081680842410279692">"এপ্ বন্ধ কৰক"</string>
-    <string name="app_launcher_app_info_action" msgid="6388181061002827660">"এপৰ তথ্য"</string>
     <string name="app_launcher_stop_app_success_toast_text" msgid="2117271772351005349">"<xliff:g id="APP_NAME">%1$s</xliff:g> বন্ধ কৰা হৈছে।"</string>
     <string name="app_launcher_stop_app_dialog_title" msgid="8276889322638001144">"এপ্ বন্ধ কৰিবনে?"</string>
     <string name="app_launcher_stop_app_dialog_text" msgid="3264059639436254372">"আপুনি কোনো এপ্‌ বলেৰে বন্ধ কৰিবলৈ চেষ্টা কৰিলে, ই অস্বাভাৱিক আচৰণ কৰিব পাৰে।"</string>
diff --git a/res/values-az/strings.xml b/res/values-az/strings.xml
index 98a8ad1..918e624 100644
--- a/res/values-az/strings.xml
+++ b/res/values-az/strings.xml
@@ -23,7 +23,6 @@
     <string name="app_launcher_title_all_apps" msgid="3215107396036838217">"Bütün tətbiqlər"</string>
     <string name="app_launcher_title_media_only" msgid="4400886555907422816">"Media tətbiqləri"</string>
     <string name="app_launcher_stop_app_action" msgid="1081680842410279692">"Tətbiqi dayandırın"</string>
-    <string name="app_launcher_app_info_action" msgid="6388181061002827660">"Tətbiq haqqında"</string>
     <string name="app_launcher_stop_app_success_toast_text" msgid="2117271772351005349">"<xliff:g id="APP_NAME">%1$s</xliff:g> dayandırılıb."</string>
     <string name="app_launcher_stop_app_dialog_title" msgid="8276889322638001144">"Tətbiq dayandırılsın?"</string>
     <string name="app_launcher_stop_app_dialog_text" msgid="3264059639436254372">"Tətbiqi dayanmağa məcbur etsəniz, o, səhv işləyə bilər."</string>
diff --git a/res/values-b+sr+Latn/strings.xml b/res/values-b+sr+Latn/strings.xml
index f3c3c0a..1b23639 100644
--- a/res/values-b+sr+Latn/strings.xml
+++ b/res/values-b+sr+Latn/strings.xml
@@ -23,7 +23,6 @@
     <string name="app_launcher_title_all_apps" msgid="3215107396036838217">"Sve aplikacije"</string>
     <string name="app_launcher_title_media_only" msgid="4400886555907422816">"Aplikacije za medije"</string>
     <string name="app_launcher_stop_app_action" msgid="1081680842410279692">"Zaustavi aplikaciju"</string>
-    <string name="app_launcher_app_info_action" msgid="6388181061002827660">"Informacije o aplikaciji"</string>
     <string name="app_launcher_stop_app_success_toast_text" msgid="2117271772351005349">"Aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> je zaustavljena."</string>
     <string name="app_launcher_stop_app_dialog_title" msgid="8276889322638001144">"Zaustavljate aplikaciju?"</string>
     <string name="app_launcher_stop_app_dialog_text" msgid="3264059639436254372">"Ako prinudno zaustavite aplikaciju, možda će se ponašati neočekivano."</string>
diff --git a/res/values-be/strings.xml b/res/values-be/strings.xml
index cadc04d..2c2fc3d 100644
--- a/res/values-be/strings.xml
+++ b/res/values-be/strings.xml
@@ -23,7 +23,6 @@
     <string name="app_launcher_title_all_apps" msgid="3215107396036838217">"Усе праграмы"</string>
     <string name="app_launcher_title_media_only" msgid="4400886555907422816">"Мультымедыйныя праграмы"</string>
     <string name="app_launcher_stop_app_action" msgid="1081680842410279692">"Спыніць праграму"</string>
-    <string name="app_launcher_app_info_action" msgid="6388181061002827660">"Звесткі пра праграму"</string>
     <string name="app_launcher_stop_app_success_toast_text" msgid="2117271772351005349">"Праграма \"<xliff:g id="APP_NAME">%1$s</xliff:g>\" спынена."</string>
     <string name="app_launcher_stop_app_dialog_title" msgid="8276889322638001144">"Спыніць праграму?"</string>
     <string name="app_launcher_stop_app_dialog_text" msgid="3264059639436254372">"Прымусовае спыненне праграмы можа прывесці да збою."</string>
diff --git a/res/values-bg/strings.xml b/res/values-bg/strings.xml
index 9b24291..bb3e106 100644
--- a/res/values-bg/strings.xml
+++ b/res/values-bg/strings.xml
@@ -23,7 +23,6 @@
     <string name="app_launcher_title_all_apps" msgid="3215107396036838217">"Всички приложения"</string>
     <string name="app_launcher_title_media_only" msgid="4400886555907422816">"Медийни приложения"</string>
     <string name="app_launcher_stop_app_action" msgid="1081680842410279692">"Спиране на приложението"</string>
-    <string name="app_launcher_app_info_action" msgid="6388181061002827660">"Информация за приложенията"</string>
     <string name="app_launcher_stop_app_success_toast_text" msgid="2117271772351005349">"Приложението <xliff:g id="APP_NAME">%1$s</xliff:g> е спряно."</string>
     <string name="app_launcher_stop_app_dialog_title" msgid="8276889322638001144">"Да се спре ли приложението?"</string>
     <string name="app_launcher_stop_app_dialog_text" msgid="3264059639436254372">"Ако принудително спрете приложение, то може да не функционира правилно."</string>
diff --git a/res/values-bn/strings.xml b/res/values-bn/strings.xml
index 4dad8a0..7508eed 100644
--- a/res/values-bn/strings.xml
+++ b/res/values-bn/strings.xml
@@ -23,7 +23,6 @@
     <string name="app_launcher_title_all_apps" msgid="3215107396036838217">"সব অ্যাপ"</string>
     <string name="app_launcher_title_media_only" msgid="4400886555907422816">"মিডিয়া অ্যাপ"</string>
     <string name="app_launcher_stop_app_action" msgid="1081680842410279692">"অ্যাপ বন্ধ করুন"</string>
-    <string name="app_launcher_app_info_action" msgid="6388181061002827660">"অ্যাপের তথ্য"</string>
     <string name="app_launcher_stop_app_success_toast_text" msgid="2117271772351005349">"<xliff:g id="APP_NAME">%1$s</xliff:g> বন্ধ করা হয়েছে।"</string>
     <string name="app_launcher_stop_app_dialog_title" msgid="8276889322638001144">"অ্যাপটি বন্ধ করবেন?"</string>
     <string name="app_launcher_stop_app_dialog_text" msgid="3264059639436254372">"আপনি কোনও অ্যাপকে জোর করে বন্ধ করলে, তা সঠিক ভাবে কাজ নাও করতে পারে।"</string>
diff --git a/res/values-bs/strings.xml b/res/values-bs/strings.xml
index 292d142..f3a7a96 100644
--- a/res/values-bs/strings.xml
+++ b/res/values-bs/strings.xml
@@ -23,7 +23,6 @@
     <string name="app_launcher_title_all_apps" msgid="3215107396036838217">"Sve aplikacije"</string>
     <string name="app_launcher_title_media_only" msgid="4400886555907422816">"Medijske aplikacije"</string>
     <string name="app_launcher_stop_app_action" msgid="1081680842410279692">"Zaustavi aplikaciju"</string>
-    <string name="app_launcher_app_info_action" msgid="6388181061002827660">"Informacije o aplikaciji"</string>
     <string name="app_launcher_stop_app_success_toast_text" msgid="2117271772351005349">"Aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> je zaustavljena."</string>
     <string name="app_launcher_stop_app_dialog_title" msgid="8276889322638001144">"Zaustaviti aplikaciju?"</string>
     <string name="app_launcher_stop_app_dialog_text" msgid="3264059639436254372">"Ako prisilno zaustavite aplikaciju, moguće je da će se ponašati nepredviđeno."</string>
diff --git a/res/values-ca/strings.xml b/res/values-ca/strings.xml
index 1c1834f..d3d2fca 100644
--- a/res/values-ca/strings.xml
+++ b/res/values-ca/strings.xml
@@ -23,7 +23,6 @@
     <string name="app_launcher_title_all_apps" msgid="3215107396036838217">"Totes les aplicacions"</string>
     <string name="app_launcher_title_media_only" msgid="4400886555907422816">"Aplicacions multimèdia"</string>
     <string name="app_launcher_stop_app_action" msgid="1081680842410279692">"Atura l\'aplicació"</string>
-    <string name="app_launcher_app_info_action" msgid="6388181061002827660">"Informació de les aplicacions"</string>
     <string name="app_launcher_stop_app_success_toast_text" msgid="2117271772351005349">"<xliff:g id="APP_NAME">%1$s</xliff:g> s\'ha aturat."</string>
     <string name="app_launcher_stop_app_dialog_title" msgid="8276889322638001144">"Vols aturar l\'aplicació?"</string>
     <string name="app_launcher_stop_app_dialog_text" msgid="3264059639436254372">"Si forces l\'aturada d\'una aplicació, és possible que no funcioni correctament."</string>
diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml
index b38905b..fa5c0b2 100644
--- a/res/values-cs/strings.xml
+++ b/res/values-cs/strings.xml
@@ -23,7 +23,6 @@
     <string name="app_launcher_title_all_apps" msgid="3215107396036838217">"Všechny aplikace"</string>
     <string name="app_launcher_title_media_only" msgid="4400886555907422816">"Mediální aplikace"</string>
     <string name="app_launcher_stop_app_action" msgid="1081680842410279692">"Zastavit aplikaci"</string>
-    <string name="app_launcher_app_info_action" msgid="6388181061002827660">"Informace o aplikaci"</string>
     <string name="app_launcher_stop_app_success_toast_text" msgid="2117271772351005349">"Aplikace <xliff:g id="APP_NAME">%1$s</xliff:g> byla zastavena."</string>
     <string name="app_launcher_stop_app_dialog_title" msgid="8276889322638001144">"Zastavit aplikaci?"</string>
     <string name="app_launcher_stop_app_dialog_text" msgid="3264059639436254372">"Vynucené zastavení může způsobit nepředvídatelné chování aplikace."</string>
diff --git a/res/values-da/strings.xml b/res/values-da/strings.xml
index 8787b4e..fecd3dc 100644
--- a/res/values-da/strings.xml
+++ b/res/values-da/strings.xml
@@ -23,7 +23,6 @@
     <string name="app_launcher_title_all_apps" msgid="3215107396036838217">"Alle apps"</string>
     <string name="app_launcher_title_media_only" msgid="4400886555907422816">"Medieapps"</string>
     <string name="app_launcher_stop_app_action" msgid="1081680842410279692">"Stands app"</string>
-    <string name="app_launcher_app_info_action" msgid="6388181061002827660">"Appoplysninger"</string>
     <string name="app_launcher_stop_app_success_toast_text" msgid="2117271772351005349">"<xliff:g id="APP_NAME">%1$s</xliff:g> er blevet standset."</string>
     <string name="app_launcher_stop_app_dialog_title" msgid="8276889322638001144">"Vil du standse appen?"</string>
     <string name="app_launcher_stop_app_dialog_text" msgid="3264059639436254372">"Hvis du tvinger en app til at standse, kan det medføre, at den ikke fungerer korrekt."</string>
diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml
index 3e63515..c31aa08 100644
--- a/res/values-de/strings.xml
+++ b/res/values-de/strings.xml
@@ -23,7 +23,6 @@
     <string name="app_launcher_title_all_apps" msgid="3215107396036838217">"Alle Apps"</string>
     <string name="app_launcher_title_media_only" msgid="4400886555907422816">"Medien-Apps"</string>
     <string name="app_launcher_stop_app_action" msgid="1081680842410279692">"App beenden"</string>
-    <string name="app_launcher_app_info_action" msgid="6388181061002827660">"App-Info"</string>
     <string name="app_launcher_stop_app_success_toast_text" msgid="2117271772351005349">"<xliff:g id="APP_NAME">%1$s</xliff:g> wurde beendet."</string>
     <string name="app_launcher_stop_app_dialog_title" msgid="8276889322638001144">"App beenden?"</string>
     <string name="app_launcher_stop_app_dialog_text" msgid="3264059639436254372">"Das Beenden der App zu erzwingen kann zu unerwünschtem Verhalten führen."</string>
diff --git a/res/values-el/strings.xml b/res/values-el/strings.xml
index 8a3b252..af034e2 100644
--- a/res/values-el/strings.xml
+++ b/res/values-el/strings.xml
@@ -23,7 +23,6 @@
     <string name="app_launcher_title_all_apps" msgid="3215107396036838217">"Όλες οι εφαρμογές"</string>
     <string name="app_launcher_title_media_only" msgid="4400886555907422816">"Εφαρμογές μέσων"</string>
     <string name="app_launcher_stop_app_action" msgid="1081680842410279692">"Διακοπή εφαρμογής"</string>
-    <string name="app_launcher_app_info_action" msgid="6388181061002827660">"Πληροφορίες εφαρμογής"</string>
     <string name="app_launcher_stop_app_success_toast_text" msgid="2117271772351005349">"Η εφαρμογή <xliff:g id="APP_NAME">%1$s</xliff:g> διακόπηκε."</string>
     <string name="app_launcher_stop_app_dialog_title" msgid="8276889322638001144">"Διακοπή εφαρμογής;"</string>
     <string name="app_launcher_stop_app_dialog_text" msgid="3264059639436254372">"Αν κάνετε αναγκαστική διακοπή μιας εφαρμογής, ενδέχεται να μην λειτουργεί σωστά."</string>
diff --git a/res/values-en-rAU/strings.xml b/res/values-en-rAU/strings.xml
index 9242fcb..c76b6eb 100644
--- a/res/values-en-rAU/strings.xml
+++ b/res/values-en-rAU/strings.xml
@@ -23,7 +23,6 @@
     <string name="app_launcher_title_all_apps" msgid="3215107396036838217">"All apps"</string>
     <string name="app_launcher_title_media_only" msgid="4400886555907422816">"Media apps"</string>
     <string name="app_launcher_stop_app_action" msgid="1081680842410279692">"Stop app"</string>
-    <string name="app_launcher_app_info_action" msgid="6388181061002827660">"App info"</string>
     <string name="app_launcher_stop_app_success_toast_text" msgid="2117271772351005349">"<xliff:g id="APP_NAME">%1$s</xliff:g> has been stopped."</string>
     <string name="app_launcher_stop_app_dialog_title" msgid="8276889322638001144">"Stop app?"</string>
     <string name="app_launcher_stop_app_dialog_text" msgid="3264059639436254372">"If you force stop an app, it may misbehave."</string>
diff --git a/res/values-en-rCA/strings.xml b/res/values-en-rCA/strings.xml
index f78d103..f4c9c54 100644
--- a/res/values-en-rCA/strings.xml
+++ b/res/values-en-rCA/strings.xml
@@ -25,8 +25,6 @@
     <string name="app_launcher_title_all_apps" msgid="3215107396036838217">"All apps"</string>
     <string name="app_launcher_title_media_only" msgid="4400886555907422816">"Media apps"</string>
     <string name="app_launcher_stop_app_action" msgid="1081680842410279692">"Stop app"</string>
-    <!-- no translation found for app_launcher_app_info_action (6388181061002827660) -->
-    <skip />
     <string name="app_launcher_stop_app_success_toast_text" msgid="2117271772351005349">"<xliff:g id="APP_NAME">%1$s</xliff:g> has been stopped."</string>
     <string name="app_launcher_stop_app_dialog_title" msgid="8276889322638001144">"Stop app?"</string>
     <string name="app_launcher_stop_app_dialog_text" msgid="3264059639436254372">"If you force stop an app, it may misbehave."</string>
diff --git a/res/values-en-rGB/strings.xml b/res/values-en-rGB/strings.xml
index 9242fcb..c76b6eb 100644
--- a/res/values-en-rGB/strings.xml
+++ b/res/values-en-rGB/strings.xml
@@ -23,7 +23,6 @@
     <string name="app_launcher_title_all_apps" msgid="3215107396036838217">"All apps"</string>
     <string name="app_launcher_title_media_only" msgid="4400886555907422816">"Media apps"</string>
     <string name="app_launcher_stop_app_action" msgid="1081680842410279692">"Stop app"</string>
-    <string name="app_launcher_app_info_action" msgid="6388181061002827660">"App info"</string>
     <string name="app_launcher_stop_app_success_toast_text" msgid="2117271772351005349">"<xliff:g id="APP_NAME">%1$s</xliff:g> has been stopped."</string>
     <string name="app_launcher_stop_app_dialog_title" msgid="8276889322638001144">"Stop app?"</string>
     <string name="app_launcher_stop_app_dialog_text" msgid="3264059639436254372">"If you force stop an app, it may misbehave."</string>
diff --git a/res/values-en-rIN/strings.xml b/res/values-en-rIN/strings.xml
index 9242fcb..c76b6eb 100644
--- a/res/values-en-rIN/strings.xml
+++ b/res/values-en-rIN/strings.xml
@@ -23,7 +23,6 @@
     <string name="app_launcher_title_all_apps" msgid="3215107396036838217">"All apps"</string>
     <string name="app_launcher_title_media_only" msgid="4400886555907422816">"Media apps"</string>
     <string name="app_launcher_stop_app_action" msgid="1081680842410279692">"Stop app"</string>
-    <string name="app_launcher_app_info_action" msgid="6388181061002827660">"App info"</string>
     <string name="app_launcher_stop_app_success_toast_text" msgid="2117271772351005349">"<xliff:g id="APP_NAME">%1$s</xliff:g> has been stopped."</string>
     <string name="app_launcher_stop_app_dialog_title" msgid="8276889322638001144">"Stop app?"</string>
     <string name="app_launcher_stop_app_dialog_text" msgid="3264059639436254372">"If you force stop an app, it may misbehave."</string>
diff --git a/res/values-en-rXC/strings.xml b/res/values-en-rXC/strings.xml
index 4f20cd5..8c0fc2e 100644
--- a/res/values-en-rXC/strings.xml
+++ b/res/values-en-rXC/strings.xml
@@ -23,7 +23,6 @@
     <string name="app_launcher_title_all_apps" msgid="3215107396036838217">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‎‎‏‏‎‎‎‎‎‎‏‏‏‏‏‏‎‏‎‏‏‎‎‏‎‎‏‏‏‏‎‎‏‎‏‏‎‏‏‎‎‏‎‎‏‎‎‎‏‏‎‏‏‏‏‏‎‎‎‎‎‎‏‏‏‎‎‎‎‏‏‎‏‎‎‏‎‎‏‎All apps‎‏‎‎‏‎"</string>
     <string name="app_launcher_title_media_only" msgid="4400886555907422816">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‎‎‏‏‎‎‎‎‎‎‏‏‏‏‏‏‎‏‏‏‏‎‏‎‎‎‏‎‎‏‏‎‎‎‏‎‏‏‏‎‎‎‎‎‎‎‏‎‏‏‏‎‏‎‏‏‎‏‎‎‏‏‏‎‏‏‏‏‎‏‎‎‏‏‎‎‎‎‎‎Media apps‎‏‎‎‏‎"</string>
     <string name="app_launcher_stop_app_action" msgid="1081680842410279692">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‎‎‏‏‎‎‎‎‎‎‏‏‏‏‏‎‎‏‏‏‏‎‎‎‎‎‎‏‎‏‏‏‎‎‏‏‎‏‏‏‏‏‏‏‎‏‏‎‎‎‏‎‎‎‎‏‏‎‏‎‏‏‏‎‎‎‎‏‏‎‎‎‎‏‏‎‎‎Stop app‎‏‎‎‏‎"</string>
-    <string name="app_launcher_app_info_action" msgid="6388181061002827660">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‎‎‏‏‎‎‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‎‎‏‎‏‎‎‏‏‏‎‏‏‎‎‎‎‎‏‏‎‏‎‏‎‎‎‎‏‏‎‏‎‏‏‏‏‏‎‎‏‏‏‎‎‏‎‏‏‏‏‎‎‎‏‏‎‎‎App info‎‏‎‎‏‎"</string>
     <string name="app_launcher_stop_app_success_toast_text" msgid="2117271772351005349">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‎‎‏‏‎‎‎‎‎‎‏‏‏‏‏‎‏‏‏‏‎‏‎‏‏‎‎‎‏‎‎‎‎‎‏‏‏‏‎‏‏‏‎‏‎‏‏‎‎‎‏‎‏‏‏‏‏‏‎‎‎‏‏‎‎‎‎‎‏‎‏‎‏‎‎‏‎‏‎‎‏‎‎‏‏‎<xliff:g id="APP_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎ has been stopped.‎‏‎‎‏‎"</string>
     <string name="app_launcher_stop_app_dialog_title" msgid="8276889322638001144">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‎‎‏‏‎‎‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‎‏‎‏‏‎‏‏‏‎‏‎‏‏‎‏‎‏‎‏‏‏‏‏‏‎‎‎‏‏‏‎‎‎‏‎‎‎‎‏‏‏‏‏‏‎‎‎‎‏‏‏‏‏‏‏‎‎‎‎Stop app?‎‏‎‎‏‎"</string>
     <string name="app_launcher_stop_app_dialog_text" msgid="3264059639436254372">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‎‎‏‏‎‎‎‎‎‎‏‏‏‏‏‏‎‏‎‏‏‎‏‎‏‎‎‏‏‎‎‎‏‎‎‎‏‎‎‏‏‏‏‎‎‏‎‏‏‎‎‎‏‏‎‎‏‎‎‏‎‎‏‏‎‎‏‏‏‎‎‏‎‏‎‎‏‎‎‎If you force stop an app, it may misbehave.‎‏‎‎‏‎"</string>
diff --git a/res/values-es-rUS/strings.xml b/res/values-es-rUS/strings.xml
index b750bcf..945dfb1 100644
--- a/res/values-es-rUS/strings.xml
+++ b/res/values-es-rUS/strings.xml
@@ -23,7 +23,6 @@
     <string name="app_launcher_title_all_apps" msgid="3215107396036838217">"Todas las apps"</string>
     <string name="app_launcher_title_media_only" msgid="4400886555907422816">"Apps multimedia"</string>
     <string name="app_launcher_stop_app_action" msgid="1081680842410279692">"Detener la app"</string>
-    <string name="app_launcher_app_info_action" msgid="6388181061002827660">"Información de la app"</string>
     <string name="app_launcher_stop_app_success_toast_text" msgid="2117271772351005349">"Se detuvo <xliff:g id="APP_NAME">%1$s</xliff:g>."</string>
     <string name="app_launcher_stop_app_dialog_title" msgid="8276889322638001144">"¿Quieres detener la app?"</string>
     <string name="app_launcher_stop_app_dialog_text" msgid="3264059639436254372">"Si fuerzas la detención de una app, es posible que funcione incorrectamente."</string>
diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml
index abed86f..487dd3c 100644
--- a/res/values-es/strings.xml
+++ b/res/values-es/strings.xml
@@ -23,7 +23,6 @@
     <string name="app_launcher_title_all_apps" msgid="3215107396036838217">"Todas las aplicaciones"</string>
     <string name="app_launcher_title_media_only" msgid="4400886555907422816">"Aplicaciones multimedia"</string>
     <string name="app_launcher_stop_app_action" msgid="1081680842410279692">"Detener aplicación"</string>
-    <string name="app_launcher_app_info_action" msgid="6388181061002827660">"Información de las aplicaciones"</string>
     <string name="app_launcher_stop_app_success_toast_text" msgid="2117271772351005349">"<xliff:g id="APP_NAME">%1$s</xliff:g> se ha detenido."</string>
     <string name="app_launcher_stop_app_dialog_title" msgid="8276889322638001144">"¿Detener la aplicación?"</string>
     <string name="app_launcher_stop_app_dialog_text" msgid="3264059639436254372">"Si fuerzas la detención de una aplicación, puede que no funcione correctamente."</string>
diff --git a/res/values-et/strings.xml b/res/values-et/strings.xml
index 0702bdb..ab68532 100644
--- a/res/values-et/strings.xml
+++ b/res/values-et/strings.xml
@@ -23,7 +23,6 @@
     <string name="app_launcher_title_all_apps" msgid="3215107396036838217">"Kõik rakendused"</string>
     <string name="app_launcher_title_media_only" msgid="4400886555907422816">"Meediarakendused"</string>
     <string name="app_launcher_stop_app_action" msgid="1081680842410279692">"Peata rakendus"</string>
-    <string name="app_launcher_app_info_action" msgid="6388181061002827660">"Rakenduse teave"</string>
     <string name="app_launcher_stop_app_success_toast_text" msgid="2117271772351005349">"<xliff:g id="APP_NAME">%1$s</xliff:g> peatati."</string>
     <string name="app_launcher_stop_app_dialog_title" msgid="8276889322638001144">"Kas peatada rakendus?"</string>
     <string name="app_launcher_stop_app_dialog_text" msgid="3264059639436254372">"Kui sundpeatate rakenduse, võib see toimida valesti."</string>
diff --git a/res/values-eu/strings.xml b/res/values-eu/strings.xml
index b8b8c0a..cd283ab 100644
--- a/res/values-eu/strings.xml
+++ b/res/values-eu/strings.xml
@@ -23,7 +23,6 @@
     <string name="app_launcher_title_all_apps" msgid="3215107396036838217">"Aplikazio guztiak"</string>
     <string name="app_launcher_title_media_only" msgid="4400886555907422816">"Multimedia-aplikazioak"</string>
     <string name="app_launcher_stop_app_action" msgid="1081680842410279692">"Gelditu aplikazioa"</string>
-    <string name="app_launcher_app_info_action" msgid="6388181061002827660">"Aplikazioari buruzko informazioa"</string>
     <string name="app_launcher_stop_app_success_toast_text" msgid="2117271772351005349">"Gelditu da <xliff:g id="APP_NAME">%1$s</xliff:g>."</string>
     <string name="app_launcher_stop_app_dialog_title" msgid="8276889322638001144">"Aplikazioa gelditu nahi duzu?"</string>
     <string name="app_launcher_stop_app_dialog_text" msgid="3264059639436254372">"Aplikazioak gelditzera behartzen badituzu, baliteke behar bezala ez funtzionatzea."</string>
diff --git a/res/values-fa/strings.xml b/res/values-fa/strings.xml
index 52638a3..7536431 100644
--- a/res/values-fa/strings.xml
+++ b/res/values-fa/strings.xml
@@ -23,7 +23,6 @@
     <string name="app_launcher_title_all_apps" msgid="3215107396036838217">"همه برنامه‌ها"</string>
     <string name="app_launcher_title_media_only" msgid="4400886555907422816">"برنامه‌های رسانه"</string>
     <string name="app_launcher_stop_app_action" msgid="1081680842410279692">"متوقف کردن برنامه"</string>
-    <string name="app_launcher_app_info_action" msgid="6388181061002827660">"اطلاعات برنامه"</string>
     <string name="app_launcher_stop_app_success_toast_text" msgid="2117271772351005349">"<xliff:g id="APP_NAME">%1$s</xliff:g> متوقف شده است."</string>
     <string name="app_launcher_stop_app_dialog_title" msgid="8276889322638001144">"برنامه متوقف شود؟"</string>
     <string name="app_launcher_stop_app_dialog_text" msgid="3264059639436254372">"توقف اجباری برنامه ممکن است باعث عملکرد نادرست آن شود."</string>
diff --git a/res/values-fi/strings.xml b/res/values-fi/strings.xml
index 0a8b253..db6c88b 100644
--- a/res/values-fi/strings.xml
+++ b/res/values-fi/strings.xml
@@ -23,7 +23,6 @@
     <string name="app_launcher_title_all_apps" msgid="3215107396036838217">"Kaikki sovellukset"</string>
     <string name="app_launcher_title_media_only" msgid="4400886555907422816">"Mediasovellukset"</string>
     <string name="app_launcher_stop_app_action" msgid="1081680842410279692">"Sulje sovellus"</string>
-    <string name="app_launcher_app_info_action" msgid="6388181061002827660">"Sovelluksen tiedot"</string>
     <string name="app_launcher_stop_app_success_toast_text" msgid="2117271772351005349">"<xliff:g id="APP_NAME">%1$s</xliff:g> on suljettu."</string>
     <string name="app_launcher_stop_app_dialog_title" msgid="8276889322638001144">"Suljetaanko sovellus?"</string>
     <string name="app_launcher_stop_app_dialog_text" msgid="3264059639436254372">"Jos pakotat sovelluksen sulkeutumaan, se ei välttämättä toimi oikein."</string>
diff --git a/res/values-fr-rCA/strings.xml b/res/values-fr-rCA/strings.xml
index d3c6242..328922d 100644
--- a/res/values-fr-rCA/strings.xml
+++ b/res/values-fr-rCA/strings.xml
@@ -23,7 +23,6 @@
     <string name="app_launcher_title_all_apps" msgid="3215107396036838217">"Toutes les applications"</string>
     <string name="app_launcher_title_media_only" msgid="4400886555907422816">"Applications multimédias"</string>
     <string name="app_launcher_stop_app_action" msgid="1081680842410279692">"Arrêter l\'application"</string>
-    <string name="app_launcher_app_info_action" msgid="6388181061002827660">"Détails de l\'application"</string>
     <string name="app_launcher_stop_app_success_toast_text" msgid="2117271772351005349">"<xliff:g id="APP_NAME">%1$s</xliff:g> s\'est arrêté."</string>
     <string name="app_launcher_stop_app_dialog_title" msgid="8276889322638001144">"Arrêter l\'application?"</string>
     <string name="app_launcher_stop_app_dialog_text" msgid="3264059639436254372">"Si vous forcez l\'arrêt d\'une application, son fonctionnement peut en être affecté."</string>
diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml
index cba3d87..d6c799d 100644
--- a/res/values-fr/strings.xml
+++ b/res/values-fr/strings.xml
@@ -23,7 +23,6 @@
     <string name="app_launcher_title_all_apps" msgid="3215107396036838217">"Toutes les applications"</string>
     <string name="app_launcher_title_media_only" msgid="4400886555907422816">"Applications multimédias"</string>
     <string name="app_launcher_stop_app_action" msgid="1081680842410279692">"Arrêter l\'appli"</string>
-    <string name="app_launcher_app_info_action" msgid="6388181061002827660">"Infos sur les applis"</string>
     <string name="app_launcher_stop_app_success_toast_text" msgid="2117271772351005349">"L\'appli <xliff:g id="APP_NAME">%1$s</xliff:g> a été arrêtée."</string>
     <string name="app_launcher_stop_app_dialog_title" msgid="8276889322638001144">"Arrêter l\'appli ?"</string>
     <string name="app_launcher_stop_app_dialog_text" msgid="3264059639436254372">"L\'arrêt forcé d\'une appli peut provoquer un fonctionnement instable."</string>
diff --git a/res/values-gl/strings.xml b/res/values-gl/strings.xml
index f4f26d9..6e59a5f 100644
--- a/res/values-gl/strings.xml
+++ b/res/values-gl/strings.xml
@@ -23,7 +23,6 @@
     <string name="app_launcher_title_all_apps" msgid="3215107396036838217">"Todas as aplicacións"</string>
     <string name="app_launcher_title_media_only" msgid="4400886555907422816">"Aplicacións multimedia"</string>
     <string name="app_launcher_stop_app_action" msgid="1081680842410279692">"Deter aplicación"</string>
-    <string name="app_launcher_app_info_action" msgid="6388181061002827660">"Información da aplicación"</string>
     <string name="app_launcher_stop_app_success_toast_text" msgid="2117271772351005349">"Detívose a aplicación <xliff:g id="APP_NAME">%1$s</xliff:g>."</string>
     <string name="app_launcher_stop_app_dialog_title" msgid="8276889322638001144">"Queres deter a aplicación?"</string>
     <string name="app_launcher_stop_app_dialog_text" msgid="3264059639436254372">"Se forzas a parada dunha aplicación, é posible que non funcione correctamente."</string>
diff --git a/res/values-gu/strings.xml b/res/values-gu/strings.xml
index 52f5d7e..8f2e135 100644
--- a/res/values-gu/strings.xml
+++ b/res/values-gu/strings.xml
@@ -23,7 +23,6 @@
     <string name="app_launcher_title_all_apps" msgid="3215107396036838217">"બધી ઍપ"</string>
     <string name="app_launcher_title_media_only" msgid="4400886555907422816">"મીડિયા ઍપ"</string>
     <string name="app_launcher_stop_app_action" msgid="1081680842410279692">"ઍપ બંધ કરો"</string>
-    <string name="app_launcher_app_info_action" msgid="6388181061002827660">"ઍપની માહિતી"</string>
     <string name="app_launcher_stop_app_success_toast_text" msgid="2117271772351005349">"<xliff:g id="APP_NAME">%1$s</xliff:g> બંધ કરવામાં આવી છે."</string>
     <string name="app_launcher_stop_app_dialog_title" msgid="8276889322638001144">"ઍપ બંધ કરીએ?"</string>
     <string name="app_launcher_stop_app_dialog_text" msgid="3264059639436254372">"જો તમે કોઈ ઍપને ફરજિયાત બંધ કરો, તો તે અયોગ્ય વર્તન કરી શકે છે."</string>
diff --git a/res/values-hi/strings.xml b/res/values-hi/strings.xml
index 4356f13..ffb7e03 100644
--- a/res/values-hi/strings.xml
+++ b/res/values-hi/strings.xml
@@ -23,7 +23,6 @@
     <string name="app_launcher_title_all_apps" msgid="3215107396036838217">"सभी ऐप्लिकेशन"</string>
     <string name="app_launcher_title_media_only" msgid="4400886555907422816">"मीडिया ऐप्लिकेशन"</string>
     <string name="app_launcher_stop_app_action" msgid="1081680842410279692">"ऐप्लिकेशन रोकें"</string>
-    <string name="app_launcher_app_info_action" msgid="6388181061002827660">"ऐप्लिकेशन की जानकारी"</string>
     <string name="app_launcher_stop_app_success_toast_text" msgid="2117271772351005349">"<xliff:g id="APP_NAME">%1$s</xliff:g> को रोका गया है."</string>
     <string name="app_launcher_stop_app_dialog_title" msgid="8276889322638001144">"क्या आपको ऐप्लिकेशन रोकना है?"</string>
     <string name="app_launcher_stop_app_dialog_text" msgid="3264059639436254372">"अगर किसी ऐप्लिकेशन को ज़बरदस्ती रोका जाता है, तो हो सकता है कि वह ठीक तरह से काम न करें."</string>
diff --git a/res/values-hr/strings.xml b/res/values-hr/strings.xml
index 0792e43..7af1fe1 100644
--- a/res/values-hr/strings.xml
+++ b/res/values-hr/strings.xml
@@ -23,7 +23,6 @@
     <string name="app_launcher_title_all_apps" msgid="3215107396036838217">"Sve aplikacije"</string>
     <string name="app_launcher_title_media_only" msgid="4400886555907422816">"Aplikacije za medijske sadržaje"</string>
     <string name="app_launcher_stop_app_action" msgid="1081680842410279692">"Zaustavi aplikaciju"</string>
-    <string name="app_launcher_app_info_action" msgid="6388181061002827660">"Informacije o aplikaciji"</string>
     <string name="app_launcher_stop_app_success_toast_text" msgid="2117271772351005349">"Aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> je zaustavljena."</string>
     <string name="app_launcher_stop_app_dialog_title" msgid="8276889322638001144">"Zaustaviti aplikaciju?"</string>
     <string name="app_launcher_stop_app_dialog_text" msgid="3264059639436254372">"Ako silom zaustavite aplikaciju, možda će se ponašati nepredviđeno."</string>
diff --git a/res/values-hu/strings.xml b/res/values-hu/strings.xml
index d8ce05c..06fa659 100644
--- a/res/values-hu/strings.xml
+++ b/res/values-hu/strings.xml
@@ -23,7 +23,6 @@
     <string name="app_launcher_title_all_apps" msgid="3215107396036838217">"Összes alkalmazás"</string>
     <string name="app_launcher_title_media_only" msgid="4400886555907422816">"Médiaalkalmazások"</string>
     <string name="app_launcher_stop_app_action" msgid="1081680842410279692">"Alkalmazás leállítása"</string>
-    <string name="app_launcher_app_info_action" msgid="6388181061002827660">"Alkalmazásadatok"</string>
     <string name="app_launcher_stop_app_success_toast_text" msgid="2117271772351005349">"<xliff:g id="APP_NAME">%1$s</xliff:g> leállítva."</string>
     <string name="app_launcher_stop_app_dialog_title" msgid="8276889322638001144">"Leállítja az alkalmazást?"</string>
     <string name="app_launcher_stop_app_dialog_text" msgid="3264059639436254372">"Ha egy alkalmazást leállásra kényszerít, lehetséges, hogy hibásan fog működni."</string>
diff --git a/res/values-hy/strings.xml b/res/values-hy/strings.xml
index 5f45ced..5cfa2d7 100644
--- a/res/values-hy/strings.xml
+++ b/res/values-hy/strings.xml
@@ -23,7 +23,6 @@
     <string name="app_launcher_title_all_apps" msgid="3215107396036838217">"Բոլոր հավելվածները"</string>
     <string name="app_launcher_title_media_only" msgid="4400886555907422816">"Մեդիա հավելվածներ"</string>
     <string name="app_launcher_stop_app_action" msgid="1081680842410279692">"Կանգնեցնել հավելվածը"</string>
-    <string name="app_launcher_app_info_action" msgid="6388181061002827660">"Հավելվածի մասին"</string>
     <string name="app_launcher_stop_app_success_toast_text" msgid="2117271772351005349">"<xliff:g id="APP_NAME">%1$s</xliff:g> հավելվածը կանգնեցվեց։"</string>
     <string name="app_launcher_stop_app_dialog_title" msgid="8276889322638001144">"Կանգնեցնե՞լ հավելվածը"</string>
     <string name="app_launcher_stop_app_dialog_text" msgid="3264059639436254372">"Հավելվածի ստիպողական կանգնեցումը կարող է ազդել դրա աշխատանքի վրա։"</string>
diff --git a/res/values-in/strings.xml b/res/values-in/strings.xml
index 1bd4efb..2cb7a4f 100644
--- a/res/values-in/strings.xml
+++ b/res/values-in/strings.xml
@@ -23,7 +23,6 @@
     <string name="app_launcher_title_all_apps" msgid="3215107396036838217">"Semua aplikasi"</string>
     <string name="app_launcher_title_media_only" msgid="4400886555907422816">"Aplikasi media"</string>
     <string name="app_launcher_stop_app_action" msgid="1081680842410279692">"Hentikan aplikasi"</string>
-    <string name="app_launcher_app_info_action" msgid="6388181061002827660">"Info aplikasi"</string>
     <string name="app_launcher_stop_app_success_toast_text" msgid="2117271772351005349">"<xliff:g id="APP_NAME">%1$s</xliff:g> telah dihentikan."</string>
     <string name="app_launcher_stop_app_dialog_title" msgid="8276889322638001144">"Hentikan aplikasi?"</string>
     <string name="app_launcher_stop_app_dialog_text" msgid="3264059639436254372">"Jika aplikasi dihentikan paksa, fungsinya mungkin akan terganggu."</string>
diff --git a/res/values-is/strings.xml b/res/values-is/strings.xml
index 557db9d..9153c50 100644
--- a/res/values-is/strings.xml
+++ b/res/values-is/strings.xml
@@ -23,7 +23,6 @@
     <string name="app_launcher_title_all_apps" msgid="3215107396036838217">"Öll forrit"</string>
     <string name="app_launcher_title_media_only" msgid="4400886555907422816">"Margmiðlunarforrit"</string>
     <string name="app_launcher_stop_app_action" msgid="1081680842410279692">"Stöðva forrit"</string>
-    <string name="app_launcher_app_info_action" msgid="6388181061002827660">"Upplýsingar um forrit"</string>
     <string name="app_launcher_stop_app_success_toast_text" msgid="2117271772351005349">"<xliff:g id="APP_NAME">%1$s</xliff:g> var stöðvað."</string>
     <string name="app_launcher_stop_app_dialog_title" msgid="8276889322638001144">"Stöðva forritið?"</string>
     <string name="app_launcher_stop_app_dialog_text" msgid="3264059639436254372">"Ef þú þvingar fram lokun forrits gæti það látið illa."</string>
diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml
index 7ddb2b7..9ac3d52 100644
--- a/res/values-it/strings.xml
+++ b/res/values-it/strings.xml
@@ -23,7 +23,6 @@
     <string name="app_launcher_title_all_apps" msgid="3215107396036838217">"Tutte le app"</string>
     <string name="app_launcher_title_media_only" msgid="4400886555907422816">"App multimediali"</string>
     <string name="app_launcher_stop_app_action" msgid="1081680842410279692">"Interrompi l\'app"</string>
-    <string name="app_launcher_app_info_action" msgid="6388181061002827660">"Informazioni app"</string>
     <string name="app_launcher_stop_app_success_toast_text" msgid="2117271772351005349">"L\'app <xliff:g id="APP_NAME">%1$s</xliff:g> è stata interrotta."</string>
     <string name="app_launcher_stop_app_dialog_title" msgid="8276889322638001144">"Interrompere l\'app?"</string>
     <string name="app_launcher_stop_app_dialog_text" msgid="3264059639436254372">"Se forzi l\'interruzione di un\'app, questa potrebbe funzionare in modo anomalo."</string>
diff --git a/res/values-iw/strings.xml b/res/values-iw/strings.xml
index 753f263..0f711d1 100644
--- a/res/values-iw/strings.xml
+++ b/res/values-iw/strings.xml
@@ -23,7 +23,6 @@
     <string name="app_launcher_title_all_apps" msgid="3215107396036838217">"כל האפליקציות"</string>
     <string name="app_launcher_title_media_only" msgid="4400886555907422816">"אפליקציות מדיה"</string>
     <string name="app_launcher_stop_app_action" msgid="1081680842410279692">"עצירת האפליקציה"</string>
-    <string name="app_launcher_app_info_action" msgid="6388181061002827660">"פרטי האפליקציה"</string>
     <string name="app_launcher_stop_app_success_toast_text" msgid="2117271772351005349">"עצרת את האפליקציה <xliff:g id="APP_NAME">%1$s</xliff:g>."</string>
     <string name="app_launcher_stop_app_dialog_title" msgid="8276889322638001144">"לעצור את האפליקציה?"</string>
     <string name="app_launcher_stop_app_dialog_text" msgid="3264059639436254372">"אם סוגרים אפליקציה באופן ידני, ייתכן שהיא לא תפעל כהלכה."</string>
diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml
index 9294f8a..1266df8 100644
--- a/res/values-ja/strings.xml
+++ b/res/values-ja/strings.xml
@@ -23,7 +23,6 @@
     <string name="app_launcher_title_all_apps" msgid="3215107396036838217">"すべてのアプリ"</string>
     <string name="app_launcher_title_media_only" msgid="4400886555907422816">"メディアアプリ"</string>
     <string name="app_launcher_stop_app_action" msgid="1081680842410279692">"アプリを停止"</string>
-    <string name="app_launcher_app_info_action" msgid="6388181061002827660">"アプリ情報"</string>
     <string name="app_launcher_stop_app_success_toast_text" msgid="2117271772351005349">"<xliff:g id="APP_NAME">%1$s</xliff:g>を停止しました。"</string>
     <string name="app_launcher_stop_app_dialog_title" msgid="8276889322638001144">"アプリを停止しますか?"</string>
     <string name="app_launcher_stop_app_dialog_text" msgid="3264059639436254372">"アプリを強制停止すると、アプリが正常に機能しないことがあります。"</string>
diff --git a/res/values-ka/strings.xml b/res/values-ka/strings.xml
index e2bbc03..5242836 100644
--- a/res/values-ka/strings.xml
+++ b/res/values-ka/strings.xml
@@ -23,7 +23,6 @@
     <string name="app_launcher_title_all_apps" msgid="3215107396036838217">"ყველა აპი"</string>
     <string name="app_launcher_title_media_only" msgid="4400886555907422816">"მედია აპები"</string>
     <string name="app_launcher_stop_app_action" msgid="1081680842410279692">"აპის შეჩერება"</string>
-    <string name="app_launcher_app_info_action" msgid="6388181061002827660">"აპის ინფორმაცია"</string>
     <string name="app_launcher_stop_app_success_toast_text" msgid="2117271772351005349">"<xliff:g id="APP_NAME">%1$s</xliff:g> შეჩერებულია."</string>
     <string name="app_launcher_stop_app_dialog_title" msgid="8276889322638001144">"გსურთ აპის შეჩერება?"</string>
     <string name="app_launcher_stop_app_dialog_text" msgid="3264059639436254372">"თუ აპს შეწყვეტას აიძულებთ, შესაძლოა, მან ცუდად განაგრძოს მუშაობა."</string>
diff --git a/res/values-kk/strings.xml b/res/values-kk/strings.xml
index 861240c..c99cec2 100644
--- a/res/values-kk/strings.xml
+++ b/res/values-kk/strings.xml
@@ -23,7 +23,6 @@
     <string name="app_launcher_title_all_apps" msgid="3215107396036838217">"Барлық қолданба"</string>
     <string name="app_launcher_title_media_only" msgid="4400886555907422816">"Мультимедиа қолданбалары"</string>
     <string name="app_launcher_stop_app_action" msgid="1081680842410279692">"Қолданбаны тоқтату"</string>
-    <string name="app_launcher_app_info_action" msgid="6388181061002827660">"Қолданба туралы ақпарат"</string>
     <string name="app_launcher_stop_app_success_toast_text" msgid="2117271772351005349">"<xliff:g id="APP_NAME">%1$s</xliff:g> тоқтатылды."</string>
     <string name="app_launcher_stop_app_dialog_title" msgid="8276889322638001144">"Қолданба тоқтатылсын ба?"</string>
     <string name="app_launcher_stop_app_dialog_text" msgid="3264059639436254372">"Қолданбаны қолмен тоқтату оның жұмысына кері әсерін тигізуі мүмкін."</string>
diff --git a/res/values-km/strings.xml b/res/values-km/strings.xml
index 18ffe92..b80c44a 100644
--- a/res/values-km/strings.xml
+++ b/res/values-km/strings.xml
@@ -23,7 +23,6 @@
     <string name="app_launcher_title_all_apps" msgid="3215107396036838217">"កម្មវិធី​ទាំងអស់"</string>
     <string name="app_launcher_title_media_only" msgid="4400886555907422816">"កម្មវិធីមេឌៀ"</string>
     <string name="app_launcher_stop_app_action" msgid="1081680842410279692">"បញ្ឈប់កម្មវិធី"</string>
-    <string name="app_launcher_app_info_action" msgid="6388181061002827660">"ព័ត៌មានកម្មវិធី"</string>
     <string name="app_launcher_stop_app_success_toast_text" msgid="2117271772351005349">"<xliff:g id="APP_NAME">%1$s</xliff:g> ត្រូវបានបញ្ឈប់។"</string>
     <string name="app_launcher_stop_app_dialog_title" msgid="8276889322638001144">"បញ្ឈប់កម្មវិធីឬ?"</string>
     <string name="app_launcher_stop_app_dialog_text" msgid="3264059639436254372">"ប្រសិនបើអ្នក​បង្ខំឱ្យបញ្ឈប់​កម្មវិធី​ កម្មវិធីអាចនឹង​ដំណើរការ​មិនត្រឹមត្រូវ។"</string>
diff --git a/res/values-kn/strings.xml b/res/values-kn/strings.xml
index 23f04ed..97bc3fc 100644
--- a/res/values-kn/strings.xml
+++ b/res/values-kn/strings.xml
@@ -23,7 +23,6 @@
     <string name="app_launcher_title_all_apps" msgid="3215107396036838217">"ಎಲ್ಲಾ ಆ್ಯಪ್‌ಗಳು"</string>
     <string name="app_launcher_title_media_only" msgid="4400886555907422816">"ಮಾಧ್ಯಮ ಆ್ಯಪ್‌ಗಳು"</string>
     <string name="app_launcher_stop_app_action" msgid="1081680842410279692">"ಆ್ಯಪ್ ಅನ್ನು ನಿಲ್ಲಿಸಿ"</string>
-    <string name="app_launcher_app_info_action" msgid="6388181061002827660">"ಆ್ಯಪ್ ಮಾಹಿತಿ"</string>
     <string name="app_launcher_stop_app_success_toast_text" msgid="2117271772351005349">"<xliff:g id="APP_NAME">%1$s</xliff:g> ಅನ್ನು ನಿಲ್ಲಿಸಲಾಗಿದೆ."</string>
     <string name="app_launcher_stop_app_dialog_title" msgid="8276889322638001144">"ಆ್ಯಪ್ ಅನ್ನು ನಿಲ್ಲಿಸಬೇಕೆ?"</string>
     <string name="app_launcher_stop_app_dialog_text" msgid="3264059639436254372">"ನೀವು ಆ್ಯಪ್ ಅನ್ನು ಬಲವಂತವಾಗಿ ನಿಲ್ಲಿಸಿದರೆ, ಅದು ಸರಿಯಾಗಿ ಕಾರ್ಯನಿರ್ವಹಿಸದಿರಬಹುದು."</string>
diff --git a/res/values-ko/strings.xml b/res/values-ko/strings.xml
index 6267718..43b11ea 100644
--- a/res/values-ko/strings.xml
+++ b/res/values-ko/strings.xml
@@ -23,7 +23,6 @@
     <string name="app_launcher_title_all_apps" msgid="3215107396036838217">"모든 앱"</string>
     <string name="app_launcher_title_media_only" msgid="4400886555907422816">"미디어 앱"</string>
     <string name="app_launcher_stop_app_action" msgid="1081680842410279692">"앱 종료"</string>
-    <string name="app_launcher_app_info_action" msgid="6388181061002827660">"앱 정보"</string>
     <string name="app_launcher_stop_app_success_toast_text" msgid="2117271772351005349">"<xliff:g id="APP_NAME">%1$s</xliff:g> 앱이 종료되었습니다."</string>
     <string name="app_launcher_stop_app_dialog_title" msgid="8276889322638001144">"앱을 종료하시겠습니까?"</string>
     <string name="app_launcher_stop_app_dialog_text" msgid="3264059639436254372">"강제로 앱을 종료하면 예기치 않은 오류가 발생할 수 있습니다."</string>
diff --git a/res/values-ky/strings.xml b/res/values-ky/strings.xml
index 7fd9458..ea02405 100644
--- a/res/values-ky/strings.xml
+++ b/res/values-ky/strings.xml
@@ -23,7 +23,6 @@
     <string name="app_launcher_title_all_apps" msgid="3215107396036838217">"Бардык колдонмолор"</string>
     <string name="app_launcher_title_media_only" msgid="4400886555907422816">"Медиа колдонмолору"</string>
     <string name="app_launcher_stop_app_action" msgid="1081680842410279692">"Колдонмону токтотуу"</string>
-    <string name="app_launcher_app_info_action" msgid="6388181061002827660">"Колдонмо маалыматы"</string>
     <string name="app_launcher_stop_app_success_toast_text" msgid="2117271772351005349">"<xliff:g id="APP_NAME">%1$s</xliff:g> токтотулду."</string>
     <string name="app_launcher_stop_app_dialog_title" msgid="8276889322638001144">"Колдонмону токтотосузбу?"</string>
     <string name="app_launcher_stop_app_dialog_text" msgid="3264059639436254372">"Колдонмону токтотсоңуз, ал туура эмес иштеп калышы мүмкүн."</string>
diff --git a/res/values-lo/strings.xml b/res/values-lo/strings.xml
index a6ac33c..ed0ba72 100644
--- a/res/values-lo/strings.xml
+++ b/res/values-lo/strings.xml
@@ -23,7 +23,6 @@
     <string name="app_launcher_title_all_apps" msgid="3215107396036838217">"ແອັບທັງໝົດ"</string>
     <string name="app_launcher_title_media_only" msgid="4400886555907422816">"ແອັບມີເດຍ"</string>
     <string name="app_launcher_stop_app_action" msgid="1081680842410279692">"ຢຸດແອັບ"</string>
-    <string name="app_launcher_app_info_action" msgid="6388181061002827660">"ຂໍ້ມູນແອັບ"</string>
     <string name="app_launcher_stop_app_success_toast_text" msgid="2117271772351005349">"ຢຸດ <xliff:g id="APP_NAME">%1$s</xliff:g> ແລ້ວ."</string>
     <string name="app_launcher_stop_app_dialog_title" msgid="8276889322638001144">"ຢຸດແອັບໄວ້ບໍ?"</string>
     <string name="app_launcher_stop_app_dialog_text" msgid="3264059639436254372">"ຫານທ່ານບັງຄັບປິດແອັບໃດໜຶ່ງ, ມັນອາດເຮັດວຽກຜິດປົກກະຕິໄດ້."</string>
diff --git a/res/values-lt/strings.xml b/res/values-lt/strings.xml
index d8ec66c..2b05166 100644
--- a/res/values-lt/strings.xml
+++ b/res/values-lt/strings.xml
@@ -23,7 +23,6 @@
     <string name="app_launcher_title_all_apps" msgid="3215107396036838217">"Visos programos"</string>
     <string name="app_launcher_title_media_only" msgid="4400886555907422816">"Medijos programos"</string>
     <string name="app_launcher_stop_app_action" msgid="1081680842410279692">"Sustabdyti programą"</string>
-    <string name="app_launcher_app_info_action" msgid="6388181061002827660">"Programos informacija"</string>
     <string name="app_launcher_stop_app_success_toast_text" msgid="2117271772351005349">"Programa „<xliff:g id="APP_NAME">%1$s</xliff:g>“ sustabdyta."</string>
     <string name="app_launcher_stop_app_dialog_title" msgid="8276889322638001144">"Sustabdyti programą?"</string>
     <string name="app_launcher_stop_app_dialog_text" msgid="3264059639436254372">"Jei priverstinai sustabdysite programą, ji gali neveikti tinkamai."</string>
diff --git a/res/values-lv/strings.xml b/res/values-lv/strings.xml
index 54cc907..234a49b 100644
--- a/res/values-lv/strings.xml
+++ b/res/values-lv/strings.xml
@@ -23,7 +23,6 @@
     <string name="app_launcher_title_all_apps" msgid="3215107396036838217">"Visas lietotnes"</string>
     <string name="app_launcher_title_media_only" msgid="4400886555907422816">"Multivides lietotnes"</string>
     <string name="app_launcher_stop_app_action" msgid="1081680842410279692">"Apturēt lietotnes darbību"</string>
-    <string name="app_launcher_app_info_action" msgid="6388181061002827660">"Informācija par lietotni"</string>
     <string name="app_launcher_stop_app_success_toast_text" msgid="2117271772351005349">"Lietotnes <xliff:g id="APP_NAME">%1$s</xliff:g> darbība ir apturēta."</string>
     <string name="app_launcher_stop_app_dialog_title" msgid="8276889322638001144">"Vai apturēt lietotnes darbību?"</string>
     <string name="app_launcher_stop_app_dialog_text" msgid="3264059639436254372">"Piespiedu kārtā apturot lietotnes darbību, var rasties šīs lietotnes darbības traucējumi."</string>
diff --git a/res/values-mk/strings.xml b/res/values-mk/strings.xml
index 2159717..f12230c 100644
--- a/res/values-mk/strings.xml
+++ b/res/values-mk/strings.xml
@@ -23,7 +23,6 @@
     <string name="app_launcher_title_all_apps" msgid="3215107396036838217">"Сите апликации"</string>
     <string name="app_launcher_title_media_only" msgid="4400886555907422816">"Апликации за аудиовизуелни содржини"</string>
     <string name="app_launcher_stop_app_action" msgid="1081680842410279692">"Запри ја апликацијата"</string>
-    <string name="app_launcher_app_info_action" msgid="6388181061002827660">"Информации за апликацијата"</string>
     <string name="app_launcher_stop_app_success_toast_text" msgid="2117271772351005349">"<xliff:g id="APP_NAME">%1$s</xliff:g> е запрена."</string>
     <string name="app_launcher_stop_app_dialog_title" msgid="8276889322638001144">"Да се запре апликацијата?"</string>
     <string name="app_launcher_stop_app_dialog_text" msgid="3264059639436254372">"Ако присилно запрете апликација, таа може да не се однесува правилно."</string>
@@ -41,7 +40,7 @@
     <string name="weather_app_name" msgid="8048146303519139993">"Временска прогноза"</string>
     <string name="fake_weather_main_text" msgid="8117240999340284429">"-- ° Претежно сончево"</string>
     <string name="fake_weather_footer_text" msgid="4570172025869749926">"Маунтин Вју • В: -- ° Н: -- °"</string>
-    <string name="hide_debug_apps" msgid="6128313544379622475">"Скриј апликации за отстранување грешки"</string>
+    <string name="hide_debug_apps" msgid="6128313544379622475">"Сокриј апликации за отстранување грешки"</string>
     <string name="show_debug_apps" msgid="4521073121286325786">"Прикажи апликации за отстранување грешки"</string>
     <string name="times_separator" msgid="6180368193321715816">"/"</string>
     <string name="recents_empty_state_text" msgid="6703106170272215731">"Нема неодамнешни ставки"</string>
diff --git a/res/values-ml/strings.xml b/res/values-ml/strings.xml
index 2683207..2235e7e 100644
--- a/res/values-ml/strings.xml
+++ b/res/values-ml/strings.xml
@@ -23,7 +23,6 @@
     <string name="app_launcher_title_all_apps" msgid="3215107396036838217">"എല്ലാ ആപ്പുകളും"</string>
     <string name="app_launcher_title_media_only" msgid="4400886555907422816">"മീഡിയ ആപ്പുകൾ"</string>
     <string name="app_launcher_stop_app_action" msgid="1081680842410279692">"ആപ്പിന്റെ പ്രവർത്തനം നിർത്തുക"</string>
-    <string name="app_launcher_app_info_action" msgid="6388181061002827660">"ആപ്പ് വിവരങ്ങൾ"</string>
     <string name="app_launcher_stop_app_success_toast_text" msgid="2117271772351005349">"<xliff:g id="APP_NAME">%1$s</xliff:g> എന്നതിന്റെ പ്രവർത്തനം നിർത്തി."</string>
     <string name="app_launcher_stop_app_dialog_title" msgid="8276889322638001144">"ആപ്പിന്റെ പ്രവർത്തനം നിർത്തണോ?"</string>
     <string name="app_launcher_stop_app_dialog_text" msgid="3264059639436254372">"ഒരു ആപ്പിന്റെ പ്രവർത്തനം നിർബന്ധിതമായി നിർത്തിയാൽ, അത് ശരിയായി പ്രവർത്തിക്കാനിടയില്ല."</string>
diff --git a/res/values-mn/strings.xml b/res/values-mn/strings.xml
index f6a49e8..3636c27 100644
--- a/res/values-mn/strings.xml
+++ b/res/values-mn/strings.xml
@@ -23,7 +23,6 @@
     <string name="app_launcher_title_all_apps" msgid="3215107396036838217">"Бүх апп"</string>
     <string name="app_launcher_title_media_only" msgid="4400886555907422816">"Медиа аппууд"</string>
     <string name="app_launcher_stop_app_action" msgid="1081680842410279692">"Аппыг зогсоох"</string>
-    <string name="app_launcher_app_info_action" msgid="6388181061002827660">"Аппын мэдээлэл"</string>
     <string name="app_launcher_stop_app_success_toast_text" msgid="2117271772351005349">"<xliff:g id="APP_NAME">%1$s</xliff:g> зогссон."</string>
     <string name="app_launcher_stop_app_dialog_title" msgid="8276889322638001144">"Аппыг зогсоох уу?"</string>
     <string name="app_launcher_stop_app_dialog_text" msgid="3264059639436254372">"Хэрэв аппыг хүчээр зогсоовол буруу ажиллаж магадгүй."</string>
diff --git a/res/values-mr/strings.xml b/res/values-mr/strings.xml
index f3da6b6..2751aca 100644
--- a/res/values-mr/strings.xml
+++ b/res/values-mr/strings.xml
@@ -23,7 +23,6 @@
     <string name="app_launcher_title_all_apps" msgid="3215107396036838217">"सर्व अ‍ॅप्स"</string>
     <string name="app_launcher_title_media_only" msgid="4400886555907422816">"मीडिया अ‍ॅप्स"</string>
     <string name="app_launcher_stop_app_action" msgid="1081680842410279692">"अ‍ॅप थांबवा"</string>
-    <string name="app_launcher_app_info_action" msgid="6388181061002827660">"अ‍ॅपची माहिती"</string>
     <string name="app_launcher_stop_app_success_toast_text" msgid="2117271772351005349">"<xliff:g id="APP_NAME">%1$s</xliff:g> थांबवले आहे."</string>
     <string name="app_launcher_stop_app_dialog_title" msgid="8276889322638001144">"अ‍ॅप थांबवायचे आहे का?"</string>
     <string name="app_launcher_stop_app_dialog_text" msgid="3264059639436254372">"तुम्ही अ‍ॅप सक्तीने थांबवल्यास, ते गैरवर्तन करू शकते."</string>
diff --git a/res/values-ms/strings.xml b/res/values-ms/strings.xml
index 89e39f3..1ebb30c 100644
--- a/res/values-ms/strings.xml
+++ b/res/values-ms/strings.xml
@@ -23,7 +23,6 @@
     <string name="app_launcher_title_all_apps" msgid="3215107396036838217">"Semua apl"</string>
     <string name="app_launcher_title_media_only" msgid="4400886555907422816">"Apl media"</string>
     <string name="app_launcher_stop_app_action" msgid="1081680842410279692">"Hentikan apl"</string>
-    <string name="app_launcher_app_info_action" msgid="6388181061002827660">"Maklumat apl"</string>
     <string name="app_launcher_stop_app_success_toast_text" msgid="2117271772351005349">"<xliff:g id="APP_NAME">%1$s</xliff:g> telah dihentikan."</string>
     <string name="app_launcher_stop_app_dialog_title" msgid="8276889322638001144">"Hentikan apl?"</string>
     <string name="app_launcher_stop_app_dialog_text" msgid="3264059639436254372">"Jika anda menghentikan apl secara paksa, mungkin fungsi apl akan terganggu."</string>
diff --git a/res/values-my/strings.xml b/res/values-my/strings.xml
index 986cdf7..e9a10a8 100644
--- a/res/values-my/strings.xml
+++ b/res/values-my/strings.xml
@@ -23,7 +23,6 @@
     <string name="app_launcher_title_all_apps" msgid="3215107396036838217">"အက်ပ်အားလုံး"</string>
     <string name="app_launcher_title_media_only" msgid="4400886555907422816">"မီဒီယာ အက်ပ်များ"</string>
     <string name="app_launcher_stop_app_action" msgid="1081680842410279692">"အက်ပ်ကို ရပ်ဆိုင်းရန်"</string>
-    <string name="app_launcher_app_info_action" msgid="6388181061002827660">"အက်ပ်အချက်အလက်"</string>
     <string name="app_launcher_stop_app_success_toast_text" msgid="2117271772351005349">"<xliff:g id="APP_NAME">%1$s</xliff:g> ရပ်ဆိုင်းသွားပြီ။"</string>
     <string name="app_launcher_stop_app_dialog_title" msgid="8276889322638001144">"အက်ပ်ကို ရပ်ဆိုင်းမလား။"</string>
     <string name="app_launcher_stop_app_dialog_text" msgid="3264059639436254372">"အက်ပ်ကို မဖြစ်မနေ ရပ်ခိုင်းလျှင် အမှားအယွင်းဖြစ်နိုင်သည်။"</string>
diff --git a/res/values-nb/strings.xml b/res/values-nb/strings.xml
index bef1344..7b0dcd7 100644
--- a/res/values-nb/strings.xml
+++ b/res/values-nb/strings.xml
@@ -23,7 +23,6 @@
     <string name="app_launcher_title_all_apps" msgid="3215107396036838217">"Alle apper"</string>
     <string name="app_launcher_title_media_only" msgid="4400886555907422816">"Medieapper"</string>
     <string name="app_launcher_stop_app_action" msgid="1081680842410279692">"Stopp appen"</string>
-    <string name="app_launcher_app_info_action" msgid="6388181061002827660">"Appinformasjon"</string>
     <string name="app_launcher_stop_app_success_toast_text" msgid="2117271772351005349">"<xliff:g id="APP_NAME">%1$s</xliff:g> er stoppet."</string>
     <string name="app_launcher_stop_app_dialog_title" msgid="8276889322638001144">"Vil du stoppe appen?"</string>
     <string name="app_launcher_stop_app_dialog_text" msgid="3264059639436254372">"Hvis du tvinger en app til å avslutte, kan det oppstå problemer."</string>
diff --git a/res/values-ne/strings.xml b/res/values-ne/strings.xml
index 8dd1234..a992df8 100644
--- a/res/values-ne/strings.xml
+++ b/res/values-ne/strings.xml
@@ -23,7 +23,6 @@
     <string name="app_launcher_title_all_apps" msgid="3215107396036838217">"सबै एपहरू"</string>
     <string name="app_launcher_title_media_only" msgid="4400886555907422816">"मिडिया एपहरू"</string>
     <string name="app_launcher_stop_app_action" msgid="1081680842410279692">"एपलाई रोक्नुहोस्"</string>
-    <string name="app_launcher_app_info_action" msgid="6388181061002827660">"एपसम्बन्धी जानकारी"</string>
     <string name="app_launcher_stop_app_success_toast_text" msgid="2117271772351005349">"<xliff:g id="APP_NAME">%1$s</xliff:g> रोकिएको छ।"</string>
     <string name="app_launcher_stop_app_dialog_title" msgid="8276889322638001144">"एपलाई रोक्ने हो?"</string>
     <string name="app_launcher_stop_app_dialog_text" msgid="3264059639436254372">"तपाईंले कुनै एपलाई जबरजस्ती रोक्नुभयो भने त्यसले सही तरिकाले काम नगर्न सक्छ।"</string>
diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml
index debc3b1..62886da 100644
--- a/res/values-nl/strings.xml
+++ b/res/values-nl/strings.xml
@@ -23,7 +23,6 @@
     <string name="app_launcher_title_all_apps" msgid="3215107396036838217">"Alle apps"</string>
     <string name="app_launcher_title_media_only" msgid="4400886555907422816">"Media-apps"</string>
     <string name="app_launcher_stop_app_action" msgid="1081680842410279692">"App stoppen"</string>
-    <string name="app_launcher_app_info_action" msgid="6388181061002827660">"App-info"</string>
     <string name="app_launcher_stop_app_success_toast_text" msgid="2117271772351005349">"<xliff:g id="APP_NAME">%1$s</xliff:g> is gestopt."</string>
     <string name="app_launcher_stop_app_dialog_title" msgid="8276889322638001144">"App stoppen?"</string>
     <string name="app_launcher_stop_app_dialog_text" msgid="3264059639436254372">"Als je een app gedwongen stopt, kan deze onverwacht gedrag vertonen."</string>
diff --git a/res/values-or/strings.xml b/res/values-or/strings.xml
index 226ce1b..b4d85e0 100644
--- a/res/values-or/strings.xml
+++ b/res/values-or/strings.xml
@@ -23,7 +23,6 @@
     <string name="app_launcher_title_all_apps" msgid="3215107396036838217">"ସମସ୍ତ ଆପ୍"</string>
     <string name="app_launcher_title_media_only" msgid="4400886555907422816">"ମିଡିଆ ଆପ୍ସ"</string>
     <string name="app_launcher_stop_app_action" msgid="1081680842410279692">"ଆପକୁ ବନ୍ଦ କରନ୍ତୁ"</string>
-    <string name="app_launcher_app_info_action" msgid="6388181061002827660">"ଆପ ସୂଚନା"</string>
     <string name="app_launcher_stop_app_success_toast_text" msgid="2117271772351005349">"<xliff:g id="APP_NAME">%1$s</xliff:g>କୁ ବନ୍ଦ କରାଯାଇଛି।"</string>
     <string name="app_launcher_stop_app_dialog_title" msgid="8276889322638001144">"ଆପକୁ ବନ୍ଦ କରିବେ?"</string>
     <string name="app_launcher_stop_app_dialog_text" msgid="3264059639436254372">"ଯଦି ଆପଣ ଏକ ଆପକୁ ବାଧ୍ୟତାର ସହ ବନ୍ଦ କରନ୍ତି, ତେବେ ଏହା ଠିକ୍ ଭାବେ କାମ ନକରିପାରେ।"</string>
diff --git a/res/values-pa/strings.xml b/res/values-pa/strings.xml
index fbde6b5..bb2e14d 100644
--- a/res/values-pa/strings.xml
+++ b/res/values-pa/strings.xml
@@ -23,7 +23,6 @@
     <string name="app_launcher_title_all_apps" msgid="3215107396036838217">"ਸਾਰੀਆਂ ਐਪਾਂ"</string>
     <string name="app_launcher_title_media_only" msgid="4400886555907422816">"ਮੀਡੀਆ ਐਪਾਂ"</string>
     <string name="app_launcher_stop_app_action" msgid="1081680842410279692">"ਐਪ ਬੰਦ ਕਰੋ"</string>
-    <string name="app_launcher_app_info_action" msgid="6388181061002827660">"ਐਪ ਜਾਣਕਾਰੀ"</string>
     <string name="app_launcher_stop_app_success_toast_text" msgid="2117271772351005349">"<xliff:g id="APP_NAME">%1$s</xliff:g> ਬੰਦ ਕਰ ਦਿੱਤੀ ਗਈ ਹੈ।"</string>
     <string name="app_launcher_stop_app_dialog_title" msgid="8276889322638001144">"ਕੀ ਐਪ ਬੰਦ ਕਰਨੀ ਹੈ?"</string>
     <string name="app_launcher_stop_app_dialog_text" msgid="3264059639436254372">"ਜੇ ਤੁਸੀਂ ਕਿਸੇ ਐਪ ਨੂੰ ਜ਼ਬਰਦਸਤੀ ਬੰਦ ਕਰਦੇ ਹੋ, ਤਾਂ ਹੋ ਸਕਦਾ ਹੈ ਇਹ ਠੀਕ ਤਰ੍ਹਾਂ ਕੰਮ ਨਾ ਕਰੇ।"</string>
diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml
index 6fd33e9..d596e81 100644
--- a/res/values-pl/strings.xml
+++ b/res/values-pl/strings.xml
@@ -23,7 +23,6 @@
     <string name="app_launcher_title_all_apps" msgid="3215107396036838217">"Wszystkie aplikacje"</string>
     <string name="app_launcher_title_media_only" msgid="4400886555907422816">"Aplikacje multimedialne"</string>
     <string name="app_launcher_stop_app_action" msgid="1081680842410279692">"Zatrzymaj aplikację"</string>
-    <string name="app_launcher_app_info_action" msgid="6388181061002827660">"Informacje o aplikacji"</string>
     <string name="app_launcher_stop_app_success_toast_text" msgid="2117271772351005349">"Aplikacja <xliff:g id="APP_NAME">%1$s</xliff:g> została zatrzymana."</string>
     <string name="app_launcher_stop_app_dialog_title" msgid="8276889322638001144">"Zatrzymać aplikację?"</string>
     <string name="app_launcher_stop_app_dialog_text" msgid="3264059639436254372">"Jeśli wymusisz zatrzymanie aplikacji, może ona zadziałać nieprawidłowo."</string>
diff --git a/res/values-pt-rPT/strings.xml b/res/values-pt-rPT/strings.xml
index 9cbe2db..a704184 100644
--- a/res/values-pt-rPT/strings.xml
+++ b/res/values-pt-rPT/strings.xml
@@ -23,7 +23,6 @@
     <string name="app_launcher_title_all_apps" msgid="3215107396036838217">"Todas as apps"</string>
     <string name="app_launcher_title_media_only" msgid="4400886555907422816">"Apps de multimédia"</string>
     <string name="app_launcher_stop_app_action" msgid="1081680842410279692">"Parar a app"</string>
-    <string name="app_launcher_app_info_action" msgid="6388181061002827660">"Informações da app"</string>
     <string name="app_launcher_stop_app_success_toast_text" msgid="2117271772351005349">"A app <xliff:g id="APP_NAME">%1$s</xliff:g> foi parada."</string>
     <string name="app_launcher_stop_app_dialog_title" msgid="8276889322638001144">"Parar a app?"</string>
     <string name="app_launcher_stop_app_dialog_text" msgid="3264059639436254372">"Se forçar a paragem de uma app, esta pode apresentar um comportamento anormal."</string>
diff --git a/res/values-pt/strings.xml b/res/values-pt/strings.xml
index 5704ce3..606d31e 100644
--- a/res/values-pt/strings.xml
+++ b/res/values-pt/strings.xml
@@ -23,7 +23,6 @@
     <string name="app_launcher_title_all_apps" msgid="3215107396036838217">"Todos os apps"</string>
     <string name="app_launcher_title_media_only" msgid="4400886555907422816">"Apps de mídia"</string>
     <string name="app_launcher_stop_app_action" msgid="1081680842410279692">"Parar o app"</string>
-    <string name="app_launcher_app_info_action" msgid="6388181061002827660">"Informações do app"</string>
     <string name="app_launcher_stop_app_success_toast_text" msgid="2117271772351005349">"O app <xliff:g id="APP_NAME">%1$s</xliff:g> foi parado."</string>
     <string name="app_launcher_stop_app_dialog_title" msgid="8276889322638001144">"Parar o app?"</string>
     <string name="app_launcher_stop_app_dialog_text" msgid="3264059639436254372">"Se você forçar o fechamento de um app, ele pode apresentar mau funcionamento."</string>
@@ -45,6 +44,6 @@
     <string name="show_debug_apps" msgid="4521073121286325786">"Mostrar apps de depuração"</string>
     <string name="times_separator" msgid="6180368193321715816">"/"</string>
     <string name="recents_empty_state_text" msgid="6703106170272215731">"Nenhum item recente"</string>
-    <string name="recents_clear_all_text" msgid="2088678071417053923">"Remover tudo"</string>
+    <string name="recents_clear_all_text" msgid="2088678071417053923">"Limpar tudo"</string>
     <string name="failure_opening_recent_task_message" msgid="9097595793150874357">"O app não está disponível"</string>
 </resources>
diff --git a/res/values-ro/strings.xml b/res/values-ro/strings.xml
index 8f5856c..2d2fd39 100644
--- a/res/values-ro/strings.xml
+++ b/res/values-ro/strings.xml
@@ -23,7 +23,6 @@
     <string name="app_launcher_title_all_apps" msgid="3215107396036838217">"Toate aplicațiile"</string>
     <string name="app_launcher_title_media_only" msgid="4400886555907422816">"Aplicații media"</string>
     <string name="app_launcher_stop_app_action" msgid="1081680842410279692">"Oprește aplicația"</string>
-    <string name="app_launcher_app_info_action" msgid="6388181061002827660">"Informații despre aplicație"</string>
     <string name="app_launcher_stop_app_success_toast_text" msgid="2117271772351005349">"<xliff:g id="APP_NAME">%1$s</xliff:g> a fost oprită."</string>
     <string name="app_launcher_stop_app_dialog_title" msgid="8276889322638001144">"Oprești aplicația?"</string>
     <string name="app_launcher_stop_app_dialog_text" msgid="3264059639436254372">"Dacă forțezi oprirea unei aplicații, aceasta se poate comporta necorespunzător."</string>
diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml
index 2fa309b..9b43573 100644
--- a/res/values-ru/strings.xml
+++ b/res/values-ru/strings.xml
@@ -23,7 +23,6 @@
     <string name="app_launcher_title_all_apps" msgid="3215107396036838217">"Все приложения"</string>
     <string name="app_launcher_title_media_only" msgid="4400886555907422816">"Мультимедийные приложения"</string>
     <string name="app_launcher_stop_app_action" msgid="1081680842410279692">"Остановить"</string>
-    <string name="app_launcher_app_info_action" msgid="6388181061002827660">"Сведения о приложении"</string>
     <string name="app_launcher_stop_app_success_toast_text" msgid="2117271772351005349">"Приложение \"<xliff:g id="APP_NAME">%1$s</xliff:g>\" остановлено."</string>
     <string name="app_launcher_stop_app_dialog_title" msgid="8276889322638001144">"Остановить приложение?"</string>
     <string name="app_launcher_stop_app_dialog_text" msgid="3264059639436254372">"Принудительное закрытие приложения может отразиться на его функциональности."</string>
diff --git a/res/values-si/strings.xml b/res/values-si/strings.xml
index 1a23879..503eb20 100644
--- a/res/values-si/strings.xml
+++ b/res/values-si/strings.xml
@@ -23,7 +23,6 @@
     <string name="app_launcher_title_all_apps" msgid="3215107396036838217">"සියලු යෙදුම්"</string>
     <string name="app_launcher_title_media_only" msgid="4400886555907422816">"මාධ්‍ය යෙදුම්"</string>
     <string name="app_launcher_stop_app_action" msgid="1081680842410279692">"යෙදුම නවත්වන්න"</string>
-    <string name="app_launcher_app_info_action" msgid="6388181061002827660">"යෙදුම් තතු"</string>
     <string name="app_launcher_stop_app_success_toast_text" msgid="2117271772351005349">"<xliff:g id="APP_NAME">%1$s</xliff:g> නතර කර ඇත."</string>
     <string name="app_launcher_stop_app_dialog_title" msgid="8276889322638001144">"යෙදුම නවත්වන්න ද?"</string>
     <string name="app_launcher_stop_app_dialog_text" msgid="3264059639436254372">"ඔබ යෙදුමක් බලෙන් නැවත වුවහොත්, එය වැරදි ලෙස ක්‍රියා කරනු ඇත."</string>
diff --git a/res/values-sk/strings.xml b/res/values-sk/strings.xml
index 7509410..a1b7285 100644
--- a/res/values-sk/strings.xml
+++ b/res/values-sk/strings.xml
@@ -23,7 +23,6 @@
     <string name="app_launcher_title_all_apps" msgid="3215107396036838217">"Všetky aplikácie"</string>
     <string name="app_launcher_title_media_only" msgid="4400886555907422816">"Mediálne aplikácie"</string>
     <string name="app_launcher_stop_app_action" msgid="1081680842410279692">"Zastaviť aplikáciu"</string>
-    <string name="app_launcher_app_info_action" msgid="6388181061002827660">"Informácie o aplikácii"</string>
     <string name="app_launcher_stop_app_success_toast_text" msgid="2117271772351005349">"Aplikácia <xliff:g id="APP_NAME">%1$s</xliff:g> bola zastavená."</string>
     <string name="app_launcher_stop_app_dialog_title" msgid="8276889322638001144">"Chcete zastaviť aplikáciu?"</string>
     <string name="app_launcher_stop_app_dialog_text" msgid="3264059639436254372">"Ak vynútite zastavenie aplikácie, môže sa správať zvláštne."</string>
diff --git a/res/values-sl/strings.xml b/res/values-sl/strings.xml
index f5be194..7a6fea1 100644
--- a/res/values-sl/strings.xml
+++ b/res/values-sl/strings.xml
@@ -23,7 +23,6 @@
     <string name="app_launcher_title_all_apps" msgid="3215107396036838217">"Vse aplikacije"</string>
     <string name="app_launcher_title_media_only" msgid="4400886555907422816">"Aplikacije za predstavnost"</string>
     <string name="app_launcher_stop_app_action" msgid="1081680842410279692">"Ustavi aplikacijo"</string>
-    <string name="app_launcher_app_info_action" msgid="6388181061002827660">"Podatki o aplikacijah"</string>
     <string name="app_launcher_stop_app_success_toast_text" msgid="2117271772351005349">"Aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> je ustavljena."</string>
     <string name="app_launcher_stop_app_dialog_title" msgid="8276889322638001144">"Želite ustaviti aplikacijo?"</string>
     <string name="app_launcher_stop_app_dialog_text" msgid="3264059639436254372">"Če boste vsilili zaustavitev aplikacije, morda ne bo pravilno delovala."</string>
diff --git a/res/values-sq/strings.xml b/res/values-sq/strings.xml
index 4bc78c8..f03d6fb 100644
--- a/res/values-sq/strings.xml
+++ b/res/values-sq/strings.xml
@@ -23,7 +23,6 @@
     <string name="app_launcher_title_all_apps" msgid="3215107396036838217">"Të gjitha aplikacionet"</string>
     <string name="app_launcher_title_media_only" msgid="4400886555907422816">"Aplikacionet e medias"</string>
     <string name="app_launcher_stop_app_action" msgid="1081680842410279692">"Ndalo aplikacionin"</string>
-    <string name="app_launcher_app_info_action" msgid="6388181061002827660">"Informacione mbi aplikacionin"</string>
     <string name="app_launcher_stop_app_success_toast_text" msgid="2117271772351005349">"<xliff:g id="APP_NAME">%1$s</xliff:g> është ndaluar."</string>
     <string name="app_launcher_stop_app_dialog_title" msgid="8276889322638001144">"Të ndalohet aplikacioni?"</string>
     <string name="app_launcher_stop_app_dialog_text" msgid="3264059639436254372">"Nëse e ndalon me forcë një aplikacion, ai mund të përjetojë çrregullime në funksionim."</string>
diff --git a/res/values-sr/strings.xml b/res/values-sr/strings.xml
index de500cc..dd7ddc2 100644
--- a/res/values-sr/strings.xml
+++ b/res/values-sr/strings.xml
@@ -23,7 +23,6 @@
     <string name="app_launcher_title_all_apps" msgid="3215107396036838217">"Све апликације"</string>
     <string name="app_launcher_title_media_only" msgid="4400886555907422816">"Апликације за медије"</string>
     <string name="app_launcher_stop_app_action" msgid="1081680842410279692">"Заустави апликацију"</string>
-    <string name="app_launcher_app_info_action" msgid="6388181061002827660">"Информације о апликацији"</string>
     <string name="app_launcher_stop_app_success_toast_text" msgid="2117271772351005349">"Апликација <xliff:g id="APP_NAME">%1$s</xliff:g> је заустављена."</string>
     <string name="app_launcher_stop_app_dialog_title" msgid="8276889322638001144">"Заустављате апликацију?"</string>
     <string name="app_launcher_stop_app_dialog_text" msgid="3264059639436254372">"Ако принудно зауставите апликацију, можда ће се понашати неочекивано."</string>
diff --git a/res/values-sv/strings.xml b/res/values-sv/strings.xml
index 5593043..28bbb1c 100644
--- a/res/values-sv/strings.xml
+++ b/res/values-sv/strings.xml
@@ -23,7 +23,6 @@
     <string name="app_launcher_title_all_apps" msgid="3215107396036838217">"Alla appar"</string>
     <string name="app_launcher_title_media_only" msgid="4400886555907422816">"Medieappar"</string>
     <string name="app_launcher_stop_app_action" msgid="1081680842410279692">"Avsluta appen"</string>
-    <string name="app_launcher_app_info_action" msgid="6388181061002827660">"Appinformation"</string>
     <string name="app_launcher_stop_app_success_toast_text" msgid="2117271772351005349">"<xliff:g id="APP_NAME">%1$s</xliff:g> har avslutats."</string>
     <string name="app_launcher_stop_app_dialog_title" msgid="8276889322638001144">"Vill du avsluta appen?"</string>
     <string name="app_launcher_stop_app_dialog_text" msgid="3264059639436254372">"Om du tvingar appen att avsluta kanske den inte fungerar som den ska."</string>
diff --git a/res/values-sw/strings.xml b/res/values-sw/strings.xml
index e29e97c..659b644 100644
--- a/res/values-sw/strings.xml
+++ b/res/values-sw/strings.xml
@@ -23,7 +23,6 @@
     <string name="app_launcher_title_all_apps" msgid="3215107396036838217">"Programu zote"</string>
     <string name="app_launcher_title_media_only" msgid="4400886555907422816">"Programu za muziki"</string>
     <string name="app_launcher_stop_app_action" msgid="1081680842410279692">"Zima programu"</string>
-    <string name="app_launcher_app_info_action" msgid="6388181061002827660">"Maelezo ya programu"</string>
     <string name="app_launcher_stop_app_success_toast_text" msgid="2117271772351005349">"Umezima programu ya <xliff:g id="APP_NAME">%1$s</xliff:g>."</string>
     <string name="app_launcher_stop_app_dialog_title" msgid="8276889322638001144">"Ungependa kuzima programu?"</string>
     <string name="app_launcher_stop_app_dialog_text" msgid="3264059639436254372">"Huenda programu isifanye kazi ipasavyo, iwapo utalazimisha kuizima."</string>
diff --git a/res/values-ta/strings.xml b/res/values-ta/strings.xml
index 5a37855..54d57be 100644
--- a/res/values-ta/strings.xml
+++ b/res/values-ta/strings.xml
@@ -23,7 +23,6 @@
     <string name="app_launcher_title_all_apps" msgid="3215107396036838217">"அனைத்து ஆப்ஸும்"</string>
     <string name="app_launcher_title_media_only" msgid="4400886555907422816">"மீடியா ஆப்ஸ்"</string>
     <string name="app_launcher_stop_app_action" msgid="1081680842410279692">"ஆப்ஸை நிறுத்து"</string>
-    <string name="app_launcher_app_info_action" msgid="6388181061002827660">"ஆப்ஸ் தகவல்கள்"</string>
     <string name="app_launcher_stop_app_success_toast_text" msgid="2117271772351005349">"<xliff:g id="APP_NAME">%1$s</xliff:g> ஆப்ஸ் நிறுத்தப்பட்டது."</string>
     <string name="app_launcher_stop_app_dialog_title" msgid="8276889322638001144">"ஆப்ஸை நிறுத்தவா?"</string>
     <string name="app_launcher_stop_app_dialog_text" msgid="3264059639436254372">"ஆப்ஸை உடனே நிறுத்தினால் அது சரியாகச் செயல்படாமல் போகக்கூடும்."</string>
diff --git a/res/values-te/strings.xml b/res/values-te/strings.xml
index 8dfa4bc..181eb9b 100644
--- a/res/values-te/strings.xml
+++ b/res/values-te/strings.xml
@@ -23,7 +23,6 @@
     <string name="app_launcher_title_all_apps" msgid="3215107396036838217">"అన్ని యాప్‌లు"</string>
     <string name="app_launcher_title_media_only" msgid="4400886555907422816">"మీడియా యాప్‌లు"</string>
     <string name="app_launcher_stop_app_action" msgid="1081680842410279692">"యాప్‌ను ఆపివేయండి"</string>
-    <string name="app_launcher_app_info_action" msgid="6388181061002827660">"యాప్ సమాచారం"</string>
     <string name="app_launcher_stop_app_success_toast_text" msgid="2117271772351005349">"<xliff:g id="APP_NAME">%1$s</xliff:g> ఆపివేయబడింది."</string>
     <string name="app_launcher_stop_app_dialog_title" msgid="8276889322638001144">"యాప్‌ను ఆపివేయాలా?"</string>
     <string name="app_launcher_stop_app_dialog_text" msgid="3264059639436254372">"మీరు యాప్‌ను ఫోర్స్ ఆపివేస్తే, అది సరిగ్గా పని చేయకపోవచ్చు."</string>
diff --git a/res/values-th/strings.xml b/res/values-th/strings.xml
index b9f5cf5..251bc13 100644
--- a/res/values-th/strings.xml
+++ b/res/values-th/strings.xml
@@ -23,7 +23,6 @@
     <string name="app_launcher_title_all_apps" msgid="3215107396036838217">"แอปทั้งหมด"</string>
     <string name="app_launcher_title_media_only" msgid="4400886555907422816">"แอปสื่อ"</string>
     <string name="app_launcher_stop_app_action" msgid="1081680842410279692">"หยุดแอป"</string>
-    <string name="app_launcher_app_info_action" msgid="6388181061002827660">"ข้อมูลแอป"</string>
     <string name="app_launcher_stop_app_success_toast_text" msgid="2117271772351005349">"หยุด <xliff:g id="APP_NAME">%1$s</xliff:g> แล้ว"</string>
     <string name="app_launcher_stop_app_dialog_title" msgid="8276889322638001144">"หยุดแอปไหม"</string>
     <string name="app_launcher_stop_app_dialog_text" msgid="3264059639436254372">"หากคุณบังคับปิดแอปพลิเคชัน อาจทำให้การทำงานผิดพลาดได้"</string>
diff --git a/res/values-tl/strings.xml b/res/values-tl/strings.xml
index e8ac644..a77a06a 100644
--- a/res/values-tl/strings.xml
+++ b/res/values-tl/strings.xml
@@ -23,7 +23,6 @@
     <string name="app_launcher_title_all_apps" msgid="3215107396036838217">"Lahat ng app"</string>
     <string name="app_launcher_title_media_only" msgid="4400886555907422816">"Mga media app"</string>
     <string name="app_launcher_stop_app_action" msgid="1081680842410279692">"Ihinto ang app"</string>
-    <string name="app_launcher_app_info_action" msgid="6388181061002827660">"Impormasyon ng app"</string>
     <string name="app_launcher_stop_app_success_toast_text" msgid="2117271772351005349">"Ihininto ang <xliff:g id="APP_NAME">%1$s</xliff:g>."</string>
     <string name="app_launcher_stop_app_dialog_title" msgid="8276889322638001144">"Ihinto ang app?"</string>
     <string name="app_launcher_stop_app_dialog_text" msgid="3264059639436254372">"Kung sapilitan mong ihihinto ang isang app, posible itong kumilos nang hindi tama."</string>
diff --git a/res/values-tr/strings.xml b/res/values-tr/strings.xml
index 063f8da..0d9965a 100644
--- a/res/values-tr/strings.xml
+++ b/res/values-tr/strings.xml
@@ -23,7 +23,6 @@
     <string name="app_launcher_title_all_apps" msgid="3215107396036838217">"Tüm uygulamalar"</string>
     <string name="app_launcher_title_media_only" msgid="4400886555907422816">"Medya uygulamaları"</string>
     <string name="app_launcher_stop_app_action" msgid="1081680842410279692">"Uygulamayı durdur"</string>
-    <string name="app_launcher_app_info_action" msgid="6388181061002827660">"Uygulama bilgileri"</string>
     <string name="app_launcher_stop_app_success_toast_text" msgid="2117271772351005349">"<xliff:g id="APP_NAME">%1$s</xliff:g> durduruldu."</string>
     <string name="app_launcher_stop_app_dialog_title" msgid="8276889322638001144">"Uygulama durdurulsun mu?"</string>
     <string name="app_launcher_stop_app_dialog_text" msgid="3264059639436254372">"Uygulamayı zorla durdurursanız hatalı davranabilir."</string>
diff --git a/res/values-uk/strings.xml b/res/values-uk/strings.xml
index 65deb84..f5c67fd 100644
--- a/res/values-uk/strings.xml
+++ b/res/values-uk/strings.xml
@@ -23,7 +23,6 @@
     <string name="app_launcher_title_all_apps" msgid="3215107396036838217">"Усі додатки"</string>
     <string name="app_launcher_title_media_only" msgid="4400886555907422816">"Мультимедійні додатки"</string>
     <string name="app_launcher_stop_app_action" msgid="1081680842410279692">"Припинити роботу додатка"</string>
-    <string name="app_launcher_app_info_action" msgid="6388181061002827660">"Про додаток"</string>
     <string name="app_launcher_stop_app_success_toast_text" msgid="2117271772351005349">"Роботу додатка <xliff:g id="APP_NAME">%1$s</xliff:g> припинено."</string>
     <string name="app_launcher_stop_app_dialog_title" msgid="8276889322638001144">"Припинити роботу додатка?"</string>
     <string name="app_launcher_stop_app_dialog_text" msgid="3264059639436254372">"Примусове вимкнення додатка може призвести до збою в його роботі."</string>
diff --git a/res/values-ur/strings.xml b/res/values-ur/strings.xml
index cc609a1..cc8aa77 100644
--- a/res/values-ur/strings.xml
+++ b/res/values-ur/strings.xml
@@ -23,7 +23,6 @@
     <string name="app_launcher_title_all_apps" msgid="3215107396036838217">"سبھی ایپس"</string>
     <string name="app_launcher_title_media_only" msgid="4400886555907422816">"میڈیا ایپس"</string>
     <string name="app_launcher_stop_app_action" msgid="1081680842410279692">"ایپ بند کریں"</string>
-    <string name="app_launcher_app_info_action" msgid="6388181061002827660">"ایپ کی معلومات"</string>
     <string name="app_launcher_stop_app_success_toast_text" msgid="2117271772351005349">"<xliff:g id="APP_NAME">%1$s</xliff:g> کو بند کر دیا گیا ہے۔"</string>
     <string name="app_launcher_stop_app_dialog_title" msgid="8276889322638001144">"ایپ بند کریں؟"</string>
     <string name="app_launcher_stop_app_dialog_text" msgid="3264059639436254372">"اگر آپ کسی ایپ کو زبردستی روک دیتے ہیں تو یہ غلط برتاؤ کر سکتی ہے۔"</string>
diff --git a/res/values-uz/strings.xml b/res/values-uz/strings.xml
index b805f71..2ea50ba 100644
--- a/res/values-uz/strings.xml
+++ b/res/values-uz/strings.xml
@@ -23,7 +23,6 @@
     <string name="app_launcher_title_all_apps" msgid="3215107396036838217">"Barcha ilovalar"</string>
     <string name="app_launcher_title_media_only" msgid="4400886555907422816">"Media ilovalari"</string>
     <string name="app_launcher_stop_app_action" msgid="1081680842410279692">"Ilovani toʻxtatish"</string>
-    <string name="app_launcher_app_info_action" msgid="6388181061002827660">"Ilova haqida"</string>
     <string name="app_launcher_stop_app_success_toast_text" msgid="2117271772351005349">"<xliff:g id="APP_NAME">%1$s</xliff:g> ilovasi toʻxtatildi."</string>
     <string name="app_launcher_stop_app_dialog_title" msgid="8276889322638001144">"Ilova toʻxtatilsinmi?"</string>
     <string name="app_launcher_stop_app_dialog_text" msgid="3264059639436254372">"Ilovani majburan toʻxtatish uning ishlashiga taʼsir koʻrsatishi mumkin."</string>
diff --git a/res/values-vi/strings.xml b/res/values-vi/strings.xml
index e8b31ac..d500b30 100644
--- a/res/values-vi/strings.xml
+++ b/res/values-vi/strings.xml
@@ -23,7 +23,6 @@
     <string name="app_launcher_title_all_apps" msgid="3215107396036838217">"Tất cả ứng dụng"</string>
     <string name="app_launcher_title_media_only" msgid="4400886555907422816">"Ứng dụng đa phương tiện"</string>
     <string name="app_launcher_stop_app_action" msgid="1081680842410279692">"Dừng ứng dụng"</string>
-    <string name="app_launcher_app_info_action" msgid="6388181061002827660">"Thông tin ứng dụng"</string>
     <string name="app_launcher_stop_app_success_toast_text" msgid="2117271772351005349">"<xliff:g id="APP_NAME">%1$s</xliff:g> đã bị dừng."</string>
     <string name="app_launcher_stop_app_dialog_title" msgid="8276889322638001144">"Dừng ứng dụng?"</string>
     <string name="app_launcher_stop_app_dialog_text" msgid="3264059639436254372">"Nếu bạn buộc một ứng dụng dừng lại, ứng dụng đó có thể hoạt động không đúng cách."</string>
diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml
index 10c72c6..1ccee54 100644
--- a/res/values-zh-rCN/strings.xml
+++ b/res/values-zh-rCN/strings.xml
@@ -23,7 +23,6 @@
     <string name="app_launcher_title_all_apps" msgid="3215107396036838217">"所有应用"</string>
     <string name="app_launcher_title_media_only" msgid="4400886555907422816">"媒体应用"</string>
     <string name="app_launcher_stop_app_action" msgid="1081680842410279692">"停止应用"</string>
-    <string name="app_launcher_app_info_action" msgid="6388181061002827660">"应用信息"</string>
     <string name="app_launcher_stop_app_success_toast_text" msgid="2117271772351005349">"已停止“<xliff:g id="APP_NAME">%1$s</xliff:g>”。"</string>
     <string name="app_launcher_stop_app_dialog_title" msgid="8276889322638001144">"要停止应用吗?"</string>
     <string name="app_launcher_stop_app_dialog_text" msgid="3264059639436254372">"强行停止某个应用可能会导致其出现异常。"</string>
diff --git a/res/values-zh-rHK/strings.xml b/res/values-zh-rHK/strings.xml
index 3c1d91c..1db6cab 100644
--- a/res/values-zh-rHK/strings.xml
+++ b/res/values-zh-rHK/strings.xml
@@ -19,11 +19,10 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_title" msgid="1995858460392177468">"Car Launcher"</string>
     <string name="reset_appgrid_title" msgid="814562055108028366">"將應用程式網格重設為按字母排序"</string>
-    <string name="reset_appgrid_dialogue_message" msgid="8275758912490987735">"此功能會移除所有自訂順序。你要繼續嗎?"</string>
+    <string name="reset_appgrid_dialogue_message" msgid="8275758912490987735">"此功能會移除所有自訂順序。您要繼續嗎?"</string>
     <string name="app_launcher_title_all_apps" msgid="3215107396036838217">"所有應用程式"</string>
     <string name="app_launcher_title_media_only" msgid="4400886555907422816">"媒體應用程式"</string>
     <string name="app_launcher_stop_app_action" msgid="1081680842410279692">"停止應用程式"</string>
-    <string name="app_launcher_app_info_action" msgid="6388181061002827660">"應用程式資料"</string>
     <string name="app_launcher_stop_app_success_toast_text" msgid="2117271772351005349">"已停止「<xliff:g id="APP_NAME">%1$s</xliff:g>」。"</string>
     <string name="app_launcher_stop_app_dialog_title" msgid="8276889322638001144">"要停止應用程式嗎?"</string>
     <string name="app_launcher_stop_app_dialog_text" msgid="3264059639436254372">"強制停止應用程式,可能會導致操作不正常。"</string>
diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml
index e9fffbd..48ba2ba 100644
--- a/res/values-zh-rTW/strings.xml
+++ b/res/values-zh-rTW/strings.xml
@@ -23,7 +23,6 @@
     <string name="app_launcher_title_all_apps" msgid="3215107396036838217">"所有應用程式"</string>
     <string name="app_launcher_title_media_only" msgid="4400886555907422816">"媒體應用程式"</string>
     <string name="app_launcher_stop_app_action" msgid="1081680842410279692">"停止應用程式"</string>
-    <string name="app_launcher_app_info_action" msgid="6388181061002827660">"應用程式資訊"</string>
     <string name="app_launcher_stop_app_success_toast_text" msgid="2117271772351005349">"「<xliff:g id="APP_NAME">%1$s</xliff:g>」已停止。"</string>
     <string name="app_launcher_stop_app_dialog_title" msgid="8276889322638001144">"要停止應用程式嗎?"</string>
     <string name="app_launcher_stop_app_dialog_text" msgid="3264059639436254372">"如果你強制停止應用程式,應用程式可能出現異常行為。"</string>
diff --git a/res/values-zu/strings.xml b/res/values-zu/strings.xml
index 135c2aa..75c90c0 100644
--- a/res/values-zu/strings.xml
+++ b/res/values-zu/strings.xml
@@ -23,7 +23,6 @@
     <string name="app_launcher_title_all_apps" msgid="3215107396036838217">"Zonke izinhlelo zokusebenza"</string>
     <string name="app_launcher_title_media_only" msgid="4400886555907422816">"Izinhlelo zokusebenza zemidiya"</string>
     <string name="app_launcher_stop_app_action" msgid="1081680842410279692">"Misa i-app"</string>
-    <string name="app_launcher_app_info_action" msgid="6388181061002827660">"Ulwazi lwe-app"</string>
     <string name="app_launcher_stop_app_success_toast_text" msgid="2117271772351005349">"I-<xliff:g id="APP_NAME">%1$s</xliff:g> imisiwe."</string>
     <string name="app_launcher_stop_app_dialog_title" msgid="8276889322638001144">"Misa i-app?"</string>
     <string name="app_launcher_stop_app_dialog_text" msgid="3264059639436254372">"Uma uphoqelela ukumisa i-app, kungenzeka ukuthi ingasebenzi kahle."</string>
diff --git a/res/values/config.xml b/res/values/config.xml
index 6cb13e7..f1e843a 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -18,8 +18,8 @@
 <resources>
     <!-- A list of package names that provide the cards to display on the home screen -->
     <string-array name="config_homeCardModuleClasses" translatable="false">
-        <item>com.android.car.carlauncher.homescreen.assistive.AssistiveCard</item>
-        <item>com.android.car.carlauncher.homescreen.audio.AudioCard</item>
+        <item>com.android.car.carlauncher.homescreen.assistive.AssistiveCardModule</item>
+        <item>com.android.car.carlauncher.homescreen.audio.AudioCardModule</item>
     </string-array>
 
     <!--
@@ -74,8 +74,6 @@
     <integer name="config_msg_send_mirroring_pkg_code">3</integer>
     <string name="config_msg_mirroring_pkg_name_key" translatable="false">msg_mirroring_pkg_name_key</string>
     <string name="config_msg_mirroring_redirect_uri_key" translatable="false">msg_mirroring_redirect_uri_key</string>
-    <!-- Number of columns in recents that should be included in a page when snapping -->
-    <integer name="config_recents_columns_per_page">2</integer>
 
     <!--
         The Activity to use as a passenger Launcher, if empty, it assumes CarLauncher can do
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 2adce32..754efaa 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -134,6 +134,6 @@
     <!--  task width can be calculated using the following formula:
       available height of thumbnail * width of screen / height of screen between the insets
       This will allow most task thumbnails to be displayed without cropping. -->
-    <dimen name="recent_task_width_first">894dp</dimen>
-    <dimen name="recent_task_width">359dp</dimen>
+    <dimen name="recent_task_width_first">1036dp</dimen>
+    <dimen name="recent_task_width">432dp</dimen>
 </resources>
diff --git a/res/values/overlayable.xml b/res/values/overlayable.xml
index df49690..3c87167 100644
--- a/res/values/overlayable.xml
+++ b/res/values/overlayable.xml
@@ -147,7 +147,6 @@
       <item type="drawable" name="page_indicator_bar"/>
       <item type="drawable" name="recent_clear_all_button_background"/>
       <item type="drawable" name="recent_dismiss_button_background"/>
-      <item type="drawable" name="recent_task_hidden_icon"/>
       <item type="id" name="app_icon"/>
       <item type="id" name="app_item"/>
       <item type="id" name="app_name"/>
@@ -166,8 +165,6 @@
       <item type="id" name="card_icon"/>
       <item type="id" name="card_name"/>
       <item type="id" name="card_view"/>
-      <item type="id" name="clear_all_button"/>
-      <item type="id" name="clear_all_button_focus_area"/>
       <item type="id" name="descriptive_text_layout"/>
       <item type="id" name="descriptive_text_with_controls_layout"/>
       <item type="id" name="divider"/>
@@ -193,6 +190,7 @@
       <item type="id" name="recent_tasks_group"/>
       <item type="id" name="recent_tasks_list"/>
       <item type="id" name="recent_tasks_list_focus_area"/>
+      <item type="id" name="recents_clear_all_button"/>
       <item type="id" name="secondary_text"/>
       <item type="id" name="start_edge"/>
       <item type="id" name="tap_for_more_text"/>
@@ -211,7 +209,6 @@
       <item type="integer" name="config_msg_register_mirroring_pkg_code"/>
       <item type="integer" name="config_msg_send_mirroring_pkg_code"/>
       <item type="integer" name="config_msg_unregister_mirroring_pkg_code"/>
-      <item type="integer" name="config_recents_columns_per_page"/>
       <item type="integer" name="ms_background_highlight_duration"/>
       <item type="integer" name="ms_drop_animation_delay"/>
       <item type="integer" name="ms_long_press_animation_duration"/>
@@ -237,9 +234,9 @@
       <item type="layout" name="descriptive_text"/>
       <item type="layout" name="optional_seek_bar_with_times"/>
       <item type="layout" name="recent_apps_row"/>
+      <item type="layout" name="recent_clear_all_view"/>
       <item type="layout" name="recent_task_view"/>
       <item type="layout" name="recent_task_view_first"/>
-      <item type="layout" name="recent_task_view_hidden"/>
       <item type="layout" name="recent_task_view_inner"/>
       <item type="layout" name="recents_activity"/>
       <item type="layout" name="tap_for_more_text"/>
@@ -284,7 +281,6 @@
       <item type="style" name="AppDisplayNameStyle"/>
       <item type="style" name="CardViewStyle"/>
       <item type="style" name="ClearAllRecentTasksButton"/>
-      <item type="style" name="ClearAllRecentTasksButtonFocusArea"/>
       <item type="style" name="ContextualSpace"/>
       <item type="style" name="HiddenRecentTaskThumbnail"/>
       <item type="style" name="HorizontalLineDivider"/>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 5fbd44a..fb4ef17 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -82,7 +82,6 @@
         <item>com.google.android.embedded.projection</item>
         <item>com.android.car.bugreport</item>
         <item>com.android.car.trust</item>
-        <item>com.android.car.datacenter</item>
         <item>com.google.android.car.diagnosticrecorder</item>
         <item>com.google.android.car.diskwriteapp</item>
         <item>com.android.documentsui</item>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index fadc1e4..73c8ae4 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -53,20 +53,14 @@
         <item name="android:layout_width">match_parent</item>
         <item name="android:layout_height">match_parent</item>
         <item name="android:paddingTop">37dp</item>
-        <item name="android:paddingBottom">32dp</item>
+        <item name="android:paddingBottom">51dp</item>
         <item name="android:scrollbars">none</item>
         <item name="android:clipToPadding">false</item>
     </style>
 
-    <style name="ClearAllRecentTasksButtonFocusArea">
-        <item name="android:layout_width">wrap_content</item>
-        <item name="android:layout_height">wrap_content</item>
-    </style>
-
     <style name="ClearAllRecentTasksButton">
         <item name="android:layout_width">wrap_content</item>
         <item name="android:layout_height">wrap_content</item>
-        <item name="android:layout_marginBottom">25dp</item>
         <item name="android:paddingVertical">20dp</item>
         <item name="android:paddingHorizontal">48dp</item>
         <item name="android:textSize">24sp</item>
diff --git a/src/com/android/car/carlauncher/AppGridActivity.java b/src/com/android/car/carlauncher/AppGridActivity.java
index b8f2b32..18c814d 100644
--- a/src/com/android/car/carlauncher/AppGridActivity.java
+++ b/src/com/android/car/carlauncher/AppGridActivity.java
@@ -162,7 +162,7 @@
         CONTROL_BAR, DEFAULT, FULL
     }
 
-    private enum Mode {
+    public enum Mode {
         ALL_APPS(R.string.app_launcher_title_all_apps,
                 APP_TYPE_LAUNCHABLES + APP_TYPE_MEDIA_SERVICES,
                 true),
@@ -202,6 +202,7 @@
                             .isRequiresDistractionOptimization();
                 }
                 mAdapter.setIsDistractionOptimizationRequired(isDistractionOptimizationRequired);
+                mAdapter.setMode(mMode);
                 // set listener to update the app grid components and apply interaction restrictions
                 // when driving state changes
                 mCarUxRestrictionsManager.registerListener(restrictionInfo -> {
@@ -294,8 +295,6 @@
                         mAdapter.setLauncherItems(launcherItems);
                         mNextScrollDestination = mSnapCallback.getSnapPosition();
                         updateScrollState();
-                        //TODO: b/275079533 Disable drag and drop ability in AppGrid with
-                        // only Media apps
                         if (mMode == Mode.ALL_APPS) {
                             mLauncherModel.maybeSaveAppsOrder();
                         }
diff --git a/src/com/android/car/carlauncher/AppGridPageSnapper.java b/src/com/android/car/carlauncher/AppGridPageSnapper.java
index fc19445..1b2bee2 100644
--- a/src/com/android/car/carlauncher/AppGridPageSnapper.java
+++ b/src/com/android/car/carlauncher/AppGridPageSnapper.java
@@ -84,19 +84,19 @@
 
         View currentPosView = getFirstMostVisibleChild(orientationHelper);
         int adapterPos = findAdapterPosition(currentPosView);
-        int posToReturn = mPrevFirstVisiblePos;
+        int posToReturn;
 
         // In the case of swiping left, the current adapter position is smaller than the previous
         // first visible position. In the case of swiping right, the current adapter position is
         // greater than the previous first visible position. In this case, if the swipe is
         // by only 1 column, the page should remain the same since we want to demonstrate some
         // stickiness
-        if (adapterPos < mPrevFirstVisiblePos) {
-            posToReturn = findFirstItemOnPrevPage(adapterPos);
-        } else if (adapterPos > mPrevFirstVisiblePos
-                && (float) adapterPos % mBlockSize / mBlockSize >= mPageSnapThreshold) {
+        if (adapterPos <= mPrevFirstVisiblePos
+                || (float) adapterPos % mBlockSize / mBlockSize < mPageSnapThreshold) {
+            posToReturn = adapterPos - adapterPos % mBlockSize;
+        } else {
             // Snap to next page
-            posToReturn = findFirstItemOnNextPage(adapterPos);
+            posToReturn = (adapterPos / mBlockSize + 1) * mBlockSize + mBlockSize - 1;
         }
         handleScrollToPos(posToReturn, orientationHelper);
         return null;
@@ -231,8 +231,7 @@
             return;
         }
 
-        // Disable the current fling behavior. When a fling happens, try to find the target
-        // snap view and go there.
+        // When a fling happens, try to find the target snap view and go there.
         mOnFlingListener = new RecyclerView.OnFlingListener() {
             @Override
             public boolean onFling(int velocityX, int velocityY) {
diff --git a/src/com/android/car/carlauncher/CarLauncher.java b/src/com/android/car/carlauncher/CarLauncher.java
index 2f2b4cd..2a50ef4 100644
--- a/src/com/android/car/carlauncher/CarLauncher.java
+++ b/src/com/android/car/carlauncher/CarLauncher.java
@@ -83,6 +83,7 @@
     private boolean mIsReadyLogged;
     private boolean mUseSmallCanvasOptimizedMap;
     private boolean mUseRemoteCarTaskView;
+    private ViewGroup mMapsCard;
 
     private final TaskStackListener mTaskStackListener = new TaskStackListener() {
         @Override
@@ -109,7 +110,8 @@
     @VisibleForTesting
     void setCarUserManager(CarUserManager carUserManager) {
         if (mTaskViewManager == null) {
-            Log.w(TAG, "Task view manager is null, cannot set CarUserManager");
+            Log.w(TAG, "Task view manager is null, cannot set CarUserManager on taskview "
+                    + "manager");
             return;
         }
         mTaskViewManager.setCarUserManager(carUserManager);
@@ -143,6 +145,9 @@
                             "Invalid passengerLauncher name=" + passengerLauncherName);
                 }
                 passengerHomeIntent = new Intent(Intent.ACTION_MAIN)
+                        // passenger launcher should be launched in home task in order to
+                        // fix TaskView layering issue
+                        .addCategory(Intent.CATEGORY_HOME)
                         .setComponent(component);
             } else {
                 // No passenger launcher is specified, then use AppsGrid as a fallback.
@@ -179,12 +184,12 @@
             setContentView(R.layout.car_launcher);
             // We don't want to show Map card unnecessarily for the headless user 0.
             if (!UserHelperLite.isHeadlessSystemUser(getUserId())) {
-                ViewGroup mapsCard = findViewById(R.id.maps_card);
-                if (mapsCard != null) {
+                mMapsCard = findViewById(R.id.maps_card);
+                if (mMapsCard != null) {
                     if (mUseRemoteCarTaskView) {
-                        setupRemoteCarTaskView(mapsCard);
+                        setupRemoteCarTaskView(mMapsCard);
                     } else {
-                        setUpTaskView(mapsCard);
+                        setUpTaskView(mMapsCard);
                     }
                 }
             }
@@ -228,6 +233,12 @@
                                                 public void onTaskViewInitialized() {
                                                     maybeLogReady();
                                                 }
+
+                                                @Override
+                                                public void onTaskViewReleased() {
+                                                    mRemoteCarTaskView = null;
+                                                    parent.removeAllViews();
+                                                }
                                             });
                                 }
 
@@ -235,6 +246,7 @@
                                 public void onDisconnected(
                                         CarTaskViewController carTaskViewController) {
                                     Log.d(TAG, "onDisconnected");
+                                    mRemoteCarTaskView = null;
                                     parent.removeAllViews();
                                 }
                             });
@@ -273,6 +285,17 @@
     }
 
     @Override
+    protected void onStart() {
+        super.onStart();
+        // The TaskViewManager might have been released if the user was switched to some other user
+        // and then switched back to the previous user before the previous user is stopped.
+        // In such a case, the TaskViewManager should be recreated.
+        if (!mUseRemoteCarTaskView && mMapsCard != null && mTaskViewManager.isReleased()) {
+            setUpTaskView(mMapsCard);
+        }
+    }
+
+    @Override
     protected void onResume() {
         super.onResume();
         maybeLogReady();
@@ -336,7 +359,7 @@
         }
         FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
         for (HomeCardModule cardModule : mHomeCardModules) {
-            transaction.replace(cardModule.getCardResId(), cardModule.getCardView());
+            transaction.replace(cardModule.getCardResId(), cardModule.getCardView().getFragment());
         }
         transaction.commitNow();
     }
diff --git a/src/com/android/car/carlauncher/CarTaskView.java b/src/com/android/car/carlauncher/CarTaskView.java
index 8b9e960..f5cf9a6 100644
--- a/src/com/android/car/carlauncher/CarTaskView.java
+++ b/src/com/android/car/carlauncher/CarTaskView.java
@@ -58,15 +58,17 @@
     private TaskViewTaskController mTaskViewTaskController;
 
     public CarTaskView(Context context, ShellTaskOrganizer organizer,
-            TaskViewTransitions taskViewTransitions, SyncTransactionQueue syncQueue) {
-        this(context, syncQueue,
+            TaskViewTransitions taskViewTransitions, SyncTransactionQueue syncQueue,
+            boolean shouldHideTask) {
+        this(context, syncQueue, shouldHideTask,
                 new TaskViewTaskController(context, organizer, taskViewTransitions, syncQueue));
     }
 
-    public CarTaskView(Context context, SyncTransactionQueue syncQueue,
+    public CarTaskView(Context context, SyncTransactionQueue syncQueue, boolean shouldHideTask,
             TaskViewTaskController taskViewTaskController) {
         super(context, taskViewTaskController);
         mTaskViewTaskController = taskViewTaskController;
+        mTaskViewTaskController.setHideTaskWithSurface(shouldHideTask);
         mSyncQueue = syncQueue;
     }
 
diff --git a/src/com/android/car/carlauncher/ControlBarActivity.java b/src/com/android/car/carlauncher/ControlBarActivity.java
index 0a9e4d8..7394d7f 100644
--- a/src/com/android/car/carlauncher/ControlBarActivity.java
+++ b/src/com/android/car/carlauncher/ControlBarActivity.java
@@ -84,7 +84,7 @@
         }
         FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
         for (HomeCardModule cardModule : mHomeCardModules) {
-            transaction.replace(cardModule.getCardResId(), cardModule.getCardView());
+            transaction.replace(cardModule.getCardResId(), cardModule.getCardView().getFragment());
         }
         transaction.commitNow();
     }
diff --git a/src/com/android/car/carlauncher/ControlledCarTaskView.java b/src/com/android/car/carlauncher/ControlledCarTaskView.java
index 645c6e6..b2b0531 100644
--- a/src/com/android/car/carlauncher/ControlledCarTaskView.java
+++ b/src/com/android/car/carlauncher/ControlledCarTaskView.java
@@ -67,7 +67,7 @@
             ControlledCarTaskViewCallbacks callbacks,
             UserManager userManager,
             TaskViewManager taskViewManager) {
-        super(context, organizer, taskViewTransitions, syncQueue);
+        super(context, organizer, taskViewTransitions, syncQueue, true);
         mCallbackExecutor = callbackExecutor;
         mConfig = controlledCarTaskViewConfig;
         mCallbacks = callbacks;
diff --git a/src/com/android/car/carlauncher/LaunchRootCarTaskView.java b/src/com/android/car/carlauncher/LaunchRootCarTaskView.java
index a0575f9..3a5f154 100644
--- a/src/com/android/car/carlauncher/LaunchRootCarTaskView.java
+++ b/src/com/android/car/carlauncher/LaunchRootCarTaskView.java
@@ -32,7 +32,6 @@
 import android.view.SurfaceControl;
 import android.window.WindowContainerTransaction;
 
-import com.android.internal.annotations.VisibleForTesting;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.taskview.TaskViewTransitions;
@@ -111,7 +110,8 @@
                     }
                     if (DBG) {
                         Log.d(TAG, "launchRootCarTaskView onTaskInfoChanged "
-                                + taskInfo.taskId + " - " + taskInfo.baseActivity);
+                                + taskInfo.taskId + " - base=" + taskInfo.baseActivity + " - top="
+                                + taskInfo.topActivity);
                     }
 
                     // Uncontrolled apps by default launch in the launch root so nothing needs to
@@ -186,7 +186,7 @@
             Executor callbackExecutor,
             LaunchRootCarTaskViewCallbacks callbacks,
             AtomicReference<CarActivityManager> carActivityManager) {
-        super(context, organizer, taskViewTransitions, syncQueue);
+        super(context, organizer, taskViewTransitions, syncQueue, false);
         mCallbacks = callbacks;
         mCallbackExecutor = callbackExecutor;
         mShellTaskOrganizer = organizer;
@@ -245,7 +245,6 @@
      * Returns the {@link android.app.ActivityManager.RunningTaskInfo} of the top task inside the
      * launch root car task view.
      */
-    @VisibleForTesting
     public ActivityManager.RunningTaskInfo getTopTaskInLaunchRootTask() {
         if (mLaunchRootStack.isEmpty()) {
             return null;
diff --git a/src/com/android/car/carlauncher/SemiControlledCarTaskView.java b/src/com/android/car/carlauncher/SemiControlledCarTaskView.java
index f6126fa..3a7b650 100644
--- a/src/com/android/car/carlauncher/SemiControlledCarTaskView.java
+++ b/src/com/android/car/carlauncher/SemiControlledCarTaskView.java
@@ -29,10 +29,13 @@
 import android.view.SurfaceControl;
 import android.window.WindowContainerTransaction;
 
+import androidx.annotation.VisibleForTesting;
+
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.taskview.TaskViewTransitions;
 
+import java.util.ArrayList;
 import java.util.Iterator;
 import java.util.LinkedHashMap;
 import java.util.List;
@@ -58,7 +61,7 @@
     private final SyncTransactionQueue mSyncQueue;
     private final LinkedHashMap<Integer, ActivityManager.RunningTaskInfo> mChildrenTaskStack =
             new LinkedHashMap<>();
-    private final List<ComponentName> mAllowListedActivities;
+    private final ArrayList<ComponentName> mAllowListedActivities;
     private final AtomicReference<CarActivityManager> mCarActivityManagerRef;
 
     private ActivityManager.RunningTaskInfo mRootTask;
@@ -149,13 +152,13 @@
             List<ComponentName> allowListedActivities,
             SemiControlledCarTaskViewCallbacks callbacks,
             AtomicReference<CarActivityManager> carActivityManager) {
-        super(context, organizer, taskViewTransitions, syncQueue);
+        super(context, organizer, taskViewTransitions, syncQueue, true);
         mCallbacks = callbacks;
         mCallbackExecutor = callbackExecutor;
         mCallbackExecutor.execute(() -> mCallbacks.onTaskViewCreated(this));
         mShellTaskOrganizer = organizer;
         mSyncQueue = syncQueue;
-        mAllowListedActivities = allowListedActivities;
+        mAllowListedActivities = new ArrayList<>(allowListedActivities);
         mCarActivityManagerRef = carActivityManager;
     }
 
@@ -209,4 +212,116 @@
     private void setRootTask(ActivityManager.RunningTaskInfo taskInfo) {
         mRootTask = taskInfo;
     }
+
+    /**
+     * Designates the given {@code activities} to be launched in this SemiControlledCarTaskView.
+     * <p>Note: If an activity is already associated with another SemiControlledCarTaskView, it's
+     * designates will be overridden.
+     *
+     * @param activities list of {@link ComponentName} of activities to be designated on the
+     *                   SemiControlledCarTaskView
+     */
+    public void addAllowListedActivities(List<ComponentName> activities) {
+        CarActivityManager carAm = mCarActivityManagerRef.get();
+        if (carAm == null) {
+            Log.wtf(TAG,
+                    "CarActivityManager is null, cannot call setPersistentActivitiesOnRootTask to"
+                            + " add activities");
+            return;
+        }
+        if (mRootTask == null) {
+            Log.wtf(TAG,
+                    "RootTask is null, cannot call setPersistentActivitiesOnRootTask to"
+                            + " add activities");
+            return;
+        }
+        List<ComponentName> activitiesToAdd = new ArrayList<>();
+        for (ComponentName activity : activities) {
+            if (!mAllowListedActivities.contains(activity)) {
+                activitiesToAdd.add(activity);
+            } else {
+                if (DBG) {
+                    Log.d(TAG, "Activity " + activity
+                            + " is already designated to this SemiControlledCarTaskView");
+                }
+            }
+        }
+        mAllowListedActivities.addAll(activitiesToAdd);
+        carAm.setPersistentActivitiesOnRootTask(activitiesToAdd, mRootTask.token.asBinder());
+    }
+
+    /**
+     * Remove the designation of the given {@code activities} to SemiControlledCarTaskView.
+     * <p>Note: If an activity is already associated with another SemiControlledCarTaskView, it's
+     * designates will be overridden.
+     *
+     * @param activities list of {@link ComponentName} of activities to be designated on the
+     *                   SemiControlledCarTaskView
+     */
+    public void removeAllowListedActivities(List<ComponentName> activities) {
+        CarActivityManager carAm = mCarActivityManagerRef.get();
+        if (carAm == null) {
+            Log.wtf(TAG,
+                    "CarActivityManager is null, cannot call setPersistentActivitiesOnRootTask to"
+                            + " remove activities");
+            return;
+        }
+        if (mRootTask == null) {
+            Log.wtf(TAG,
+                    "RootTask is null, cannot call setPersistentActivitiesOnRootTask to"
+                            + " add activities");
+            return;
+        }
+        List<ComponentName> activitiesToRemove = new ArrayList<>();
+        for (ComponentName activity : activities) {
+            if (mAllowListedActivities.contains(activity)) {
+                activitiesToRemove.add(activity);
+            } else {
+                if (DBG) {
+                    Log.d(TAG, "Activity " + activity
+                            + " was not designated to this SemiControlledCarTaskView");
+                }
+            }
+        }
+        mAllowListedActivities.removeAll(activitiesToRemove);
+        carAm.setPersistentActivitiesOnRootTask(activitiesToRemove, /* rootTaskToken= */ null);
+    }
+
+    /**
+     * Sets the designations for SemiControlledCarTaskView. Adds the designation of the given
+     * {@code activities} to SemiControlledCarTaskView and removes the designations of activities
+     * that are not in {@code activities}.
+     *
+     * <p>Note:
+     * If an activity is already associated with another SemiControlledCarTaskView, it's
+     * designates will be overridden.
+     *
+     * @param activities list of {@link ComponentName} of activities to be designated on the
+     *                   SemiControlledCarTaskView
+     */
+    public void setAllowListedActivities(List<ComponentName> activities) {
+        CarActivityManager carAm = mCarActivityManagerRef.get();
+        if (carAm == null) {
+            Log.wtf(TAG,
+                    "CarActivityManager is null, cannot call setPersistentActivitiesOnRootTask to"
+                            + " remove activities");
+            return;
+        }
+        if (mRootTask == null) {
+            Log.wtf(TAG,
+                    "RootTask is null, cannot call setPersistentActivitiesOnRootTask to"
+                            + " add activities");
+            return;
+        }
+        List<ComponentName> activitiesToRemove = new ArrayList<>(mAllowListedActivities);
+        carAm.setPersistentActivitiesOnRootTask(activitiesToRemove, /* rootTaskToken= */ null);
+        carAm.setPersistentActivitiesOnRootTask(activities, mRootTask.token.asBinder());
+        mAllowListedActivities.clear();
+        mAllowListedActivities.addAll(activities);
+    }
+
+    @VisibleForTesting
+    List<ComponentName> getPersistentActivities() {
+        return mAllowListedActivities;
+    }
 }
diff --git a/src/com/android/car/carlauncher/TaskViewManager.java b/src/com/android/car/carlauncher/TaskViewManager.java
index a2d3cc6..ef76219 100644
--- a/src/com/android/car/carlauncher/TaskViewManager.java
+++ b/src/com/android/car/carlauncher/TaskViewManager.java
@@ -48,6 +48,7 @@
 import android.util.Slog;
 import android.view.WindowManagerGlobal;
 import android.window.TaskAppearedInfo;
+import android.window.WindowContainerToken;
 import android.window.WindowContainerTransaction;
 
 import com.android.car.carlauncher.taskstack.TaskStackChangeListeners;
@@ -104,6 +105,7 @@
     private CarUserManager mCarUserManager;
     private Activity mContext;
     private Car mCar;
+    private boolean mReleased = false;
 
     private final TaskStackListener mTaskStackListener = new TaskStackListener() {
         @Override
@@ -232,7 +234,7 @@
             ShellTaskOrganizer shellTaskOrganizer, SyncTransactionQueue syncQueue,
             Transitions transitions, ShellInit shellInit, ShellController shellController,
             StartingWindowController startingWindowController) {
-        if (DBG) Slog.d(TAG, "TaskViewManager(): " + context);
+        if (DBG) Slog.d(TAG, "TaskViewManager(), u=" + context.getUserId());
         mContext = context;
         mShellExecutor = handlerExecutor;
         mTaskOrganizer = shellTaskOrganizer;
@@ -348,6 +350,18 @@
     }
 
     /**
+     * updates the window visibility associated with {@link WindowContainerToken}
+     *
+     * @param token {@link WindowContainerToken} of the window that needs to be hidden
+     * @param visibility {true} if window needs to be displayed {false} otherwise
+     */
+    public void updateTaskVisibility(WindowContainerToken token, boolean visibility) {
+        WindowContainerTransaction wct = new WindowContainerTransaction();
+        wct.setHidden(token, !visibility);
+        mSyncQueue.queue(wct);
+    }
+
+    /**
      * Creates a {@link SemiControlledCarTaskView}.
      *
      * @param callbackExecutor the executor which the {@link SemiControlledCarTaskViewCallbacks}
@@ -372,9 +386,9 @@
      * Releases {@link TaskViewManager} and unregisters the underlying {@link ShellTaskOrganizer}.
      * It also removes all TaskViews which are created by this {@link TaskViewManager}.
      */
-    private void release() {
+    void release() {
         mShellExecutor.execute(() -> {
-            if (DBG) Slog.d(TAG, "TaskViewManager.release");
+            if (DBG) Slog.d(TAG, "TaskViewManager.release, u=" + mContext.getUser());
 
             if (mCarUserManager != null) {
                 mCarUserManager.removeListener(mUserLifecycleListener);
@@ -410,6 +424,7 @@
             if (mCar != null) {
                 mCar.disconnect();
             }
+            mReleased = true;
         });
     }
 
@@ -515,9 +530,71 @@
      * Returns the {@link android.app.ActivityManager.RunningTaskInfo} of the top task inside the
      * launch root car task view.
      */
-    @VisibleForTesting
     public ActivityManager.RunningTaskInfo getTopTaskInLaunchRootTask() {
         return mLaunchRootCarTaskView != null
                 ? mLaunchRootCarTaskView.getTopTaskInLaunchRootTask() : null;
     }
+
+    boolean isReleased() {
+        return mReleased;
+    }
+
+    /**
+     * Adds {@code activities} to allowed list of {@code carTaskView} if this car task view is a
+     * known {@link SemiControlledCarTaskView}.
+     */
+    public void addAllowListedActivities(@NonNull CarTaskView carTaskView,
+            List<ComponentName> activities) {
+        if (activities.size() == 0) {
+            if (DBG) {
+                Log.d(TAG, "No activity to add to allowlist");
+            }
+            return;
+        }
+        for (SemiControlledCarTaskView semiControlledCarTaskView: mSemiControlledTaskViews) {
+            if (semiControlledCarTaskView.equals(carTaskView)) {
+                semiControlledCarTaskView.addAllowListedActivities(activities);
+                return;
+            }
+        }
+    }
+
+    /**
+     * Removes {@code activities} from allowed list of {@code carTaskView} if this CarTaskView is a
+     * known SemiControlledCarTaskView.
+     */
+    public void removeAllowListedActivities(@NonNull CarTaskView carTaskView,
+            List<ComponentName> activities) {
+        if (activities.size() == 0) {
+            if (DBG) {
+                Log.d(TAG, "No activity to remove from allowlist");
+            }
+            return;
+        }
+        for (SemiControlledCarTaskView semiControlledCarTaskView: mSemiControlledTaskViews) {
+            if (semiControlledCarTaskView.equals(carTaskView)) {
+                semiControlledCarTaskView.removeAllowListedActivities(activities);
+                return;
+            }
+        }
+    }
+
+    /**
+     * Sets {@code activities} to be the allowed list of {@code carTaskView} if this CarTaskView
+     * is a known SemiControlledCarTaskView.
+     */
+    public void setAllowListedActivities(CarTaskView carTaskView, List<ComponentName> activities) {
+        if (activities.size() == 0) {
+            if (DBG) {
+                Log.d(TAG, "No activity to remove from allowlist");
+            }
+            return;
+        }
+        for (SemiControlledCarTaskView semiControlledCarTaskView: mSemiControlledTaskViews) {
+            if (semiControlledCarTaskView.equals(carTaskView)) {
+                semiControlledCarTaskView.setAllowListedActivities(activities);
+                return;
+            }
+        }
+    }
 }
diff --git a/src/com/android/car/carlauncher/homescreen/CardPresenter.java b/src/com/android/car/carlauncher/homescreen/CardPresenter.java
index 12609bd..b63e6c3 100644
--- a/src/com/android/car/carlauncher/homescreen/CardPresenter.java
+++ b/src/com/android/car/carlauncher/homescreen/CardPresenter.java
@@ -30,26 +30,6 @@
         mView = view;
     }
 
-    @Override
-    public void onModelUpdated(HomeCardInterface.Model model) {
-        if (model != null && model.getCardHeader() != null) {
-            mView.updateHeaderView(model.getCardHeader());
-            if (model.getCardContent() != null) {
-                mView.updateContentView(model.getCardContent());
-            }
-        } else {
-            mView.hideCard();
-        }
-    }
-
-    @Override
-    public void onModelUpdated(HomeCardInterface.Model model, boolean updateProgress) {
-        if (model == null || model.getCardContent() == null || model.getCardHeader() == null) {
-            return;
-        }
-        mView.updateContentView(model.getCardContent(), updateProgress);
-    }
-
     public Fragment getFragment() {
         return mView.getFragment();
     }
diff --git a/src/com/android/car/carlauncher/homescreen/HomeCardFragment.java b/src/com/android/car/carlauncher/homescreen/HomeCardFragment.java
index 57f9388..3a09a00 100644
--- a/src/com/android/car/carlauncher/homescreen/HomeCardFragment.java
+++ b/src/com/android/car/carlauncher/homescreen/HomeCardFragment.java
@@ -16,9 +16,6 @@
 
 package com.android.car.carlauncher.homescreen;
 
-import static com.android.car.carlauncher.homescreen.ui.CardContent.HomeCardContentType.DESCRIPTIVE_TEXT_WITH_CONTROLS;
-
-import android.content.res.ColorStateList;
 import android.graphics.drawable.Drawable;
 import android.os.Bundle;
 import android.util.Size;
@@ -28,20 +25,16 @@
 import android.view.ViewStub;
 import android.widget.ImageButton;
 import android.widget.ImageView;
-import android.widget.ProgressBar;
-import android.widget.SeekBar;
 import android.widget.TextView;
 
 import androidx.fragment.app.Fragment;
 
 import com.android.car.apps.common.CrossfadeImageView;
 import com.android.car.carlauncher.R;
-import com.android.car.carlauncher.homescreen.audio.MediaViewModel.PlaybackCallback;
 import com.android.car.carlauncher.homescreen.ui.CardContent;
 import com.android.car.carlauncher.homescreen.ui.CardHeader;
 import com.android.car.carlauncher.homescreen.ui.DescriptiveTextView;
 import com.android.car.carlauncher.homescreen.ui.DescriptiveTextWithControlsView;
-import com.android.car.carlauncher.homescreen.ui.SeekBarViewModel;
 import com.android.car.carlauncher.homescreen.ui.TextBlockView;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ArrayUtils;
@@ -58,18 +51,12 @@
  */
 public class HomeCardFragment extends Fragment implements HomeCardInterface.View {
 
-    private HomeCardInterface.Presenter mPresenter;
     private Size mSize;
     private View mCardBackground;
     private CrossfadeImageView mCardBackgroundImage;
     private View mRootView;
     private TextView mCardTitle;
     private ImageView mCardIcon;
-    private ProgressBar mOptionalProgressBar;
-    private SeekBar mOptionalSeekBar;
-    private TextView mOptionalTimes;
-    private ViewGroup mOptionalSeekBarWithTimesContainer;
-    private int mOptionalSeekBarColor;
 
     // Views from card_content_text_block.xml
     private View mTextBlockLayoutView;
@@ -93,31 +80,37 @@
     private ImageButton mControlBarCenterButton;
     private ImageButton mControlBarRightButton;
 
-    private boolean mTrackingTouch;
-    private PlaybackCallback mPlaybackCallback;
-    private SeekBar.OnSeekBarChangeListener mOnSeekBarChangeListener =
-            new SeekBar.OnSeekBarChangeListener() {
-                @Override
-                public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
-                }
 
-                @Override
-                public void onStartTrackingTouch(SeekBar seekBar) {
-                    mTrackingTouch = true;
-                }
+    private boolean mViewCreated;
+    private OnViewLifecycleChangeListener mOnViewLifecycleChangeListener;
 
-                @Override
-                public void onStopTrackingTouch(SeekBar seekBar) {
-                    if (mTrackingTouch) {
-                        mPlaybackCallback.seekTo(seekBar.getProgress());
-                    }
-                    mTrackingTouch = false;
-                }
-            };
+    private OnViewClickListener mOnViewClickListener;
 
-    @Override
-    public void setPresenter(HomeCardInterface.Presenter presenter) {
-        mPresenter = presenter;
+    /**
+     * Interface definition for a callback to be invoked for a view lifecycle changes.
+     */
+    public interface OnViewLifecycleChangeListener {
+
+        /**
+         * Called when a view has been Created.
+         */
+        void onViewCreated();
+
+        /**
+         * Called when a view has been destroyed.
+         */
+        void onViewDestroyed();
+    }
+
+    /**
+     * Interface definition for a callback to be invoked when a view is clicked.
+     */
+    public interface OnViewClickListener {
+
+        /**
+         * Called when a view has been clicked.
+         */
+        void onViewClicked();
     }
 
     @Override
@@ -132,15 +125,19 @@
     @Override
     public void onViewCreated(View view, Bundle savedInstanceState) {
         super.onViewCreated(view, savedInstanceState);
-        mPresenter.onViewCreated();
-        mRootView.setOnClickListener(v -> mPresenter.onViewClicked(v));
+        mViewCreated = true;
+        if (mOnViewLifecycleChangeListener != null) {
+            mOnViewLifecycleChangeListener.onViewCreated();
+        }
+        mRootView.setOnClickListener(v -> mOnViewClickListener.onViewClicked());
     }
 
     @Override
     public void onDestroy() {
         super.onDestroy();
-        if (mPresenter != null) {
-            mPresenter.onViewDestroyed();
+        mViewCreated = false;
+        if (mOnViewLifecycleChangeListener != null) {
+            mOnViewLifecycleChangeListener.onViewDestroyed();
         }
         mSize = null;
     }
@@ -151,6 +148,28 @@
     }
 
     /**
+     * Register a callback to be invoked when this view lifecycle changes.
+     *
+     * @param onViewLifecycleChangeListener The callback that will run
+     */
+    public void setOnViewLifecycleChangeListener(
+            OnViewLifecycleChangeListener onViewLifecycleChangeListener) {
+        mOnViewLifecycleChangeListener = onViewLifecycleChangeListener;
+        if (mViewCreated) {
+            mOnViewLifecycleChangeListener.onViewCreated();
+        }
+    }
+
+    /**
+     * Register a callback to be invoked when this view is clicked.
+     *
+     * @param onViewClickListener The callback that will run
+     */
+    public void setOnViewClickListener(OnViewClickListener onViewClickListener) {
+        mOnViewClickListener = onViewClickListener;
+    }
+
+    /**
      * Returns the size of the card or null if the view hasn't yet been laid out
      */
     protected Size getCardSize() {
@@ -172,7 +191,6 @@
     /**
      * Updates the card's header: name and icon of source app
      */
-    @Override
     public void updateHeaderView(CardHeader header) {
         requireActivity().runOnUiThread(() -> {
             mRootView.setVisibility(View.VISIBLE);
@@ -181,7 +199,9 @@
         });
     }
 
-    @Override
+    /**
+     * Updates the card's content
+     */
     public final void updateContentView(CardContent content) {
         requireActivity().runOnUiThread(() -> {
             hideAllViews();
@@ -189,13 +209,6 @@
         });
     }
 
-    @Override
-    public void updateContentView(CardContent content, boolean updateProgress) {
-        requireActivity().runOnUiThread(() ->
-                updateSeekBarAndTimes(content, updateProgress)
-        );
-    }
-
 
     /**
      * Child classes can override this method for updating their specific types of card content
@@ -225,63 +238,6 @@
         }
     }
 
-    protected void updateSeekBarAndTimes(CardContent content, boolean updateProgress) {
-        DescriptiveTextWithControlsView descriptiveTextWithControlsContent =
-                (DescriptiveTextWithControlsView) content;
-        if (!isSeekbarWithTimesAvailable() || content.getType() != DESCRIPTIVE_TEXT_WITH_CONTROLS
-                || descriptiveTextWithControlsContent.getSeekBarViewModel() == null) {
-            return;
-        }
-
-        SeekBarViewModel seekBarViewModel =
-                descriptiveTextWithControlsContent.getSeekBarViewModel();
-        boolean shouldUseSeekBar = seekBarViewModel.isSeekEnabled();
-
-        if (updateProgress) {
-            ProgressBar progressBar = shouldUseSeekBar ? getOptionalSeekBar()
-                    : getOptionalProgressBar();
-            progressBar.setProgress(seekBarViewModel.getProgress(), /* animate = */ true);
-        } else {
-            SeekBar seekBar = getOptionalSeekBar();
-            ProgressBar progressBar = getOptionalProgressBar();
-            if (shouldUseSeekBar) {
-                updateSeekBar(seekBar, seekBarViewModel);
-            } else {
-                updateProgressBar(progressBar, seekBarViewModel);
-            }
-            seekBar.setVisibility(shouldUseSeekBar ? View.VISIBLE : View.GONE);
-            progressBar.setVisibility(shouldUseSeekBar ? View.GONE : View.VISIBLE);
-        }
-        getOptionalTimes().setText(seekBarViewModel.getTimes());
-
-    }
-
-    private void updateProgressBar(ProgressBar progressBar, SeekBarViewModel seekBarViewModel) {
-        if (mOptionalSeekBarColor != seekBarViewModel.getSeekBarColor()) {
-            mOptionalSeekBarColor = seekBarViewModel.getSeekBarColor();
-            progressBar.setProgressTintList(ColorStateList.valueOf(mOptionalSeekBarColor));
-        }
-        progressBar.setProgress(seekBarViewModel.getProgress(), /* animate = */ true);
-    }
-
-    private void updateSeekBar(SeekBar seekBar, SeekBarViewModel seekBarViewModel) {
-        mPlaybackCallback = seekBarViewModel.getPlaybackCallback();
-        seekBar.setOnSeekBarChangeListener(mOnSeekBarChangeListener);
-        if (mOptionalSeekBarColor != seekBarViewModel.getSeekBarColor()) {
-            mOptionalSeekBarColor = seekBarViewModel.getSeekBarColor();
-            seekBar.setThumbTintList(ColorStateList.valueOf(mOptionalSeekBarColor));
-            seekBar.setProgressTintList(ColorStateList.valueOf(mOptionalSeekBarColor));
-        }
-        seekBar.setProgress(seekBarViewModel.getProgress(), /* animate = */ true);
-    }
-
-    private boolean isSeekbarWithTimesAvailable() {
-        return (getOptionalSeekbarWithTimesContainer() != null
-                && getOptionalSeekbarWithTimesContainer().getVisibility() == View.VISIBLE)
-                && getOptionalSeekBar() != null
-                && getOptionalTimes() != null;
-    }
-
     protected final void updateDescriptiveTextOnlyView(CharSequence primaryText,
             CharSequence secondaryText, Drawable optionalImage, CharSequence tapForMoreText) {
         getDescriptiveTextOnlyLayoutView().setVisibility(View.VISIBLE);
@@ -343,7 +299,6 @@
         getTextBlockLayoutView().setVisibility(View.GONE);
         getDescriptiveTextOnlyLayoutView().setVisibility(View.GONE);
         getDescriptiveTextWithControlsLayoutView().setVisibility(View.GONE);
-        getOptionalSeekbarWithTimesContainer().setVisibility(View.GONE);
     }
 
     protected final View getRootView() {
@@ -364,35 +319,6 @@
         return mCardBackgroundImage;
     }
 
-    private ProgressBar getOptionalProgressBar() {
-        if (mOptionalProgressBar == null) {
-            mOptionalProgressBar = getRootView().findViewById(R.id.optional_progress_bar);
-        }
-        return mOptionalProgressBar;
-    }
-
-    private SeekBar getOptionalSeekBar() {
-        if (mOptionalSeekBar == null) {
-            mOptionalSeekBar = getRootView().findViewById(R.id.optional_seek_bar);
-        }
-        return mOptionalSeekBar;
-    }
-
-    private TextView getOptionalTimes() {
-        if (mOptionalTimes == null) {
-            mOptionalTimes = getRootView().findViewById(R.id.optional_times);
-        }
-        return mOptionalTimes;
-    }
-
-    protected ViewGroup getOptionalSeekbarWithTimesContainer() {
-        if (mOptionalSeekBarWithTimesContainer == null) {
-            mOptionalSeekBarWithTimesContainer = getRootView().findViewById(
-                    R.id.optional_seek_bar_with_times_container);
-        }
-        return mOptionalSeekBarWithTimesContainer;
-    }
-
     protected final View getDescriptiveTextOnlyLayoutView() {
         if (mDescriptiveTextOnlyLayoutView == null) {
             ViewStub stub = mRootView.findViewById(R.id.descriptive_text_layout);
diff --git a/src/com/android/car/carlauncher/homescreen/HomeCardInterface.java b/src/com/android/car/carlauncher/homescreen/HomeCardInterface.java
index 70db6c4..5b42436 100644
--- a/src/com/android/car/carlauncher/homescreen/HomeCardInterface.java
+++ b/src/com/android/car/carlauncher/homescreen/HomeCardInterface.java
@@ -20,9 +20,6 @@
 
 import androidx.fragment.app.Fragment;
 
-import com.android.car.carlauncher.homescreen.ui.CardContent;
-import com.android.car.carlauncher.homescreen.ui.CardHeader;
-
 import java.util.List;
 
 /**
@@ -46,33 +43,12 @@
     interface View {
 
         /**
-         * Sets the {@link Presenter} that will manage this View.
-         */
-        void setPresenter(Presenter presenter);
-
-        /**
          * Called by the Presenter to remove the entire card from view if there is no data to
          * display.
          */
         void hideCard();
 
         /**
-         * Called by the Presenter when there is a change in the source of the card's content.
-         * This updates the app name and app icon displayed on the card.
-         */
-        void updateHeaderView(CardHeader header);
-
-        /**
-         * Called by the Presenter to update the card's content.
-         */
-        void updateContentView(CardContent content);
-
-        /**
-         * Called by the Presenter to update the seekbar and times
-         */
-        default void updateContentView(CardContent content, boolean showTimes) {}
-
-        /**
          * Returns the {@link Fragment} with which the View is associated.
          */
         Fragment getFragment();
@@ -90,40 +66,10 @@
          */
         void setView(View view);
 
-
         /**
          * Sets the list of {@link Model} that the Presenter will use as sources of content.
          */
         void setModels(List<Model> models);
-
-        /**
-         * Called by the View when its view has been created.
-         * This signals the presenter to initialize the relevant models it will use as data sources
-         * and start listening for updates.
-         */
-        void onViewCreated();
-
-        /**
-         * Called by the View when it is destroyed to allow the presenter to clean up any models
-         */
-        void onViewDestroyed();
-
-        /**
-         * Called by the View when it is clicked
-         */
-        default void onViewClicked(android.view.View v) {}
-
-        /**
-         * Called by one of the Presenter's models when it has updated information to display on
-         * the card.
-         */
-        void onModelUpdated(Model model);
-
-        /**
-         * Called by one of the Presenter's models when it has updated progress related information
-         * to display on the card.
-         */
-        default void onModelUpdated(Model model, boolean updateProgress) {}
     }
 
     /**
@@ -136,36 +82,31 @@
     interface Model {
 
         /**
-         * Gets the {@link CardHeader} to display for the model.
-         * If there is no content to display, this returns null.
+         * Interface definition for a callback to be invoked for when model has updates.
          */
-        CardHeader getCardHeader();
+        interface OnModelUpdateListener {
+            /**
+             * Called when model is updated.
+             */
+            void onModelUpdate(HomeCardInterface.Model model);
+        }
 
         /**
-         * Gets the {@link CardContent} to display for the model
+         * Registers OnModelUpdateListener on the model.
          */
-        CardContent getCardContent();
-
-        /**
-         * Sets the Presenter for the model. The model updates its presenter of changes and the
-         * presenter manages updating the UI.
-         */
-        void setPresenter(HomeCardInterface.Presenter presenter);
+        void setOnModelUpdateListener(OnModelUpdateListener onModelUpdateListener);
 
         /**
          * Called by the Presenter to create the Model when the View is created.
          * Should be called after the Model's Presenter has been set with setPresenter
          */
-        default void onCreate(Context context) {}
+        default void onCreate(Context context) {
+        }
 
         /**
          * Called by the Presenter to destroy the Model when the View is destroyed
          */
-        default void onDestroy(Context context) {}
-
-        /**
-         * Called by the Presenter to handle when the View is clicked
-         */
-        default void onClick(android.view.View view) {}
+        default void onDestroy(Context context) {
+        }
     }
 }
diff --git a/src/com/android/car/carlauncher/homescreen/HomeCardModule.java b/src/com/android/car/carlauncher/homescreen/HomeCardModule.java
index 6818975..8658edd 100644
--- a/src/com/android/car/carlauncher/homescreen/HomeCardModule.java
+++ b/src/com/android/car/carlauncher/homescreen/HomeCardModule.java
@@ -51,12 +51,12 @@
     /**
      * Returns the card's {@link Presenter}
      */
-    CardPresenter getCardPresenter();
+    HomeCardInterface.Presenter getCardPresenter();
 
 
     /**
      * Returns the card's {@link View}
      */
-    HomeCardFragment getCardView();
+    HomeCardInterface.View getCardView();
 
 }
diff --git a/src/com/android/car/carlauncher/homescreen/assistive/AssistiveCard.java b/src/com/android/car/carlauncher/homescreen/assistive/AssistiveCardModule.java
similarity index 94%
rename from src/com/android/car/carlauncher/homescreen/assistive/AssistiveCard.java
rename to src/com/android/car/carlauncher/homescreen/assistive/AssistiveCardModule.java
index 1aa98ee..4243cfa 100644
--- a/src/com/android/car/carlauncher/homescreen/assistive/AssistiveCard.java
+++ b/src/com/android/car/carlauncher/homescreen/assistive/AssistiveCardModule.java
@@ -30,7 +30,7 @@
  * Home screen card that displays general assistive content including projection status and
  * static weather data.
  */
-public class AssistiveCard implements HomeCardModule {
+public class AssistiveCardModule implements HomeCardModule {
 
     private ViewModelProvider mViewModelProvider;
     private AssistiveCardPresenter mAssistiveCardPresenter;
@@ -62,7 +62,6 @@
         if (mAssistiveCardView == null) {
             mAssistiveCardView = new HomeCardFragment();
             getCardPresenter().setView(mAssistiveCardView);
-            mAssistiveCardView.setPresenter(getCardPresenter());
         }
         return mAssistiveCardView;
     }
diff --git a/src/com/android/car/carlauncher/homescreen/assistive/AssistiveCardPresenter.java b/src/com/android/car/carlauncher/homescreen/assistive/AssistiveCardPresenter.java
index 92f8eb4..58c9502 100644
--- a/src/com/android/car/carlauncher/homescreen/assistive/AssistiveCardPresenter.java
+++ b/src/com/android/car/carlauncher/homescreen/assistive/AssistiveCardPresenter.java
@@ -16,10 +16,15 @@
 
 package com.android.car.carlauncher.homescreen.assistive;
 
-import android.view.View;
+import android.content.Intent;
+import android.util.Log;
+import android.widget.Toast;
 
+import com.android.car.carlauncher.R;
 import com.android.car.carlauncher.homescreen.CardPresenter;
+import com.android.car.carlauncher.homescreen.HomeCardFragment;
 import com.android.car.carlauncher.homescreen.HomeCardInterface;
+import com.android.internal.annotations.VisibleForTesting;
 
 import java.util.List;
 
@@ -28,68 +33,105 @@
  */
 public class AssistiveCardPresenter extends CardPresenter {
 
-    private HomeCardInterface.Model mCurrentModel;
+    private static final String TAG = "AssistiveCardPresenter";
+    private HomeCardFragment mHomeCardFragment;
+
+    private AssistiveModel mCurrentModel;
     private List<HomeCardInterface.Model> mModels;
 
+    private HomeCardFragment.OnViewClickListener mOnViewClickListener =
+            new HomeCardFragment.OnViewClickListener() {
+                @Override
+                public void onViewClicked() {
+                    Intent intent = mCurrentModel.getIntent();
+                    if (intent == null) {
+                        Log.e(TAG, "No intent to handle view click");
+                        return;
+                    }
+                    if (intent.resolveActivity(
+                            mHomeCardFragment.getContext().getPackageManager()) != null) {
+                        mHomeCardFragment.getContext().startActivity(intent);
+                    } else {
+                        Log.e(TAG, "No activity component found to handle intent with action: "
+                                + intent.getAction());
+                        Toast.makeText(mHomeCardFragment.getContext(),
+                                mHomeCardFragment.getContext().getResources().getString(
+                                        R.string.projected_onclick_launch_error_toast_text),
+                                Toast.LENGTH_SHORT).show();
+                    }
+                }
+            };
+
+    private HomeCardFragment.OnViewLifecycleChangeListener mOnViewLifecycleChangeListener =
+            new HomeCardFragment.OnViewLifecycleChangeListener() {
+                @Override
+                public void onViewCreated() {
+                    for (HomeCardInterface.Model model : mModels) {
+                        model.setOnModelUpdateListener(mOnModelUpdateListener);
+                        model.onCreate(getFragment().requireContext());
+                    }
+                }
+
+                @Override
+                public void onViewDestroyed() {
+                    if (mModels != null) {
+                        for (HomeCardInterface.Model model : mModels) {
+                            model.onDestroy(getFragment().requireContext());
+                        }
+                    }
+                }
+            };
+
+    @VisibleForTesting
+    HomeCardInterface.Model.OnModelUpdateListener mOnModelUpdateListener =
+            new HomeCardInterface.Model.OnModelUpdateListener() {
+                @Override
+                public void onModelUpdate(HomeCardInterface.Model model) {
+                    AssistiveModel assistiveModel = (AssistiveModel) model;
+                    if (assistiveModel.getCardHeader() == null) {
+                        if (mCurrentModel != null
+                                && assistiveModel.getClass() == mCurrentModel.getClass()) {
+                            if (mModels != null) {
+                                // Check if any other models have content to display
+                                for (HomeCardInterface.Model candidate : mModels) {
+                                    if (((AssistiveModel) candidate).getCardHeader() != null) {
+                                        mCurrentModel = (AssistiveModel) candidate;
+                                        updateCurrentModelInFragment();
+                                        return;
+                                    }
+                                }
+                            }
+                        } else {
+                            // Otherwise, another model is already on display,
+                            return;
+                        }
+                    }
+                    mCurrentModel = assistiveModel;
+                    updateCurrentModelInFragment();
+                }
+            };
+
+    @Override
+    public void setView(HomeCardInterface.View view) {
+        super.setView(view);
+        mHomeCardFragment = (HomeCardFragment) view;
+        mHomeCardFragment.setOnViewClickListener(mOnViewClickListener);
+        mHomeCardFragment.setOnViewLifecycleChangeListener(mOnViewLifecycleChangeListener);
+    }
+
     @Override
     public void setModels(List<HomeCardInterface.Model> models) {
         mModels = models;
     }
 
-    /**
-     * Called when the View is created
-     */
-    @Override
-    public void onViewCreated() {
-        for (HomeCardInterface.Model model : mModels) {
-            model.setPresenter(this);
-            model.onCreate(getFragment().requireContext());
-        }
-    }
-
-    /**
-     * Called when the View is destroyed
-     */
-    @Override
-    public void onViewDestroyed() {
-        if (mModels != null) {
-            for (HomeCardInterface.Model model : mModels) {
-                model.onDestroy(getFragment().requireContext());
+    private void updateCurrentModelInFragment() {
+        if (mCurrentModel != null && mCurrentModel.getCardHeader() != null) {
+            mHomeCardFragment.updateHeaderView(mCurrentModel.getCardHeader());
+            if (mCurrentModel.getCardContent() != null) {
+                mHomeCardFragment.updateContentView(mCurrentModel.getCardContent());
             }
+        } else {
+            mHomeCardFragment.hideCard();
         }
     }
-
-    /**
-     * Called when the View is clicked
-     */
-    @Override
-    public void onViewClicked(View v) {
-        mCurrentModel.onClick(v);
-    }
-
-    /**
-     * Called when a Model is updated.
-     */
-    @Override
-    public void onModelUpdated(HomeCardInterface.Model model) {
-        if (model.getCardHeader() == null) {
-            if (mCurrentModel != null && model.getClass() == mCurrentModel.getClass()) {
-                if (mModels != null) {
-                    // Check if any other models have content to display
-                    for (HomeCardInterface.Model candidate : mModels) {
-                        if (candidate.getCardHeader() != null) {
-                            mCurrentModel = candidate;
-                            super.onModelUpdated(candidate);
-                            return;
-                        }
-                    }
-                }
-            } else {
-                // Otherwise, another model is already on display,
-                return;
-            }
-        }
-        mCurrentModel = model;
-        super.onModelUpdated(model);
-    }
 }
diff --git a/src/com/android/car/carlauncher/homescreen/assistive/AssistiveModel.java b/src/com/android/car/carlauncher/homescreen/assistive/AssistiveModel.java
new file mode 100644
index 0000000..a4b3ac0
--- /dev/null
+++ b/src/com/android/car/carlauncher/homescreen/assistive/AssistiveModel.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2023 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.car.carlauncher.homescreen.assistive;
+
+import android.content.Intent;
+
+import com.android.car.carlauncher.homescreen.HomeCardInterface;
+import com.android.car.carlauncher.homescreen.ui.CardContent;
+import com.android.car.carlauncher.homescreen.ui.CardHeader;
+
+/**
+ * An extension of {@link HomeCardInterface.Model} for assistive models.
+ */
+public interface AssistiveModel extends HomeCardInterface.Model {
+
+    /**
+     * Gets the {@link CardHeader} to display for the model.
+     * If there is no content to display, this returns null.
+     */
+    CardHeader getCardHeader();
+
+    /**
+     * Gets the {@link CardContent} to display for the model
+     */
+    CardContent getCardContent();
+    /**
+     * Called by the Presenter to getIntent when the View is clicked
+     */
+    default Intent getIntent() {
+        return null;
+    }
+}
diff --git a/src/com/android/car/carlauncher/homescreen/assistive/FakeWeatherModel.java b/src/com/android/car/carlauncher/homescreen/assistive/FakeWeatherModel.java
index 45d6596..75c721c 100644
--- a/src/com/android/car/carlauncher/homescreen/assistive/FakeWeatherModel.java
+++ b/src/com/android/car/carlauncher/homescreen/assistive/FakeWeatherModel.java
@@ -17,10 +17,8 @@
 package com.android.car.carlauncher.homescreen.assistive;
 
 import android.content.Context;
-import android.content.res.Resources;
 
 import com.android.car.carlauncher.R;
-import com.android.car.carlauncher.homescreen.HomeCardInterface;
 import com.android.car.carlauncher.homescreen.ui.CardContent;
 import com.android.car.carlauncher.homescreen.ui.CardHeader;
 import com.android.car.carlauncher.homescreen.ui.DescriptiveTextView;
@@ -28,11 +26,10 @@
 /**
  * Displays static weather information to provide default content for the assistive card.
  */
-public class FakeWeatherModel implements HomeCardInterface.Model {
-
-    private HomeCardInterface.Presenter mPresenter;
+public class FakeWeatherModel implements AssistiveModel {
     private CardHeader mCardHeader;
     private DescriptiveTextView mCardContent;
+    private OnModelUpdateListener mOnModelUpdateListener;
 
     @Override
     public void onCreate(Context context) {
@@ -43,7 +40,7 @@
                 /* title= */ context.getString(R.string.fake_weather_main_text),
                 /* subtitle= */ null,
                 /* footer= */ context.getString(R.string.fake_weather_footer_text));
-        mPresenter.onModelUpdated(this);
+        mOnModelUpdateListener.onModelUpdate(this);
     }
 
     @Override
@@ -57,7 +54,7 @@
     }
 
     @Override
-    public void setPresenter(HomeCardInterface.Presenter presenter) {
-        mPresenter = presenter;
+    public void setOnModelUpdateListener(OnModelUpdateListener onModelUpdateListener) {
+        mOnModelUpdateListener = onModelUpdateListener;
     }
 }
diff --git a/src/com/android/car/carlauncher/homescreen/assistive/ProjectionModel.java b/src/com/android/car/carlauncher/homescreen/assistive/ProjectionModel.java
index cde8503..cb6491e 100644
--- a/src/com/android/car/carlauncher/homescreen/assistive/ProjectionModel.java
+++ b/src/com/android/car/carlauncher/homescreen/assistive/ProjectionModel.java
@@ -27,13 +27,10 @@
 import android.graphics.drawable.Drawable;
 import android.icu.text.MessageFormat;
 import android.util.Log;
-import android.view.View;
-import android.widget.Toast;
 
 import androidx.annotation.Nullable;
 
 import com.android.car.carlauncher.R;
-import com.android.car.carlauncher.homescreen.HomeCardInterface;
 import com.android.car.carlauncher.homescreen.ui.CardContent;
 import com.android.car.carlauncher.homescreen.ui.CardHeader;
 import com.android.car.carlauncher.homescreen.ui.DescriptiveTextView;
@@ -45,11 +42,9 @@
  * The {@link HomeCardInterface.Model} for projection status
  */
 public class ProjectionModel implements CarProjectionManager.ProjectionStatusListener,
-        HomeCardInterface.Model {
+        AssistiveModel {
 
     private static final String TAG = "ProjectionModel";
-
-    private HomeCardInterface.Presenter mPresenter;
     @Nullable
     private Car mCar;
     @Nullable
@@ -64,17 +59,22 @@
     private CharSequence mTapToLaunchText;
     private Intent mIntent;
 
+    private OnModelUpdateListener mOnModelUpdateListener;
+
     @Override
     public void onCreate(Context context) {
         mCar = Car.createCar(context.getApplicationContext(), null,
                 Car.CAR_WAIT_TIMEOUT_DO_NOT_WAIT,
                 (Car car, boolean ready) -> {
                     if (ready) {
-                        mCarProjectionManager = (CarProjectionManager)
-                                car.getCarManager(Car.PROJECTION_SERVICE);
-                        mCarProjectionManager.registerProjectionStatusListener(this);
+                        mCarProjectionManager = car.getCarManager(CarProjectionManager.class);
                     } else {
                         mCarProjectionManager = null;
+                    }
+
+                    if (mCarProjectionManager != null) {
+                        mCarProjectionManager.registerProjectionStatusListener(this);
+                    } else {
                         onProjectionStatusChanged(
                                 ProjectionStatus.PROJECTION_STATE_INACTIVE, null, null);
                     }
@@ -110,21 +110,13 @@
     }
 
     @Override
-    public void onClick(View v) {
-        if (mIntent.resolveActivity(v.getContext().getPackageManager()) != null) {
-            v.getContext().startActivity(mIntent);
-        } else {
-            Log.e(TAG, "No activity component found to handle intent with action: "
-                    + mIntent.getAction());
-            Toast.makeText(v.getContext(),
-                    mResources.getString(R.string.projected_onclick_launch_error_toast_text),
-                    Toast.LENGTH_SHORT).show();
-        }
+    public Intent getIntent() {
+        return mIntent;
     }
 
     @Override
-    public void setPresenter(HomeCardInterface.Presenter presenter) {
-        mPresenter = presenter;
+    public void setOnModelUpdateListener(OnModelUpdateListener onModelUpdateListener) {
+        mOnModelUpdateListener = onModelUpdateListener;
     }
 
     @Override
@@ -136,7 +128,7 @@
         if (state == ProjectionStatus.PROJECTION_STATE_INACTIVE || packageName == null) {
             if (mAppName != null) {
                 mAppName = null;
-                mPresenter.onModelUpdated(this);
+                mOnModelUpdateListener.onModelUpdate(this);
             }
             return;
         }
@@ -153,7 +145,7 @@
         mAppIcon = applicationInfo.loadIcon(mPackageManager);
         mStatusMessage = getStatusMessage(packageName, details);
         mIntent = mPackageManager.getLaunchIntentForPackage(packageName);
-        mPresenter.onModelUpdated(this);
+        mOnModelUpdateListener.onModelUpdate(this);
     }
 
     @Nullable
diff --git a/src/com/android/car/carlauncher/homescreen/audio/AudioCard.java b/src/com/android/car/carlauncher/homescreen/audio/AudioCardModule.java
similarity index 95%
rename from src/com/android/car/carlauncher/homescreen/audio/AudioCard.java
rename to src/com/android/car/carlauncher/homescreen/audio/AudioCardModule.java
index 083f5b0..6ffb692 100644
--- a/src/com/android/car/carlauncher/homescreen/audio/AudioCard.java
+++ b/src/com/android/car/carlauncher/homescreen/audio/AudioCardModule.java
@@ -32,7 +32,7 @@
 /**
  * Home screen card that displays audio related content
  */
-public class AudioCard implements HomeCardModule {
+public class AudioCardModule implements HomeCardModule {
 
     private static final String TAG = "HomeScreenAudioCard";
 
@@ -77,7 +77,6 @@
         if (mAudioCardView == null) {
             mAudioCardView = new AudioFragment();
             getCardPresenter().setView(mAudioCardView);
-            mAudioCardView.setPresenter(getCardPresenter());
         }
         return mAudioCardView;
     }
diff --git a/src/com/android/car/carlauncher/homescreen/audio/AudioFragment.java b/src/com/android/car/carlauncher/homescreen/audio/AudioFragment.java
index 8124457..25abd14 100644
--- a/src/com/android/car/carlauncher/homescreen/audio/AudioFragment.java
+++ b/src/com/android/car/carlauncher/homescreen/audio/AudioFragment.java
@@ -16,22 +16,27 @@
 
 package com.android.car.carlauncher.homescreen.audio;
 
+import android.content.res.ColorStateList;
 import android.graphics.Bitmap;
 import android.os.Bundle;
 import android.text.TextUtils;
 import android.util.Size;
 import android.view.View;
+import android.view.ViewGroup;
 import android.view.ViewStub;
 import android.widget.Chronometer;
+import android.widget.ProgressBar;
+import android.widget.SeekBar;
 import android.widget.TextView;
 
 import com.android.car.apps.common.BitmapUtils;
 import com.android.car.apps.common.ImageUtils;
 import com.android.car.carlauncher.R;
 import com.android.car.carlauncher.homescreen.HomeCardFragment;
-import com.android.car.carlauncher.homescreen.HomeCardInterface;
 import com.android.car.carlauncher.homescreen.ui.CardContent;
 import com.android.car.carlauncher.homescreen.ui.DescriptiveTextWithControlsView;
+import com.android.car.carlauncher.homescreen.ui.SeekBarViewModel;
+import com.android.car.media.common.PlaybackControlsActionBar;
 
 
 /**
@@ -40,7 +45,17 @@
  */
 public class AudioFragment extends HomeCardFragment {
 
-    private AudioPresenter mPresenter;
+    /**
+     * Interface definition for a callback to be invoked when a media layout is inflated.
+     */
+    public interface OnMediaViewInitializedListener {
+
+        /**
+         * Called when a media layout is inflated.
+         */
+        void onMediaViewInitialized();
+    }
+
     private Chronometer mChronometer;
     private View mChronometerSeparator;
     private float mBlurRadius;
@@ -48,16 +63,51 @@
 
     // Views from card_content_media.xml, which is used only for the media card
     private View mMediaLayoutView;
+    private View mMediaControlBarView;
     private TextView mMediaTitle;
     private TextView mMediaSubtitle;
+    private ProgressBar mProgressBar;
+    private SeekBar mSeekBar;
+    private TextView mTimes;
+    private ViewGroup mSeekBarWithTimesContainer;
+    private int mSeekBarColor;
+    private MediaViewModel.PlaybackCallback mPlaybackCallback;
 
     private boolean mShowSeekBar;
+    private boolean mTrackingTouch;
 
-    @Override
-    public void setPresenter(HomeCardInterface.Presenter presenter) {
-        super.setPresenter(presenter);
-        mPresenter = (AudioPresenter) presenter;
-    }
+    private OnMediaViewInitializedListener mOnMediaViewInitializedListener;
+
+    private SeekBar.OnSeekBarChangeListener mOnSeekBarChangeListener =
+            new SeekBar.OnSeekBarChangeListener() {
+                @Override
+                public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+                }
+
+                @Override
+                public void onStartTrackingTouch(SeekBar seekBar) {
+                    mTrackingTouch = true;
+                }
+
+                @Override
+                public void onStopTrackingTouch(SeekBar seekBar) {
+                    if (mTrackingTouch && mPlaybackCallback != null) {
+                        mPlaybackCallback.seekTo(seekBar.getProgress());
+                    }
+                    mTrackingTouch = false;
+                }
+            };
+
+    private CardContent.CardBackgroundImage mCardBackgroundImage;
+    private View.OnLayoutChangeListener mOnRootLayoutChangeListener =
+            (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
+                boolean isWidthChanged = left - right != oldLeft - oldRight;
+                boolean isHeightChanged = top - bottom != oldTop - oldBottom;
+                boolean isSizeChanged = isWidthChanged || isHeightChanged;
+                if (isSizeChanged) {
+                    resizeCardBackgroundImage();
+                }
+            };
 
     @Override
     public void onCreate(Bundle savedInstanceState) {
@@ -70,6 +120,12 @@
     }
 
     @Override
+    public void onViewCreated(View view, Bundle savedInstanceState) {
+        super.onViewCreated(view, savedInstanceState);
+        getRootView().addOnLayoutChangeListener(mOnRootLayoutChangeListener);
+    }
+
+    @Override
     public void updateContentViewInternal(CardContent content) {
         if (content.getType() == CardContent.HomeCardContentType.DESCRIPTIVE_TEXT_WITH_CONTROLS) {
             DescriptiveTextWithControlsView audioContent =
@@ -84,7 +140,7 @@
                         audioContent.getCenterControl(), audioContent.getRightControl());
                 updateAudioDuration(audioContent);
             }
-            updateSeekBarAndTimes(audioContent, false);
+            updateSeekBarAndTimes(audioContent.getSeekBarViewModel(), false);
         } else {
             super.updateContentViewInternal(content);
         }
@@ -95,6 +151,7 @@
         super.hideAllViews();
         getCardBackground().setVisibility(View.GONE);
         getMediaLayoutView().setVisibility(View.GONE);
+        getSeekbarWithTimesContainer().setVisibility(View.GONE);
     }
 
     private Chronometer getChronometer() {
@@ -113,37 +170,53 @@
             mMediaLayoutView = stub.inflate();
             mMediaTitle = mMediaLayoutView.findViewById(R.id.primary_text);
             mMediaSubtitle = mMediaLayoutView.findViewById(R.id.secondary_text);
-            View mediaControlBarView = mMediaLayoutView.findViewById(
+            mMediaControlBarView = mMediaLayoutView.findViewById(
                     R.id.media_playback_controls_bar);
-            mPresenter.initializeControlsActionBar(mediaControlBarView);
+            mOnMediaViewInitializedListener.onMediaViewInitialized();
         }
         return mMediaLayoutView;
     }
 
-    private void updateBackgroundImage(CardContent.CardBackgroundImage cardBackgroundImage) {
-        if (getCardSize() != null) {
-            if (cardBackgroundImage.getForeground() == null) {
-                cardBackgroundImage = mDefaultCardBackgroundImage;
-            }
-            int maxDimen = Math.max(getCardBackgroundImage().getWidth(),
-                    getCardBackgroundImage().getHeight());
-            // Prioritize size of background image view. Otherwise, use size of whole card
-            if (maxDimen == 0) {
-                maxDimen = Math.max(getCardSize().getWidth(), getCardSize().getHeight());
-            }
-            Size scaledSize = new Size(maxDimen, maxDimen);
-            Bitmap imageBitmap = BitmapUtils.fromDrawable(cardBackgroundImage.getForeground(),
-                    scaledSize);
-            Bitmap blurredBackground = ImageUtils.blur(getContext(), imageBitmap, scaledSize,
-                    mBlurRadius);
+    public PlaybackControlsActionBar getPlaybackControlsActionBar() {
+        return (PlaybackControlsActionBar) mMediaControlBarView;
+    }
 
-            if (cardBackgroundImage.getBackground() != null) {
-                getCardBackgroundImage().setBackground(cardBackgroundImage.getBackground());
-                getCardBackgroundImage().setClipToOutline(true);
-            }
-            getCardBackgroundImage().setImageBitmap(blurredBackground, /* showAnimation= */ true);
-            getCardBackground().setVisibility(View.VISIBLE);
+    private void resizeCardBackgroundImage() {
+        if (mCardBackgroundImage == null || mCardBackgroundImage.getForeground() == null) {
+            mCardBackgroundImage = mDefaultCardBackgroundImage;
         }
+        int maxDimen = Math.max(getCardBackgroundImage().getWidth(),
+                getCardBackgroundImage().getHeight());
+        // Prioritize size of background image view. Otherwise, use size of whole card
+        if (maxDimen == 0) {
+            Size cardSize = getCardSize();
+
+            if (cardSize == null) {
+                return;
+            }
+            maxDimen = Math.max(cardSize.getWidth(), cardSize.getHeight());
+        }
+
+        if (maxDimen == 0) {
+            return;
+        }
+        Size scaledSize = new Size(maxDimen, maxDimen);
+        Bitmap imageBitmap = BitmapUtils.fromDrawable(mCardBackgroundImage.getForeground(),
+                scaledSize);
+        Bitmap blurredBackground = ImageUtils.blur(getContext(), imageBitmap, scaledSize,
+                mBlurRadius);
+
+        if (mCardBackgroundImage.getBackground() != null) {
+            getCardBackgroundImage().setBackground(mCardBackgroundImage.getBackground());
+            getCardBackgroundImage().setClipToOutline(true);
+        }
+        getCardBackgroundImage().setImageBitmap(blurredBackground, /* showAnimation= */ true);
+        getCardBackground().setVisibility(View.VISIBLE);
+    }
+
+    private void updateBackgroundImage(CardContent.CardBackgroundImage cardBackgroundImage) {
+        mCardBackgroundImage = cardBackgroundImage;
+        resizeCardBackgroundImage();
     }
 
     private void updateMediaView(CharSequence title, CharSequence subtitle) {
@@ -151,8 +224,8 @@
         mMediaTitle.setText(title);
         mMediaSubtitle.setText(subtitle);
         mMediaSubtitle.setVisibility(TextUtils.isEmpty(subtitle) ? View.GONE : View.VISIBLE);
-        if (getOptionalSeekbarWithTimesContainer() != null) {
-            getOptionalSeekbarWithTimesContainer().setVisibility(
+        if (getSeekbarWithTimesContainer() != null) {
+            getSeekbarWithTimesContainer().setVisibility(
                     mShowSeekBar ? View.VISIBLE : View.GONE);
         }
     }
@@ -168,4 +241,104 @@
             mChronometerSeparator.setVisibility(View.GONE);
         }
     }
+
+    public void setOnMediaViewInitializedListener(
+            OnMediaViewInitializedListener onMediaViewInitializedListener) {
+        mOnMediaViewInitializedListener = onMediaViewInitializedListener;
+    }
+
+    /**
+     * Updates the seekbar/progress bar progress and times
+     */
+    public void updateProgress(SeekBarViewModel seekBarViewModel, boolean updateProgress) {
+        requireActivity().runOnUiThread(() -> {
+            updateSeekBarAndTimes(seekBarViewModel, updateProgress);
+        });
+    }
+
+    private void updateSeekBarAndTimes(SeekBarViewModel seekBarViewModel, boolean updateProgress) {
+        if (!isSeekbarWithTimesAvailable() || seekBarViewModel == null) {
+            return;
+        }
+
+        boolean shouldUseSeekBar = seekBarViewModel.isSeekEnabled();
+
+        if (updateProgress) {
+            ProgressBar progressBar = shouldUseSeekBar ? getSeekBar()
+                    : getProgressBar();
+            progressBar.setProgress(seekBarViewModel.getProgress(), /* animate = */ true);
+        } else {
+            SeekBar seekBar = getSeekBar();
+            ProgressBar progressBar = getProgressBar();
+            if (shouldUseSeekBar) {
+                updateSeekBar(seekBar, seekBarViewModel);
+            } else {
+                updateProgressBar(progressBar, seekBarViewModel);
+            }
+            seekBar.setVisibility(shouldUseSeekBar ? View.VISIBLE : View.GONE);
+            progressBar.setVisibility(shouldUseSeekBar ? View.GONE : View.VISIBLE);
+        }
+
+        if (seekBarViewModel.getTimes() == null || seekBarViewModel.getTimes().length() == 0) {
+            getTimes().setVisibility(View.GONE);
+        } else {
+            getTimes().setText(seekBarViewModel.getTimes());
+            getTimes().setVisibility(View.VISIBLE);
+        }
+    }
+
+    private void updateProgressBar(ProgressBar progressBar, SeekBarViewModel seekBarViewModel) {
+        if (mSeekBarColor != seekBarViewModel.getSeekBarColor()) {
+            mSeekBarColor = seekBarViewModel.getSeekBarColor();
+            progressBar.setProgressTintList(ColorStateList.valueOf(mSeekBarColor));
+        }
+        progressBar.setProgress(seekBarViewModel.getProgress(), /* animate = */ true);
+    }
+
+    private void updateSeekBar(SeekBar seekBar, SeekBarViewModel seekBarViewModel) {
+        mPlaybackCallback = seekBarViewModel.getPlaybackCallback();
+        if (mSeekBarColor != seekBarViewModel.getSeekBarColor()) {
+            mSeekBarColor = seekBarViewModel.getSeekBarColor();
+            seekBar.setThumbTintList(ColorStateList.valueOf(mSeekBarColor));
+            seekBar.setProgressTintList(ColorStateList.valueOf(mSeekBarColor));
+        }
+        seekBar.setProgress(seekBarViewModel.getProgress(), /* animate = */ true);
+    }
+
+    private boolean isSeekbarWithTimesAvailable() {
+        return (getSeekbarWithTimesContainer() != null
+                && getSeekbarWithTimesContainer().getVisibility() == View.VISIBLE)
+                && getSeekBar() != null
+                && getTimes() != null;
+    }
+
+    private ProgressBar getProgressBar() {
+        if (mProgressBar == null) {
+            mProgressBar = getRootView().findViewById(R.id.optional_progress_bar);
+        }
+        return mProgressBar;
+    }
+
+    private SeekBar getSeekBar() {
+        if (mSeekBar == null) {
+            mSeekBar = getRootView().findViewById(R.id.optional_seek_bar);
+            mSeekBar.setOnSeekBarChangeListener(mOnSeekBarChangeListener);
+        }
+        return mSeekBar;
+    }
+
+    private TextView getTimes() {
+        if (mTimes == null) {
+            mTimes = getRootView().findViewById(R.id.optional_times);
+        }
+        return mTimes;
+    }
+
+    protected ViewGroup getSeekbarWithTimesContainer() {
+        if (mSeekBarWithTimesContainer == null) {
+            mSeekBarWithTimesContainer = getRootView().findViewById(
+                    R.id.optional_seek_bar_with_times_container);
+        }
+        return mSeekBarWithTimesContainer;
+    }
 }
diff --git a/src/com/android/car/carlauncher/homescreen/audio/AudioModel.java b/src/com/android/car/carlauncher/homescreen/audio/AudioModel.java
new file mode 100644
index 0000000..1d1409b
--- /dev/null
+++ b/src/com/android/car/carlauncher/homescreen/audio/AudioModel.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2023 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.car.carlauncher.homescreen.audio;
+
+import android.content.Intent;
+
+import com.android.car.carlauncher.homescreen.HomeCardInterface;
+import com.android.car.carlauncher.homescreen.ui.CardContent;
+import com.android.car.carlauncher.homescreen.ui.CardHeader;
+
+/**
+ * An extension of {@link HomeCardInterface.Model} for audio models.
+ */
+public interface AudioModel  extends HomeCardInterface.Model {
+    /**
+     * Called by the Presenter to getIntent when the View is clicked
+     */
+    Intent getIntent();
+
+    /**
+     * Gets the {@link CardHeader} to display for the model.
+     * If there is no content to display, this returns null.
+     */
+    CardHeader getCardHeader();
+
+    /**
+     * Gets the {@link CardContent} to display for the model
+     */
+    CardContent getCardContent();
+
+    /**
+     * Interface definition for a callback to be invoked for when audio model has progress updates.
+     */
+    interface OnProgressUpdateListener {
+
+        /**
+         * Called when progress is updated.
+         */
+        default void onProgressUpdate(AudioModel model, boolean updateProgress) {}
+    }
+
+    /**
+     * Registers OnProgressUpdateListener on the model.
+     */
+    default void setOnProgressUpdateListener(OnProgressUpdateListener onProgressUpdateListener) {}
+}
diff --git a/src/com/android/car/carlauncher/homescreen/audio/AudioPresenter.java b/src/com/android/car/carlauncher/homescreen/audio/AudioPresenter.java
deleted file mode 100644
index c185a2d..0000000
--- a/src/com/android/car/carlauncher/homescreen/audio/AudioPresenter.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2023 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.car.carlauncher.homescreen.audio;
-
-import android.view.View;
-
-import com.android.car.carlauncher.homescreen.HomeCardInterface;
-
-/**
- * An extension of {@link HomeCardInterface.Presenter} used to initialize controls action bar.
- */
-public interface AudioPresenter extends HomeCardInterface.Presenter {
-    /**
-     * Initialize action bar by setting its playback view model & lifecycle owner.
-     */
-    void initializeControlsActionBar(View actionBar);
-}
diff --git a/src/com/android/car/carlauncher/homescreen/audio/HomeAudioCardPresenter.java b/src/com/android/car/carlauncher/homescreen/audio/HomeAudioCardPresenter.java
index c5af270..9579975 100644
--- a/src/com/android/car/carlauncher/homescreen/audio/HomeAudioCardPresenter.java
+++ b/src/com/android/car/carlauncher/homescreen/audio/HomeAudioCardPresenter.java
@@ -16,11 +16,15 @@
 
 package com.android.car.carlauncher.homescreen.audio;
 
-import android.view.View;
+import android.app.ActivityOptions;
+import android.content.Intent;
+import android.view.Display;
 
 import com.android.car.carlauncher.homescreen.CardPresenter;
+import com.android.car.carlauncher.homescreen.HomeCardFragment;
 import com.android.car.carlauncher.homescreen.HomeCardInterface;
-import com.android.car.media.common.PlaybackControlsActionBar;
+import com.android.car.carlauncher.homescreen.ui.DescriptiveTextWithControlsView;
+import com.android.internal.annotations.VisibleForTesting;
 
 import java.util.List;
 
@@ -30,12 +34,130 @@
  * For the audio card, the {@link AudioFragment} implements the View and displays information on
  * media from a {@link MediaViewModel}.
  */
-public class HomeAudioCardPresenter extends CardPresenter implements AudioPresenter {
+public class HomeAudioCardPresenter extends CardPresenter {
 
-    private HomeCardInterface.Model mCurrentModel;
+    private AudioFragment mAudioFragment;
+
+    private AudioModel mCurrentModel;
     private List<HomeCardInterface.Model> mModelList;
     private MediaViewModel mMediaViewModel;
 
+    private HomeCardFragment.OnViewClickListener mOnViewClickListener =
+            new HomeCardFragment.OnViewClickListener() {
+                @Override
+                public void onViewClicked() {
+                    Intent intent = mCurrentModel.getIntent();
+                    if (intent != null) {
+                        ActivityOptions options = ActivityOptions.makeBasic();
+                        options.setLaunchDisplayId(Display.DEFAULT_DISPLAY);
+                        mAudioFragment.getContext().startActivity(intent, options.toBundle());
+                    }
+                }
+            };
+
+    private HomeCardFragment.OnViewLifecycleChangeListener mOnViewLifecycleChangeListener =
+            new HomeCardFragment.OnViewLifecycleChangeListener() {
+                @Override
+                public void onViewCreated() {
+                    for (HomeCardInterface.Model model : mModelList) {
+                        if (model.getClass() == MediaViewModel.class) {
+                            mMediaViewModel = (MediaViewModel) model;
+                            mMediaViewModel.setOnProgressUpdateListener(
+                                    mOnProgressUpdateListener);
+                        }
+                        model.setOnModelUpdateListener(mOnModelUpdateListener);
+                        model.onCreate(getFragment().requireContext());
+                    }
+                }
+
+                @Override
+                public void onViewDestroyed() {
+                    if (mModelList != null) {
+                        for (HomeCardInterface.Model model : mModelList) {
+                            model.onDestroy(getFragment().requireContext());
+                        }
+                    }
+                }
+            };
+
+    @VisibleForTesting
+    HomeCardInterface.Model.OnModelUpdateListener mOnModelUpdateListener =
+            new HomeCardInterface.Model.OnModelUpdateListener() {
+                @Override
+                public void onModelUpdate(HomeCardInterface.Model model) {
+                    AudioModel audioModel = (AudioModel) model;
+                    // Null card header indicates the model has no content to display
+                    if (audioModel.getCardHeader() == null) {
+                        if (mCurrentModel != null
+                                && audioModel.getClass() == mCurrentModel.getClass()) {
+                            // If the model currently on display is updating to empty content,
+                            // check if there
+                            // is media content to display. If there is no media content the
+                            // super method is
+                            // called with empty content, which hides the card.
+                            if (mMediaViewModel != null
+                                    && mMediaViewModel.getCardHeader() != null) {
+                                mCurrentModel = mMediaViewModel;
+                                updateCurrentModelInFragment();
+                                return;
+                            }
+                        } else {
+                            // Otherwise, another model is already on display, so don't update
+                            // with this
+                            // empty content since that would hide the card.
+                            return;
+                        }
+                    } else if (mCurrentModel != null
+                            && mCurrentModel.getClass() == InCallModel.class
+                            && audioModel.getClass() != InCallModel.class) {
+                        // If the Model has content, check if currentModel on display is an
+                        // ongoing phone call.
+                        // If there is any ongoing phone call, do not update the View
+                        // if the model trying to update View is NOT a phone call.
+                        return;
+                    }
+                    mCurrentModel = audioModel;
+                    updateCurrentModelInFragment();
+                }
+            };
+
+    @VisibleForTesting
+    AudioModel.OnProgressUpdateListener mOnProgressUpdateListener =
+            new AudioModel.OnProgressUpdateListener() {
+                @Override
+                public void onProgressUpdate(AudioModel model, boolean updateProgress) {
+                    if (model == null || model.getCardContent() == null
+                            || model.getCardHeader() == null) {
+                        return;
+                    }
+                    DescriptiveTextWithControlsView descriptiveTextWithControlsContent =
+                            (DescriptiveTextWithControlsView) model.getCardContent();
+                    mAudioFragment.updateProgress(
+                            descriptiveTextWithControlsContent.getSeekBarViewModel(),
+                            updateProgress);
+                }
+            };
+
+    private AudioFragment.OnMediaViewInitializedListener mOnMediaViewInitializedListener =
+            new AudioFragment.OnMediaViewInitializedListener() {
+                @Override
+                public void onMediaViewInitialized() {
+                    // set playbackviewmodel on playback control actions view
+                    mAudioFragment.getPlaybackControlsActionBar().setModel(
+                            mMediaViewModel.getPlaybackViewModel(),
+                            mAudioFragment.getViewLifecycleOwner());
+                }
+            };
+
+    @Override
+    public void setView(HomeCardInterface.View view) {
+        super.setView(view);
+        mAudioFragment = (AudioFragment) view;
+        mAudioFragment.setOnViewLifecycleChangeListener(mOnViewLifecycleChangeListener);
+        mAudioFragment.setOnViewClickListener(mOnViewClickListener);
+        mAudioFragment.setOnMediaViewInitializedListener(mOnMediaViewInitializedListener);
+    }
+
     @Override
     public void setModels(List<HomeCardInterface.Model> models) {
         mModelList = models;
@@ -45,84 +167,18 @@
         return mModelList;
     }
 
-    protected HomeCardInterface.Model getCurrentModel() {
+    protected AudioModel getCurrentModel() {
         return mCurrentModel;
     }
 
-    /**
-     * Called when the View is created
-     */
-    @Override
-    public void onViewCreated() {
-        for (HomeCardInterface.Model model : mModelList) {
-            if (model.getClass() == MediaViewModel.class) {
-                mMediaViewModel = (MediaViewModel) model;
+    private void updateCurrentModelInFragment() {
+        if (mCurrentModel != null && mCurrentModel.getCardHeader() != null) {
+            mAudioFragment.updateHeaderView(mCurrentModel.getCardHeader());
+            if (mCurrentModel.getCardContent() != null) {
+                mAudioFragment.updateContentView(mCurrentModel.getCardContent());
             }
-            model.setPresenter(this);
-            model.onCreate(getFragment().requireContext());
+        } else {
+            mAudioFragment.hideCard();
         }
     }
-
-    /**
-     * Called when the View is destroyed
-     */
-    @Override
-    public void onViewDestroyed() {
-        if (mModelList != null) {
-            for (HomeCardInterface.Model model : mModelList) {
-                model.onDestroy(getFragment().requireContext());
-            }
-        }
-    }
-
-    /**
-     * Called when the View is clicked
-     */
-    @Override
-    public void onViewClicked(View v) {
-        mCurrentModel.onClick(v);
-    }
-
-    /**
-     * Updates the View appropriately when a Model has new content.
-     *
-     * If the updated model has content, it is displayed, regardless of what is currently shown on
-     * the card. Otherwise if the model on display is updating to empty content (eg. when a call
-     * ends, the InCallModel header and content are updated to null), default to showing the media
-     * model if it has content.
-     */
-    @Override
-    public void onModelUpdated(HomeCardInterface.Model model) {
-        // Null card header indicates the model has no content to display
-        if (model.getCardHeader() == null) {
-            if (mCurrentModel != null && model.getClass() == mCurrentModel.getClass()) {
-                // If the model currently on display is updating to empty content, check if there
-                // is media content to display. If there is no media content the super method is
-                // called with empty content, which hides the card.
-                if (mMediaViewModel != null && mMediaViewModel.getCardHeader() != null) {
-                    mCurrentModel = mMediaViewModel;
-                    super.onModelUpdated(mMediaViewModel);
-                    return;
-                }
-            } else {
-                // Otherwise, another model is already on display, so don't update with this
-                // empty content since that would hide the card.
-                return;
-            }
-        } else if (mCurrentModel != null && mCurrentModel.getClass() == InCallModel.class
-                && model.getClass() != InCallModel.class) {
-            // If the Model has content, check if currentModel on display is an ongoing phone call.
-            // If there is any ongoing phone call, do not update the View
-            // if the model trying to update View is NOT a phone call.
-            return;
-        }
-        mCurrentModel = model;
-        super.onModelUpdated(model);
-    }
-
-    @Override
-    public void initializeControlsActionBar(View actionBar) {
-        ((PlaybackControlsActionBar) actionBar).setModel(mMediaViewModel.getPlaybackViewModel(),
-                getFragment().getViewLifecycleOwner());
-    }
 }
diff --git a/src/com/android/car/carlauncher/homescreen/audio/InCallModel.java b/src/com/android/car/carlauncher/homescreen/audio/InCallModel.java
index a58632f..a36c040 100644
--- a/src/com/android/car/carlauncher/homescreen/audio/InCallModel.java
+++ b/src/com/android/car/carlauncher/homescreen/audio/InCallModel.java
@@ -16,17 +16,17 @@
 
 package com.android.car.carlauncher.homescreen.audio;
 
+import static android.content.pm.PackageManager.GET_RESOLVED_FILTER;
+
 import android.Manifest;
 import android.app.ActivityOptions;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
-import android.content.ServiceConnection;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.graphics.drawable.Drawable;
 import android.os.Bundle;
-import android.os.IBinder;
 import android.telecom.Call;
 import android.telecom.CallAudioState;
 import android.telecom.PhoneAccountHandle;
@@ -41,18 +41,18 @@
 import androidx.core.content.ContextCompat;
 
 import com.android.car.carlauncher.R;
-import com.android.car.carlauncher.homescreen.HomeCardInterface;
 import com.android.car.carlauncher.homescreen.audio.telecom.InCallServiceImpl;
 import com.android.car.carlauncher.homescreen.ui.CardContent;
 import com.android.car.carlauncher.homescreen.ui.CardHeader;
 import com.android.car.carlauncher.homescreen.ui.DescriptiveTextWithControlsView;
+import com.android.car.telephony.calling.InCallServiceManager;
 import com.android.car.telephony.common.CallDetail;
 import com.android.car.telephony.common.TelecomUtils;
-import com.android.car.telephony.selfmanaged.SelfManagedCallUtil;
-import com.android.car.ui.utils.CarUxRestrictionsUtil;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ArrayUtils;
 
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
 import java.io.FileNotFoundException;
 import java.io.InputStream;
 import java.time.Clock;
@@ -61,14 +61,21 @@
 /**
  * The {@link HomeCardInterface.Model} for ongoing phone calls.
  */
-public class InCallModel implements HomeCardInterface.Model, InCallServiceImpl.InCallListener {
+public class InCallModel implements AudioModel, InCallServiceImpl.InCallListener,
+        PropertyChangeListener {
 
     private static final String TAG = "InCallModel";
+    private static final String PROPERTY_IN_CALL_SERVICE = "PROPERTY_IN_CALL_SERVICE";
+    private static final String CAR_APP_SERVICE_INTERFACE = "androidx.car.app.CarAppService";
+    private static final String CAR_APP_ACTIVITY_INTERFACE =
+            "androidx.car.app.activity.CarAppActivity";
+    /** androidx.car.app.CarAppService.CATEGORY_CALLING_APP from androidx car app library. */
+    private static final String CAR_APP_CATEGORY_CALLING = "androidx.car.app.category.CALLING";
     private static final boolean DEBUG = false;
+    protected static InCallServiceManager sInCallServiceManager;
 
     private Context mContext;
     private TelecomManager mTelecomManager;
-    private SelfManagedCallUtil mSelfManagedCallUtil;
 
     private PackageManager mPackageManager;
     private final Clock mElapsedTimeClock;
@@ -77,7 +84,6 @@
     private CompletableFuture<Void> mPhoneNumberInfoFuture;
 
     private InCallServiceImpl mInCallService;
-    private HomeCardInterface.Presenter mPresenter;
 
     private CardHeader mDefaultDialerCardHeader;
     private CardHeader mCardHeader;
@@ -88,24 +94,7 @@
     private DescriptiveTextWithControlsView.Control mEndCallButton;
     private DescriptiveTextWithControlsView.Control mDialpadButton;
     private Drawable mContactImageBackground;
-
-    private final ServiceConnection mInCallServiceConnection = new ServiceConnection() {
-        @Override
-        public void onServiceConnected(ComponentName name, IBinder service) {
-            if (DEBUG) Log.d(TAG, "onServiceConnected: " + name + ", service: " + service);
-            mInCallService = ((InCallServiceImpl.LocalBinder) service).getService();
-            mInCallService.addListener(InCallModel.this);
-            if (mInCallService.getCalls() != null && !mInCallService.getCalls().isEmpty()) {
-                handleActiveCall(mInCallService.getCalls().get(0));
-            }
-        }
-
-        @Override
-        public void onServiceDisconnected(ComponentName name) {
-            if (DEBUG) Log.d(TAG, "onServiceDisconnected: " + name);
-            mInCallService = null;
-        }
-    };
+    private OnModelUpdateListener mOnModelUpdateListener;
 
     private Call.Callback mCallback = new Call.Callback() {
         @Override
@@ -123,8 +112,6 @@
     public void onCreate(Context context) {
         mContext = context;
         mTelecomManager = context.getSystemService(TelecomManager.class);
-        CarUxRestrictionsUtil carUxRestrictionsUtil = CarUxRestrictionsUtil.getInstance(context);
-        mSelfManagedCallUtil = new SelfManagedCallUtil(mContext, carUxRestrictionsUtil);
 
         mOngoingCallSubtitle = context.getResources().getString(R.string.ongoing_call_text);
         mDialingCallSubtitle = context.getResources().getString(R.string.dialing_call_text);
@@ -136,17 +123,21 @@
         mDefaultDialerCardHeader = createCardHeader(mTelecomManager.getDefaultDialerPackage());
         mCardHeader = mDefaultDialerCardHeader;
 
-        Intent intent = new Intent(context, InCallServiceImpl.class);
-        intent.setAction(InCallServiceImpl.ACTION_LOCAL_BIND);
-        context.getApplicationContext().bindService(intent, mInCallServiceConnection,
-                Context.BIND_AUTO_CREATE);
+        sInCallServiceManager = InCallServiceManagerProvider.get();
+        sInCallServiceManager.addObserver(this);
+        if (sInCallServiceManager.getInCallService() != null) {
+            onInCallServiceConnected();
+        }
     }
 
     @Override
     public void onDestroy(Context context) {
+        sInCallServiceManager.removeObserver(this);
         if (mInCallService != null) {
+            if (mInCallService.getCalls() != null && !mInCallService.getCalls().isEmpty()) {
+                onCallRemoved(mInCallService.getCalls().get(0));
+            }
             mInCallService.removeListener(InCallModel.this);
-            context.getApplicationContext().unbindService(mInCallServiceConnection);
             mInCallService = null;
         }
         if (mPhoneNumberInfoFuture != null) {
@@ -155,8 +146,8 @@
     }
 
     @Override
-    public void setPresenter(HomeCardInterface.Presenter presenter) {
-        mPresenter = presenter;
+    public void setOnModelUpdateListener(OnModelUpdateListener onModelUpdateListener) {
+        mOnModelUpdateListener = onModelUpdateListener;
     }
 
     @Override
@@ -175,9 +166,9 @@
      * display as one of the requirements to fill this role is to provide an ongoing call UI.
      */
     @Override
-    public void onClick(View view) {
+    public Intent getIntent() {
         Intent intent = null;
-        if (isSelfManagedCall() && mSelfManagedCallUtil.canShowCalInCallView()) {
+        if (isSelfManagedCall()) {
             Bundle extras = mCurrentCall.getDetails().getExtras();
             ComponentName componentName = extras == null ? null : extras.getParcelable(
                     Intent.EXTRA_COMPONENT_NAME, ComponentName.class);
@@ -187,14 +178,31 @@
             } else {
                 String callingAppPackageName = getCallingAppPackageName();
                 if (!TextUtils.isEmpty(callingAppPackageName)) {
-                    intent = mPackageManager.getLaunchIntentForPackage(callingAppPackageName);
+                    if (isCarAppCallingService(callingAppPackageName)) {
+                        intent = new Intent();
+                        intent.setComponent(
+                                new ComponentName(
+                                        callingAppPackageName, CAR_APP_ACTIVITY_INTERFACE));
+                        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+                    } else {
+                        intent = mPackageManager.getLaunchIntentForPackage(callingAppPackageName);
+                    }
                 }
             }
         } else {
             intent = mPackageManager.getLaunchIntentForPackage(
                     mTelecomManager.getDefaultDialerPackage());
         }
+        return intent;
+    }
 
+    /**
+     * Clicking the card opens the default dialer application that fills the role of {@link
+     * android.app.role.RoleManager#ROLE_DIALER}. This application will have an appropriate UI to
+     * display as one of the requirements to fill this role is to provide an ongoing call UI.
+     */
+    public void onClick(View view) {
+        Intent intent = getIntent();
         if (intent != null) {
             // Launch activity in the default app task container: the display area where
             // applications are launched by default.
@@ -215,9 +223,14 @@
      */
     @Override
     public void onCallAdded(Call call) {
-        if (call != null) {
+        if (call == null) {
+            return;
+        }
+        mCurrentCall = call;
+        call.registerCallback(mCallback);
+        @Call.CallState int callState = call.getDetails().getState();
+        if (callState == Call.STATE_ACTIVE || callState == Call.STATE_DIALING) {
             handleActiveCall(call);
-            call.registerCallback(mCallback);
         }
     }
 
@@ -230,7 +243,7 @@
         mCurrentCall = null;
         mCardHeader = null;
         mCardContent = null;
-        mPresenter.onModelUpdated(this);
+        mOnModelUpdateListener.onModelUpdate(this);
         if (call != null) {
             call.unregisterCallback(mCallback);
         }
@@ -245,7 +258,7 @@
         // This is implemented to listen to changes to audio from other sources and update the
         // content accordingly.
         if (updateMuteButtonIconState(audioState)) {
-            mPresenter.onModelUpdated(this);
+            mOnModelUpdateListener.onModelUpdate(this);
         }
     }
 
@@ -284,7 +297,7 @@
     void updateModelWithPhoneNumber(String number, @Call.CallState int callState) {
         String formattedNumber = TelecomUtils.getFormattedNumber(mContext, number);
         mCardContent = createPhoneCardContent(null, formattedNumber, callState);
-        mPresenter.onModelUpdated(this);
+        mOnModelUpdateListener.onModelUpdate(this);
     }
 
     /**
@@ -333,16 +346,15 @@
         mCardContent = createPhoneCardContent(
                 new CardContent.CardBackgroundImage(contactImage, mContactImageBackground),
                 contactName, callState);
-        mPresenter.onModelUpdated(this);
+        mOnModelUpdateListener.onModelUpdate(this);
     }
 
-    private void handleActiveCall(@NonNull Call call) {
-        @Call.CallState int callState = call.getDetails().getState();
-        if (callState != Call.STATE_ACTIVE && callState != Call.STATE_DIALING) {
-            return;
-        }
-        mCurrentCall = call;
+    protected Call getCurrentCall() {
+        return mCurrentCall;
+    }
 
+    protected void handleActiveCall(@NonNull Call call) {
+        @Call.CallState int callState = call.getDetails().getState();
         CallDetail callDetails = CallDetail.fromTelecomCallDetail(call.getDetails());
         if (callDetails.isSelfManaged()) {
             String packageName = getCallingAppPackageName();
@@ -445,4 +457,30 @@
         }
         return null;
     }
+
+    @Override
+    public void propertyChange(PropertyChangeEvent evt) {
+        Log.d(TAG, "InCallService has updated.");
+        if (PROPERTY_IN_CALL_SERVICE.equals(evt.getPropertyName())
+                && sInCallServiceManager.getInCallService() != null) {
+            onInCallServiceConnected();
+        }
+    }
+
+    private void onInCallServiceConnected() {
+        Log.d(TAG, "InCall service is connected");
+        mInCallService = (InCallServiceImpl) sInCallServiceManager.getInCallService();
+        mInCallService.addListener(this);
+        if (mInCallService.getCalls() != null && !mInCallService.getCalls().isEmpty()) {
+            onCallAdded(mInCallService.getCalls().get(0));
+        }
+    }
+
+    private boolean isCarAppCallingService(String packageName) {
+        Intent intent =
+                new Intent(CAR_APP_SERVICE_INTERFACE)
+                        .setPackage(packageName)
+                        .addCategory(CAR_APP_CATEGORY_CALLING);
+        return !mPackageManager.queryIntentServices(intent, GET_RESOLVED_FILTER).isEmpty();
+    }
 }
diff --git a/src/com/android/car/carlauncher/homescreen/audio/InCallServiceManagerProvider.java b/src/com/android/car/carlauncher/homescreen/audio/InCallServiceManagerProvider.java
new file mode 100644
index 0000000..0121692
--- /dev/null
+++ b/src/com/android/car/carlauncher/homescreen/audio/InCallServiceManagerProvider.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2023 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.car.carlauncher.homescreen.audio;
+
+import com.android.car.telephony.calling.InCallServiceManager;
+
+/**
+ * Provides an instance of {@link InCallServiceManager} which manages the InCallServiceImpl.
+ */
+public class InCallServiceManagerProvider {
+    private static final InCallServiceManager sInstance = new InCallServiceManager();
+
+    private InCallServiceManagerProvider() {}
+
+    /**
+     * Creates a singleton instance of {@link InCallServiceManager}  .
+     */
+    public static InCallServiceManager get() {
+        return sInstance;
+    }
+}
diff --git a/src/com/android/car/carlauncher/homescreen/audio/MediaViewModel.java b/src/com/android/car/carlauncher/homescreen/audio/MediaViewModel.java
index 9d714f7..21c347a 100644
--- a/src/com/android/car/carlauncher/homescreen/audio/MediaViewModel.java
+++ b/src/com/android/car/carlauncher/homescreen/audio/MediaViewModel.java
@@ -19,7 +19,6 @@
 import static android.car.media.CarMediaIntents.EXTRA_MEDIA_COMPONENT;
 import static android.car.media.CarMediaManager.MEDIA_SOURCE_MODE_PLAYBACK;
 
-import android.app.ActivityOptions;
 import android.app.Application;
 import android.car.media.CarMediaIntents;
 import android.content.Context;
@@ -28,8 +27,6 @@
 import android.graphics.drawable.Drawable;
 import android.util.Log;
 import android.util.Size;
-import android.view.Display;
-import android.view.View;
 
 import androidx.annotation.NonNull;
 import androidx.lifecycle.AndroidViewModel;
@@ -56,7 +53,7 @@
  * ViewModel for media. Uses both a {@link MediaSourceViewModel} and a {@link PlaybackViewModel}
  * for data on the audio source and audio metadata (such as song title), respectively.
  */
-public class MediaViewModel extends AndroidViewModel implements HomeCardInterface.Model {
+public class MediaViewModel extends AndroidViewModel implements AudioModel {
 
     private static final String TAG = "MediaViewModel";
 
@@ -94,6 +91,8 @@
     private ImageBinder<MediaItemMetadata.ArtworkRef> mAlbumArtBinder;
     private Drawable mAlbumImageBitmap;
     private Drawable mMediaBackground;
+    private OnModelUpdateListener mOnModelUpdateListener;
+    private OnProgressUpdateListener mOnProgressUpdateListener;
     private Observer<Object> mMediaSourceColorObserver = x -> updateMediaSourceColor();
     private Observer<Object> mMetadataObserver = x -> updateModelMetadata();
     private Observer<Object> mPlaybackControllerObserver = controller -> updatePlaybackController();
@@ -115,8 +114,9 @@
                 if (playbackStateWrapper != null
                         && mIsSeekEnabled != playbackStateWrapper.isSeekToEnabled()) {
                     mIsSeekEnabled = playbackStateWrapper.isSeekToEnabled();
-                    if (mAudioPresenter != null) {
-                        mAudioPresenter.onModelUpdated(/* model = */ this, /* updateProgress = */
+                    if (mOnProgressUpdateListener != null) {
+                        mOnProgressUpdateListener.onProgressUpdate(/* model = */
+                                this, /* updateProgress = */
                                 false);
                     }
                 }
@@ -154,7 +154,7 @@
         mAlbumArtBinder = new ImageBinder<>(ImageBinder.PlaceholderType.FOREGROUND, maxArtSize,
                 drawable -> {
                     mAlbumImageBitmap = drawable;
-                    mAudioPresenter.onModelUpdated(/* model = */ this);
+                    mOnModelUpdateListener.onModelUpdate(/* model = */ this);
                 });
         mSourceViewModel.getPrimaryMediaSource().observeForever(mMediaSourceObserver);
         mPlaybackViewModel.getMetadata().observeForever(mMetadataObserver);
@@ -169,7 +169,9 @@
                 com.android.car.carlauncher.R.integer.optional_seekbar_max);
         mUseMediaSourceColor = resources.getBoolean(R.bool.use_media_source_color_for_seek_bar);
         mTimesSeparator = resources.getString(com.android.car.carlauncher.R.string.times_separator);
-        mAudioPresenter.onModelUpdated(/* model = */ this);
+        mOnModelUpdateListener.onModelUpdate(/* model = */ this);
+
+        updateModel(); // Make sure the name of the media source properly reflects the locale.
     }
 
     @Override
@@ -181,28 +183,24 @@
     }
 
     @Override
-    public void onClick(View v) {
-        // Launch activity in the default app task container: the display area where
-        // applications are launched by default.
-        // If not set, activity launches in the calling TDA.
-        ActivityOptions options = ActivityOptions.makeBasic();
-        options.setLaunchDisplayId(Display.DEFAULT_DISPLAY);
-        MediaSource mediaSource = mSourceViewModel.getPrimaryMediaSource().getValue();
+    public Intent getIntent() {
+        MediaSource mediaSource = getMediaSourceViewModel().getPrimaryMediaSource().getValue();
         Intent intent = new Intent(CarMediaIntents.ACTION_MEDIA_TEMPLATE);
         if (mediaSource != null) {
             intent.putExtra(EXTRA_MEDIA_COMPONENT,
                     mediaSource.getBrowseServiceComponentName().flattenToString());
         }
-        v.getContext().startActivity(intent, options.toBundle());
+        return intent;
     }
 
-
-    /**
-     * Sets the Presenter, which will handle updating the UI
-     */
     @Override
-    public void setPresenter(HomeCardInterface.Presenter presenter) {
-        mAudioPresenter = presenter;
+    public void setOnModelUpdateListener(OnModelUpdateListener onModelUpdateListener) {
+        mOnModelUpdateListener = onModelUpdateListener;
+    }
+
+    @Override
+    public void setOnProgressUpdateListener(OnProgressUpdateListener onProgressUpdateListener) {
+        mOnProgressUpdateListener = onProgressUpdateListener;
     }
 
     @Override
@@ -252,21 +250,22 @@
                     && !AppLauncherUtils.isVideoApp(mContext.getPackageManager(),
                     mediaSource.getPackageName())) {
                 if (Log.isLoggable(TAG, Log.INFO)) {
-                    Log.i(TAG, "Setting Media view to source " + mediaSource.getDisplayName());
+                    Log.i(TAG, "Setting Media view to source "
+                            + mediaSource.getDisplayName(mContext));
                 }
-                mAppName = mediaSource.getDisplayName();
+                mAppName = mediaSource.getDisplayName(mContext);
                 mAppIcon = mediaSource.getIcon();
                 mCardHeader = new CardHeader(mAppName, mAppIcon);
                 updateMetadata();
                 updateProgress();
                 updateMediaSourceColor();
+                mOnModelUpdateListener.onModelUpdate(/* model = */ this);
             } else {
                 if (Log.isLoggable(TAG, Log.INFO)) {
                     Log.i(TAG, "Not resetting media widget for video apps or apps "
                             + "that do not support media browse");
                 }
             }
-            mAudioPresenter.onModelUpdated(/* model = */ this);
         }
     }
 
@@ -277,7 +276,7 @@
         if (metadataChanged()) {
             updateMetadata();
             if (mCardHeader != null) {
-                mAudioPresenter.onModelUpdated(/* model = */ this);
+                mOnModelUpdateListener.onModelUpdate(/* model = */ this);
             }
         }
     }
@@ -287,7 +286,8 @@
         mSeekBarColor = (mediaSourceColors == null || !mUseMediaSourceColor)
                 ? mDefaultSeekBarColor
                 : mediaSourceColors.getAccentColor(mDefaultSeekBarColor);
-        mAudioPresenter.onModelUpdated(/* model = */ this, /* updateProgress = */ false);
+        mOnProgressUpdateListener.onProgressUpdate(/* model = */ this, /* updateProgress = */
+                false);
     }
 
     private void updateProgress() {
@@ -303,7 +303,8 @@
                 : (int) (mSeekBarMax * playbackProgress.getProgressFraction());
         if (mProgress != progress) {
             mProgress = progress;
-            mAudioPresenter.onModelUpdated(/* model = */ this, /* updateProgress = */ true);
+            mOnProgressUpdateListener.onProgressUpdate(/* model = */ this, /* updateProgress = */
+                    true);
         }
     }
 
@@ -354,7 +355,7 @@
             }
             return true;
         }
-        if (mediaSource != null && (mAppName != mediaSource.getDisplayName()
+        if (mediaSource != null && (mAppName != mediaSource.getDisplayName(mContext)
                 || mAppIcon != mediaSource.getIcon())) {
             if (Log.isLoggable(TAG, Log.DEBUG)) {
                 Log.d(TAG, "new media source is " + mediaSource.toString());
diff --git a/src/com/android/car/carlauncher/homescreen/audio/telecom/InCallServiceImpl.java b/src/com/android/car/carlauncher/homescreen/audio/telecom/InCallServiceImpl.java
index bae5866..ee65263 100644
--- a/src/com/android/car/carlauncher/homescreen/audio/telecom/InCallServiceImpl.java
+++ b/src/com/android/car/carlauncher/homescreen/audio/telecom/InCallServiceImpl.java
@@ -26,6 +26,7 @@
 import android.util.Log;
 
 import com.android.car.carlauncher.homescreen.audio.InCallModel;
+import com.android.car.carlauncher.homescreen.audio.InCallServiceManagerProvider;
 
 import java.util.ArrayList;
 
@@ -47,6 +48,18 @@
     private ArrayList<InCallListener> mInCallListeners = new ArrayList<>();
 
     @Override
+    public void onCreate() {
+        super.onCreate();
+        InCallServiceManagerProvider.get().setInCallService(this);
+    }
+
+    @Override
+    public void onDestroy() {
+        InCallServiceManagerProvider.get().setInCallService(null);
+        super.onDestroy();
+    }
+
+    @Override
     public void onCallAdded(Call call) {
         if (DEBUG) Log.d(TAG, "onCallAdded: " + call);
         for (InCallListener listener : mInCallListeners) {
diff --git a/src/com/android/car/carlauncher/recents/CarQuickStepService.java b/src/com/android/car/carlauncher/recents/CarQuickStepService.java
index 4639e6e..a4c820e 100644
--- a/src/com/android/car/carlauncher/recents/CarQuickStepService.java
+++ b/src/com/android/car/carlauncher/recents/CarQuickStepService.java
@@ -136,6 +136,11 @@
         }
 
         @Override
+        public void onAssistantOverrideInvoked(int invocationType) {
+            // no-op
+        }
+
+        @Override
         public void onSystemUiStateChanged(int stateFlags) {
             // no-op
         }
@@ -156,26 +161,11 @@
         }
 
         @Override
-        public void onScreenTurnedOn() {
-            // no-op
-        }
-
-        @Override
         public void onNavButtonsDarkIntensityChanged(float darkIntensity) {
             // no-op
         }
 
         @Override
-        public void onScreenTurningOn() {
-            // no-op
-        }
-
-        @Override
-        public void onScreenTurningOff() {
-            // no-op
-        }
-
-        @Override
         public void enterStageSplitFromRunningApp(boolean leftOrTop) {
             // no-op
         }
diff --git a/src/com/android/car/carlauncher/recents/CarRecentsActivity.java b/src/com/android/car/carlauncher/recents/CarRecentsActivity.java
index ed1cebf..740c87f 100644
--- a/src/com/android/car/carlauncher/recents/CarRecentsActivity.java
+++ b/src/com/android/car/carlauncher/recents/CarRecentsActivity.java
@@ -25,7 +25,6 @@
 import android.graphics.Rect;
 import android.os.Bundle;
 import android.view.View;
-import android.view.ViewTreeObserver;
 import android.view.WindowInsets;
 import android.view.WindowMetrics;
 import android.widget.Toast;
@@ -40,7 +39,6 @@
 import com.android.car.carlauncher.R;
 import com.android.car.carlauncher.recents.view.RecentTasksAdapter;
 import com.android.car.carlauncher.recents.view.RecentsRecyclerView;
-import com.android.car.carlauncher.recents.view.TaskSnapHelper;
 import com.android.car.carlauncher.recents.view.TaskTouchHelperCallback;
 
 import java.util.HashSet;
@@ -62,8 +60,6 @@
     private Animator mClearAllAnimator;
     private NonDODisabledTaskProvider mNonDODisabledTaskProvider;
     private Set<String> mPackagesToHideFromRecents;
-    private TaskSnapHelper mTaskSnapHelper;
-    private ViewTreeObserver.OnTouchModeChangeListener mOnTouchModeChangeListener;
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
@@ -100,7 +96,10 @@
         mGridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
             @Override
             public int getSpanSize(int position) {
-                if (position == 0) {
+                boolean isLastPosition = mRecentsRecyclerView != null
+                        && mRecentsRecyclerView.getAdapter() != null
+                        && position == mRecentsRecyclerView.getAdapter().getItemCount() - 1;
+                if (position == 0 || isLastPosition) {
                     return gridSpanCount;
                 }
                 return 1;
@@ -122,23 +121,6 @@
                 getResources().getFloat(R.dimen.recent_task_swiped_threshold)));
         itemTouchHelper.attachToRecyclerView(mRecentsRecyclerView);
 
-        mTaskSnapHelper = new TaskSnapHelper(gridSpanCount,
-                getResources().getInteger(R.integer.config_recents_columns_per_page));
-        mTaskSnapHelper.attachToRecyclerView(mRecentsRecyclerView);
-
-        mOnTouchModeChangeListener = isInTouchMode -> {
-            if (isInTouchMode) {
-                mTaskSnapHelper.attachToRecyclerView(mRecentsRecyclerView);
-            } else {
-                mTaskSnapHelper.attachToRecyclerView(null);
-            }
-        };
-        mRecentsRecyclerView.getViewTreeObserver()
-                .addOnTouchModeChangeListener(mOnTouchModeChangeListener);
-
-        mRecentsRecyclerView.setAdapter(new RecentTasksAdapter(this, getLayoutInflater(),
-                itemTouchHelper));
-
         mClearAllAnimator = AnimatorInflater.loadAnimator(this,
                 R.animator.recents_clear_all);
         mClearAllAnimator.addListener(new AnimatorListenerAdapter() {
@@ -150,8 +132,10 @@
             }
         });
         mClearAllAnimator.setTarget(mRecentsRecyclerView);
-        View clearAllButton = findViewById(R.id.clear_all_button);
-        clearAllButton.setOnClickListener(v -> mClearAllAnimator.start());
+        View.OnClickListener clearAllOnClickListener = v -> mClearAllAnimator.start();
+
+        mRecentsRecyclerView.setAdapter(new RecentTasksAdapter(this, getLayoutInflater(),
+                itemTouchHelper, clearAllOnClickListener));
     }
 
     @Override
@@ -185,8 +169,6 @@
         mRecentTasksViewModel.terminate();
         mClearAllAnimator.end();
         mClearAllAnimator.removeAllListeners();
-        mRecentsRecyclerView.getViewTreeObserver()
-                .removeOnTouchModeChangeListener(mOnTouchModeChangeListener);
     }
 
     @Override
diff --git a/src/com/android/car/carlauncher/recents/RecentsUtils.java b/src/com/android/car/carlauncher/recents/RecentsUtils.java
new file mode 100644
index 0000000..a0c5786
--- /dev/null
+++ b/src/com/android/car/carlauncher/recents/RecentsUtils.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2023 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.car.carlauncher.recents;
+
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
+
+/**
+ * Utility methods that are used by Recents classes.
+ */
+public class RecentsUtils {
+    /**
+     * Checks if the items in the RecyclerView are arranged from right to left.
+     *
+     * @return true if the layout is right to left.
+     */
+    public static boolean areItemsRightToLeft(@NonNull RecyclerView recyclerView) {
+        boolean isLayoutReversed = false;
+        if (recyclerView.getLayoutManager() instanceof LinearLayoutManager) {
+            isLayoutReversed = ((LinearLayoutManager) recyclerView.getLayoutManager())
+                    .getReverseLayout();
+        }
+        return recyclerView.isLayoutRtl() ^ isLayoutReversed;
+    }
+}
diff --git a/src/com/android/car/carlauncher/recents/view/BaseViewHolder.java b/src/com/android/car/carlauncher/recents/view/BaseTaskViewHolder.java
similarity index 93%
rename from src/com/android/car/carlauncher/recents/view/BaseViewHolder.java
rename to src/com/android/car/carlauncher/recents/view/BaseTaskViewHolder.java
index 4cff06e..8bea629 100644
--- a/src/com/android/car/carlauncher/recents/view/BaseViewHolder.java
+++ b/src/com/android/car/carlauncher/recents/view/BaseTaskViewHolder.java
@@ -30,11 +30,11 @@
 /**
  * Base ViewHolder for Recent tasks.
  */
-public class BaseViewHolder extends RecyclerView.ViewHolder {
+public class BaseTaskViewHolder extends RecyclerView.ViewHolder {
     private final ImageView mThumbnailImageView;
     private final ImageView mIconImageView;
 
-    public BaseViewHolder(@NonNull View itemView) {
+    public BaseTaskViewHolder(@NonNull View itemView) {
         super(itemView);
         mThumbnailImageView = itemView.findViewById(R.id.task_thumbnail);
         mIconImageView = itemView.findViewById(R.id.task_icon);
diff --git a/src/com/android/car/carlauncher/recents/view/ClearAllViewHolder.java b/src/com/android/car/carlauncher/recents/view/ClearAllViewHolder.java
new file mode 100644
index 0000000..1f47b51
--- /dev/null
+++ b/src/com/android/car/carlauncher/recents/view/ClearAllViewHolder.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2023 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.car.carlauncher.recents.view;
+
+import android.view.View;
+import android.widget.Button;
+
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.android.car.carlauncher.R;
+
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * ViewHolder to contain the clear all button.
+ */
+public class ClearAllViewHolder extends RecyclerView.ViewHolder {
+    /**
+     * @param onClickListener the onClickListener to be attached to the clear all button.
+     */
+    public ClearAllViewHolder(@NotNull View itemView, View.OnClickListener onClickListener) {
+        super(itemView);
+        Button clearAllButton = itemView.findViewById(R.id.recents_clear_all_button);
+        clearAllButton.setOnClickListener(onClickListener);
+    }
+}
diff --git a/src/com/android/car/carlauncher/recents/view/RecentTasksAdapter.java b/src/com/android/car/carlauncher/recents/view/RecentTasksAdapter.java
index e82e3de..9be766e 100644
--- a/src/com/android/car/carlauncher/recents/view/RecentTasksAdapter.java
+++ b/src/com/android/car/carlauncher/recents/view/RecentTasksAdapter.java
@@ -19,7 +19,6 @@
 import android.annotation.IntDef;
 import android.content.Context;
 import android.graphics.Bitmap;
-import android.graphics.Color;
 import android.graphics.drawable.Drawable;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -27,7 +26,6 @@
 
 import androidx.annotation.NonNull;
 import androidx.annotation.VisibleForTesting;
-import androidx.recyclerview.widget.GridLayoutManager;
 import androidx.recyclerview.widget.ItemTouchHelper;
 import androidx.recyclerview.widget.RecyclerView;
 
@@ -41,20 +39,20 @@
 /**
  * Adapter that is used to display the list of Recent tasks.
  * ViewTypes in this adapter:
- * - FIRST_ITEM_VIEW_TYPE:  First task has special handling since it is takes up more area.
- * - HIDDEN_ITEM_VIEW_TYPE: Hidden ViewHolders are added to/removed from the end to always maintain
- *                          complete pages.
- * - DEFAULT_ITEM_VIEW_TYPE: all other view holders that hold a recent task.
+ * - FIRST_TASK_ITEM_VIEW_TYPE:  First task has special handling since it is takes up more area.
+ * - DEFAULT_TASK_ITEM_VIEW_TYPE: all other view holders that hold a recent task.
+ * - CLEAR_ALL_VIEW_TYPE: represents the view that contains the clear all button.
  */
-public class RecentTasksAdapter extends RecyclerView.Adapter<BaseViewHolder> implements
+public class RecentTasksAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> implements
         RecentTasksViewModel.RecentTasksChangeListener {
-    @IntDef({RecentsItemViewType.DEFAULT_ITEM_VIEW_TYPE, RecentsItemViewType.FIRST_ITEM_VIEW_TYPE,
-            RecentsItemViewType.HIDDEN_ITEM_VIEW_TYPE})
+    @IntDef({RecentsItemViewType.DEFAULT_TASK_ITEM_VIEW_TYPE,
+            RecentsItemViewType.FIRST_TASK_ITEM_VIEW_TYPE,
+            RecentsItemViewType.CLEAR_ALL_VIEW_TYPE})
     @Retention(RetentionPolicy.SOURCE)
     @interface RecentsItemViewType {
-        int DEFAULT_ITEM_VIEW_TYPE = 0;
-        int FIRST_ITEM_VIEW_TYPE = 1;
-        int HIDDEN_ITEM_VIEW_TYPE = 2;
+        int DEFAULT_TASK_ITEM_VIEW_TYPE = 0;
+        int FIRST_TASK_ITEM_VIEW_TYPE = 1;
+        int CLEAR_ALL_VIEW_TYPE = 2;
     }
 
     private static final byte THUMBNAIL_UPDATED = 0x1; // 00000001
@@ -62,54 +60,48 @@
     private final RecentTasksViewModel mRecentTasksViewModel;
     private final LayoutInflater mLayoutInflater;
     private final ItemTouchHelper mItemTouchHelper;
+    private final View.OnClickListener mClearAllOnClickListener;
     private final float mStartSwipeThreshold;
-    private final int mColumnsPerPage;
-    private final Drawable mHiddenTaskIcon;
-    private final Bitmap mHiddenThumbnail;
-    private int mEmptyViewHolderCount;
-    private int mSpanCount;
 
     public RecentTasksAdapter(Context context, LayoutInflater layoutInflater,
-            ItemTouchHelper itemTouchHelper) {
-        this(context, layoutInflater, itemTouchHelper, RecentTasksViewModel.getInstance());
+            ItemTouchHelper itemTouchHelper, View.OnClickListener clearAllOnClickListener) {
+        this(context, layoutInflater, itemTouchHelper, RecentTasksViewModel.getInstance(),
+                clearAllOnClickListener);
     }
 
     @VisibleForTesting
     public RecentTasksAdapter(Context context, LayoutInflater layoutInflater,
-            ItemTouchHelper itemTouchHelper, RecentTasksViewModel recentTasksViewModel) {
+            ItemTouchHelper itemTouchHelper, RecentTasksViewModel recentTasksViewModel,
+            View.OnClickListener clearAllOnClickListener) {
         mRecentTasksViewModel = recentTasksViewModel;
         mRecentTasksViewModel.addRecentTasksChangeListener(this);
         mLayoutInflater = layoutInflater;
         mItemTouchHelper = itemTouchHelper;
-        mColumnsPerPage = context.getResources().getInteger(
-                R.integer.config_recents_columns_per_page);
         mStartSwipeThreshold = context.getResources().getFloat(
                 R.dimen.recent_task_start_swipe_threshold);
-        mHiddenTaskIcon = context.getResources().getDrawable(
-                R.drawable.recent_task_hidden_icon, /* theme= */ null);
-        mHiddenThumbnail = mRecentTasksViewModel.createThumbnail(
-                Color.argb(/* alpha= */ 0, /* red= */ 0, /* green= */ 0, /* blue= */ 0));
+        mClearAllOnClickListener = clearAllOnClickListener;
     }
 
     @NonNull
     @Override
-    public BaseViewHolder onCreateViewHolder(@NonNull ViewGroup parent,
+    public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent,
             @RecentsItemViewType int viewType) {
         switch (viewType) {
-            case RecentsItemViewType.FIRST_ITEM_VIEW_TYPE:
+            case RecentsItemViewType.FIRST_TASK_ITEM_VIEW_TYPE:
                 return new TaskViewHolder(mLayoutInflater.inflate(R.layout.recent_task_view_first,
                         parent, /* attachToRoot= */ false));
-            case RecentsItemViewType.HIDDEN_ITEM_VIEW_TYPE:
-                return new BaseViewHolder(mLayoutInflater.inflate(R.layout.recent_task_view_hidden,
-                        parent, /* attachToRoot= */ false));
+            case RecentsItemViewType.CLEAR_ALL_VIEW_TYPE:
+                return new ClearAllViewHolder(
+                        mLayoutInflater.inflate(R.layout.recent_clear_all_view, parent,
+                                /* attachToRoot= */ false), mClearAllOnClickListener);
             default:
-                return new TaskViewHolder(mLayoutInflater.inflate(R.layout.recent_task_view, parent,
-                        /* attachToRoot= */ false));
+                return new TaskViewHolder(mLayoutInflater.inflate(R.layout.recent_task_view,
+                        parent, /* attachToRoot= */ false));
         }
     }
 
     @Override
-    public void onBindViewHolder(@NonNull BaseViewHolder holder, int position) {
+    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
         if (holder instanceof TaskViewHolder) {
             TaskViewHolder taskViewHolder = (TaskViewHolder) holder;
             Drawable taskIcon = mRecentTasksViewModel.getRecentTaskIconAt(position);
@@ -122,35 +114,34 @@
                     /* dismissTaskClickListener= */ new DismissTaskClickListener(position),
                     /* taskTouchListener= */
                     new TaskTouchListener(mStartSwipeThreshold, mItemTouchHelper, holder));
-            return;
         }
-        holder.bind(mHiddenTaskIcon, mHiddenThumbnail);
     }
 
     @Override
-    public void onBindViewHolder(@NonNull BaseViewHolder holder, int position,
+    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position,
             @NonNull List<Object> payloads) {
-        if (payloads.isEmpty()) {
+        if (payloads.isEmpty() || !(holder instanceof BaseTaskViewHolder)) {
             super.onBindViewHolder(holder, position, payloads);
             return;
         }
+        BaseTaskViewHolder baseTaskViewHolder = (BaseTaskViewHolder) holder;
         payloads.forEach(payload -> {
             if (payload instanceof Byte) {
                 byte updateType = (Byte) payload;
                 if ((updateType & THUMBNAIL_UPDATED) > 0) {
                     Bitmap taskThumbnail = mRecentTasksViewModel.getRecentTaskThumbnailAt(position);
-                    holder.updateThumbnail(taskThumbnail);
+                    baseTaskViewHolder.updateThumbnail(taskThumbnail);
                 }
                 if ((updateType & ICON_UPDATED) > 0) {
                     Drawable taskIcon = mRecentTasksViewModel.getRecentTaskIconAt(position);
-                    holder.updateIcon(taskIcon);
+                    baseTaskViewHolder.updateIcon(taskIcon);
                 }
             }
         });
     }
 
     @Override
-    public void onViewAttachedToWindow(@NonNull BaseViewHolder holder) {
+    public void onViewAttachedToWindow(@NonNull RecyclerView.ViewHolder holder) {
         super.onViewAttachedToWindow(holder);
         if (holder instanceof TaskViewHolder) {
             ((TaskViewHolder) holder).attachedToWindow();
@@ -158,7 +149,7 @@
     }
 
     @Override
-    public void onViewDetachedFromWindow(@NonNull BaseViewHolder holder) {
+    public void onViewDetachedFromWindow(@NonNull RecyclerView.ViewHolder holder) {
         super.onViewDetachedFromWindow(holder);
         if (holder instanceof TaskViewHolder) {
             ((TaskViewHolder) holder).detachedFromWindow();
@@ -167,27 +158,20 @@
 
     @Override
     public int getItemCount() {
-        return mRecentTasksViewModel.getRecentTasksSize() + mEmptyViewHolderCount;
+        // +1 to account for clear-all button at the end of the list
+        return mRecentTasksViewModel.getRecentTasksSize() + 1;
     }
 
     @Override
     public int getItemViewType(int position) {
         if (position == 0) {
-            return RecentsItemViewType.FIRST_ITEM_VIEW_TYPE;
+            return RecentsItemViewType.FIRST_TASK_ITEM_VIEW_TYPE;
         }
-        if (position >= mRecentTasksViewModel.getRecentTasksSize()) {
-            return RecentsItemViewType.HIDDEN_ITEM_VIEW_TYPE;
+        if (position == getItemCount() - 1) {
+            // last item is always clear all view
+            return RecentsItemViewType.CLEAR_ALL_VIEW_TYPE;
         }
-        return RecentsItemViewType.DEFAULT_ITEM_VIEW_TYPE;
-    }
-
-    @Override
-    public void onAttachedToRecyclerView(@NonNull RecyclerView recyclerView) {
-        super.onAttachedToRecyclerView(recyclerView);
-        if (recyclerView.getLayoutManager() instanceof GridLayoutManager) {
-            GridLayoutManager layoutManager = (GridLayoutManager) recyclerView.getLayoutManager();
-            mSpanCount = layoutManager.getSpanCount();
-        }
+        return RecentsItemViewType.DEFAULT_TASK_ITEM_VIEW_TYPE;
     }
 
     @Override
@@ -196,9 +180,6 @@
         if (tasksCount <= 0) {
             return;
         }
-        mEmptyViewHolderCount = calculateEmptyItemsNeededToCompletePages(
-                mRecentTasksViewModel.getRecentTasksSize() - 1,
-                mSpanCount, mColumnsPerPage);
         notifyDataSetChanged();
     }
 
@@ -214,8 +195,6 @@
 
     @Override
     public void onAllRecentTasksRemoved(int countRemoved) {
-        countRemoved += mEmptyViewHolderCount;
-        mEmptyViewHolderCount = 0;
         this.notifyItemRangeRemoved(0, countRemoved);
     }
 
@@ -223,34 +202,6 @@
     public void onRecentTaskRemoved(int position) {
         notifyItemRemoved(position);
         notifyItemRangeChanged(position, mRecentTasksViewModel.getRecentTasksSize() - position);
-
-        int newEmptyViewHolderCount = calculateEmptyItemsNeededToCompletePages(
-                mRecentTasksViewModel.getRecentTasksSize() - 1,
-                mSpanCount, mColumnsPerPage);
-        int emptyViewHolderCountChange = newEmptyViewHolderCount - mEmptyViewHolderCount;
-        if (emptyViewHolderCountChange > 0) {
-            notifyItemRangeInserted(getItemCount(), emptyViewHolderCountChange);
-        } else if (emptyViewHolderCountChange < 0) {
-            notifyItemRangeRemoved(mRecentTasksViewModel.getRecentTasksSize(),
-                    Math.abs(emptyViewHolderCountChange));
-        }
-        mEmptyViewHolderCount = newEmptyViewHolderCount;
-    }
-
-    private int calculateEmptyItemsNeededToCompletePages(int listLength, int spanSize,
-            int colPerPage) {
-        if (listLength <= 0) {
-            return 0;
-        }
-
-        int itemsPerPage = colPerPage * spanSize;
-        int lastPageItems = (listLength % itemsPerPage);
-        return lastPageItems == 0 ? 0 : itemsPerPage - lastPageItems;
-    }
-
-    @VisibleForTesting
-    void setEmptyViewHolderCount(int emptyViewHolderCount) {
-        mEmptyViewHolderCount = emptyViewHolderCount;
     }
 }
 
diff --git a/src/com/android/car/carlauncher/recents/view/RecentsRecyclerView.java b/src/com/android/car/carlauncher/recents/view/RecentsRecyclerView.java
index 7cfc0a8..eae5c56 100644
--- a/src/com/android/car/carlauncher/recents/view/RecentsRecyclerView.java
+++ b/src/com/android/car/carlauncher/recents/view/RecentsRecyclerView.java
@@ -25,13 +25,15 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.Px;
-import androidx.recyclerview.widget.LinearLayoutManager;
 import androidx.recyclerview.widget.RecyclerView;
 
 import com.android.car.carlauncher.R;
 import com.android.car.carlauncher.recents.RecentTasksViewModel;
+import com.android.car.carlauncher.recents.RecentsUtils;
 import com.android.internal.annotations.VisibleForTesting;
 
+import org.jetbrains.annotations.NotNull;
+
 /**
  * RecyclerView that centers the first and last elements of the Recent task list by adding
  * appropriate padding.
@@ -40,7 +42,6 @@
     private final int mFirstItemWidth;
     private final int mColSpacing;
     private final int mItemWidth;
-    private final int mColsPerPage;
     private RecentTasksViewModel mRecentTasksViewModel;
     private WindowMetrics mWindowMetrics;
 
@@ -65,7 +66,6 @@
         mFirstItemWidth = getResources().getDimensionPixelSize(R.dimen.recent_task_width_first);
         mItemWidth = getResources().getDimensionPixelSize(R.dimen.recent_task_width);
         mColSpacing = getResources().getDimensionPixelSize(R.dimen.recent_task_col_space);
-        mColsPerPage = getResources().getInteger(R.integer.config_recents_columns_per_page);
     }
 
     @VisibleForTesting
@@ -86,40 +86,83 @@
         if (direction != View.FOCUS_FORWARD && direction != View.FOCUS_BACKWARD) {
             return super.focusSearch(focused, direction);
         }
-        boolean goForward = direction == View.FOCUS_FORWARD;
-        if (shouldBeReversed()) {
-            goForward = !goForward;
-        }
+        boolean shouldGoToNextView = shouldGoToNextView(direction);
         ViewHolder focusedViewHolder = findContainingViewHolder(focused);
         if (focusedViewHolder == null) {
             return null;
         }
-
-        View taskDismissButton = focusedViewHolder.itemView.findViewById(R.id.task_dismiss_button);
-        View taskThumbnail = focusedViewHolder.itemView.findViewById(R.id.task_thumbnail);
-        if (focused == taskDismissButton && goForward) {
-            return taskThumbnail;
+        if (focusedViewHolder instanceof BaseTaskViewHolder) {
+            View taskDismissButton = focusedViewHolder.itemView.findViewById(
+                    R.id.task_dismiss_button);
+            View taskThumbnail = focusedViewHolder.itemView.findViewById(R.id.task_thumbnail);
+            if (focused == taskDismissButton && shouldGoToNextView) {
+                return taskThumbnail;
+            }
+            if (focused == taskThumbnail && !shouldGoToNextView) {
+                return taskDismissButton;
+            }
         }
-        if (focused == taskThumbnail && !goForward) {
-            return taskDismissButton;
-        }
-
         int position = focusedViewHolder.getAbsoluteAdapterPosition();
         if (position == NO_POSITION) {
             return null;
         }
-        if (goForward) {
-            ++position;
-        } else {
-            --position;
-        }
 
-        ViewHolder nextFocusViewHolder = findViewHolderForAdapterPosition(position);
+        return getNextFocusView(direction,
+                shouldGoToNextView ? ++position : --position, /* tryScrolling= */ true);
+    }
+
+    /**
+     * Should be called to find the view in the next viewHolder to take focus.
+     *
+     * @param nextPosition next view holder position to be focused.
+     * @param tryScrolling should try to scroll to find the next view holder.
+     *                     This would only happen if view holder at {@code nextPosition} is null.
+     * @return the next view to be focused.
+     */
+    @Nullable
+    private View getNextFocusView(int direction, int nextPosition, boolean tryScrolling) {
+        ViewHolder nextFocusViewHolder = findViewHolderForAdapterPosition(nextPosition);
+
         if (nextFocusViewHolder == null) {
+            if (tryScrolling) {
+                this.smoothScrollBy(direction == View.FOCUS_FORWARD ? mItemWidth : -mItemWidth, 0);
+                this.addOnScrollListener(new OnScrollListener() {
+                    @Override
+                    public void onScrollStateChanged(@NotNull RecyclerView recyclerView,
+                            int newState) {
+                        super.onScrollStateChanged(recyclerView, newState);
+                        if (newState == SCROLL_STATE_IDLE) {
+                            RecentsRecyclerView.this.removeOnScrollListener(this);
+                            View nextFocusedView = getNextFocusView(direction, nextPosition,
+                                    /* tryScrolling= */ false);
+                            if (nextFocusedView != null) {
+                                nextFocusedView.requestFocus();
+                            }
+                        }
+                    }
+                });
+            }
             return null;
         }
-        return goForward ? nextFocusViewHolder.itemView.findViewById(R.id.task_dismiss_button)
-                : nextFocusViewHolder.itemView.findViewById(R.id.task_thumbnail);
+        if (nextFocusViewHolder instanceof BaseTaskViewHolder) {
+            return shouldGoToNextView(direction)
+                    ? nextFocusViewHolder.itemView.findViewById(R.id.task_dismiss_button)
+                    : nextFocusViewHolder.itemView.findViewById(R.id.task_thumbnail);
+        }
+        if (nextFocusViewHolder instanceof ClearAllViewHolder) {
+            return nextFocusViewHolder.itemView.findViewById(R.id.recents_clear_all_button);
+        }
+        return nextFocusViewHolder.itemView;
+
+    }
+
+    /**
+     * @return {@code true} if the {@code direction} is meant to move the focus to next
+     * view {@code false} for previous view.
+     */
+    private boolean shouldGoToNextView(int direction) {
+        return (direction == View.FOCUS_FORWARD)
+                == !RecentsUtils.areItemsRightToLeft(this);
     }
 
     /**
@@ -131,16 +174,9 @@
             setPadding(/* firstItemPadding= */ 0, /* lastItemPadding= */ 0);
             return;
         }
-        int firstItemPadding, lastItemPadding;
-        firstItemPadding = calculateFirstItemPadding(mWindowMetrics.getBounds().width());
-        if (mRecentTasksViewModel.getRecentTasksSize() == 1) {
-            // only one element is left, center it by adding equal padding
-            lastItemPadding = firstItemPadding;
-        } else {
-            lastItemPadding = calculateLastItemPadding(mWindowMetrics.getBounds().width());
-        }
-        setPadding(/* firstItemPadding= */ firstItemPadding,
-                /* lastItemPadding= */ lastItemPadding);
+        setPadding(/* firstItemPadding= */ calculateFirstItemPadding(
+                        mWindowMetrics.getBounds().width()),
+                /* lastItemPadding= */ calculateLastItemPadding());
     }
 
     @Px
@@ -153,31 +189,21 @@
 
     @Px
     @VisibleForTesting
-    int calculateLastItemPadding(@Px int windowWidth) {
-        // This assumes that RecyclerView's width is same as the windowWidth. This is to add padding
-        // before RecyclerView or its children is drawn.
-        return Math.max(0, (windowWidth - (mColsPerPage * (mItemWidth + mColSpacing))) / 2);
+    int calculateLastItemPadding() {
+        // no-op
+        return 0;
     }
 
-
     /**
      * @param firstItemPadding padding set to recyclerView to fit the first item.
      * @param lastItemPadding  padding set to recyclerView to fit the last item.
      */
     private void setPadding(@Px int firstItemPadding, @Px int lastItemPadding) {
-        boolean shouldBeReversed = shouldBeReversed();
+        boolean shouldBeReversed = RecentsUtils.areItemsRightToLeft(this);
         setPaddingRelative(
                 /* start= */ shouldBeReversed ? lastItemPadding : firstItemPadding,
                 getPaddingTop(),
                 /* end= */ shouldBeReversed ? firstItemPadding : lastItemPadding,
                 getPaddingBottom());
     }
-
-    private boolean shouldBeReversed() {
-        boolean isLayoutReversed = false;
-        if (getLayoutManager() instanceof LinearLayoutManager) {
-            isLayoutReversed = ((LinearLayoutManager) getLayoutManager()).getReverseLayout();
-        }
-        return isLayoutRtl() ^ isLayoutReversed;
-    }
 }
diff --git a/src/com/android/car/carlauncher/recents/view/TaskSnapHelper.java b/src/com/android/car/carlauncher/recents/view/TaskSnapHelper.java
deleted file mode 100644
index ec932d4..0000000
--- a/src/com/android/car/carlauncher/recents/view/TaskSnapHelper.java
+++ /dev/null
@@ -1,207 +0,0 @@
-/*
- * Copyright (C) 2023 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.car.carlauncher.recents.view;
-
-import android.view.View;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.recyclerview.widget.RecyclerView;
-import androidx.recyclerview.widget.RecyclerView.LayoutManager;
-import androidx.recyclerview.widget.SnapHelper;
-
-/**
- * Snaps the first item or to the center of a page of items to the center of the RecyclerView.
- */
-public class TaskSnapHelper extends SnapHelper {
-    private final int mSpanCount;
-    private final int mColPerPage;
-    private final int mItemsInPageCount;
-    private RecyclerView mRecyclerView;
-
-    public TaskSnapHelper(int spanCount, int colPerPage) {
-        mSpanCount = spanCount;
-        mColPerPage = colPerPage;
-        mItemsInPageCount = mColPerPage * mSpanCount;
-    }
-
-    @Nullable
-    @Override
-    public int[] calculateDistanceToFinalSnap(@NonNull LayoutManager layoutManager,
-            @NonNull View targetView) {
-        if (mRecyclerView == null) {
-            return new int[]{0, 0};
-        }
-        int adapterPosition = mRecyclerView.getChildAdapterPosition(targetView);
-        int childCenter = Integer.MAX_VALUE;
-        if (adapterPosition == 0) {
-            childCenter = findCenterOfView(targetView);
-        } else {
-            int targetPosition = getChildPosition(adapterPosition, layoutManager, mRecyclerView);
-            if (targetPosition == RecyclerView.NO_POSITION) {
-                return new int[]{0, 0};
-            }
-            childCenter = findCenterOfPage(targetPosition, mItemsInPageCount, layoutManager);
-        }
-        if (childCenter == Integer.MAX_VALUE) {
-            return new int[]{0, 0};
-        }
-        int center = layoutManager.getWidth() / 2;
-        return new int[]{childCenter - center, 0};
-    }
-
-    /**
-     * @return View at {@code 0} adapter position if that view is to be snapped else returns 1st
-     * view in the page that is to be snapped
-     */
-    @Nullable
-    @Override
-    public View findSnapView(LayoutManager layoutManager) {
-        int childCount = layoutManager.getChildCount();
-        if (childCount == 0 || mRecyclerView == null) {
-            return null;
-        }
-        int center = layoutManager.getWidth() / 2;
-        View closestView = null;
-        int closestDistance = Integer.MAX_VALUE;
-        int i = 0;
-        while (i < childCount) {
-            View child = layoutManager.getChildAt(i);
-            if (child == null) {
-                i++;
-                continue;
-            }
-            int adapterPosition = mRecyclerView.getChildAdapterPosition(child);
-            if (adapterPosition == RecyclerView.NO_POSITION) {
-                i++;
-                continue;
-            }
-            int childCenter;
-            if (adapterPosition == 0) {
-                childCenter = findCenterOfView(child);
-                i++;
-            } else {
-                int colNum = (int) Math.ceil(adapterPosition / (float) mSpanCount);
-                // following calculations have -1 to account for the first element not following
-                // regular grid span
-                boolean isStartCol = ((colNum - 1) % mColPerPage) == 0;
-                boolean isFirstInStartCol = ((adapterPosition - 1) % mSpanCount) == 0;
-                if (isStartCol && isFirstInStartCol) {
-                    childCenter = findCenterOfPage(i, mItemsInPageCount, layoutManager);
-                    i += mItemsInPageCount;
-                } else {
-                    i++;
-                    continue;
-                }
-            }
-            if (childCenter == Integer.MAX_VALUE) {
-                continue;
-            }
-            int distanceToCenter = Math.abs(center - childCenter);
-            if (distanceToCenter < closestDistance) {
-                closestView = child;
-                closestDistance = distanceToCenter;
-            }
-        }
-        return closestView;
-    }
-
-    @Override
-    public int findTargetSnapPosition(LayoutManager layoutManager, int velocityX, int velocityY) {
-        return RecyclerView.NO_POSITION;
-    }
-
-    @Override
-    public void attachToRecyclerView(@Nullable RecyclerView recyclerView)
-            throws IllegalStateException {
-        super.attachToRecyclerView(recyclerView);
-        if (mRecyclerView == recyclerView) {
-            return;
-        }
-        if (mRecyclerView != null) {
-            mRecyclerView.setOnFlingListener(null);
-        }
-        mRecyclerView = recyclerView;
-        if (mRecyclerView == null) {
-            return;
-        }
-        mRecyclerView.setOnFlingListener(new RecyclerView.OnFlingListener() {
-            @Override
-            public boolean onFling(int velocityX, int velocityY) {
-                if (mRecyclerView == null) {
-                    return false;
-                }
-                RecyclerView.LayoutManager layoutManager = mRecyclerView.getLayoutManager();
-                if (layoutManager == null) {
-                    return false;
-                }
-                calculateDistanceToFinalSnapAndScroll(layoutManager, mRecyclerView,
-                        findSnapView(layoutManager));
-                return true;
-            }
-        });
-    }
-
-    private void calculateDistanceToFinalSnapAndScroll(@NonNull LayoutManager layoutManager,
-            @NonNull RecyclerView recyclerView, @Nullable View targetView) {
-        if (targetView == null) {
-            return;
-        }
-        int[] snapDistance = calculateDistanceToFinalSnap(layoutManager, targetView);
-        if (snapDistance == null || (snapDistance[0] == 0 && snapDistance[1] == 0)) {
-            return;
-        }
-        recyclerView.smoothScrollBy(snapDistance[0], snapDistance[1]);
-    }
-
-    private int getChildPosition(int adapterPosition, @NonNull LayoutManager layoutManager,
-            @NonNull RecyclerView recyclerView) {
-        if (adapterPosition == RecyclerView.NO_POSITION) return RecyclerView.NO_POSITION;
-        for (int i = 0; i < layoutManager.getChildCount(); i++) {
-            View child = layoutManager.getChildAt(i);
-            if (child != null && adapterPosition == recyclerView.getChildAdapterPosition(child)) {
-                return i;
-            }
-        }
-        return RecyclerView.NO_POSITION;
-    }
-
-    private int findCenterOfView(@NonNull View child) {
-        int left = child.getLeft();
-        int right = child.getRight();
-        return (left + right) / 2;
-    }
-
-    private int findCenterOfPage(int startViewInPageIndex, int numOfViewsInPage,
-            LayoutManager layoutManager) {
-        int averageCenter = 0;
-        int numOfViewsPresent = 0;
-        for (int i = startViewInPageIndex; i < startViewInPageIndex + numOfViewsInPage; i++) {
-            View child = layoutManager.getChildAt(i);
-            if (child == null) {
-                continue;
-            }
-            averageCenter += findCenterOfView(child);
-            numOfViewsPresent++;
-        }
-        if (numOfViewsPresent == 0) {
-            return Integer.MAX_VALUE;
-        }
-        averageCenter = averageCenter / numOfViewsPresent;
-        return averageCenter;
-    }
-}
diff --git a/src/com/android/car/carlauncher/recents/view/TaskViewHolder.java b/src/com/android/car/carlauncher/recents/view/TaskViewHolder.java
index 068ec8a..420c50f 100644
--- a/src/com/android/car/carlauncher/recents/view/TaskViewHolder.java
+++ b/src/com/android/car/carlauncher/recents/view/TaskViewHolder.java
@@ -31,7 +31,7 @@
  * Builds onto BaseViewHolder by adding functionality of disabled state and adding click and touch
  * listeners.
  */
-public class TaskViewHolder extends BaseViewHolder {
+public class TaskViewHolder extends BaseTaskViewHolder {
     private final ImageView mThumbnailImageView;
     private final ImageView mIconImageView;
     private final ImageView mDismissButton;
diff --git a/src/com/android/car/carlauncher/recyclerview/AppGridAdapter.java b/src/com/android/car/carlauncher/recyclerview/AppGridAdapter.java
index ee78d5c..c56fb62 100644
--- a/src/com/android/car/carlauncher/recyclerview/AppGridAdapter.java
+++ b/src/com/android/car/carlauncher/recyclerview/AppGridAdapter.java
@@ -29,6 +29,7 @@
 import androidx.recyclerview.widget.DiffUtil;
 import androidx.recyclerview.widget.RecyclerView;
 
+import com.android.car.carlauncher.AppGridActivity.Mode;
 import com.android.car.carlauncher.AppGridPageSnapper;
 import com.android.car.carlauncher.AppItem;
 import com.android.car.carlauncher.LauncherItem;
@@ -67,6 +68,7 @@
     private int mPageScrollDestination;
     // the global bounding rect of the app grid including margins (excluding page indicator bar)
     private Rect mPageBound;
+    private Mode mAppGridMode;
 
     public AppGridAdapter(Context context, int numOfCols, int numOfRows,
             LauncherViewModel dataModel, AppItemViewHolder.AppItemDragCallback dragCallback,
@@ -82,6 +84,15 @@
             LayoutInflater layoutInflater, LauncherViewModel dataModel,
             AppItemViewHolder.AppItemDragCallback dragCallback,
             AppGridPageSnapper.AppGridPageSnapCallback snapCallback) {
+        this(context, numOfCols, numOfRows, pageOrientation, layoutInflater,
+                dataModel, dragCallback, snapCallback, Mode.ALL_APPS);
+    }
+
+    public AppGridAdapter(Context context, int numOfCols, int numOfRows,
+            @PageOrientation int pageOrientation,
+            LayoutInflater layoutInflater, LauncherViewModel dataModel,
+            AppItemViewHolder.AppItemDragCallback dragCallback,
+            AppGridPageSnapper.AppGridPageSnapCallback snapCallback, Mode mode) {
         mContext = context;
         mInflater = layoutInflater;
         mNumOfCols = numOfCols;
@@ -92,6 +103,7 @@
         mIndexingHelper = new PageIndexingHelper(numOfCols, numOfRows, pageOrientation);
         mGridOrderedLauncherItems = new ArrayList<>();
         mDataModel = dataModel;
+        mAppGridMode = mode;
     }
 
     /**
@@ -115,6 +127,16 @@
         // notifyDataSetChanged will rebind distraction optimization to all app items
         notifyDataSetChanged();
     }
+
+    /**
+     * Updates the current app grid mode to {@code mode}, then
+     * rebind the view holders.
+     */
+    public void setMode(Mode mode) {
+        mAppGridMode = mode;
+        notifyDataSetChanged();
+    }
+
     /**
      * Sets a new list of launcher items to be displayed in the app grid.
      * This should only be called by onChanged() in the observer as a response to data change in the
@@ -162,7 +184,7 @@
         holder.itemView.setLayoutParams(layoutParams);
 
         AppItemViewHolder.BindInfo bindInfo = new AppItemViewHolder.BindInfo(
-                mIsDistractionOptimizationRequired, mPageBound);
+                mIsDistractionOptimizationRequired, mPageBound, mAppGridMode);
         int adapterIndex = mIndexingHelper.gridPositionToAdaptorIndex(position);
         if (adapterIndex >= mLauncherItems.size()) {
             // the current view holder is an empty item used to pad the last page.
diff --git a/src/com/android/car/carlauncher/recyclerview/AppItemViewHolder.java b/src/com/android/car/carlauncher/recyclerview/AppItemViewHolder.java
index d678b46..18d57cb 100644
--- a/src/com/android/car/carlauncher/recyclerview/AppItemViewHolder.java
+++ b/src/com/android/car/carlauncher/recyclerview/AppItemViewHolder.java
@@ -44,6 +44,7 @@
 import androidx.annotation.Nullable;
 import androidx.recyclerview.widget.RecyclerView;
 
+import com.android.car.carlauncher.AppGridActivity;
 import com.android.car.carlauncher.AppGridPageSnapper.AppGridPageSnapCallback;
 import com.android.car.carlauncher.AppItemDragShadowBuilder;
 import com.android.car.carlauncher.AppMetaData;
@@ -93,9 +94,17 @@
     public static class BindInfo {
         private final boolean mIsDistractionOptimizationRequired;
         private final Rect mPageBound;
-        public BindInfo(boolean isDistractionOptimizationRequired, Rect pageBound) {
+        private final AppGridActivity.Mode mMode;
+        public BindInfo(boolean isDistractionOptimizationRequired,
+                Rect pageBound,
+                AppGridActivity.Mode mode) {
             this.mIsDistractionOptimizationRequired = isDistractionOptimizationRequired;
             this.mPageBound = pageBound;
+            this.mMode = mode;
+        }
+
+        public BindInfo(boolean isDistractionOptimizationRequired, Rect pageBound) {
+            this(isDistractionOptimizationRequired, pageBound, AppGridActivity.Mode.ALL_APPS);
         }
     }
 
@@ -146,6 +155,7 @@
         }
         boolean isDistractionOptimizationRequired = bindInfo.mIsDistractionOptimizationRequired;
         mPageBound = bindInfo.mPageBound;
+        AppGridActivity.Mode mode = bindInfo.mMode;
 
         mHasAppMetadata = true;
         mAppItemView.setFocusable(true);
@@ -215,7 +225,10 @@
                             mActionDownY = event.getY();
                             mCanStartDragAction = false;
                         } else if (action == MotionEvent.ACTION_MOVE
-                                && shouldStartDragAndDrop(event, mActionDownX, mActionDownY)) {
+                                && shouldStartDragAndDrop(event,
+                                mActionDownX,
+                                mActionDownY,
+                                mode)) {
                             startDragAndDrop(event.getX(), event.getY());
                             mCanStartDragAction = false;
                         } else if (action == MotionEvent.ACTION_UP
@@ -357,7 +370,11 @@
 
 
     private boolean shouldStartDragAndDrop(MotionEvent event, float actionDownX,
-            float actionDownY) {
+            float actionDownY, AppGridActivity.Mode mode) {
+        // If App Grid is not in all apps mode, we should not allow drag and drop
+        if (mode != AppGridActivity.Mode.ALL_APPS) {
+            return false;
+        }
         // the move event should be with in the bounds of the app icon
         boolean isEventWithinIcon = event.getX() >= 0 && event.getY() >= 0
                 && event.getX() < mIconScaledSize && event.getY() < mIconScaledSize;
diff --git a/tests/src/com/android/car/carlauncher/AppGridPageSnapperTest.java b/tests/src/com/android/car/carlauncher/AppGridPageSnapperTest.java
index 123badc..25a5c59 100644
--- a/tests/src/com/android/car/carlauncher/AppGridPageSnapperTest.java
+++ b/tests/src/com/android/car/carlauncher/AppGridPageSnapperTest.java
@@ -45,6 +45,7 @@
 import android.widget.TextView;
 
 import androidx.annotation.NonNull;
+import androidx.recyclerview.R;
 import androidx.recyclerview.widget.GridLayoutManager;
 import androidx.recyclerview.widget.RecyclerView;
 import androidx.test.core.app.ActivityScenario;
@@ -101,6 +102,7 @@
                     GridLayoutManager.HORIZONTAL, false);
             rv.setLayoutManager(gridLayoutManager);
             rv.setAdapter(adapter);
+            RecyclerViewIdlingResource.register(mActivityRule.getScenario());
         });
         // Check if first item on the first page is displayed
         onView(withText(getItemText(0, 0))).check(matches(isCompletelyDisplayed()));
@@ -120,15 +122,16 @@
         onView(withText(getItemText(0, 0))).check(matches(isCompletelyDisplayed()));
         // Check if last item on the first page is displayed
         onView(withText(getItemText(mItemPerPage - 1, 0))).check(matches(isCompletelyDisplayed()));
-        RecyclerViewIdlingResource.register(mActivityRule.getScenario());
 
         mActivityRule.getScenario().onActivity(activity -> {
             RecyclerView rv = activity.requireViewById(R.id.list);
             rv.smoothScrollBy(rv.getWidth() / 2, 0);
         });
 
-        // Check if first item on the second page is displayed
+        // Check if first 3 items on the second page is displayed
         onView(withText(getItemText(0, 1))).check(matches(isDisplayed()));
+        onView(withText(getItemText(1, 1))).check(matches(isCompletelyDisplayed()));
+        onView(withText(getItemText(2, 1))).check(matches(isCompletelyDisplayed()));
         // Check if last item on the second page is displayed
         onView(withText(getItemText(mItemPerPage - 1, 1))).check(matches(isDisplayed()));
     }
@@ -149,6 +152,7 @@
                     false);
             rv.setLayoutManager(gridLayoutManager);
             rv.setAdapter(adapter);
+            RecyclerViewIdlingResource.register(mActivityRule.getScenario());
         });
 
         onView(withText(getItemText(0, 0))).check(matches(isCompletelyDisplayed()));
@@ -169,7 +173,6 @@
         onView(withText(getItemText(0, 0))).check(matches(isCompletelyDisplayed()));
         // Check if last item on the first page is displayed
         onView(withText(getItemText(mItemPerPage - 1, 0))).check(matches(isCompletelyDisplayed()));
-        RecyclerViewIdlingResource.register(mActivityRule.getScenario());
 
         mActivityRule.getScenario().onActivity(activity -> {
             RecyclerView rv = activity.requireViewById(R.id.list);
@@ -199,6 +202,7 @@
                     false);
             rv.setLayoutManager(gridLayoutManager);
             rv.setAdapter(adapter);
+            RecyclerViewIdlingResource.register(mActivityRule.getScenario());
         });
 
         // Check if first item on the first page is displayed
@@ -219,14 +223,16 @@
         onView(withText(getItemText(0, 0))).check(matches(isCompletelyDisplayed()));
         // Check if last item on the first page is displayed
         onView(withText(getItemText(mItemPerPage - 1, 0))).check(matches(isCompletelyDisplayed()));
-        RecyclerViewIdlingResource.register(mActivityRule.getScenario());
 
         mActivityRule.getScenario().onActivity(activity -> {
             RecyclerView rv = activity.requireViewById(R.id.list);
             rv.smoothScrollBy(rv.getWidth() / 2, 0);
         });
-        // Check if first item on the second page is displayed
+        // Check if first 3 items on the second page is displayed
         onView(withText(getItemText(0, 1))).check(matches(isCompletelyDisplayed()));
+        onView(withText(getItemText(1, 1))).check(matches(isCompletelyDisplayed()));
+        onView(withText(getItemText(2, 1))).check(matches(isCompletelyDisplayed()));
+
         // Check if last item on the second page is displayed
         onView(withText(getItemText(mItemPerPage - 1, 1))).check(matches(isCompletelyDisplayed()));
 
@@ -245,8 +251,11 @@
             RecyclerView rv = activity.requireViewById(R.id.list);
             rv.smoothScrollBy(-rv.getWidth() / 2, 0);
         });
-        // Check if last item on the second page is displayed
+        // Check if last 3 items on the second page is displayed
         onView(withText(getItemText(mItemPerPage - 1, 1))).check(matches(isCompletelyDisplayed()));
+        onView(withText(getItemText(mItemPerPage - 2, 1))).check(matches(isCompletelyDisplayed()));
+        onView(withText(getItemText(mItemPerPage - 3, 1))).check(matches(isCompletelyDisplayed()));
+
         // Check if first item on the second page is displayed
         onView(withText(getItemText(0, 1))).check(matches(isCompletelyDisplayed()));
     }
@@ -265,6 +274,7 @@
                     GridLayoutManager.HORIZONTAL, false);
             rv.setLayoutManager(gridLayoutManager);
             rv.setAdapter(adapter);
+            RecyclerViewIdlingResource.register(mActivityRule.getScenario());
         });
 
         // Check if first item on the first page is displayed
@@ -285,7 +295,6 @@
         onView(withText(getItemText(0, 0))).check(matches(isCompletelyDisplayed()));
         // Check if last item on the first page is displayed
         onView(withText(getItemText(mItemPerPage - 1, 0))).check(matches(isCompletelyDisplayed()));
-        RecyclerViewIdlingResource.register(mActivityRule.getScenario());
 
         mActivityRule.getScenario().onActivity(activity -> {
             RecyclerView rv = activity.requireViewById(R.id.list);
@@ -302,8 +311,11 @@
             RecyclerView rv = activity.requireViewById(R.id.list);
             rv.smoothScrollBy(rv.getWidth() / 10, 0);
         });
-        // Check if last item on the second page is displayed
+        // Check if last 3 items on the second page is displayed
         onView(withText(getItemText(mItemPerPage - 1, 1))).check(matches(isCompletelyDisplayed()));
+        onView(withText(getItemText(mItemPerPage - 2, 1))).check(matches(isCompletelyDisplayed()));
+        onView(withText(getItemText(mItemPerPage - 3, 1))).check(matches(isCompletelyDisplayed()));
+
         // Check if first item on the second page is displayed
         onView(withText(getItemText(0, 1))).check(matches(isCompletelyDisplayed()));
     }
@@ -322,6 +334,8 @@
                     GridLayoutManager.HORIZONTAL, false);
             rv.setLayoutManager(gridLayoutManager);
             rv.setAdapter(adapter);
+            RecyclerViewIdlingResource.register(mActivityRule.getScenario());
+
         });
 
         // Check if first item on the first page is displayed
@@ -342,7 +356,6 @@
         onView(withText(getItemText(0, 0))).check(matches(isCompletelyDisplayed()));
         // Check if last item on the first page is displayed
         onView(withText(getItemText(mItemPerPage - 1, 0))).check(matches(isCompletelyDisplayed()));
-        RecyclerViewIdlingResource.register(mActivityRule.getScenario());
         simulateFling(Direction.Right);
         verify(mPageSnapper, times(1)).findFirstItemOnNextPage(anyInt());
 
@@ -364,6 +377,8 @@
                     GridLayoutManager.HORIZONTAL, false);
             rv.setLayoutManager(gridLayoutManager);
             rv.setAdapter(adapter);
+            RecyclerViewIdlingResource.register(mActivityRule.getScenario());
+
         });
 
         // Check if first item on the first page is displayed
@@ -384,14 +399,10 @@
         onView(withText(getItemText(0, 0))).check(matches(isCompletelyDisplayed()));
         // Check if last item on the first page is displayed
         onView(withText(getItemText(mItemPerPage - 1, 0))).check(matches(isCompletelyDisplayed()));
-        RecyclerViewIdlingResource.register(mActivityRule.getScenario());
         simulateFling(Direction.Right);
         simulateFling(Direction.Left);
 
         verify(mPageSnapper, times(1)).findFirstItemOnNextPage(anyInt());
-        verify(mPageSnapper, times(1)).findFirstItemOnPrevPage(anyInt());
-
-
         // Check if first item on the first page is displayed
         onView(withText(getItemText(0, 0))).check(matches(isCompletelyDisplayed()));
     }
@@ -449,18 +460,16 @@
     }
 
     public static class RecyclerViewIdlingResource implements IdlingResource, AutoCloseable {
-        private final RecyclerView mRecyclerView;
+        private boolean mIdle = true;
         private ResourceCallback mResourceCallback;
 
         public RecyclerViewIdlingResource(RecyclerView rv) {
-            mRecyclerView = rv;
             rv.addOnScrollListener(new RecyclerView.OnScrollListener() {
                 @Override
                 public void onScrollStateChanged(@NonNull @NotNull RecyclerView recyclerView,
                         int newState) {
-                    if ((newState == RecyclerView.SCROLL_STATE_IDLE
-                            || newState == RecyclerView.SCROLL_STATE_DRAGGING)
-                            && mResourceCallback != null) {
+                    mIdle = newState == RecyclerView.SCROLL_STATE_IDLE;
+                    if (mIdle && mResourceCallback != null) {
                         mResourceCallback.onTransitionToIdle();
                     }
                 }
@@ -479,7 +488,7 @@
 
         @Override
         public boolean isIdleNow() {
-            return mRecyclerView.getScrollState() == mRecyclerView.SCROLL_STATE_IDLE;
+            return mIdle;
         }
 
         @Override
diff --git a/tests/src/com/android/car/carlauncher/AppLauncherUtilsTest.java b/tests/src/com/android/car/carlauncher/AppLauncherUtilsTest.java
index 9e0901b..3e5d2f3 100644
--- a/tests/src/com/android/car/carlauncher/AppLauncherUtilsTest.java
+++ b/tests/src/com/android/car/carlauncher/AppLauncherUtilsTest.java
@@ -63,6 +63,7 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.platform.app.InstrumentationRegistry;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -101,18 +102,34 @@
     private CarMediaManager mCarMediaManager;
     private CarPackageManager mCarPackageManager;
     private XmlPullParserFactory mParserFactory;
+    private Car mCar;
 
     @Before
     public void setUp() throws Exception {
-        Car car = Car.createCar(mMockContext);
-        mCarPackageManager = (CarPackageManager) car.getCarManager(Car.PACKAGE_SERVICE);
-        mCarMediaManager = (CarMediaManager) car.getCarManager(Car.CAR_MEDIA_SERVICE);
-        when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+        mCar = Car.createCar(mMockContext, /* handler = */ null, Car.CAR_WAIT_TIMEOUT_WAIT_FOREVER,
+                (car, ready) -> {
+                    if (!ready) {
+                        mCarPackageManager = null;
+                        mCarMediaManager = null;
+                        return;
+                    }
+                    mCarPackageManager = (CarPackageManager) car.getCarManager(Car.PACKAGE_SERVICE);
+                    mCarMediaManager = (CarMediaManager) car.getCarManager(Car.CAR_MEDIA_SERVICE);
+                    when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+                });
 
         mParserFactory = XmlPullParserFactory.newInstance();
         mParserFactory.setNamespaceAware(true);
     }
 
+    @After
+    public void tearDown() throws Exception {
+        if (mCar != null && mCar.isConnected()) {
+            mCar.disconnect();
+            mCar = null;
+        }
+    }
+
     @Override
     protected void onSessionBuilder(CustomMockitoSessionBuilder session) {
         session.spyStatic(Settings.Secure.class);
diff --git a/tests/src/com/android/car/carlauncher/LauncherViewModelTest.java b/tests/src/com/android/car/carlauncher/LauncherViewModelTest.java
index 63a0c90..a5d8c14 100644
--- a/tests/src/com/android/car/carlauncher/LauncherViewModelTest.java
+++ b/tests/src/com/android/car/carlauncher/LauncherViewModelTest.java
@@ -26,6 +26,7 @@
 import android.content.ComponentName;
 import android.graphics.drawable.Drawable;
 
+import androidx.lifecycle.Observer;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Before;
@@ -43,6 +44,7 @@
 import java.util.List;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
 import java.util.function.Consumer;
 
 @RunWith(AndroidJUnit4.class)
@@ -121,30 +123,31 @@
     }
 
     @Test
-    public void testConcurrentGenerateWithAlphabetizedApps() throws IOException {
-        LauncherItemHelper helper = mock(LauncherItemHelper.class);
-        mLauncherModel.setLauncherItemHelper(helper);
-
+    public void test_concurrentExecution() throws InterruptedException {
+        ExecutorService pool = Executors.newCachedThreadPool();
         for (int i = 0; i < 100; i++) {
-            ExecutorService fetchOrderExecutorService = Executors.newSingleThreadExecutor();
-            fetchOrderExecutorService.execute(() -> {
+            pool.execute(() -> {
                 mLauncherModel.updateAppsOrder();
-                fetchOrderExecutorService.shutdown();
             });
-
-            ExecutorService alphabetizeExecutorService = Executors.newSingleThreadExecutor();
-            alphabetizeExecutorService.execute(() -> {
+            pool.execute(() -> {
                 mLauncherModel.generateAlphabetizedAppOrder(mLauncherAppsInfo);
-                alphabetizeExecutorService.shutdown();
             });
 
         }
-
-        mLauncherModel.getCurrentLauncher().observeForever(launcherItems -> {
-            assertEquals(3, launcherItems.size());
-            assertEquals("A", launcherItems.get(0).getPackageName());
-            assertEquals("B", launcherItems.get(1).getPackageName());
-            assertEquals("C", launcherItems.get(2).getPackageName());
+        pool.shutdown(); // Disable new tasks from being submitted
+        if (!pool.awaitTermination(30, TimeUnit.SECONDS)) {
+            pool.shutdownNow(); // Cancel currently executing tasks
+        }
+        mLauncherModel.getCurrentLauncher().observeForever(new Observer<>() {
+            @Override
+            public void onChanged(List<LauncherItem> launcherItems) {
+                assertEquals(3, launcherItems.size());
+                assertEquals("A", launcherItems.get(0).getPackageName());
+                assertEquals("B", launcherItems.get(1).getPackageName());
+                assertEquals("C", launcherItems.get(2).getPackageName());
+                //remove observer after assertion
+                mLauncherModel.getCurrentLauncher().removeObserver(this);
+            }
         });
     }
 
diff --git a/tests/src/com/android/car/carlauncher/TaskViewManagerTest.java b/tests/src/com/android/car/carlauncher/TaskViewManagerTest.java
index fd870fe..1e2411d 100644
--- a/tests/src/com/android/car/carlauncher/TaskViewManagerTest.java
+++ b/tests/src/com/android/car/carlauncher/TaskViewManagerTest.java
@@ -32,6 +32,7 @@
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.notNull;
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doNothing;
@@ -93,6 +94,7 @@
 import org.mockito.Mock;
 import org.mockito.invocation.InvocationOnMock;
 
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Set;
 import java.util.concurrent.CountDownLatch;
@@ -492,6 +494,93 @@
     }
 
     @Test
+    public void testAddAllowListedActivities() throws Exception {
+        ComponentName componentName1 = ComponentName.unflattenFromString("com.example/.Activity1");
+        ComponentName componentName2 = ComponentName.unflattenFromString("com.example/.Activity2");
+        List<ComponentName> persistentActivities =
+                List.of(componentName1);
+        SemiControlledCarTaskViewCallbacks mockCallbacks = mock(
+                SemiControlledCarTaskViewCallbacks.class);
+        TaskViewManager taskViewManager = createTaskViewManager();
+        runOnMainAndWait(() -> {});
+        mCarServiceLifecycleListener.onLifecycleChanged(mCar, true);
+        // Set up a SemiControlledCarTaskView
+        AtomicReference<ShellTaskOrganizer.TaskListener> rootTaskListener = new AtomicReference<>();
+        SemiControlledCarTaskView taskView = setUpSemiControlledTaskView(taskViewManager,
+                rootTaskListener, /* rootTaskId = */ 1, persistentActivities, mockCallbacks);
+        runOnMainAndWait(() -> {});
+
+        // Action
+        List<ComponentName> activities = new ArrayList<>(persistentActivities);
+        activities.add(componentName2);
+        taskViewManager.addAllowListedActivities(taskView, activities);
+
+        // Assert
+        verify(mCarActivityManager).setPersistentActivitiesOnRootTask(eq(List.of(componentName2)),
+                any());
+    }
+
+    @Test
+    public void testRemoveAllowListedActivities() throws Exception {
+        List<ComponentName> activities =
+                List.of(ComponentName.unflattenFromString("com.example/.MainActivity"));
+        SemiControlledCarTaskViewCallbacks mockCallbacks = mock(
+                SemiControlledCarTaskViewCallbacks.class);
+        TaskViewManager taskViewManager = createTaskViewManager();
+        runOnMainAndWait(() -> {});
+        mCarServiceLifecycleListener.onLifecycleChanged(mCar, true);
+        // Set up a SemiControlledCarTaskView
+        AtomicReference<ShellTaskOrganizer.TaskListener> rootTaskListener = new AtomicReference<>();
+        SemiControlledCarTaskView taskView = setUpSemiControlledTaskView(taskViewManager,
+                rootTaskListener, /* rootTaskId = */ 1, activities, mockCallbacks);
+        verify(mCarActivityManager).setPersistentActivitiesOnRootTask(eq(activities), any());
+
+        // Action
+        taskViewManager.removeAllowListedActivities(taskView, activities);
+
+        // Assert
+        verify(mCarActivityManager).setPersistentActivitiesOnRootTask(eq(activities), eq(null));
+        assertThat(taskView.getPersistentActivities().size()).isEqualTo(0);
+    }
+
+    @Test
+    public void testSetAllowListedActivities() throws Exception {
+        ComponentName componentName1 = ComponentName.unflattenFromString("com.example/.Activity1");
+        ComponentName componentName2 = ComponentName.unflattenFromString("com.example/.Activity2");
+        ComponentName componentName3 = ComponentName.unflattenFromString("com.example/.Activity3");
+        List<ComponentName> activities1 =
+                List.of(componentName1, componentName2);
+        List<ComponentName> activities2 =
+                List.of(componentName2, componentName3);
+
+        SemiControlledCarTaskViewCallbacks mockCallbacks = mock(
+                SemiControlledCarTaskViewCallbacks.class);
+        TaskViewManager taskViewManager = createTaskViewManager();
+        runOnMainAndWait(() -> {
+        });
+        mCarServiceLifecycleListener.onLifecycleChanged(mCar, true);
+        // Set up a SemiControlledCarTaskView
+        AtomicReference<ShellTaskOrganizer.TaskListener> rootTaskListener = new AtomicReference<>();
+        SemiControlledCarTaskView taskView = setUpSemiControlledTaskView(taskViewManager,
+                rootTaskListener, /* rootTaskId = */ 1, activities1, mockCallbacks);
+        runOnMainAndWait(() -> {
+        });
+        verify(mCarActivityManager).setPersistentActivitiesOnRootTask(eq(activities1), notNull());
+        assertThat(taskView.getPersistentActivities()).isEqualTo(activities1);
+
+        // Action
+        taskViewManager.setAllowListedActivities(taskView, activities2);
+        runOnMainAndWait(() -> {
+        });
+
+        // Assert
+        verify(mCarActivityManager).setPersistentActivitiesOnRootTask(eq(activities1), eq(null));
+        verify(mCarActivityManager, atLeastOnce()).setPersistentActivitiesOnRootTask(
+                eq(activities2), notNull());
+        assertThat(taskView.getPersistentActivities()).isEqualTo(activities2);
+    }
+
+    @Test
     public void testTaskInfoChanged_semiControlledTaskView_topTaskUpdated() throws Exception {
         TaskViewManager taskViewManager = createTaskViewManager();
         runOnMainAndWait(() -> {});
@@ -576,8 +665,9 @@
             List<ComponentName> persistentActivities,
             SemiControlledCarTaskViewCallbacks callbacks)
             throws Exception {
+        IBinder taskToken = new Binder();
         ActivityManager.RunningTaskInfo rootTaskInfo =
-                createMultiWindowTask(rootTaskId).getTaskInfo();
+                createMultiWindowTask(rootTaskId, taskToken).getTaskInfo();
         doAnswer(invocation -> {
             listener.set(invocation.getArgument(2));
             listener.get().onTaskAppeared(rootTaskInfo, mLeash);
diff --git a/tests/src/com/android/car/carlauncher/homescreen/CardPresenterTest.java b/tests/src/com/android/car/carlauncher/homescreen/CardPresenterTest.java
deleted file mode 100644
index 43bea36..0000000
--- a/tests/src/com/android/car/carlauncher/homescreen/CardPresenterTest.java
+++ /dev/null
@@ -1,122 +0,0 @@
-/*
- * 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.car.carlauncher.homescreen;
-
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import com.android.car.carlauncher.homescreen.ui.CardHeader;
-import com.android.car.carlauncher.homescreen.ui.DescriptiveTextView;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.util.List;
-
-@RunWith(JUnit4.class)
-public class CardPresenterTest {
-
-    private static final CardHeader CARD_HEADER = new CardHeader("appName", /* cardIcon = */ null);
-    private static final DescriptiveTextView CARD_CONTENT = new DescriptiveTextView(/* image = */
-            null, "title", "subtitle");
-
-    private CardPresenter mPresenter;
-
-    @Mock
-    private HomeCardInterface.View mView;
-    @Mock
-    private HomeCardInterface.Model mModel;
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-        mPresenter = new CardPresenter() {
-            @Override
-            public void setModels(List<HomeCardInterface.Model> models) {
-            }
-
-            @Override
-            public void onViewCreated() {
-            }
-
-            @Override
-            public void onViewDestroyed() {
-            }
-        };
-        mPresenter.setView(mView);
-    }
-
-    @Test
-    public void onModelUpdated_nullModel_hidesFragment() {
-        mPresenter.onModelUpdated(null);
-
-        verify(mView).hideCard();
-    }
-
-    @Test
-    public void onModelUpdated_validModel_updatesFragment() {
-        when(mModel.getCardHeader()).thenReturn(CARD_HEADER);
-        when(mModel.getCardContent()).thenReturn(CARD_CONTENT);
-
-        mPresenter.onModelUpdated(mModel);
-
-        verify(mView).updateHeaderView(CARD_HEADER);
-        verify(mView).updateContentView(CARD_CONTENT);
-    }
-
-    @Test
-    public void onModelUpdated_validHeaderNullContent_showsHeaderOnly() {
-        when(mModel.getCardHeader()).thenReturn(CARD_HEADER);
-        when(mModel.getCardContent()).thenReturn(null);
-
-        mPresenter.onModelUpdated(mModel);
-
-        verify(mView).updateHeaderView(CARD_HEADER);
-        verify(mView, never()).updateContentView(any());
-    }
-
-    @Test
-    public void onModelUpdated_nullHeaderValidContent_hidesFragment() {
-        when(mModel.getCardHeader()).thenReturn(null);
-        when(mModel.getCardContent()).thenReturn(CARD_CONTENT);
-
-        mPresenter.onModelUpdated(mModel);
-
-        verify(mView).hideCard();
-        verify(mView, never()).updateContentView(CARD_CONTENT);
-    }
-
-    @Test
-    public void onModelUpdated_withTimes() {
-        when(mModel.getCardContent()).thenReturn(CARD_CONTENT);
-        when(mModel.getCardHeader()).thenReturn(CARD_HEADER);
-
-        mPresenter.onModelUpdated(mModel, /* showTimes= */ true);
-        verify(mView).updateContentView(any(), /* showTimes= */ eq(true));
-
-        mPresenter.onModelUpdated(mModel, false);
-        verify(mView).updateContentView(any(), /* showTimes= */ eq(false));
-    }
-
-}
diff --git a/tests/src/com/android/car/carlauncher/homescreen/assistive/AssistiveCardPresenterTest.java b/tests/src/com/android/car/carlauncher/homescreen/assistive/AssistiveCardPresenterTest.java
index 28a5717..cdc2e91 100644
--- a/tests/src/com/android/car/carlauncher/homescreen/assistive/AssistiveCardPresenterTest.java
+++ b/tests/src/com/android/car/carlauncher/homescreen/assistive/AssistiveCardPresenterTest.java
@@ -24,6 +24,7 @@
 
 import android.view.View;
 
+import com.android.car.carlauncher.homescreen.HomeCardFragment;
 import com.android.car.carlauncher.homescreen.HomeCardInterface;
 import com.android.car.carlauncher.homescreen.ui.CardHeader;
 import com.android.car.carlauncher.homescreen.ui.DescriptiveTextView;
@@ -48,12 +49,14 @@
     @Mock
     private View mFragmentView;
     @Mock
-    private HomeCardInterface.View mView;
+    private HomeCardFragment mView;
     @Mock
-    private HomeCardInterface.Model mModel;
+    private AssistiveModel mModel;
     @Mock
     private ProjectionModel mOtherModel;
 
+    private HomeCardInterface.Model.OnModelUpdateListener mOnModelUpdateListener;
+
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
@@ -61,41 +64,37 @@
         when(mModel.getCardContent()).thenReturn(CARD_CONTENT);
         mPresenter = new AssistiveCardPresenter();
         mPresenter.setView(mView);
+        mOnModelUpdateListener = mPresenter.mOnModelUpdateListener;
     }
 
     @Test
     public void onModelUpdated_updatesFragment() {
-        mPresenter.onModelUpdated(mModel);
-        mPresenter.onViewClicked(mFragmentView);
+        mOnModelUpdateListener.onModelUpdate(mModel);
 
         verify(mView).updateHeaderView(CARD_HEADER);
         verify(mView).updateContentView(CARD_CONTENT);
-        verify(mModel).onClick(mFragmentView);
     }
 
     @Test
     public void onModelUpdated_nullDifferentModel_doesNotUpdate() {
         when(mOtherModel.getCardHeader()).thenReturn(null);
-        mPresenter.onModelUpdated(mModel);
+        mOnModelUpdateListener.onModelUpdate(mModel);
         reset(mView);
 
-        mPresenter.onModelUpdated(mOtherModel);
-        mPresenter.onViewClicked(mFragmentView);
+        mOnModelUpdateListener.onModelUpdate(mOtherModel);
 
         verify(mView, never()).hideCard();
         verify(mView, never()).updateHeaderView(any());
         verify(mView, never()).updateContentView(any());
-        verify(mModel).onClick(mFragmentView);
-        verify(mOtherModel, never()).onClick(any());
     }
 
     @Test
     public void onModelUpdated_nullSameModel_updatesFragment() {
-        mPresenter.onModelUpdated(mModel);
+        mOnModelUpdateListener.onModelUpdate(mModel);
         reset(mView);
         when(mModel.getCardHeader()).thenReturn(null);
 
-        mPresenter.onModelUpdated(mModel);
+        mOnModelUpdateListener.onModelUpdate(mModel);
 
         verify(mView).hideCard();
     }
@@ -104,7 +103,7 @@
     public void onModelUpdated_nullModelAndNullCurrentModel_doesNotUpdate() {
         when(mModel.getCardHeader()).thenReturn(null);
 
-        mPresenter.onModelUpdated(mModel);
+        mOnModelUpdateListener.onModelUpdate(mModel);
 
         verify(mView, never()).hideCard();
     }
diff --git a/tests/src/com/android/car/carlauncher/homescreen/assistive/ProjectionModelTest.java b/tests/src/com/android/car/carlauncher/homescreen/assistive/ProjectionModelTest.java
index cbf4530..7d4715c 100644
--- a/tests/src/com/android/car/carlauncher/homescreen/assistive/ProjectionModelTest.java
+++ b/tests/src/com/android/car/carlauncher/homescreen/assistive/ProjectionModelTest.java
@@ -16,13 +16,19 @@
 
 package com.android.car.carlauncher.homescreen.assistive;
 
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
-import static org.mockito.Mockito.any;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
+import android.car.Car;
+import android.car.CarProjectionManager;
 import android.car.projection.ProjectionStatus;
 import android.content.Context;
 import android.icu.text.MessageFormat;
@@ -38,8 +44,10 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
+import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
 
 import java.util.Collections;
 import java.util.Map;
@@ -80,63 +88,96 @@
                     NONPROJECTING_DEVICE).build();
 
     private ProjectionModel mModel;
+    private MockitoSession mSession;
 
     @Mock
-    private HomeCardInterface.Presenter mPresenter;
+    private HomeCardInterface.Model.OnModelUpdateListener mOnModelUpdateListener;
+    @Mock
+    private Car mMockCar;
+    @Mock
+    private CarProjectionManager mProjectionManager;
 
     @Before
     public void setUp() {
-        MockitoAnnotations.initMocks(this);
-        mModel = new ProjectionModel();
-        mModel.setPresenter(mPresenter);
-        mModel.onCreate(mContext);
-        reset(mPresenter);
+        mSession = mockitoSession()
+                .initMocks(this)
+                .mockStatic(Car.class)
+                .strictness(Strictness.LENIENT)
+                .startMocking();
+        when(mMockCar.getCarManager(CarProjectionManager.class)).thenReturn(mProjectionManager);
+        when(Car.createCar(any(), any(), anyLong(), any())).thenReturn(mMockCar);
     }
 
     @After
     public void tearDown() {
         mModel.onDestroy(mContext);
+        if (mSession != null) {
+            mSession.finishMocking();
+        }
+    }
+
+    @Test
+    public void onCreate_carConnected_registerProjectionStatusListener() {
+        ArgumentCaptor<Car.CarServiceLifecycleListener> carLifecycleCaptor =
+                ArgumentCaptor.forClass(Car.CarServiceLifecycleListener.class);
+        when(Car.createCar(any(), any(), anyLong(), carLifecycleCaptor.capture())).then(
+                invocation -> {
+                    Car.CarServiceLifecycleListener listener = carLifecycleCaptor.getValue();
+                    listener.onLifecycleChanged(mMockCar, true);
+                    return mMockCar;
+                });
+
+        createModel();
+
+        verify(() -> Car.createCar(any(), any(), anyLong(), any()));
+        verify(mProjectionManager).registerProjectionStatusListener(any());
     }
 
     @Test
     public void noChange_doesNotCallPresenter() {
-        verify(mPresenter, never()).onModelUpdated(any());
+        createModel();
+
+        verify(mOnModelUpdateListener, never()).onModelUpdate(any());
         assertNull(mModel.getCardHeader());
         assertNull(mModel.getCardContent());
     }
 
     @Test
     public void changeProjectionStatusToProjectingDevice_callsPresenter() {
+        createModel();
         sendProjectionStatus(mProjectingDeviceProjectionStatus);
 
-        verify(mPresenter).onModelUpdated(mModel);
+        verify(mOnModelUpdateListener).onModelUpdate(mModel);
         DescriptiveTextView content = (DescriptiveTextView) mModel.getCardContent();
         assertEquals(PROJECTING_DEVICE_NAME, String.valueOf(content.getSubtitle()));
     }
 
     @Test
     public void changeProjectionStatusToNonProjectingDevice_callsPresenter() {
+        createModel();
         sendProjectionStatus(mNonProjectingDeviceProjectionStatus);
 
-        verify(mPresenter).onModelUpdated(mModel);
+        verify(mOnModelUpdateListener).onModelUpdate(mModel);
         DescriptiveTextView content = (DescriptiveTextView) mModel.getCardContent();
         assertEquals(NONPROJECTING_DEVICE_NAME, String.valueOf(content.getSubtitle()));
     }
 
     @Test
     public void changeProjectionStatusToSingleProjectingAndNonProjectingDevice_callsPresenter() {
+        createModel();
         sendProjectionStatus(mProjectingAndNonProjectingDeviceProjectionStatus);
 
-        verify(mPresenter).onModelUpdated(mModel);
+        verify(mOnModelUpdateListener).onModelUpdate(mModel);
         DescriptiveTextView content = (DescriptiveTextView) mModel.getCardContent();
         assertEquals(PROJECTING_DEVICE_NAME, String.valueOf(content.getSubtitle()));
     }
 
     @Test
     public void changeProjectionStatusToMultipleProjectingAndNonProjectingDevice_callsPresenter() {
+        createModel();
         sendProjectionStatus(mProjectingMultipleAndNonProjectingDeviceProjectionStatus);
 
-        verify(mPresenter).onModelUpdated(mModel);
+        verify(mOnModelUpdateListener).onModelUpdate(mModel);
         DescriptiveTextView content = (DescriptiveTextView) mModel.getCardContent();
 
         String formattedPluralString = MessageFormat.format(mContext.getString(
@@ -149,18 +190,26 @@
 
     @Test
     public void changeProjectionStatusToInactive_callsPresenter() {
+        createModel();
         sendProjectionStatus(mProjectingDeviceProjectionStatus);
-        reset(mPresenter);
+        reset(mOnModelUpdateListener);
 
         sendProjectionStatus(mInactiveProjectionStatus);
 
-        verify(mPresenter).onModelUpdated(mModel);
+        verify(mOnModelUpdateListener).onModelUpdate(mModel);
         assertNull(mModel.getCardHeader());
         assertNull(mModel.getCardContent());
     }
 
+    private void createModel() {
+        mModel = new ProjectionModel();
+        mModel.setOnModelUpdateListener(mOnModelUpdateListener);
+        mModel.onCreate(mContext);
+        reset(mOnModelUpdateListener);
+    }
+
     private void sendProjectionStatus(ProjectionStatus status) {
-        reset(mPresenter);
+        reset(mOnModelUpdateListener);
         mModel.onProjectionStatusChanged(
                 status.getState(),
                 status.getPackageName(),
diff --git a/tests/src/com/android/car/carlauncher/homescreen/audio/HomeAudioCardPresenterTest.java b/tests/src/com/android/car/carlauncher/homescreen/audio/HomeAudioCardPresenterTest.java
index b53d5bd..160245d 100644
--- a/tests/src/com/android/car/carlauncher/homescreen/audio/HomeAudioCardPresenterTest.java
+++ b/tests/src/com/android/car/carlauncher/homescreen/audio/HomeAudioCardPresenterTest.java
@@ -49,12 +49,14 @@
     @Mock
     private View mFragmentView;
     @Mock
-    private HomeCardInterface.View mView;
+    private AudioFragment mView;
     @Mock
-    private HomeCardInterface.Model mModel;
+    private AudioModel mModel;
     @Mock
     private InCallModel mOtherModel;
 
+    private HomeCardInterface.Model.OnModelUpdateListener mOnModelUpdateListener;
+
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
@@ -62,32 +64,28 @@
         when(mModel.getCardContent()).thenReturn(CARD_CONTENT);
         mPresenter = new HomeAudioCardPresenter();
         mPresenter.setView(mView);
+        mOnModelUpdateListener = mPresenter.mOnModelUpdateListener;
     }
 
     @Test
     public void onModelUpdated_updatesFragment() {
-        mPresenter.onModelUpdated(mModel);
-        mPresenter.onViewClicked(mFragmentView);
+        mOnModelUpdateListener.onModelUpdate(mModel);
 
         verify(mView).updateHeaderView(CARD_HEADER);
         verify(mView).updateContentView(CARD_CONTENT);
-        verify(mModel).onClick(mFragmentView);
     }
 
     @Test
     public void onModelUpdated_nullDifferentModel_doesNotUpdate() {
         when(mOtherModel.getCardHeader()).thenReturn(null);
-        mPresenter.onModelUpdated(mModel);
+        mOnModelUpdateListener.onModelUpdate(mModel);
         reset(mView);
 
-        mPresenter.onModelUpdated(mOtherModel);
-        mPresenter.onViewClicked(mFragmentView);
+        mOnModelUpdateListener.onModelUpdate(mOtherModel);
 
         verify(mView, never()).hideCard();
         verify(mView, never()).updateHeaderView(any());
         verify(mView, never()).updateContentView(any());
-        verify(mModel).onClick(mFragmentView);
-        verify(mOtherModel, never()).onClick(any());
     }
 
     @Test
@@ -99,10 +97,10 @@
                 /* image = */ null, "callerNumber", "ongoingCall");
         when(mOtherModel.getCardHeader()).thenReturn(callModelHeader);
         when(mOtherModel.getCardContent()).thenReturn(callModelContent);
-        mPresenter.onModelUpdated(mOtherModel);
+        mOnModelUpdateListener.onModelUpdate(mOtherModel);
 
         // send MediaModel update during ongoing call
-        mPresenter.onModelUpdated(mModel);
+        mOnModelUpdateListener.onModelUpdate(mModel);
 
         //verify call
         verify(mView).updateHeaderView(callModelHeader);
@@ -114,11 +112,11 @@
 
     @Test
     public void onModelUpdated_nullSameModel_updatesFragment() {
-        mPresenter.onModelUpdated(mModel);
+        mOnModelUpdateListener.onModelUpdate(mModel);
         reset(mView);
         when(mModel.getCardHeader()).thenReturn(null);
 
-        mPresenter.onModelUpdated(mModel);
+        mOnModelUpdateListener.onModelUpdate(mModel);
 
         verify(mView).hideCard();
     }
@@ -127,7 +125,7 @@
     public void onModelUpdated_nullModelAndNullCurrentModel_updatesFragment() {
         when(mModel.getCardHeader()).thenReturn(null);
 
-        mPresenter.onModelUpdated(mModel);
+        mOnModelUpdateListener.onModelUpdate(mModel);
 
         verify(mView, never()).hideCard();
     }
diff --git a/tests/src/com/android/car/carlauncher/homescreen/audio/InCallModelTest.java b/tests/src/com/android/car/carlauncher/homescreen/audio/InCallModelTest.java
index 549e8d0..346f708 100644
--- a/tests/src/com/android/car/carlauncher/homescreen/audio/InCallModelTest.java
+++ b/tests/src/com/android/car/carlauncher/homescreen/audio/InCallModelTest.java
@@ -63,7 +63,7 @@
     private Context mContext;
 
     @Mock
-    private HomeCardInterface.Presenter mPresenter;
+    private HomeCardInterface.Model.OnModelUpdateListener mOnModelUpdateListener;
     @Mock
     private Clock mClock;
 
@@ -74,7 +74,7 @@
         MockitoAnnotations.initMocks(this);
         mContext = ApplicationProvider.getApplicationContext();
         mInCallModel = new InCallModel(mClock);
-        mInCallModel.setPresenter(mPresenter);
+        mInCallModel.setOnModelUpdateListener(mOnModelUpdateListener);
         mInCallModel.onCreate(mContext);
         Resources resources = ApplicationProvider.getApplicationContext().getResources();
         mOngoingCallSecondaryText = resources.getString(R.string.ongoing_call_text);
@@ -88,21 +88,21 @@
 
     @Test
     public void noChange_doesNotCallPresenter() {
-        verify(mPresenter, never()).onModelUpdated(any());
+        verify(mOnModelUpdateListener, never()).onModelUpdate(any());
     }
 
     @Test
     public void onCallRemoved_callsPresenter() {
         mInCallModel.onCallRemoved(mCall);
 
-        verify(mPresenter).onModelUpdated(mInCallModel);
+        verify(mOnModelUpdateListener).onModelUpdate(mInCallModel);
     }
 
     @Test
     public void updateModelWithPhoneNumber_active_setsPhoneNumberAndSubtitle() {
         mInCallModel.updateModelWithPhoneNumber(PHONE_NUMBER, Call.STATE_ACTIVE);
 
-        verify(mPresenter).onModelUpdated(mInCallModel);
+        verify(mOnModelUpdateListener).onModelUpdate(mInCallModel);
         DescriptiveTextWithControlsView content =
                 (DescriptiveTextWithControlsView) mInCallModel.getCardContent();
         String formattedNumber = TelecomUtils.getFormattedNumber(
@@ -115,7 +115,7 @@
     public void updateModelWithPhoneNumber_dialing_setsPhoneNumberAndSubtitle() {
         mInCallModel.updateModelWithPhoneNumber(PHONE_NUMBER, Call.STATE_DIALING);
 
-        verify(mPresenter).onModelUpdated(mInCallModel);
+        verify(mOnModelUpdateListener).onModelUpdate(mInCallModel);
         DescriptiveTextWithControlsView content =
                 (DescriptiveTextWithControlsView) mInCallModel.getCardContent();
         String formattedNumber = TelecomUtils.getFormattedNumber(
@@ -131,7 +131,7 @@
                 /* typeLabel = */ null, /*  lookupKey = */ null);
         mInCallModel.updateModelWithContact(phoneInfo, Call.STATE_ACTIVE);
 
-        verify(mPresenter).onModelUpdated(mInCallModel);
+        verify(mOnModelUpdateListener).onModelUpdate(mInCallModel);
         DescriptiveTextWithControlsView content =
                 (DescriptiveTextWithControlsView) mInCallModel.getCardContent();
         assertEquals(content.getTitle(), DISPLAY_NAME);
@@ -147,7 +147,7 @@
                 /* lookupKey = */ null);
         mInCallModel.updateModelWithContact(phoneInfo, Call.STATE_ACTIVE);
 
-        verify(mPresenter).onModelUpdated(mInCallModel);
+        verify(mOnModelUpdateListener).onModelUpdate(mInCallModel);
         DescriptiveTextWithControlsView content =
                 (DescriptiveTextWithControlsView) mInCallModel.getCardContent();
         assertEquals(content.getTitle(), DISPLAY_NAME);
@@ -162,7 +162,7 @@
                 /* typeLabel = */ null, /*  lookupKey = */ null);
         mInCallModel.updateModelWithContact(phoneInfo, Call.STATE_DIALING);
 
-        verify(mPresenter).onModelUpdated(mInCallModel);
+        verify(mOnModelUpdateListener).onModelUpdate(mInCallModel);
         DescriptiveTextWithControlsView content =
                 (DescriptiveTextWithControlsView) mInCallModel.getCardContent();
         assertEquals(content.getTitle(), DISPLAY_NAME);
@@ -179,7 +179,7 @@
                 CallAudioState.ROUTE_WIRED_OR_EARPIECE, CallAudioState.ROUTE_WIRED_OR_EARPIECE);
         mInCallModel.updateMuteButtonDrawableState(new int[0]);
         mInCallModel.onCallAudioStateChanged(callAudioState);
-        verify(mPresenter, times(1)).onModelUpdated(mInCallModel);
+        verify(mOnModelUpdateListener, times(1)).onModelUpdate(mInCallModel);
 
     }
 
@@ -192,7 +192,7 @@
                 CallAudioState.ROUTE_WIRED_OR_EARPIECE, CallAudioState.ROUTE_WIRED_OR_EARPIECE);
         mInCallModel.updateMuteButtonDrawableState(new int[0]);
         mInCallModel.onCallAudioStateChanged(callAudioState);
-        verify(mPresenter, times(0)).onModelUpdated(mInCallModel);
+        verify(mOnModelUpdateListener, times(0)).onModelUpdate(mInCallModel);
     }
 
     @Test
diff --git a/tests/src/com/android/car/carlauncher/homescreen/audio/MediaViewModelTest.java b/tests/src/com/android/car/carlauncher/homescreen/audio/MediaViewModelTest.java
index e496c44..14252b0 100644
--- a/tests/src/com/android/car/carlauncher/homescreen/audio/MediaViewModelTest.java
+++ b/tests/src/com/android/car/carlauncher/homescreen/audio/MediaViewModelTest.java
@@ -90,7 +90,10 @@
     @Mock
     private PlaybackProgress mProgress;
     @Mock
-    private HomeCardInterface.Presenter mPresenter;
+    private HomeCardInterface.Model.OnModelUpdateListener mOnModelUpdateListener;
+    @Mock
+    private AudioModel.OnProgressUpdateListener mOnProgressUpdateListener;
+
 
     // The tests use the MediaViewModel's observers. To avoid errors with invoking observeForever
     // on a background thread, this rule configures LiveData to execute each task synchronously.
@@ -109,11 +112,12 @@
         when(mPlaybackViewModel.getProgress()).thenReturn(mLiveProgress);
         when(mPlaybackViewModel.getPlaybackStateWrapper()).thenReturn(mLivePlaybackState);
         when(mPlaybackViewModel.getPlaybackController()).thenReturn(mPlaybackController);
-        mMediaViewModel.setPresenter(mPresenter);
+        mMediaViewModel.setOnModelUpdateListener(mOnModelUpdateListener);
+        mMediaViewModel.setOnProgressUpdateListener(mOnProgressUpdateListener);
         mMediaViewModel.onCreate(ApplicationProvider.getApplicationContext());
         mSeekBarMax = ApplicationProvider.getApplicationContext().getResources().getInteger(
                 com.android.car.carlauncher.R.integer.optional_seekbar_max);
-        reset(mPresenter);
+        reset(mOnModelUpdateListener);
     }
 
     @After
@@ -123,7 +127,7 @@
 
     @Test
     public void noChange_doesNotCallPresenter() {
-        verify(mPresenter, never()).onModelUpdated(any());
+        verify(mOnModelUpdateListener, never()).onModelUpdate(any());
         assertNull(mMediaViewModel.getCardHeader());
         DescriptiveTextWithControlsView content =
                 (DescriptiveTextWithControlsView) mMediaViewModel.getCardContent();
@@ -133,7 +137,7 @@
 
     @Test
     public void changeSourceAndMetadata_updatesModel() {
-        when(mMediaSource.getDisplayName()).thenReturn(APP_NAME);
+        when(mMediaSource.getDisplayName(any())).thenReturn(APP_NAME);
         when(mMediaSource.getIcon()).thenReturn(APP_ICON);
         when(mMetadata.getSubtitle()).thenReturn(ARTIST_NAME);
         when(mMetadata.getTitle()).thenReturn(SONG_TITLE);
@@ -143,7 +147,7 @@
 
         // Model is updated exactly twice: once when source is set (null metadata)
         // and again when the metadata is set
-        verify(mPresenter, times(2)).onModelUpdated(mMediaViewModel);
+        verify(mOnModelUpdateListener, times(2)).onModelUpdate(mMediaViewModel);
         CardHeader header = mMediaViewModel.getCardHeader();
         assertEquals(header.getCardTitle(), APP_NAME);
         assertNull(header.getCardIcon());
@@ -155,12 +159,12 @@
 
     @Test
     public void changeSourceOnly_updatesModel() {
-        when(mMediaSource.getDisplayName()).thenReturn(APP_NAME);
+        when(mMediaSource.getDisplayName(any())).thenReturn(APP_NAME);
         when(mMediaSource.getIcon()).thenReturn(APP_ICON);
 
         mLiveMediaSource.setValue(mMediaSource);
 
-        verify(mPresenter).onModelUpdated(mMediaViewModel);
+        verify(mOnModelUpdateListener, times(2)).onModelUpdate(mMediaViewModel);
         CardHeader header = mMediaViewModel.getCardHeader();
         assertEquals(header.getCardTitle(), APP_NAME);
         assertNull(header.getCardIcon());
@@ -178,7 +182,7 @@
 
         mLiveMetadata.setValue(mMetadata);
 
-        verify(mPresenter, never()).onModelUpdated(any());
+        verify(mOnModelUpdateListener, never()).onModelUpdate(any());
         assertNull(mMediaViewModel.getCardHeader());
         DescriptiveTextWithControlsView content =
                 (DescriptiveTextWithControlsView) mMediaViewModel.getCardContent();
@@ -195,7 +199,7 @@
 
         mLiveProgress.setValue(mProgress);
 
-        verify(mPresenter).onModelUpdated(mMediaViewModel, IS_TIME_AVAILABLE);
+        verify(mOnProgressUpdateListener).onProgressUpdate(mMediaViewModel, IS_TIME_AVAILABLE);
         DescriptiveTextWithControlsView content =
                 (DescriptiveTextWithControlsView) mMediaViewModel.getCardContent();
         SeekBarViewModel seekBarViewModel = content.getSeekBarViewModel();
@@ -213,11 +217,10 @@
         mLiveProgress.setValue(mProgress);
         mLiveColors.setValue(mColors);
 
-        verify(mPresenter, times(1)).onModelUpdated(mMediaViewModel, false);
+        verify(mOnProgressUpdateListener, times(1)).onProgressUpdate(mMediaViewModel, false);
         DescriptiveTextWithControlsView content =
                 (DescriptiveTextWithControlsView) mMediaViewModel.getCardContent();
         SeekBarViewModel seekBarViewModel = content.getSeekBarViewModel();
         assertEquals(seekBarViewModel.getSeekBarColor(), COLORS);
     }
 }
-
diff --git a/tests/src/com/android/car/carlauncher/recents/RecentsUtilsTest.java b/tests/src/com/android/car/carlauncher/recents/RecentsUtilsTest.java
new file mode 100644
index 0000000..99a4133
--- /dev/null
+++ b/tests/src/com/android/car/carlauncher/recents/RecentsUtilsTest.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2023 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.car.carlauncher.recents;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.when;
+
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+public class RecentsUtilsTest {
+    @Mock
+    private RecyclerView mRecyclerView;
+    @Mock
+    private LinearLayoutManager mLinearLayoutManager;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+        when(mRecyclerView.getLayoutManager()).thenReturn(mLinearLayoutManager);
+    }
+
+    @Test
+    public void areItemsRightToLeft_returnsFalse_RTL_reversedLayout() {
+        when(mLinearLayoutManager.getReverseLayout()).thenReturn(true);
+        when(mRecyclerView.isLayoutRtl()).thenReturn(true);
+
+        assertFalse(RecentsUtils.areItemsRightToLeft(mRecyclerView));
+    }
+
+    @Test
+    public void areItemsRightToLeft_returnsTrue_RTL_noReversedLayout() {
+        when(mLinearLayoutManager.getReverseLayout()).thenReturn(false);
+        when(mRecyclerView.isLayoutRtl()).thenReturn(true);
+
+        assertTrue(RecentsUtils.areItemsRightToLeft(mRecyclerView));
+    }
+
+    @Test
+    public void areItemsRightToLeft_returnsTrue_LTR_reversedLayout() {
+        when(mLinearLayoutManager.getReverseLayout()).thenReturn(true);
+        when(mRecyclerView.isLayoutRtl()).thenReturn(false);
+
+        assertTrue(RecentsUtils.areItemsRightToLeft(mRecyclerView));
+    }
+
+    @Test
+    public void areItemsRightToLeft_returnsFalse_LTR_noReversedLayout() {
+        when(mLinearLayoutManager.getReverseLayout()).thenReturn(false);
+        when(mRecyclerView.isLayoutRtl()).thenReturn(false);
+
+        assertFalse(RecentsUtils.areItemsRightToLeft(mRecyclerView));
+    }
+}
diff --git a/tests/src/com/android/car/carlauncher/recents/view/RecentTasksAdapterTest.java b/tests/src/com/android/car/carlauncher/recents/view/RecentTasksAdapterTest.java
deleted file mode 100644
index 80d9c04..0000000
--- a/tests/src/com/android/car/carlauncher/recents/view/RecentTasksAdapterTest.java
+++ /dev/null
@@ -1,175 +0,0 @@
-/*
- * Copyright (C) 2023 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.car.carlauncher.recents.view;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.content.Context;
-import android.content.pm.ApplicationInfo;
-import android.testing.TestableContext;
-import android.view.LayoutInflater;
-
-import androidx.recyclerview.widget.GridLayoutManager;
-import androidx.recyclerview.widget.ItemTouchHelper;
-import androidx.recyclerview.widget.RecyclerView;
-import androidx.test.platform.app.InstrumentationRegistry;
-
-import com.android.car.carlauncher.R;
-import com.android.car.carlauncher.recents.RecentTasksViewModel;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-public class RecentTasksAdapterTest {
-    private static final int COL_PER_PAGE = 2;
-    private static final int SPAN_COUNT = 2;
-
-    private RecentTasksAdapter mRecentTasksAdapter;
-
-    @Mock
-    private RecentTasksViewModel mRecentTasksViewModel;
-    @Mock
-    private LayoutInflater mLayoutInflater;
-    @Mock
-    private ItemTouchHelper mItemTouchHelper;
-    @Mock
-    private RecyclerView mRecyclerView;
-    @Mock
-    private GridLayoutManager mGridLayoutManager;
-
-    @Rule
-    public final TestableContext mContext = new TestableContext(
-            InstrumentationRegistry.getInstrumentation().getTargetContext()) {
-        @Override
-        public Context createApplicationContext(ApplicationInfo application, int flags) {
-            return this;
-        }
-    };
-
-    @Before
-    public void setup() {
-        MockitoAnnotations.initMocks(this);
-        mContext.getOrCreateTestableResources().addOverride(
-                R.integer.config_recents_columns_per_page, /* value= */ COL_PER_PAGE);
-        when(mGridLayoutManager.getSpanCount()).thenReturn(SPAN_COUNT);
-        when(mRecyclerView.getLayoutManager()).thenReturn(mGridLayoutManager);
-        mRecentTasksAdapter = new RecentTasksAdapter(mContext, mLayoutInflater, mItemTouchHelper,
-                mRecentTasksViewModel);
-        mRecentTasksAdapter = spy(mRecentTasksAdapter);
-        mRecentTasksAdapter.onAttachedToRecyclerView(mRecyclerView);
-    }
-
-    @Test
-    public void tasksFetched_completePage_getItemCount_returnsSameItemCount() {
-        // 1st item + 1 page of 4 items
-        when(mRecentTasksViewModel.getRecentTasksSize()).thenReturn(5);
-
-        mRecentTasksAdapter.onRecentTasksFetched();
-
-        assertThat(mRecentTasksAdapter.getItemCount()).isEqualTo(5);
-    }
-
-    @Test
-    public void tasksFetched_incompletePage_getItemCount_returnsCompletePageCount() {
-        // 1st item + 1 page of 3 items
-        when(mRecentTasksViewModel.getRecentTasksSize()).thenReturn(4);
-
-        mRecentTasksAdapter.onRecentTasksFetched();
-
-        assertThat(mRecentTasksAdapter.getItemCount()).isEqualTo(5);
-    }
-
-    @Test
-    public void taskRemoved_incompleteToCompletePage_getItemCount_returnsSameItemCount() {
-        // 1st item + 1 page of 4 items + 1 page of 1 item - removing 1 item
-        when(mRecentTasksViewModel.getRecentTasksSize()).thenReturn(5);
-        // last page had 1 item, so 3 hidden items needed to complete the page
-        mRecentTasksAdapter.setEmptyViewHolderCount(3);
-
-        mRecentTasksAdapter.onRecentTaskRemoved(2);
-
-        assertThat(mRecentTasksAdapter.getItemCount()).isEqualTo(5);
-    }
-
-    @Test
-    public void taskRemoved_incompleteToCompletePage_hiddenItemsRemoved() {
-        // 1st item + 1 page of 4 items + 1 page of 1 item - removing 1 item
-        when(mRecentTasksViewModel.getRecentTasksSize()).thenReturn(5);
-        // last page had 1 item, so 3 hidden items needed to complete the page
-        mRecentTasksAdapter.setEmptyViewHolderCount(3);
-
-        mRecentTasksAdapter.onRecentTaskRemoved(2);
-
-        verify(mRecentTasksAdapter, times(1)).notifyItemRangeRemoved(5, 3);
-    }
-
-    @Test
-    public void taskRemoved_completeToIncompletePage_getItemCount_returnsCompletePageCount() {
-        // 1st item + 1 page of 4 items - removing 1 item
-        when(mRecentTasksViewModel.getRecentTasksSize()).thenReturn(4);
-        // last page had 4 items, so no hidden items needed
-        mRecentTasksAdapter.setEmptyViewHolderCount(0);
-
-        mRecentTasksAdapter.onRecentTaskRemoved(2);
-
-        assertThat(mRecentTasksAdapter.getItemCount()).isEqualTo(5);
-    }
-
-    @Test
-    public void taskRemoved_completeToIncompletePage_hiddenItemsAdded() {
-        // 1st item + 1 page of 4 items - removing 1 item
-        when(mRecentTasksViewModel.getRecentTasksSize()).thenReturn(4);
-        // last page had 4 items, so no hidden items needed
-        mRecentTasksAdapter.setEmptyViewHolderCount(0);
-
-        mRecentTasksAdapter.onRecentTaskRemoved(2);
-
-        verify(mRecentTasksAdapter, times(1)).notifyItemRangeInserted(4, 1);
-    }
-
-    @Test
-    public void taskRemoved_incompleteToIncompletePage_getItemCount_returnsCompletePageCount() {
-        // 1st item + 1 page of 3 items - removing 1 item
-        when(mRecentTasksViewModel.getRecentTasksSize()).thenReturn(3);
-        // last page had 3 items, so 1 hidden item needed to complete the page
-        mRecentTasksAdapter.setEmptyViewHolderCount(1);
-
-        mRecentTasksAdapter.onRecentTaskRemoved(2);
-
-        assertThat(mRecentTasksAdapter.getItemCount()).isEqualTo(5);
-    }
-
-    @Test
-    public void taskRemoved_incompleteToIncompletePage_hiddenItemsAdded() {
-        // 1st item + 1 page of 3 items - removing 1 item
-        when(mRecentTasksViewModel.getRecentTasksSize()).thenReturn(3);
-        // last page had 3 items, so 1 hidden item needed to complete the page
-        mRecentTasksAdapter.setEmptyViewHolderCount(1);
-
-        mRecentTasksAdapter.onRecentTaskRemoved(2);
-
-        verify(mRecentTasksAdapter, times(1)).notifyItemRangeInserted(4, 1);
-    }
-}
diff --git a/tests/src/com/android/car/carlauncher/recents/view/RecentsRecyclerViewTest.java b/tests/src/com/android/car/carlauncher/recents/view/RecentsRecyclerViewTest.java
index 46076df..eb0f2d7 100644
--- a/tests/src/com/android/car/carlauncher/recents/view/RecentsRecyclerViewTest.java
+++ b/tests/src/com/android/car/carlauncher/recents/view/RecentsRecyclerViewTest.java
@@ -16,10 +16,16 @@
 
 package com.android.car.carlauncher.recents.view;
 
+import static androidx.recyclerview.widget.RecyclerView.SCROLL_STATE_IDLE;
+
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
@@ -29,6 +35,8 @@
 import android.util.LayoutDirection;
 import android.view.View;
 import android.view.WindowMetrics;
+import android.widget.Button;
+import android.widget.ImageView;
 
 import androidx.recyclerview.widget.GridLayoutManager;
 import androidx.recyclerview.widget.RecyclerView;
@@ -38,11 +46,12 @@
 import com.android.car.carlauncher.R;
 import com.android.car.carlauncher.recents.RecentTasksViewModel;
 
-import org.jetbrains.annotations.NotNull;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
@@ -52,7 +61,6 @@
     private static final int FIRST_TASK_WIDTH = 800;
     private static final int TASK_WIDTH = 300;
     private static final int COL_SPACING_BETWEEN_TASKS = 50;
-    private static final int COL_PER_PAGE = 2;
     private static final int CURRENT_ADAPTER_POSITION = 2;
     private static final int PREVIOUS_ADAPTER_POSITION = 1;
     private static final int NEXT_ADAPTER_POSITION = 3;
@@ -64,29 +72,38 @@
     private WindowMetrics mWindowMetrics;
     @Mock
     private GridLayoutManager mGridLayoutManager;
-
     @Mock
     private View mCurrentTaskView;
     @Mock
-    private View mCurrentThumbnailView;
+    private ImageView mCurrentThumbnailView;
     @Mock
     private View mCurrentDismissView;
     @Mock
     private View mNextTaskView;
     @Mock
-    private View mNextThumbnailView;
+    private ImageView mNextThumbnailView;
     @Mock
     private View mNextDismissView;
     @Mock
     private View mPreviousTaskView;
     @Mock
-    private View mPreviousThumbnailView;
+    private ImageView mPreviousThumbnailView;
     @Mock
     private View mPreviousDismissView;
+    @Mock
+    private View mClearAllView;
+    @Mock
+    private Button mClearAllButtonView;
+    @Mock
+    private View.OnClickListener mClearAllButtonClickListener;
 
-    private RecyclerView.ViewHolder mCurrentViewHolder;
-    private RecyclerView.ViewHolder mNextViewHolder;
-    private RecyclerView.ViewHolder mPreviousViewHolder;
+    @Captor
+    ArgumentCaptor<RecyclerView.OnScrollListener> mOnScrollListenerCaptor;
+
+    private BaseTaskViewHolder mCurrentTaskViewHolder;
+    private BaseTaskViewHolder mNextTaskViewHolder;
+    private BaseTaskViewHolder mPreviousTaskViewHolder;
+    private ClearAllViewHolder mClearAllTaskViewHolder;
 
     @Rule
     public final TestableContext mContext = new TestableContext(
@@ -110,8 +127,6 @@
                 /* value= */ TASK_WIDTH);
         mContext.getOrCreateTestableResources().addOverride(R.dimen.recent_task_col_space,
                 /* value= */ COL_SPACING_BETWEEN_TASKS);
-        mContext.getOrCreateTestableResources().addOverride(
-                R.integer.config_recents_columns_per_page, /* value= */ COL_PER_PAGE);
 
         mRecentsRecyclerView = new RecentsRecyclerView(mContext, mRecentTasksViewModel,
                 mWindowMetrics);
@@ -139,16 +154,6 @@
     }
 
     @Test
-    public void resetPadding_setsTheSameStartAndEndPadding_whenOneRecentTaskPresent() {
-        when(mRecentTasksViewModel.getRecentTasksSize()).thenReturn(1);
-
-        mRecentsRecyclerView.resetPadding();
-
-        assertThat(mRecentsRecyclerView.getPaddingStart())
-                .isEqualTo(mRecentsRecyclerView.getPaddingEnd());
-    }
-
-    @Test
     public void resetPadding_setsStartPadding_toStart_noReverseLayout_noRTL() {
         mRecentsRecyclerView.setLayoutDirection(LayoutDirection.LTR);
         when(mGridLayoutManager.getReverseLayout()).thenReturn(false);
@@ -166,7 +171,7 @@
         when(mGridLayoutManager.getReverseLayout()).thenReturn(false);
         when(mRecentTasksViewModel.getRecentTasksSize()).thenReturn(5);
 
-        int endPadding = mRecentsRecyclerView.calculateLastItemPadding(WINDOW_WIDTH);
+        int endPadding = mRecentsRecyclerView.calculateLastItemPadding();
         mRecentsRecyclerView.resetPadding();
 
         assertThat(mRecentsRecyclerView.getPaddingEnd()).isEqualTo(endPadding);
@@ -190,7 +195,7 @@
         when(mGridLayoutManager.getReverseLayout()).thenReturn(false);
         when(mRecentTasksViewModel.getRecentTasksSize()).thenReturn(5);
 
-        int endPadding = mRecentsRecyclerView.calculateLastItemPadding(WINDOW_WIDTH);
+        int endPadding = mRecentsRecyclerView.calculateLastItemPadding();
         mRecentsRecyclerView.resetPadding();
 
         assertThat(mRecentsRecyclerView.getPaddingStart()).isEqualTo(endPadding);
@@ -214,7 +219,7 @@
         when(mGridLayoutManager.getReverseLayout()).thenReturn(true);
         when(mRecentTasksViewModel.getRecentTasksSize()).thenReturn(5);
 
-        int endPadding = mRecentsRecyclerView.calculateLastItemPadding(WINDOW_WIDTH);
+        int endPadding = mRecentsRecyclerView.calculateLastItemPadding();
         mRecentsRecyclerView.resetPadding();
 
         assertThat(mRecentsRecyclerView.getPaddingStart()).isEqualTo(endPadding);
@@ -238,7 +243,7 @@
         when(mGridLayoutManager.getReverseLayout()).thenReturn(false);
         when(mRecentTasksViewModel.getRecentTasksSize()).thenReturn(5);
 
-        int endPadding = mRecentsRecyclerView.calculateLastItemPadding(WINDOW_WIDTH);
+        int endPadding = mRecentsRecyclerView.calculateLastItemPadding();
         mRecentsRecyclerView.resetPadding();
 
         assertThat(mRecentsRecyclerView.getPaddingEnd()).isEqualTo(endPadding);
@@ -249,7 +254,7 @@
         mRecentsRecyclerView.setLayoutDirection(LayoutDirection.LTR);
         when(mGridLayoutManager.getReverseLayout()).thenReturn(false);
         when(mCurrentDismissView.isFocused()).thenReturn(true);
-        doReturn(mCurrentViewHolder).when(mRecentsRecyclerView).findContainingViewHolder(
+        doReturn(mCurrentTaskViewHolder).when(mRecentsRecyclerView).findContainingViewHolder(
                 mCurrentDismissView);
 
         View nextFocusedView = mRecentsRecyclerView.focusSearch(mCurrentDismissView,
@@ -263,9 +268,9 @@
         mRecentsRecyclerView.setLayoutDirection(LayoutDirection.LTR);
         when(mGridLayoutManager.getReverseLayout()).thenReturn(false);
         when(mCurrentThumbnailView.isFocused()).thenReturn(true);
-        doReturn(mCurrentViewHolder).when(mRecentsRecyclerView).findContainingViewHolder(
+        doReturn(mCurrentTaskViewHolder).when(mRecentsRecyclerView).findContainingViewHolder(
                 mCurrentThumbnailView);
-        doReturn(mNextViewHolder).when(mRecentsRecyclerView).findViewHolderForAdapterPosition(
+        doReturn(mNextTaskViewHolder).when(mRecentsRecyclerView).findViewHolderForAdapterPosition(
                 NEXT_ADAPTER_POSITION);
 
         View nextFocusedView = mRecentsRecyclerView.focusSearch(mCurrentThumbnailView,
@@ -279,7 +284,7 @@
         mRecentsRecyclerView.setLayoutDirection(LayoutDirection.LTR);
         when(mGridLayoutManager.getReverseLayout()).thenReturn(true);
         when(mCurrentDismissView.isFocused()).thenReturn(true);
-        doReturn(mCurrentViewHolder).when(mRecentsRecyclerView).findContainingViewHolder(
+        doReturn(mCurrentTaskViewHolder).when(mRecentsRecyclerView).findContainingViewHolder(
                 mCurrentDismissView);
 
         View nextFocusedView = mRecentsRecyclerView.focusSearch(mCurrentDismissView,
@@ -293,9 +298,9 @@
         mRecentsRecyclerView.setLayoutDirection(LayoutDirection.LTR);
         when(mGridLayoutManager.getReverseLayout()).thenReturn(true);
         when(mCurrentThumbnailView.isFocused()).thenReturn(true);
-        doReturn(mCurrentViewHolder).when(mRecentsRecyclerView).findContainingViewHolder(
+        doReturn(mCurrentTaskViewHolder).when(mRecentsRecyclerView).findContainingViewHolder(
                 mCurrentThumbnailView);
-        doReturn(mNextViewHolder).when(mRecentsRecyclerView).findViewHolderForAdapterPosition(
+        doReturn(mNextTaskViewHolder).when(mRecentsRecyclerView).findViewHolderForAdapterPosition(
                 NEXT_ADAPTER_POSITION);
 
         View nextFocusedView = mRecentsRecyclerView.focusSearch(mCurrentThumbnailView,
@@ -309,7 +314,7 @@
         mRecentsRecyclerView.setLayoutDirection(LayoutDirection.LTR);
         when(mGridLayoutManager.getReverseLayout()).thenReturn(false);
         when(mCurrentThumbnailView.isFocused()).thenReturn(true);
-        doReturn(mCurrentViewHolder).when(mRecentsRecyclerView).findContainingViewHolder(
+        doReturn(mCurrentTaskViewHolder).when(mRecentsRecyclerView).findContainingViewHolder(
                 mCurrentThumbnailView);
 
         View nextFocusedView = mRecentsRecyclerView.focusSearch(mCurrentThumbnailView,
@@ -323,9 +328,10 @@
         mRecentsRecyclerView.setLayoutDirection(LayoutDirection.LTR);
         when(mGridLayoutManager.getReverseLayout()).thenReturn(false);
         when(mCurrentDismissView.isFocused()).thenReturn(true);
-        doReturn(mCurrentViewHolder).when(mRecentsRecyclerView).findContainingViewHolder(
+        doReturn(mCurrentTaskViewHolder).when(mRecentsRecyclerView).findContainingViewHolder(
                 mCurrentDismissView);
-        doReturn(mPreviousViewHolder).when(mRecentsRecyclerView).findViewHolderForAdapterPosition(
+        doReturn(mPreviousTaskViewHolder).when(
+                mRecentsRecyclerView).findViewHolderForAdapterPosition(
                 PREVIOUS_ADAPTER_POSITION);
 
         View nextFocusedView = mRecentsRecyclerView.focusSearch(mCurrentDismissView,
@@ -339,7 +345,7 @@
         mRecentsRecyclerView.setLayoutDirection(LayoutDirection.LTR);
         when(mGridLayoutManager.getReverseLayout()).thenReturn(true);
         when(mCurrentThumbnailView.isFocused()).thenReturn(true);
-        doReturn(mCurrentViewHolder).when(mRecentsRecyclerView).findContainingViewHolder(
+        doReturn(mCurrentTaskViewHolder).when(mRecentsRecyclerView).findContainingViewHolder(
                 mCurrentThumbnailView);
 
         View nextFocusedView = mRecentsRecyclerView.focusSearch(mCurrentThumbnailView,
@@ -353,9 +359,10 @@
         mRecentsRecyclerView.setLayoutDirection(LayoutDirection.LTR);
         when(mGridLayoutManager.getReverseLayout()).thenReturn(true);
         when(mCurrentDismissView.isFocused()).thenReturn(true);
-        doReturn(mCurrentViewHolder).when(mRecentsRecyclerView).findContainingViewHolder(
+        doReturn(mCurrentTaskViewHolder).when(mRecentsRecyclerView).findContainingViewHolder(
                 mCurrentDismissView);
-        doReturn(mPreviousViewHolder).when(mRecentsRecyclerView).findViewHolderForAdapterPosition(
+        doReturn(mPreviousTaskViewHolder).when(
+                mRecentsRecyclerView).findViewHolderForAdapterPosition(
                 PREVIOUS_ADAPTER_POSITION);
 
         View nextFocusedView = mRecentsRecyclerView.focusSearch(mCurrentDismissView,
@@ -364,6 +371,69 @@
         assertThat(nextFocusedView).isEqualTo(mPreviousThumbnailView);
     }
 
+    @Test
+    public void focusSearch_nextViewNotFound_scrollOnce_nextViewFound_focusRequested() {
+        mRecentsRecyclerView.setLayoutDirection(LayoutDirection.LTR);
+        when(mGridLayoutManager.getReverseLayout()).thenReturn(false);
+        when(mCurrentThumbnailView.isFocused()).thenReturn(true);
+        doReturn(mCurrentTaskViewHolder).when(mRecentsRecyclerView).findContainingViewHolder(
+                mCurrentThumbnailView);
+        doReturn(null).when(mRecentsRecyclerView).findViewHolderForAdapterPosition(
+                NEXT_ADAPTER_POSITION);
+
+        View nextFocusedView = mRecentsRecyclerView.focusSearch(mCurrentThumbnailView,
+                View.FOCUS_FORWARD);
+
+        // since view is not loaded, no focused view is returned
+        assertThat(nextFocusedView).isEqualTo(null);
+        verify(mRecentsRecyclerView, times(1)).smoothScrollBy(anyInt(), eq(0));
+        doReturn(mNextTaskViewHolder).when(mRecentsRecyclerView)
+                .findViewHolderForAdapterPosition(NEXT_ADAPTER_POSITION);
+        verify(mRecentsRecyclerView).addOnScrollListener(mOnScrollListenerCaptor.capture());
+        mOnScrollListenerCaptor.getValue().onScrollStateChanged(mRecentsRecyclerView,
+                SCROLL_STATE_IDLE);
+        verify(mNextDismissView).requestFocus();
+    }
+
+    @Test
+    public void focusSearch_nextViewNotFound_scrollOnce_nextViewNotFound_noMoreScroll() {
+        mRecentsRecyclerView.setLayoutDirection(LayoutDirection.LTR);
+        when(mGridLayoutManager.getReverseLayout()).thenReturn(false);
+        when(mCurrentThumbnailView.isFocused()).thenReturn(true);
+        doReturn(mCurrentTaskViewHolder).when(mRecentsRecyclerView).findContainingViewHolder(
+                mCurrentThumbnailView);
+        doReturn(null).when(mRecentsRecyclerView).findViewHolderForAdapterPosition(
+                NEXT_ADAPTER_POSITION);
+
+        View nextFocusedView = mRecentsRecyclerView.focusSearch(mCurrentThumbnailView,
+                View.FOCUS_FORWARD);
+
+        // since view is not loaded, no focused view is returned
+        assertThat(nextFocusedView).isEqualTo(null);
+        verify(mRecentsRecyclerView, times(1)).smoothScrollBy(anyInt(), eq(0));
+        // view holder still null at NEXT_ADAPTER_POSITION
+        verify(mRecentsRecyclerView).addOnScrollListener(mOnScrollListenerCaptor.capture());
+        mOnScrollListenerCaptor.getValue().onScrollStateChanged(mRecentsRecyclerView,
+                SCROLL_STATE_IDLE);
+        verify(mRecentsRecyclerView, times(1)).smoothScrollBy(anyInt(), eq(0));
+    }
+
+    @Test
+    public void focusSearch_thumbnailFocused_forward_clearAllViewFocus_noReverseLayout_NoRTL() {
+        mRecentsRecyclerView.setLayoutDirection(LayoutDirection.LTR);
+        when(mGridLayoutManager.getReverseLayout()).thenReturn(false);
+        when(mCurrentThumbnailView.isFocused()).thenReturn(true);
+        doReturn(mCurrentTaskViewHolder).when(mRecentsRecyclerView).findContainingViewHolder(
+                mCurrentThumbnailView);
+        doReturn(mClearAllTaskViewHolder).when(mRecentsRecyclerView)
+                .findViewHolderForAdapterPosition(NEXT_ADAPTER_POSITION);
+
+        View nextFocusedView = mRecentsRecyclerView.focusSearch(mCurrentThumbnailView,
+                View.FOCUS_FORWARD);
+
+        assertThat(nextFocusedView).isEqualTo(mClearAllButtonView);
+    }
+
     private void initViews() {
         when(mCurrentTaskView.findViewById(R.id.task_thumbnail)).thenReturn(mCurrentThumbnailView);
         when(mCurrentTaskView.findViewById(R.id.task_dismiss_button)).thenReturn(
@@ -380,18 +450,20 @@
                 mPreviousDismissView);
         when(mPreviousThumbnailView.getId()).thenReturn(R.id.task_thumbnail);
         when(mPreviousDismissView.getId()).thenReturn(R.id.task_dismiss_button);
+        when(mClearAllView.findViewById(R.id.recents_clear_all_button)).thenReturn(
+                mClearAllButtonView);
 
-        mCurrentViewHolder = spy(new TestViewHolder(mCurrentTaskView));
-        doReturn(CURRENT_ADAPTER_POSITION).when(mCurrentViewHolder).getAbsoluteAdapterPosition();
-        mNextViewHolder = spy(new TestViewHolder(mNextTaskView));
-        doReturn(NEXT_ADAPTER_POSITION).when(mNextViewHolder).getAbsoluteAdapterPosition();
-        mPreviousViewHolder = spy(new TestViewHolder(mPreviousTaskView));
-        doReturn(PREVIOUS_ADAPTER_POSITION).when(mPreviousViewHolder).getAbsoluteAdapterPosition();
-    }
-
-    private static class TestViewHolder extends RecyclerView.ViewHolder {
-        TestViewHolder(@NotNull View itemView) {
-            super(itemView);
-        }
+        mCurrentTaskViewHolder = spy(new BaseTaskViewHolder(mCurrentTaskView));
+        doReturn(CURRENT_ADAPTER_POSITION).when(
+                mCurrentTaskViewHolder).getAbsoluteAdapterPosition();
+        mNextTaskViewHolder = spy(new BaseTaskViewHolder(mNextTaskView));
+        doReturn(NEXT_ADAPTER_POSITION).when(mNextTaskViewHolder).getAbsoluteAdapterPosition();
+        mPreviousTaskViewHolder = spy(new BaseTaskViewHolder(mPreviousTaskView));
+        doReturn(PREVIOUS_ADAPTER_POSITION).when(
+                mPreviousTaskViewHolder).getAbsoluteAdapterPosition();
+        mClearAllTaskViewHolder = spy(
+                new ClearAllViewHolder(mClearAllView, mClearAllButtonClickListener));
+        doReturn(NEXT_ADAPTER_POSITION).when(
+                mClearAllTaskViewHolder).getAbsoluteAdapterPosition();
     }
 }
diff --git a/tests/src/com/android/car/carlauncher/recents/view/TaskSnapHelperTest.java b/tests/src/com/android/car/carlauncher/recents/view/TaskSnapHelperTest.java
deleted file mode 100644
index 6dd87cd..0000000
--- a/tests/src/com/android/car/carlauncher/recents/view/TaskSnapHelperTest.java
+++ /dev/null
@@ -1,403 +0,0 @@
-/*
- * Copyright (C) 2023 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.car.carlauncher.recents.view;
-
-import static androidx.test.espresso.Espresso.onView;
-import static androidx.test.espresso.assertion.ViewAssertions.matches;
-import static androidx.test.espresso.matcher.ViewMatchers.isCompletelyDisplayed;
-import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
-import static androidx.test.espresso.matcher.ViewMatchers.withId;
-import static androidx.test.espresso.matcher.ViewMatchers.withText;
-
-import static org.mockito.Mockito.mock;
-
-import android.content.Context;
-import android.graphics.Color;
-import android.graphics.Rect;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.TextView;
-
-import androidx.annotation.NonNull;
-import androidx.recyclerview.widget.GridLayoutManager;
-import androidx.recyclerview.widget.RecyclerView;
-import androidx.test.core.app.ActivityScenario;
-import androidx.test.espresso.IdlingRegistry;
-import androidx.test.espresso.IdlingResource;
-import androidx.test.ext.junit.rules.ActivityScenarioRule;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-
-import com.android.car.carlauncher.TestActivity;
-import com.android.car.carlauncher.test.R;
-
-import org.hamcrest.Description;
-import org.hamcrest.TypeSafeDiagnosingMatcher;
-import org.hamcrest.core.CombinableMatcher;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Random;
-
-@RunWith(AndroidJUnit4.class)
-public class TaskSnapHelperTest {
-    private static final int FIRST_TASK_WIDTH = 800;
-    private static final int TASK_WIDTH = 300;
-    private static final boolean IS_REVERSED_LAYOUT = true;
-    private static final int GRID_LAYOUT_DIRECTION = GridLayoutManager.HORIZONTAL;
-    private static final int SPAN_COUNT = 2;
-    private static final int COL_PER_PAGE = 2;
-    private static final int TIP_OVER_VARIANCE = 5;
-    // the mid-point between the first task view and first consequent page of task views such that
-    // the center of first task view is equidistant from the center of the center of the page
-    private static final int MID_POINT_BETWEEN_FIRST_VIEW_AND_FIRST_PAGE =
-            FIRST_TASK_WIDTH / 4 + TASK_WIDTH / 2;
-    private TaskSnapHelper mTaskSnapHelper;
-    private int mWindowWidth;
-    @Rule
-    public ActivityScenarioRule<TestActivity> mActivityRule = new ActivityScenarioRule<>(
-            TestActivity.class);
-
-    @Before
-    public void setup() {
-        mActivityRule.getScenario().onActivity(
-                activity -> activity.setContentView(R.xml.empty_test_activity));
-        onView(withId(R.id.list)).check(matches(isDisplayed()));
-        mActivityRule.getScenario().onActivity(activity -> {
-            Context testableContext = mock(Context.class);
-            RecyclerView rv = activity.requireViewById(R.id.list);
-            GridLayoutManager gridLayoutManager = new GridLayoutManager(testableContext, SPAN_COUNT,
-                    GRID_LAYOUT_DIRECTION, IS_REVERSED_LAYOUT);
-            gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
-                @Override
-                public int getSpanSize(int position) {
-                    if (position == 0) {
-                        return SPAN_COUNT;
-                    }
-                    return 1;
-                }
-            });
-            rv.setLayoutManager(gridLayoutManager);
-            // add padding to make sure the first and last element can be centered
-            mWindowWidth =
-                    activity.getWindowManager().getCurrentWindowMetrics().getBounds().width();
-            rv.setPaddingRelative(TASK_WIDTH, rv.getPaddingTop(),
-                    (mWindowWidth - FIRST_TASK_WIDTH) / 2,
-                    rv.getPaddingBottom());
-            rv.setAdapter(new TestAdapter(100));
-        });
-
-    }
-
-    @After
-    public void tearDown() {
-        for (IdlingResource idlingResource : IdlingRegistry.getInstance().getResources()) {
-            IdlingRegistry.getInstance().unregister(idlingResource);
-        }
-        if (mActivityRule != null) {
-            mActivityRule.getScenario().close();
-        }
-    }
-
-    @Test
-    public void testScrollFromInitialPageToNextPage() {
-        assertPageInCenter(/* pageNumber= */ 0);
-        RecyclerViewIdlingResource.register(mActivityRule.getScenario());
-
-        mActivityRule.getScenario().onActivity(activity -> {
-            RecyclerView rv = activity.requireViewById(R.id.list);
-            mTaskSnapHelper = new TaskSnapHelper(SPAN_COUNT, COL_PER_PAGE);
-            mTaskSnapHelper.attachToRecyclerView(rv);
-            rv.smoothScrollBy(-(MID_POINT_BETWEEN_FIRST_VIEW_AND_FIRST_PAGE + TIP_OVER_VARIANCE),
-                    0);
-        });
-
-        assertPageInCenter(/* pageNumber= */ 1);
-    }
-
-    @Test
-    public void testScrollFromFirstPageToNextPage() {
-        RecyclerViewIdlingResource.register(mActivityRule.getScenario());
-        mActivityRule.getScenario().onActivity(activity -> {
-            RecyclerView rv = activity.requireViewById(R.id.list);
-            rv.smoothScrollBy(-(FIRST_TASK_WIDTH / 2 + TASK_WIDTH), 0);
-        });
-        assertPageInCenter(/* pageNumber= */ 1);
-
-        mActivityRule.getScenario().onActivity(activity -> {
-            RecyclerView rv = activity.requireViewById(R.id.list);
-            mTaskSnapHelper = new TaskSnapHelper(SPAN_COUNT, COL_PER_PAGE);
-            mTaskSnapHelper.attachToRecyclerView(rv);
-            rv.smoothScrollBy(-(TASK_WIDTH + TIP_OVER_VARIANCE), 0);
-        });
-
-        assertPageInCenter(/* pageNumber= */ 2);
-    }
-
-    @Test
-    public void testScrollFromInitialPageStaysOnInitialPage() {
-        assertPageInCenter(/* pageNumber= */ 0);
-        RecyclerViewIdlingResource.register(mActivityRule.getScenario());
-
-        mActivityRule.getScenario().onActivity(activity -> {
-            RecyclerView rv = activity.requireViewById(R.id.list);
-            mTaskSnapHelper = new TaskSnapHelper(SPAN_COUNT, COL_PER_PAGE);
-            mTaskSnapHelper.attachToRecyclerView(rv);
-            rv.smoothScrollBy(-(MID_POINT_BETWEEN_FIRST_VIEW_AND_FIRST_PAGE - TIP_OVER_VARIANCE),
-                    0);
-        });
-
-        assertPageInCenter(/* pageNumber= */ 0);
-    }
-
-    @Test
-    public void testScrollFromFirstPageStaysOnSamePage() {
-        RecyclerViewIdlingResource.register(mActivityRule.getScenario());
-        mActivityRule.getScenario().onActivity(activity -> {
-            RecyclerView rv = activity.requireViewById(R.id.list);
-            rv.smoothScrollBy(-(FIRST_TASK_WIDTH / 2 + TASK_WIDTH), 0);
-        });
-        assertPageInCenter(/* pageNumber= */ 1);
-
-        mActivityRule.getScenario().onActivity(activity -> {
-            RecyclerView rv = activity.requireViewById(R.id.list);
-            mTaskSnapHelper = new TaskSnapHelper(SPAN_COUNT, COL_PER_PAGE);
-            mTaskSnapHelper.attachToRecyclerView(rv);
-            rv.smoothScrollBy(-(TASK_WIDTH - TIP_OVER_VARIANCE), 0);
-        });
-
-        assertPageInCenter(/* pageNumber= */ 1);
-    }
-
-    @Test
-    public void testScrollFromFirstPageToPreviousPage() {
-        RecyclerViewIdlingResource.register(mActivityRule.getScenario());
-        mActivityRule.getScenario().onActivity(activity -> {
-            RecyclerView rv = activity.requireViewById(R.id.list);
-            rv.smoothScrollBy(-(FIRST_TASK_WIDTH / 2 + TASK_WIDTH), 0);
-        });
-        assertPageInCenter(/* pageNumber= */ 1);
-
-        mActivityRule.getScenario().onActivity(activity -> {
-            RecyclerView rv = activity.requireViewById(R.id.list);
-            mTaskSnapHelper = new TaskSnapHelper(SPAN_COUNT, COL_PER_PAGE);
-            mTaskSnapHelper.attachToRecyclerView(rv);
-            rv.smoothScrollBy(MID_POINT_BETWEEN_FIRST_VIEW_AND_FIRST_PAGE + TIP_OVER_VARIANCE, 0);
-        });
-
-        assertPageInCenter(/* pageNumber= */ 0);
-    }
-
-    @Test
-    public void testScrollFromSecondPageToPreviousPage() {
-        RecyclerViewIdlingResource.register(mActivityRule.getScenario());
-        mActivityRule.getScenario().onActivity(activity -> {
-            RecyclerView rv = activity.requireViewById(R.id.list);
-            rv.smoothScrollBy(-(FIRST_TASK_WIDTH / 2 + TASK_WIDTH * 3), 0);
-        });
-        assertPageInCenter(/* pageNumber= */ 2);
-
-        mActivityRule.getScenario().onActivity(activity -> {
-            RecyclerView rv = activity.requireViewById(R.id.list);
-            mTaskSnapHelper = new TaskSnapHelper(SPAN_COUNT, COL_PER_PAGE);
-            mTaskSnapHelper.attachToRecyclerView(rv);
-            rv.smoothScrollBy((TASK_WIDTH + TIP_OVER_VARIANCE), 0);
-        });
-
-        assertPageInCenter(/* pageNumber= */ 1);
-    }
-
-    private static String getViewTextAtPosition(int position) {
-        return ("test " + position);
-    }
-
-    /**
-     * @param pageNumber page that should be checked to be in the center.
-     *                   - 0 indicates the item at adapter position 0.
-     *                   - 1+ indicates the page with {@code SPAN_COUNT * COL_PER_PAGE} items.
-     */
-    private void assertPageInCenter(int pageNumber) {
-        if (pageNumber < 0) {
-            return;
-        }
-        int expectedCenter;
-        if (pageNumber == 0) {
-            expectedCenter = mWindowWidth / 2;
-            onView(withText(getViewTextAtPosition(0))).check(matches((new CombinableMatcher<>(
-                    isCompletelyDisplayed())).and(new IsViewInCenterMatcher(expectedCenter))));
-            return;
-        }
-        int itemsPerPage = SPAN_COUNT * COL_PER_PAGE;
-        int itemPosition = 1 + (itemsPerPage * (pageNumber - 1));
-
-        expectedCenter = (mWindowWidth + TASK_WIDTH) / 2;
-        onView(withText(getViewTextAtPosition(itemPosition++))).check(
-                matches((new CombinableMatcher<>(isCompletelyDisplayed()))
-                        .and(new IsViewInCenterMatcher(expectedCenter))));
-        onView(withText(getViewTextAtPosition(itemPosition++))).check(
-                matches((new CombinableMatcher<>(isCompletelyDisplayed()))
-                        .and(new IsViewInCenterMatcher(expectedCenter))));
-
-        expectedCenter = (mWindowWidth - TASK_WIDTH) / 2;
-        onView(withText(getViewTextAtPosition(itemPosition++))).check(
-                matches((new CombinableMatcher<>(isCompletelyDisplayed()))
-                        .and(new IsViewInCenterMatcher(expectedCenter))));
-        onView(withText(getViewTextAtPosition(itemPosition))).check(
-                matches((new CombinableMatcher<>(isCompletelyDisplayed()))
-                        .and(new IsViewInCenterMatcher(expectedCenter))));
-    }
-
-    private static class TestAdapter extends RecyclerView.Adapter<TestViewHolder> {
-        private final List<String> mData;
-
-        TestAdapter(int itemCount) {
-            mData = new ArrayList<>(itemCount);
-            for (int i = 0; i < itemCount; i++) {
-                mData.add(getViewTextAtPosition(i));
-            }
-        }
-
-        @NonNull
-        @Override
-        public TestViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {
-            LayoutInflater inflater = LayoutInflater.from(viewGroup.getContext());
-            View view = inflater.inflate(R.xml.test_list_item, viewGroup,
-                    /* attachToRoot= */ false);
-            return new TestViewHolder(view);
-        }
-
-        @Override
-        public void onBindViewHolder(@NonNull TestViewHolder testViewHolder, int i) {
-            ViewGroup.LayoutParams layoutParams = testViewHolder.itemView.getLayoutParams();
-            if (i == 0) {
-                layoutParams.width = FIRST_TASK_WIDTH;
-            } else {
-                layoutParams.width = TASK_WIDTH;
-            }
-            testViewHolder.itemView.setLayoutParams(layoutParams);
-            testViewHolder.bind(mData.get(i));
-        }
-
-        @Override
-        public int getItemCount() {
-            return mData.size();
-        }
-    }
-
-    private static class TestViewHolder extends RecyclerView.ViewHolder {
-        TestViewHolder(View view) {
-            super(view);
-            // added different background color for easier debugging
-            Random random = new Random();
-            int color = Color.argb(255, random.nextInt(256), random.nextInt(256),
-                    random.nextInt(256));
-            view.setBackgroundColor(color);
-        }
-
-        void bind(String text) {
-            ((TextView) itemView.findViewById(R.id.text)).setText(text);
-        }
-    }
-
-    public static class RecyclerViewIdlingResource implements IdlingResource, AutoCloseable {
-        private final RecyclerView mRecyclerView;
-        private ResourceCallback mResourceCallback;
-
-        public RecyclerViewIdlingResource(RecyclerView rv) {
-            mRecyclerView = rv;
-            rv.addOnScrollListener(new RecyclerView.OnScrollListener() {
-                @Override
-                public void onScrollStateChanged(@NonNull RecyclerView recyclerView,
-                        int newState) {
-                    if (newState == RecyclerView.SCROLL_STATE_IDLE && mResourceCallback != null) {
-                        mResourceCallback.onTransitionToIdle();
-                    }
-                }
-
-                @Override
-                public void onScrolled(@NonNull RecyclerView recyclerView, int dx,
-                        int dy) {
-                }
-            });
-        }
-
-        @Override
-        public String getName() {
-            return RecyclerViewIdlingResource.class.getSimpleName();
-        }
-
-        @Override
-        public boolean isIdleNow() {
-            return mRecyclerView.getScrollState() == RecyclerView.SCROLL_STATE_IDLE;
-        }
-
-        @Override
-        public void registerIdleTransitionCallback(ResourceCallback callback) {
-            mResourceCallback = callback;
-        }
-
-        @Override
-        public void close() throws Exception {
-            IdlingRegistry.getInstance().unregister(this);
-        }
-
-        public static RecyclerViewIdlingResource register(ActivityScenario<TestActivity> scenario) {
-            final RecyclerViewIdlingResource[] idlingResources = new RecyclerViewIdlingResource[1];
-            scenario.onActivity((activity -> idlingResources[0] = new RecyclerViewIdlingResource(
-                    activity.findViewById(R.id.list))));
-            IdlingRegistry.getInstance().register(idlingResources[0]);
-            return idlingResources[0];
-        }
-    }
-
-    private static class IsViewInCenterMatcher extends TypeSafeDiagnosingMatcher<View> {
-        int mExpectedCenter;
-
-        IsViewInCenterMatcher(int expectedCenter) {
-            mExpectedCenter = expectedCenter;
-        }
-
-        @Override
-        protected boolean matchesSafely(View item, Description mismatchDescription) {
-            Rect viewPosition = new Rect();
-            boolean isViewVisible = item.getGlobalVisibleRect(viewPosition);
-            if (!isViewVisible) {
-                mismatchDescription.appendText("view was not visible to the user");
-                return false;
-            }
-            int centerOfChild = (viewPosition.right + viewPosition.left) / 2;
-            if (centerOfChild != mExpectedCenter) {
-                mismatchDescription.appendText("view was off from expected center of ")
-                        .appendValue(mExpectedCenter).appendText(" by ")
-                        .appendValue(mExpectedCenter - centerOfChild);
-                return false;
-            }
-            return true;
-        }
-
-        @Override
-        public void describeTo(Description description) {
-            description.appendText("view's center matches with the expected center: ")
-                    .appendValue(mExpectedCenter);
-        }
-    }
-}