Merge changes from topic "ia-apps-notifications" into sc-dev

* changes:
  Implement notifications screen
  Separate apps and notifications
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 8ba9061..d44be77 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -205,11 +205,28 @@
         </activity>
 
         <activity
-            android:name=".common.CarSettingActivities$AppsAndNotificationsActivity"
+            android:name=".common.CarSettingActivities$AppsActivity"
             android:windowSoftInputMode="adjustPan"
             android:exported="true">
+            <intent-filter>
+                <action android:name="android.settings.APPLICATION_SETTINGS" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
             <meta-data android:name="com.android.car.settings.TOP_LEVEL_HEADER_KEY"
-                       android:value="@string/hk_AppsAndNotificationsActivity" />
+                       android:value="@string/hk_AppsActivity" />
+            <meta-data android:name="distractionOptimized" android:value="true"/>
+        </activity>
+
+        <activity
+            android:name=".common.CarSettingActivities$NotificationsActivity"
+            android:windowSoftInputMode="adjustPan"
+            android:exported="true">
+            <intent-filter>
+                <action android:name="android.settings.NOTIFICATION_SETTINGS" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+            <meta-data android:name="com.android.car.settings.TOP_LEVEL_HEADER_KEY"
+                       android:value="@string/hk_NotificationsActivity" />
             <meta-data android:name="distractionOptimized" android:value="true"/>
         </activity>
 
@@ -378,7 +395,6 @@
             android:windowSoftInputMode="adjustPan"
             android:exported="true">
             <intent-filter android:priority="1">
-                <action android:name="android.settings.APPLICATION_SETTINGS" />
                 <action android:name="android.settings.MANAGE_APPLICATIONS_SETTINGS" />
                 <action android:name="android.settings.MANAGE_ALL_APPLICATIONS_SETTINGS" />
                 <category android:name="android.intent.category.DEFAULT" />
@@ -475,7 +491,6 @@
                       -d "package:<package name>" -->
             <intent-filter android:priority="100">
                 <action android:name="android.settings.APPLICATION_DETAILS_SETTINGS" />
-                <action android:name="android.settings.NOTIFICATION_SETTINGS" />
                 <action android:name="android.settings.CHANNEL_NOTIFICATION_SETTINGS" />
                 <action android:name="android.settings.APP_NOTIFICATION_SETTINGS" />
                 <action android:name="android.intent.action.AUTO_REVOKE_PERMISSIONS" />
@@ -488,7 +503,6 @@
                       -e "android.provider.extra.APP_PACKAGE" "<package name>" -->
             <intent-filter android:priority="100">
                 <action android:name="android.settings.APPLICATION_DETAILS_SETTINGS" />
-                <action android:name="android.settings.NOTIFICATION_SETTINGS" />
                 <action android:name="android.settings.CHANNEL_NOTIFICATION_SETTINGS" />
                 <action android:name="android.settings.APP_NOTIFICATION_SETTINGS" />
                 <action android:name="android.intent.action.AUTO_REVOKE_PERMISSIONS" />
diff --git a/res/color/top_level_notifications_background.xml b/res/color/top_level_notifications_background.xml
new file mode 100644
index 0000000..046a35c
--- /dev/null
+++ b/res/color/top_level_notifications_background.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+          xmlns:app="http://schemas.android.com/apk/res-auto">
+    <item android:state_enabled="false"
+          android:alpha="?android:attr/disabledAlpha"
+          android:color="#DD4C9D"/>
+    <item app:state_ux_restricted="true"
+          android:alpha="?android:attr/disabledAlpha"
+          android:color="#DD4C9D"/>
+    <item android:color="#DD4C9D"/>
+</selector>
\ No newline at end of file
diff --git a/res/drawable/ic_top_level_notifications.xml b/res/drawable/ic_top_level_notifications.xml
new file mode 100644
index 0000000..d4d07b2
--- /dev/null
+++ b/res/drawable/ic_top_level_notifications.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item>
+        <com.android.car.settings.common.TopLevelIconShapeDrawable
+            android:width="@dimen/icon_size"
+            android:height="@dimen/icon_size"
+            android:tint="@color/top_level_notifications_background"/>
+    </item>
+    <item
+        android:start="@dimen/top_level_foreground_icon_inset"
+        android:top="@dimen/top_level_foreground_icon_inset"
+        android:end="@dimen/top_level_foreground_icon_inset"
+        android:bottom="@dimen/top_level_foreground_icon_inset">
+        <vector
+            android:width="@dimen/icon_size"
+            android:height="@dimen/icon_size"
+            android:viewportHeight="24.0"
+            android:viewportWidth="24.0">
+            <path
+                android:fillColor="@color/top_level_notifications_icon"
+                android:pathData="M18,17v-7c0,-2.79 -1.91,-5.14 -4.5,-5.8V3.5C13.5,2.67 12.83,2 12,2s-1.5,0.67 -1.5,1.5v0.7C7.91,4.86 6,7.21 6,10v7H4v2h16v-2H18z"/>
+            <path
+                android:fillColor="@color/top_level_notifications_icon"
+                android:pathData="M12,22c1.1,0 2,-0.9 2,-2h-4C10,21.1 10.9,22 12,22z"/>
+        </vector>
+    </item>
+</layer-list>
\ No newline at end of file
diff --git a/res/values/colors.xml b/res/values/colors.xml
index 7b52b16..d1cf0d7 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -25,6 +25,7 @@
     <color name="top_level_units_icon">@color/top_level_icon_default</color>
     <color name="top_level_location_icon">@color/top_level_icon_default</color>
     <color name="top_level_applications_icon">@color/top_level_icon_default</color>
+    <color name="top_level_notifications_icon">@color/top_level_icon_default</color>
     <color name="top_level_date_time_icon">@color/top_level_icon_default</color>
     <color name="top_level_profiles_and_accounts_icon">@color/top_level_icon_default</color>
     <color name="top_level_privacy_icon">@color/top_level_icon_default</color>
diff --git a/res/values/header_keys.xml b/res/values/header_keys.xml
index cafd8db..4763d29 100644
--- a/res/values/header_keys.xml
+++ b/res/values/header_keys.xml
@@ -27,7 +27,8 @@
     <string name="hk_BluetoothSettingsActivity">@string/pk_bluetooth_settings_entry</string>
     <string name="hk_UnitsSettingsActivity">@string/pk_units_settings_entry</string>
     <string name="hk_LocationSettingsActivity">@string/pk_location_settings_entry</string>
-    <string name="hk_AppsAndNotificationsActivity">@string/pk_apps_and_notifications_settings_entry</string>
+    <string name="hk_AppsActivity">@string/pk_apps_settings_entry</string>
+    <string name="hk_NotificationsActivity">@string/pk_notifications_settings_entry</string>
     <string name="hk_DatetimeSettingsActivity">@string/pk_date_time_settings_entry</string>
     <string name="hk_ProfileDetailsActivity">@string/pk_profiles_and_accounts_settings_entry</string>
     <string name="hk_PrivacySettingsActivity">@string/pk_privacy_settings_entry</string>
@@ -41,20 +42,20 @@
     <string name="hk_MobileNetworkActivity">@string/pk_network_and_internet_entry</string>
     <string name="hk_MobileNetworkListActivity">@string/pk_network_and_internet_entry</string>
     <string name="hk_DataUsageActivity">@string/pk_network_and_internet_entry</string>
-    <string name="hk_ApplicationsSettingsActivity">@string/pk_apps_and_notifications_settings_entry</string>
-    <string name="hk_SpecialAccessSettingsActivity">@string/pk_apps_and_notifications_settings_entry</string>
+    <string name="hk_ApplicationsSettingsActivity">@string/pk_apps_settings_entry</string>
+    <string name="hk_SpecialAccessSettingsActivity">@string/pk_apps_settings_entry</string>
     <string name="hk_LanguagesAndInputActivity">@string/pk_system_settings_entry</string>
     <string name="hk_AboutSettingsActivity">@string/pk_system_settings_entry</string>
     <string name="hk_LegalInformationActivity">@string/pk_system_settings_entry</string>
     <string name="hk_ResetOptionsActivity">@string/pk_system_settings_entry</string>
     <string name="hk_AddWifiActivity">@string/pk_network_and_internet_entry</string>
     <string name="hk_WifiPreferencesActivity">@string/pk_network_and_internet_entry</string>
-    <string name="hk_ApplicationsDetailsActivity">@string/pk_apps_and_notifications_settings_entry</string>
-    <string name="hk_ModifySystemSettingsActivity">@string/pk_apps_and_notifications_settings_entry</string>
-    <string name="hk_NotificationAccessActivity">@string/pk_apps_and_notifications_settings_entry</string>
-    <string name="hk_PremiumSmsAccessActivity">@string/pk_apps_and_notifications_settings_entry</string>
-    <string name="hk_UsageAccessActivity">@string/pk_apps_and_notifications_settings_entry</string>
-    <string name="hk_WifiControlActivity">@string/pk_apps_and_notifications_settings_entry</string>
+    <string name="hk_ApplicationsDetailsActivity">@string/pk_apps_settings_entry</string>
+    <string name="hk_ModifySystemSettingsActivity">@string/pk_apps_settings_entry</string>
+    <string name="hk_NotificationAccessActivity">@string/pk_apps_settings_entry</string>
+    <string name="hk_PremiumSmsAccessActivity">@string/pk_apps_settings_entry</string>
+    <string name="hk_UsageAccessActivity">@string/pk_apps_settings_entry</string>
+    <string name="hk_WifiControlActivity">@string/pk_apps_settings_entry</string>
     <string name="hk_ChooseAccountActivity">@string/pk_profiles_and_accounts_settings_entry</string>
     <string name="hk_LanguagePickerActivity">@string/pk_system_settings_entry</string>
     <string name="hk_DefaultAutofillPickerActivity">@string/pk_system_settings_entry</string>
diff --git a/res/values/integers.xml b/res/values/integers.xml
index a1937f7..193c052 100644
--- a/res/values/integers.xml
+++ b/res/values/integers.xml
@@ -54,4 +54,11 @@
 
     <!-- Maximum number of networks to be shown in LimitedWifiEntryListPreferenceController -->
     <integer name="limited_wifi_entry_list_count">3</integer>
+
+    <!-- Maximum number of networks to be shown in RecentNotificationsAppsPreferenceController -->
+    <integer name="recent_notifications_apps_list_count">3</integer>
+
+    <!-- Maximum number of days old for a notification to be shown in
+     RecentNotificationsAppsPreferenceController -->
+    <integer name="recent_notifications_days_threshold">3</integer>
 </resources>
diff --git a/res/values/overlayable.xml b/res/values/overlayable.xml
index b2ba7a8..342214c 100644
--- a/res/values/overlayable.xml
+++ b/res/values/overlayable.xml
@@ -97,6 +97,8 @@
       <item type="color" name="top_level_location_icon"/>
       <item type="color" name="top_level_network_and_internet_background"/>
       <item type="color" name="top_level_network_and_internet_icon"/>
+      <item type="color" name="top_level_notifications_background"/>
+      <item type="color" name="top_level_notifications_icon"/>
       <item type="color" name="top_level_privacy_background"/>
       <item type="color" name="top_level_privacy_icon"/>
       <item type="color" name="top_level_profiles_and_accounts_background"/>
@@ -315,6 +317,7 @@
       <item type="drawable" name="ic_top_level_display"/>
       <item type="drawable" name="ic_top_level_location"/>
       <item type="drawable" name="ic_top_level_network_and_internet"/>
+      <item type="drawable" name="ic_top_level_notifications"/>
       <item type="drawable" name="ic_top_level_privacy"/>
       <item type="drawable" name="ic_top_level_profiles_and_accounts"/>
       <item type="drawable" name="ic_top_level_security"/>
@@ -463,6 +466,8 @@
       <item type="integer" name="millisecond_app_data_update_interval"/>
       <item type="integer" name="millisecond_max_app_load_wait_interval"/>
       <item type="integer" name="quick_setting_column_count"/>
+      <item type="integer" name="recent_notifications_apps_list_count"/>
+      <item type="integer" name="recent_notifications_days_threshold"/>
       <item type="integer" name="user_switcher_num_col"/>
       <item type="layout" name="action_buttons_preference"/>
       <item type="layout" name="bluetooth_pin_confirm"/>
@@ -804,7 +809,7 @@
       <item type="string" name="hk_AddWifiActivity"/>
       <item type="string" name="hk_ApplicationsDetailsActivity"/>
       <item type="string" name="hk_ApplicationsSettingsActivity"/>
-      <item type="string" name="hk_AppsAndNotificationsActivity"/>
+      <item type="string" name="hk_AppsActivity"/>
       <item type="string" name="hk_AssistantAndVoiceSettingsActivity"/>
       <item type="string" name="hk_BluetoothDevicePickerActivity"/>
       <item type="string" name="hk_BluetoothSettingsActivity"/>
@@ -825,6 +830,7 @@
       <item type="string" name="hk_ModifySystemSettingsActivity"/>
       <item type="string" name="hk_NetworkAndInternetActivity"/>
       <item type="string" name="hk_NotificationAccessActivity"/>
+      <item type="string" name="hk_NotificationsActivity"/>
       <item type="string" name="hk_PremiumSmsAccessActivity"/>
       <item type="string" name="hk_PrivacySettingsActivity"/>
       <item type="string" name="hk_ProfileDetailsActivity"/>
@@ -1299,7 +1305,7 @@
       <item type="xml" name="application_details_fragment"/>
       <item type="xml" name="application_launch_settings_fragment"/>
       <item type="xml" name="applications_settings_fragment"/>
-      <item type="xml" name="apps_and_notifications_fragment"/>
+      <item type="xml" name="apps_fragment"/>
       <item type="xml" name="assistant_and_voice_fragment"/>
       <item type="xml" name="bluetooth_device_details_fragment"/>
       <item type="xml" name="bluetooth_device_picker_fragment"/>
@@ -1336,6 +1342,7 @@
       <item type="xml" name="modify_system_settings_fragment"/>
       <item type="xml" name="network_and_internet_fragment"/>
       <item type="xml" name="notification_access_fragment"/>
+      <item type="xml" name="notifications_fragment"/>
       <item type="xml" name="preferred_engine_fragment"/>
       <item type="xml" name="premium_sms_access_fragment"/>
       <item type="xml" name="privacy_settings_fragment"/>
diff --git a/res/values/preference_keys.xml b/res/values/preference_keys.xml
index 8c4b8d9..b1412e3 100644
--- a/res/values/preference_keys.xml
+++ b/res/values/preference_keys.xml
@@ -30,8 +30,8 @@
     <string name="pk_bluetooth_settings_entry" translatable="false">bluetooth_settings_entry
     </string>
     <string name="pk_location_settings_entry" translatable="false">location_settings_entry</string>
-    <string name="pk_apps_and_notifications_settings_entry" translatable="false">
-        apps_and_notifications_settings_entry
+    <string name="pk_notifications_settings_entry" translatable="false">
+        notifications_settings_entry
     </string>
     <string name="pk_date_time_settings_entry" translatable="false">date_time_settings_entry
     </string>
@@ -41,6 +41,9 @@
     <string name="pk_privacy_settings_entry" translatable="false">privacy_settings_entry</string>
     <string name="pk_storage_settings_entry" translatable="false">storage_settings_entry</string>
     <string name="pk_security_settings_entry" translatable="false">security_settings_entry</string>
+    <string name="pk_apps_settings_entry" translatable="false">
+        apps_settings_entry
+    </string>
     <string name="pk_assistant_and_voice_settings_entry" translatable="false">
         assistant_and_voice_settings_entry
     </string>
@@ -134,7 +137,15 @@
         bluetooth_device_action_buttons
     </string>
 
-    <!-- Applications and Notifications Settings -->
+    <!-- Notifications Settings -->
+    <string name="pk_notifications_settings_recently_sent" translatable="false">
+        notifications_settings_recently_sent
+    </string>
+    <string name="pk_notifications_settings_all_apps" translatable="false">
+        notifications_settings_all_apps
+    </string>
+
+    <!-- Applications Settings -->
     <string name="pk_applications_settings_screen_entry" translatable="false">
         applications_settings_screen_entry
     </string>
diff --git a/res/values/preference_screen_keys.xml b/res/values/preference_screen_keys.xml
index 47c3462..0c6dbc4 100644
--- a/res/values/preference_screen_keys.xml
+++ b/res/values/preference_screen_keys.xml
@@ -28,7 +28,7 @@
     <string name="psk_application_details" translatable="false">application_details_screen</string>
     <string name="psk_application_launch_settings" translatable="false">application_launch_settings_screen</string>
     <string name="psk_applications_settings" translatable="false">applications_settings_screen</string>
-    <string name="psk_apps_and_notifications" translatable="false">apps_and_notifications_screen</string>
+    <string name="psk_apps" translatable="false">apps_screen</string>
     <string name="psk_app_storage_settings_details" translatable="false">app_storage_settings_details_screen</string>
     <string name="psk_bluetooth_device_details" translatable="false">bluetooth_device_details_screen</string>
     <string name="psk_bluetooth_device_picker" translatable="false">bluetooth_device_picker_screen</string>
@@ -64,6 +64,7 @@
     <string name="psk_modify_system_settings" translatable="false">modify_system_settings_screen</string>
     <string name="psk_network_and_internet" translatable="false">network_and_internet_screen</string>
     <string name="psk_notification_access" translatable="false">notification_access_screen</string>
+    <string name="psk_notifications" translatable="false">notifications_screen</string>
     <string name="psk_preferred_engine" translatable="false">preferred_engine_screen</string>
     <string name="psk_premium_sms_access" translatable="false">premium_sms_access_screen</string>
     <string name="psk_privacy_settings" translatable="false">privacy_settings_screen</string>
diff --git a/res/xml/apps_and_notifications_fragment.xml b/res/xml/apps_fragment.xml
similarity index 95%
rename from res/xml/apps_and_notifications_fragment.xml
rename to res/xml/apps_fragment.xml
index 81f7fdf..9cdf3b0 100644
--- a/res/xml/apps_and_notifications_fragment.xml
+++ b/res/xml/apps_fragment.xml
@@ -18,8 +18,8 @@
 <PreferenceScreen
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:settings="http://schemas.android.com/apk/res-auto"
-    android:title="@string/apps_and_notifications_settings"
-    android:key="@string/psk_apps_and_notifications">
+    android:title="@string/apps_settings_title"
+    android:key="@string/psk_apps">
     <Preference
         android:fragment="com.android.car.settings.applications.ApplicationsSettingsFragment"
         android:key="@string/pk_applications_settings_screen_entry"
diff --git a/res/xml/homepage_fragment.xml b/res/xml/homepage_fragment.xml
index 5df4229..44190bd 100644
--- a/res/xml/homepage_fragment.xml
+++ b/res/xml/homepage_fragment.xml
@@ -46,10 +46,10 @@
         </intent>
     </com.android.car.settings.common.LogicalPreferenceGroup>
     <com.android.car.settings.common.TopLevelPreference
-        android:fragment="com.android.car.settings.applications.AppsAndNotificationsFragment"
-        android:icon="@drawable/ic_top_level_applications"
-        android:key="@string/pk_apps_and_notifications_settings_entry"
-        android:title="@string/apps_and_notifications_settings"
+        android:fragment="com.android.car.settings.notifications.NotificationsFragment"
+        android:icon="@drawable/ic_top_level_notifications"
+        android:key="@string/pk_notifications_settings_entry"
+        android:title="@string/notifications_label"
         settings:controller="com.android.car.settings.common.DefaultRestrictionsPreferenceController"/>
     <com.android.car.settings.common.TopLevelPreference
         android:fragment="com.android.car.settings.sound.SoundSettingsFragment"
@@ -87,6 +87,12 @@
         android:title="@string/security_settings_title"
         settings:controller="com.android.car.settings.security.SecurityEntryPreferenceController"/>
     <com.android.car.settings.common.TopLevelPreference
+        android:fragment="com.android.car.settings.applications.AppsFragment"
+        android:icon="@drawable/ic_top_level_applications"
+        android:key="@string/pk_apps_settings_entry"
+        android:title="@string/apps_settings_title"
+        settings:controller="com.android.car.settings.common.DefaultRestrictionsPreferenceController"/>
+    <com.android.car.settings.common.TopLevelPreference
         android:fragment="com.android.car.settings.applications.assist.AssistantAndVoiceFragment"
         android:icon="@drawable/ic_top_level_assistant_and_voice"
         android:key="@string/pk_assistant_and_voice_settings_entry"
diff --git a/res/xml/notifications_fragment.xml b/res/xml/notifications_fragment.xml
new file mode 100644
index 0000000..5bd0814
--- /dev/null
+++ b/res/xml/notifications_fragment.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<PreferenceScreen
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:settings="http://schemas.android.com/apk/res-auto"
+    android:title="@string/notifications_label"
+    android:key="@string/psk_notifications">
+    <PreferenceCategory
+        android:key="@string/pk_notifications_settings_recently_sent"
+        android:title="@string/notifications_recently_sent"
+        settings:controller="com.android.car.settings.notifications.RecentNotificationsAppsPreferenceController"/>
+    <PreferenceCategory
+        android:key="@string/pk_notifications_settings_all_apps"
+        android:title="@string/notifications_all_apps"
+        settings:controller="com.android.car.settings.notifications.NotificationsAppListPreferenceController"/>
+</PreferenceScreen>
diff --git a/src/com/android/car/settings/applications/AppsAndNotificationsFragment.java b/src/com/android/car/settings/applications/AppsFragment.java
similarity index 74%
rename from src/com/android/car/settings/applications/AppsAndNotificationsFragment.java
rename to src/com/android/car/settings/applications/AppsFragment.java
index ecaf0cc..5845a1c 100644
--- a/src/com/android/car/settings/applications/AppsAndNotificationsFragment.java
+++ b/src/com/android/car/settings/applications/AppsFragment.java
@@ -16,27 +16,28 @@
 
 package com.android.car.settings.applications;
 
+import android.provider.Settings;
+
 import androidx.annotation.XmlRes;
 
 import com.android.car.settings.R;
-import com.android.car.settings.common.CarSettingActivities;
 import com.android.car.settings.common.SettingsFragment;
 import com.android.car.settings.search.CarBaseSearchIndexProvider;
 import com.android.settingslib.search.SearchIndexable;
 
-/** Shows subsettings related to apps and notifications. */
+/** Shows subsettings related to apps. */
 @SearchIndexable
-public class AppsAndNotificationsFragment extends SettingsFragment {
+public class AppsFragment extends SettingsFragment {
     @Override
     @XmlRes
     protected int getPreferenceScreenResId() {
-        return R.xml.apps_and_notifications_fragment;
+        return R.xml.apps_fragment;
     }
 
     /**
      * Data provider for Settings Search.
      */
     public static final CarBaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
-            new CarBaseSearchIndexProvider(R.xml.apps_and_notifications_fragment,
-                    CarSettingActivities.AppsAndNotificationsActivity.class);
+            new CarBaseSearchIndexProvider(R.xml.apps_fragment,
+                    Settings.ACTION_APPLICATION_SETTINGS);
 }
diff --git a/src/com/android/car/settings/applications/NotificationsPreferenceController.java b/src/com/android/car/settings/applications/NotificationsPreferenceController.java
index 3236f62..6c4c264 100644
--- a/src/com/android/car/settings/applications/NotificationsPreferenceController.java
+++ b/src/com/android/car/settings/applications/NotificationsPreferenceController.java
@@ -16,38 +16,27 @@
 
 package com.android.car.settings.applications;
 
-import static android.app.NotificationManager.IMPORTANCE_NONE;
-import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
-
-import android.app.INotificationManager;
-import android.app.NotificationChannel;
 import android.car.drivingstate.CarUxRestrictions;
 import android.content.Context;
 import android.content.pm.PackageInfo;
-import android.os.ServiceManager;
 
 import androidx.preference.TwoStatePreference;
 
 import com.android.car.settings.common.FragmentController;
 import com.android.car.settings.common.Logger;
-import com.android.car.settings.common.PreferenceController;
-import com.android.internal.annotations.VisibleForTesting;
+import com.android.car.settings.notifications.BaseNotificationsPreferenceController;
 
 /**
  * Controller for preference which enables / disables showing notifications for an application.
  */
-public class NotificationsPreferenceController extends PreferenceController<TwoStatePreference> {
+public class NotificationsPreferenceController extends
+        BaseNotificationsPreferenceController<TwoStatePreference> {
 
     private static final Logger LOG = new Logger(NotificationsPreferenceController.class);
 
     private String mPackageName;
     private int mUid;
 
-    @VisibleForTesting
-    INotificationManager mNotificationManager =
-            INotificationManager.Stub.asInterface(
-                    ServiceManager.getService(Context.NOTIFICATION_SERVICE));
-
     public NotificationsPreferenceController(Context context, String preferenceKey,
             FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
         super(context, preferenceKey, fragmentController, uxRestrictions);
@@ -68,40 +57,12 @@
 
     @Override
     protected void updateState(TwoStatePreference preference) {
-        preference.setChecked(isNotificationsEnabled());
+        preference.setChecked(areNotificationsEnabled(mPackageName, mUid));
     }
 
     @Override
     protected boolean handlePreferenceChanged(TwoStatePreference preference, Object newValue) {
         boolean enabled = (boolean) newValue;
-
-        try {
-            if (mNotificationManager.onlyHasDefaultChannel(mPackageName, mUid)) {
-                NotificationChannel defaultChannel =
-                        mNotificationManager.getNotificationChannelForPackage(
-                                mPackageName,
-                                mUid,
-                                NotificationChannel.DEFAULT_CHANNEL_ID,
-                                /* conversationId= */ null,
-                                /* includeDeleted= */ true);
-                defaultChannel.setImportance(enabled ? IMPORTANCE_UNSPECIFIED : IMPORTANCE_NONE);
-                mNotificationManager
-                        .updateNotificationChannelForPackage(mPackageName, mUid, defaultChannel);
-            }
-            mNotificationManager.setNotificationsEnabledForPackage(mPackageName, mUid, enabled);
-        } catch (Exception e) {
-            LOG.w("Error querying notification setting for package");
-            return false;
-        }
-        return true;
-    }
-
-    private boolean isNotificationsEnabled() {
-        try {
-            return mNotificationManager.areNotificationsEnabledForPackage(mPackageName, mUid);
-        } catch (Exception e) {
-            LOG.w("Error querying notification setting for package");
-            return false;
-        }
+        return toggleNotificationsSetting(mPackageName, mUid, enabled);
     }
 }
diff --git a/src/com/android/car/settings/common/CarSettingActivities.java b/src/com/android/car/settings/common/CarSettingActivities.java
index 5231055..a9574e3 100644
--- a/src/com/android/car/settings/common/CarSettingActivities.java
+++ b/src/com/android/car/settings/common/CarSettingActivities.java
@@ -30,7 +30,7 @@
 import com.android.car.settings.accounts.ChooseAccountFragment;
 import com.android.car.settings.applications.ApplicationDetailsFragment;
 import com.android.car.settings.applications.ApplicationsSettingsFragment;
-import com.android.car.settings.applications.AppsAndNotificationsFragment;
+import com.android.car.settings.applications.AppsFragment;
 import com.android.car.settings.applications.assist.AssistantAndVoiceFragment;
 import com.android.car.settings.applications.defaultapps.DefaultAutofillPickerFragment;
 import com.android.car.settings.applications.specialaccess.ModifySystemSettingsFragment;
@@ -50,6 +50,7 @@
 import com.android.car.settings.network.MobileNetworkFragment;
 import com.android.car.settings.network.MobileNetworkListFragment;
 import com.android.car.settings.network.NetworkAndInternetFragment;
+import com.android.car.settings.notifications.NotificationsFragment;
 import com.android.car.settings.privacy.PrivacySettingsFragment;
 import com.android.car.settings.privacy.VehicleDataFragment;
 import com.android.car.settings.profiles.ProfileDetailsFragment;
@@ -187,13 +188,24 @@
     }
 
     /**
-     * Apps and Notifications Activity.
+     * Apps Activity.
      */
-    public static class AppsAndNotificationsActivity extends BaseCarSettingsActivity {
+    public static class AppsActivity extends BaseCarSettingsActivity {
         @Nullable
         @Override
         protected Fragment getInitialFragment() {
-            return new AppsAndNotificationsFragment();
+            return new AppsFragment();
+        }
+    }
+
+    /**
+     * Notifications Activity.
+     */
+    public static class NotificationsActivity extends BaseCarSettingsActivity {
+        @Nullable
+        @Override
+        protected Fragment getInitialFragment() {
+            return new NotificationsFragment();
         }
     }
 
diff --git a/src/com/android/car/settings/notifications/BaseNotificationsPreferenceController.java b/src/com/android/car/settings/notifications/BaseNotificationsPreferenceController.java
new file mode 100644
index 0000000..9b389f0
--- /dev/null
+++ b/src/com/android/car/settings/notifications/BaseNotificationsPreferenceController.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.notifications;
+
+import static android.app.NotificationManager.IMPORTANCE_NONE;
+import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
+
+import android.app.INotificationManager;
+import android.app.NotificationChannel;
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+import android.os.ServiceManager;
+
+import androidx.annotation.VisibleForTesting;
+import androidx.preference.Preference;
+
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.Logger;
+import com.android.car.settings.common.PreferenceController;
+
+/**
+ * Base notifications class that handles checking and changing notification availability
+ *
+ * @param <T> The upper bound on the type of {@link Preference} on which the controller
+ *            expects to operate.
+ */
+public abstract class BaseNotificationsPreferenceController<T extends Preference> extends
+        PreferenceController<T> {
+
+    private static final Logger LOG = new Logger(BaseNotificationsPreferenceController.class);
+
+    @VisibleForTesting
+    public INotificationManager mNotificationManager = INotificationManager.Stub.asInterface(
+            ServiceManager.getService(Context.NOTIFICATION_SERVICE));
+
+    public BaseNotificationsPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+    }
+
+    /**
+     * Changes the notifications availability of the specified app
+     *
+     * @param packageName Package name of app
+     * @param uid Uid of app
+     * @param enabled Whether to enable or disable the notification
+     * @return Whether changing the notification availability succeeded or not
+     */
+    public boolean toggleNotificationsSetting(String packageName, int uid, boolean enabled) {
+        try {
+            if (mNotificationManager.onlyHasDefaultChannel(packageName, uid)) {
+                NotificationChannel defaultChannel =
+                        mNotificationManager.getNotificationChannelForPackage(
+                                packageName,
+                                uid,
+                                NotificationChannel.DEFAULT_CHANNEL_ID,
+                                /* conversationId= */ null,
+                                /* includeDeleted= */ true);
+                defaultChannel.setImportance(enabled ? IMPORTANCE_UNSPECIFIED : IMPORTANCE_NONE);
+                mNotificationManager
+                        .updateNotificationChannelForPackage(packageName, uid, defaultChannel);
+            }
+            mNotificationManager.setNotificationsEnabledForPackage(packageName, uid, enabled);
+        } catch (Exception e) {
+            LOG.w("Error querying notification setting for package");
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Checks whether notifications are enabled for specified app
+     *
+     * @param packageName Package name of the app
+     * @param uid Uid of the app
+     * @return Whether notifications are enabled for the specified app
+     */
+    public boolean areNotificationsEnabled(String packageName, int uid) {
+        try {
+            return mNotificationManager.areNotificationsEnabledForPackage(packageName, uid);
+        } catch (Exception e) {
+            LOG.w("Error querying notification setting for package");
+            return false;
+        }
+    }
+}
diff --git a/src/com/android/car/settings/notifications/NotificationsAppListPreferenceController.java b/src/com/android/car/settings/notifications/NotificationsAppListPreferenceController.java
new file mode 100644
index 0000000..178316d
--- /dev/null
+++ b/src/com/android/car/settings/notifications/NotificationsAppListPreferenceController.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.notifications;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+
+import androidx.preference.Preference;
+import androidx.preference.PreferenceCategory;
+
+import com.android.car.settings.applications.ApplicationDetailsFragment;
+import com.android.car.settings.applications.ApplicationListItemManager;
+import com.android.car.settings.common.FragmentController;
+import com.android.car.ui.preference.CarUiTwoActionSwitchPreference;
+import com.android.settingslib.applications.ApplicationsState;
+
+import java.util.ArrayList;
+
+/**
+ * Controller for of list of preferences that enable / disable showing notifications for an
+ * application.
+ */
+public class NotificationsAppListPreferenceController extends
+        BaseNotificationsPreferenceController<PreferenceCategory> implements
+        ApplicationListItemManager.AppListItemListener {
+
+    private NotificationsFragment.NotificationSwitchListener mNotificationSwitchListener;
+
+    public NotificationsAppListPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+    }
+
+    public void setNotificationSwitchListener(
+            NotificationsFragment.NotificationSwitchListener listener) {
+        mNotificationSwitchListener = listener;
+    }
+
+    @Override
+    protected Class<PreferenceCategory> getPreferenceType() {
+        return PreferenceCategory.class;
+    }
+
+    @Override
+    public void onDataLoaded(ArrayList<ApplicationsState.AppEntry> apps) {
+        getPreference().removeAll();
+        for (ApplicationsState.AppEntry appEntry : apps) {
+            getPreference().addPreference(
+                    createPreference(appEntry.label, appEntry.sizeStr, appEntry.icon,
+                            appEntry.info.packageName, appEntry.info.uid));
+        }
+    }
+
+    private Preference createPreference(String title, String summary, Drawable icon,
+            String packageName, int uid) {
+        CarUiTwoActionSwitchPreference preference =
+                new CarUiTwoActionSwitchPreference(getContext());
+        preference.setTitle(title);
+        preference.setSummary(summary);
+        preference.setIcon(icon);
+        preference.setKey(packageName);
+        preference.setOnPreferenceClickListener(p -> {
+            getFragmentController().launchFragment(
+                    ApplicationDetailsFragment.getInstance(packageName));
+            return true;
+        });
+
+        preference.setOnSecondaryActionClickListener((newValue) -> {
+            toggleNotificationsSetting(packageName, uid, newValue);
+            if (mNotificationSwitchListener != null) {
+                mNotificationSwitchListener.onSwitchChanged();
+            }
+        });
+        preference.setSecondaryActionChecked(areNotificationsEnabled(packageName, uid));
+
+        return preference;
+    }
+}
diff --git a/src/com/android/car/settings/notifications/NotificationsFragment.java b/src/com/android/car/settings/notifications/NotificationsFragment.java
new file mode 100644
index 0000000..02229f1
--- /dev/null
+++ b/src/com/android/car/settings/notifications/NotificationsFragment.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.notifications;
+
+import static com.android.car.settings.storage.StorageUtils.maybeInitializeVolume;
+
+import android.app.Application;
+import android.content.Context;
+import android.os.Bundle;
+import android.os.storage.StorageManager;
+import android.os.storage.VolumeInfo;
+import android.provider.Settings;
+
+import androidx.annotation.XmlRes;
+
+import com.android.car.settings.R;
+import com.android.car.settings.applications.ApplicationListItemManager;
+import com.android.car.settings.common.SettingsFragment;
+import com.android.car.settings.search.CarBaseSearchIndexProvider;
+import com.android.settingslib.applications.ApplicationsState;
+import com.android.settingslib.search.SearchIndexable;
+
+/** Shows subsettings related to notifications. */
+@SearchIndexable
+public class NotificationsFragment extends SettingsFragment {
+
+    private ApplicationListItemManager mAppListItemManager;
+    @Override
+    @XmlRes
+    protected int getPreferenceScreenResId() {
+        return R.xml.notifications_fragment;
+    }
+
+    @Override
+    public void onAttach(Context context) {
+        super.onAttach(context);
+
+        Application application = requireActivity().getApplication();
+        ApplicationsState applicationsState = ApplicationsState.getInstance(application);
+        StorageManager sm = context.getSystemService(StorageManager.class);
+        VolumeInfo volume = maybeInitializeVolume(sm, getArguments());
+
+        NotificationsAppListPreferenceController notificationsAppListController =
+                use(NotificationsAppListPreferenceController.class,
+                        R.string.pk_notifications_settings_all_apps);
+        RecentNotificationsAppsPreferenceController recentNotificationsController =
+                use(RecentNotificationsAppsPreferenceController.class,
+                        R.string.pk_notifications_settings_recently_sent);
+
+        mAppListItemManager = new ApplicationListItemManager(volume, getLifecycle(),
+                applicationsState,
+                getContext().getResources().getInteger(
+                        R.integer.millisecond_app_data_update_interval),
+                getContext().getResources().getInteger(
+                        R.integer.millisecond_max_app_load_wait_interval));
+        mAppListItemManager.registerListener(notificationsAppListController);
+        mAppListItemManager.registerListener(recentNotificationsController);
+        recentNotificationsController.setApplicationsState(applicationsState);
+
+        // Since an app can show in both sections, use a listener to notify the other section to
+        // update in order to maintain consistency
+        notificationsAppListController.setNotificationSwitchListener(() ->
+                mAppListItemManager.onPackageListChanged());
+        recentNotificationsController.setNotificationSwitchListener(() ->
+                mAppListItemManager.onPackageListChanged());
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        mAppListItemManager.startLoading(getAppFilter(), ApplicationsState.ALPHA_COMPARATOR);
+    }
+
+    @Override
+    public void onStart() {
+        super.onStart();
+        mAppListItemManager.onFragmentStart();
+    }
+
+    @Override
+    public void onStop() {
+        super.onStop();
+        mAppListItemManager.onFragmentStop();
+    }
+
+    private ApplicationsState.AppFilter getAppFilter() {
+        // Display only non-system apps
+        return ApplicationsState.FILTER_DOWNLOADED_AND_LAUNCHER_AND_INSTANT;
+    }
+
+    /**
+     * Callback that is called when a app's notification setting is toggled
+     */
+    public interface NotificationSwitchListener {
+        /**
+         * An app's notification setting has been changed
+         */
+        void onSwitchChanged();
+    }
+
+    /**
+     * Data provider for Settings Search.
+     */
+    public static final CarBaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
+            new CarBaseSearchIndexProvider(R.xml.notifications_fragment,
+                    Settings.ACTION_NOTIFICATION_SETTINGS);
+}
diff --git a/src/com/android/car/settings/notifications/RecentNotificationsAppsPreferenceController.java b/src/com/android/car/settings/notifications/RecentNotificationsAppsPreferenceController.java
new file mode 100644
index 0000000..074cf8e
--- /dev/null
+++ b/src/com/android/car/settings/notifications/RecentNotificationsAppsPreferenceController.java
@@ -0,0 +1,234 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.notifications;
+
+import android.app.usage.IUsageStatsManager;
+import android.app.usage.UsageEvents;
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.service.notification.NotifyingApp;
+import android.text.format.DateUtils;
+
+import androidx.annotation.VisibleForTesting;
+import androidx.collection.ArrayMap;
+import androidx.preference.PreferenceCategory;
+
+import com.android.car.settings.R;
+import com.android.car.settings.applications.ApplicationDetailsFragment;
+import com.android.car.settings.applications.ApplicationListItemManager;
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.Logger;
+import com.android.car.ui.preference.CarUiTwoActionSwitchPreference;
+import com.android.settingslib.applications.ApplicationsState;
+import com.android.settingslib.utils.ThreadUtils;
+
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * This controller displays a list of recently used apps. Only non-system apps are displayed.
+ * This class is largely taken from
+ * {@link com.android.settings.notification.RecentNotifyingAppsPreferenceController}
+ */
+public class RecentNotificationsAppsPreferenceController extends
+        BaseNotificationsPreferenceController<PreferenceCategory> implements
+        ApplicationListItemManager.AppListItemListener {
+
+    private static final Logger LOG = new Logger(RecentNotificationsAppsPreferenceController.class);
+
+    private static final String KEY_PLACEHOLDER = "app";
+
+    @VisibleForTesting
+    IUsageStatsManager mUsageStatsManager;
+
+    private final Integer mUserId;
+    private final int mRecentAppsMaxCount;
+    private final int mDaysThreshold;
+    private List<NotifyingApp> mApps;
+    private ApplicationsState mApplicationsState;
+    private NotificationsFragment.NotificationSwitchListener mNotificationSwitchListener;
+
+    public RecentNotificationsAppsPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+        mUsageStatsManager = IUsageStatsManager.Stub.asInterface(
+                ServiceManager.getService(Context.USAGE_STATS_SERVICE));
+        mUserId = context.getUserId();
+        mRecentAppsMaxCount = context.getResources()
+                .getInteger(R.integer.recent_notifications_apps_list_count);
+        mDaysThreshold = context.getResources()
+                .getInteger(R.integer.recent_notifications_days_threshold);
+    }
+
+    public void setApplicationsState(ApplicationsState applicationsState) {
+        mApplicationsState = applicationsState;
+    }
+
+    public void setNotificationSwitchListener(
+            NotificationsFragment.NotificationSwitchListener listener) {
+        mNotificationSwitchListener = listener;
+    }
+
+    @Override
+    protected Class<PreferenceCategory> getPreferenceType() {
+        return PreferenceCategory.class;
+    }
+
+    @Override
+    public void onDataLoaded(ArrayList<ApplicationsState.AppEntry> apps) {
+        // App entries updated, refresh since filtered apps may have changed
+        refresh();
+    }
+
+    @Override
+    public void updateState(PreferenceCategory preference) {
+        super.updateState(preference);
+        refresh();
+    }
+
+    private void refresh() {
+        ThreadUtils.postOnBackgroundThread(() -> {
+            reloadData();
+            List<NotifyingApp> recentApps = getDisplayableRecentAppList();
+            ThreadUtils.postOnMainThread(() -> {
+                if (recentApps != null && !recentApps.isEmpty()) {
+                    getPreference().setVisible(true);
+                    displayRecentApps(recentApps);
+                } else {
+                    getPreference().setVisible(false);
+                    getPreference().removeAll();
+                }
+            });
+        });
+    }
+
+    private void reloadData() {
+        Calendar calendar = Calendar.getInstance();
+        calendar.add(Calendar.DAY_OF_YEAR, -mDaysThreshold);
+        UsageEvents events = null;
+        try {
+            events = mUsageStatsManager.queryEventsForUser(calendar.getTimeInMillis(),
+                    System.currentTimeMillis(), mUserId, getContext().getPackageName());
+        } catch (RemoteException e) {
+            LOG.e("Failed querying user events", e);
+        }
+
+        if (events != null) {
+            ArrayMap<String, NotifyingApp> aggregatedStats = new ArrayMap<>();
+
+            UsageEvents.Event event = new UsageEvents.Event();
+            while (events.hasNextEvent()) {
+                events.getNextEvent(event);
+                if (event.getEventType() == UsageEvents.Event.NOTIFICATION_INTERRUPTION) {
+                    NotifyingApp app =
+                            aggregatedStats.get(getKey(mUserId, event.getPackageName()));
+                    if (app == null) {
+                        app = new NotifyingApp();
+                        aggregatedStats.put(getKey(mUserId, event.getPackageName()), app);
+                        app.setPackage(event.getPackageName());
+                        app.setUserId(mUserId);
+                    }
+                    if (event.getTimeStamp() > app.getLastNotified()) {
+                        app.setLastNotified(event.getTimeStamp());
+                    }
+                }
+            }
+            mApps = new ArrayList<>();
+            mApps.addAll(aggregatedStats.values());
+        }
+    }
+
+    private List<NotifyingApp> getDisplayableRecentAppList() {
+        Collections.sort(mApps);
+        List<NotifyingApp> displayableApps = new ArrayList<>(mRecentAppsMaxCount);
+        int count = 0;
+        for (NotifyingApp app : mApps) {
+            try {
+                ApplicationsState.AppEntry appEntry = mApplicationsState.getEntry(
+                        app.getPackage(), app.getUserId());
+                if (appEntry == null || isSystemApp(appEntry)) {
+                    continue;
+                }
+                displayableApps.add(app);
+                count++;
+                if (count >= mRecentAppsMaxCount) {
+                    break;
+                }
+            } catch (Exception e) {
+                LOG.e("Failed to find app " + app.getPackage() + "/" + app.getUserId(), e);
+            }
+        }
+        return displayableApps;
+    }
+
+    private void displayRecentApps(List<NotifyingApp> recentApps) {
+        int keyIndex = 1;
+        int recentAppsCount = recentApps.size();
+        for (int i = 0; i < recentAppsCount; i++, keyIndex++) {
+            NotifyingApp app = recentApps.get(i);
+            // Bind recent apps to existing prefs if possible, or create a new pref.
+            String pkgName = app.getPackage();
+            ApplicationsState.AppEntry appEntry =
+                    mApplicationsState.getEntry(app.getPackage(), app.getUserId());
+            if (appEntry == null || appEntry.label == null) {
+                continue;
+            }
+
+            CarUiTwoActionSwitchPreference pref = getPreference()
+                    .findPreference(KEY_PLACEHOLDER + keyIndex);
+            if (pref == null) {
+                pref = new CarUiTwoActionSwitchPreference(getContext());
+                pref.setKey(KEY_PLACEHOLDER + keyIndex);
+                getPreference().addPreference(pref);
+            }
+            pref.setTitle(appEntry.label);
+            pref.setIcon(appEntry.icon);
+            pref.setSummary(DateUtils.getRelativeTimeSpanString(app.getLastNotified(),
+                    System.currentTimeMillis(), DateUtils.SECOND_IN_MILLIS));
+            pref.setOnPreferenceClickListener(p -> {
+                getFragmentController().launchFragment(
+                        ApplicationDetailsFragment.getInstance(pkgName));
+                return true;
+            });
+
+            pref.setOnSecondaryActionClickListener((newValue) -> {
+                toggleNotificationsSetting(pkgName, appEntry.info.uid, newValue);
+                if (mNotificationSwitchListener != null) {
+                    mNotificationSwitchListener.onSwitchChanged();
+                }
+            });
+            pref.setSecondaryActionChecked(areNotificationsEnabled(pkgName, appEntry.info.uid));
+        }
+        // If there are less than SHOW_RECENT_APP_COUNT recent apps, remove placeholders
+        for (int i = keyIndex; i <= mRecentAppsMaxCount; i++) {
+            getPreference().removePreferenceRecursively(KEY_PLACEHOLDER + i);
+        }
+    }
+
+    private String getKey(int userId, String pkg) {
+        return userId + "|" + pkg;
+    }
+
+    /** Returns true if the app for the given package name is a system app for this device */
+    private boolean isSystemApp(ApplicationsState.AppEntry appEntry) {
+        return !ApplicationsState.FILTER_DOWNLOADED_AND_LAUNCHER_AND_INSTANT.filterApp(appEntry);
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/applications/NotificationsPreferenceControllerTest.java b/tests/unit/src/com/android/car/settings/applications/NotificationsPreferenceControllerTest.java
similarity index 76%
rename from tests/robotests/src/com/android/car/settings/applications/NotificationsPreferenceControllerTest.java
rename to tests/unit/src/com/android/car/settings/applications/NotificationsPreferenceControllerTest.java
index 072fb44..8dd774a 100644
--- a/tests/robotests/src/com/android/car/settings/applications/NotificationsPreferenceControllerTest.java
+++ b/tests/unit/src/com/android/car/settings/applications/NotificationsPreferenceControllerTest.java
@@ -23,33 +23,40 @@
 
 import android.app.INotificationManager;
 import android.app.NotificationChannel;
+import android.car.drivingstate.CarUxRestrictions;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
 
-import androidx.lifecycle.Lifecycle;
+import androidx.lifecycle.LifecycleOwner;
 import androidx.preference.SwitchPreference;
 import androidx.preference.TwoStatePreference;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 
-import com.android.car.settings.common.PreferenceControllerTestHelper;
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.PreferenceControllerTestUtil;
+import com.android.car.settings.testutils.TestLifecycleOwner;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
-import org.robolectric.RobolectricTestRunner;
-import org.robolectric.RuntimeEnvironment;
 
-@RunWith(RobolectricTestRunner.class)
+@RunWith(AndroidJUnit4.class)
 public class NotificationsPreferenceControllerTest {
     private static final String PKG_NAME = "package.name";
     private static final int UID = 1001010;
-    private Context mContext;
+
+    private Context mContext = ApplicationProvider.getApplicationContext();
+    private LifecycleOwner mLifecycleOwner;
+    private CarUxRestrictions mCarUxRestrictions;
     private NotificationsPreferenceController mController;
-    private PreferenceControllerTestHelper<NotificationsPreferenceController>
-            mPreferenceControllerHelper;
     private TwoStatePreference mTwoStatePreference;
+
+    @Mock
+    private FragmentController mFragmentController;
     @Mock
     private INotificationManager mMockManager;
     @Mock
@@ -58,13 +65,16 @@
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
+        mLifecycleOwner = new TestLifecycleOwner();
+        mCarUxRestrictions = new CarUxRestrictions.Builder(/* reqOpt= */ true,
+                CarUxRestrictions.UX_RESTRICTIONS_BASELINE, /* timestamp= */ 0).build();
 
-        mContext = RuntimeEnvironment.application;
         mTwoStatePreference = new SwitchPreference(mContext);
 
-        mPreferenceControllerHelper = new PreferenceControllerTestHelper<>(mContext,
-                NotificationsPreferenceController.class, mTwoStatePreference);
-        mController = mPreferenceControllerHelper.getController();
+        mController = new NotificationsPreferenceController(mContext,
+                /* preferenceKey= */ "key", mFragmentController, mCarUxRestrictions);
+        PreferenceControllerTestUtil.assignPreference(mController, mTwoStatePreference);
+
         mController.mNotificationManager = mMockManager;
 
         PackageInfo packageInfo = new PackageInfo();
@@ -81,7 +91,7 @@
     public void onCreate_notificationEnabled_isChecked() throws Exception {
         when(mMockManager.areNotificationsEnabledForPackage(PKG_NAME, UID)).thenReturn(true);
 
-        mPreferenceControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+        mController.onCreate(mLifecycleOwner);
 
         assertThat(mTwoStatePreference.isChecked()).isTrue();
     }
@@ -90,7 +100,7 @@
     public void onCreate_notificationDisabled_isNotChecked() throws Exception {
         when(mMockManager.areNotificationsEnabledForPackage(PKG_NAME, UID)).thenReturn(false);
 
-        mPreferenceControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+        mController.onCreate(mLifecycleOwner);
 
         assertThat(mTwoStatePreference.isChecked()).isFalse();
     }
diff --git a/tests/unit/src/com/android/car/settings/notifications/NotificationsAppListPreferenceControllerTest.java b/tests/unit/src/com/android/car/settings/notifications/NotificationsAppListPreferenceControllerTest.java
new file mode 100644
index 0000000..a5f4717
--- /dev/null
+++ b/tests/unit/src/com/android/car/settings/notifications/NotificationsAppListPreferenceControllerTest.java
@@ -0,0 +1,218 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.notifications;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.INotificationManager;
+import android.app.NotificationChannel;
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+
+import androidx.lifecycle.LifecycleOwner;
+import androidx.preference.PreferenceCategory;
+import androidx.preference.PreferenceGroup;
+import androidx.preference.PreferenceManager;
+import androidx.preference.PreferenceScreen;
+import androidx.test.annotation.UiThreadTest;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.car.settings.R;
+import com.android.car.settings.applications.ApplicationDetailsFragment;
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.PreferenceControllerTestUtil;
+import com.android.car.settings.testutils.TestLifecycleOwner;
+import com.android.car.ui.preference.CarUiTwoActionSwitchPreference;
+import com.android.settingslib.applications.ApplicationsState;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+
+@RunWith(AndroidJUnit4.class)
+public class NotificationsAppListPreferenceControllerTest {
+    private static final String PKG_NAME = "package.name";
+    private static final int UID = 1001010;
+    private static final int ID = 1;
+    private static final String LABEL = "label";
+    private static final String SUMMARY = "summary";
+
+    private Context mContext = ApplicationProvider.getApplicationContext();
+    private LifecycleOwner mLifecycleOwner;
+    private PreferenceGroup mPreferenceCategory;
+    private NotificationsAppListPreferenceController mPreferenceController;
+    private CarUxRestrictions mCarUxRestrictions;
+    private ArrayList<ApplicationsState.AppEntry> mAppEntryList;
+
+    @Mock
+    private FragmentController mFragmentController;
+    @Mock
+    private INotificationManager mMockManager;
+    @Mock
+    private NotificationChannel mMockChannel;
+
+    @Before
+    @UiThreadTest
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mLifecycleOwner = new TestLifecycleOwner();
+        mCarUxRestrictions = new CarUxRestrictions.Builder(/* reqOpt= */ true,
+                CarUxRestrictions.UX_RESTRICTIONS_BASELINE, /* timestamp= */ 0).build();
+
+        PreferenceManager preferenceManager = new PreferenceManager(mContext);
+        PreferenceScreen screen = preferenceManager.createPreferenceScreen(mContext);
+        mPreferenceCategory = new PreferenceCategory(mContext);
+        screen.addPreference(mPreferenceCategory);
+
+        mPreferenceController = new NotificationsAppListPreferenceController(mContext,
+                /* preferenceKey= */ "key", mFragmentController, mCarUxRestrictions);
+        PreferenceControllerTestUtil.assignPreference(mPreferenceController, mPreferenceCategory);
+
+        mPreferenceController.mNotificationManager = mMockManager;
+
+        ApplicationInfo applicationInfo = new ApplicationInfo();
+        applicationInfo.packageName = PKG_NAME;
+        applicationInfo.uid = UID;
+        applicationInfo.sourceDir = "";
+        ApplicationsState.AppEntry appEntry =
+                new ApplicationsState.AppEntry(mContext, applicationInfo, ID);
+        appEntry.label = LABEL;
+        appEntry.sizeStr = SUMMARY;
+        appEntry.icon = mContext.getDrawable(R.drawable.test_icon);
+
+        mAppEntryList = new ArrayList<>();
+        mAppEntryList.add(appEntry);
+    }
+
+    @Test
+    public void onCreate_createsPreference() throws Exception {
+        when(mMockManager.areNotificationsEnabledForPackage(PKG_NAME, UID)).thenReturn(true);
+
+        mPreferenceController.onCreate(mLifecycleOwner);
+        mPreferenceController.onDataLoaded(mAppEntryList);
+
+        assertThat(mPreferenceCategory.getPreferenceCount()).isEqualTo(1);
+
+        CarUiTwoActionSwitchPreference preference = (CarUiTwoActionSwitchPreference)
+                mPreferenceCategory.getPreference(0);
+
+        assertThat(preference.getTitle()).isEqualTo(LABEL);
+        assertThat(preference.getSummary()).isEqualTo(SUMMARY);
+        assertThat(preference.getIcon()).isNotNull();
+    }
+
+    @Test
+    public void onCreate_notificationEnabled_isChecked() throws Exception {
+        when(mMockManager.areNotificationsEnabledForPackage(PKG_NAME, UID)).thenReturn(true);
+
+        mPreferenceController.onCreate(mLifecycleOwner);
+        mPreferenceController.onDataLoaded(mAppEntryList);
+
+        CarUiTwoActionSwitchPreference preference = (CarUiTwoActionSwitchPreference)
+                mPreferenceCategory.getPreference(0);
+
+        assertThat(preference.isSecondaryActionChecked()).isTrue();
+    }
+
+    @Test
+    public void onCreate_notificationDisabled_isNotChecked() throws Exception {
+        when(mMockManager.areNotificationsEnabledForPackage(PKG_NAME, UID)).thenReturn(false);
+
+        mPreferenceController.onCreate(mLifecycleOwner);
+        mPreferenceController.onDataLoaded(mAppEntryList);
+
+        CarUiTwoActionSwitchPreference preference = (CarUiTwoActionSwitchPreference)
+                mPreferenceCategory.getPreference(0);
+
+        assertThat(preference.isSecondaryActionChecked()).isFalse();
+    }
+
+    @Test
+    public void toggle_setEnable_enablingNotification() throws Exception {
+        when(mMockManager.areNotificationsEnabledForPackage(PKG_NAME, UID)).thenReturn(false);
+        when(mMockManager.onlyHasDefaultChannel(PKG_NAME, UID)).thenReturn(false);
+
+        mPreferenceController.onCreate(mLifecycleOwner);
+        mPreferenceController.onDataLoaded(mAppEntryList);
+
+        CarUiTwoActionSwitchPreference preference = (CarUiTwoActionSwitchPreference)
+                mPreferenceCategory.getPreference(0);
+
+        preference.performSecondaryActionClick();
+
+        verify(mMockManager).setNotificationsEnabledForPackage(PKG_NAME, UID, true);
+    }
+
+    @Test
+    public void toggle_setDisable_disablingNotification() throws Exception {
+        when(mMockManager.areNotificationsEnabledForPackage(PKG_NAME, UID)).thenReturn(true);
+        when(mMockManager.onlyHasDefaultChannel(PKG_NAME, UID)).thenReturn(false);
+
+        mPreferenceController.onCreate(mLifecycleOwner);
+        mPreferenceController.onDataLoaded(mAppEntryList);
+
+        CarUiTwoActionSwitchPreference preference = (CarUiTwoActionSwitchPreference)
+                mPreferenceCategory.getPreference(0);
+
+        preference.performSecondaryActionClick();
+
+        verify(mMockManager).setNotificationsEnabledForPackage(PKG_NAME, UID, false);
+    }
+
+    @Test
+    public void toggle_onlyHasDefaultChannel_updateChannel() throws Exception {
+        when(mMockManager.areNotificationsEnabledForPackage(PKG_NAME, UID)).thenReturn(false);
+        when(mMockManager.onlyHasDefaultChannel(PKG_NAME, UID)).thenReturn(true);
+        when(mMockManager
+                .getNotificationChannelForPackage(
+                        PKG_NAME, UID, NotificationChannel.DEFAULT_CHANNEL_ID, null, true))
+                .thenReturn(mMockChannel);
+
+        mPreferenceController.onCreate(mLifecycleOwner);
+        mPreferenceController.onDataLoaded(mAppEntryList);
+
+        CarUiTwoActionSwitchPreference preference = (CarUiTwoActionSwitchPreference)
+                mPreferenceCategory.getPreference(0);
+
+        preference.performSecondaryActionClick();
+
+        verify(mMockManager).updateNotificationChannelForPackage(PKG_NAME, UID, mMockChannel);
+    }
+
+    @Test
+    @UiThreadTest
+    public void clickPreference_shouldOpenApplicationDetailsFragment() {
+        mPreferenceController.onCreate(mLifecycleOwner);
+        mPreferenceController.onDataLoaded(mAppEntryList);
+
+        CarUiTwoActionSwitchPreference preference = (CarUiTwoActionSwitchPreference)
+                mPreferenceCategory.getPreference(0);
+        preference.performClick();
+
+        verify(mFragmentController).launchFragment(any(ApplicationDetailsFragment.class));
+    }
+}
diff --git a/tests/unit/src/com/android/car/settings/notifications/RecentNotificationsAppsPreferenceControllerTest.java b/tests/unit/src/com/android/car/settings/notifications/RecentNotificationsAppsPreferenceControllerTest.java
new file mode 100644
index 0000000..56bcfd8
--- /dev/null
+++ b/tests/unit/src/com/android/car/settings/notifications/RecentNotificationsAppsPreferenceControllerTest.java
@@ -0,0 +1,234 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.notifications;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.after;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.INotificationManager;
+import android.app.usage.IUsageStatsManager;
+import android.app.usage.UsageEvents;
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.os.Parcel;
+import android.os.UserHandle;
+
+import androidx.lifecycle.LifecycleOwner;
+import androidx.preference.PreferenceCategory;
+import androidx.preference.PreferenceGroup;
+import androidx.preference.PreferenceManager;
+import androidx.preference.PreferenceScreen;
+import androidx.test.annotation.UiThreadTest;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.PreferenceControllerTestUtil;
+import com.android.car.settings.testutils.TestLifecycleOwner;
+import com.android.settingslib.applications.ApplicationsState;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+public class RecentNotificationsAppsPreferenceControllerTest {
+    private static final int TIMEOUT = 5000;
+
+    private Context mContext = ApplicationProvider.getApplicationContext();
+    private LifecycleOwner mLifecycleOwner;
+    private PreferenceGroup mPreferenceCategory;
+    private RecentNotificationsAppsPreferenceController mPreferenceController;
+    private CarUxRestrictions mCarUxRestrictions;
+    private int mMaxEntryCount;
+
+    @Mock
+    private FragmentController mFragmentController;
+    @Mock
+    private INotificationManager mMockManager;
+    @Mock
+    private ApplicationsState mMockApplicationsState;
+    @Mock
+    private IUsageStatsManager mMockUsageStatsManager;
+    @Mock
+    private ApplicationInfo mApplicationInfo;
+
+    @Before
+    @UiThreadTest
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mLifecycleOwner = new TestLifecycleOwner();
+        mCarUxRestrictions = new CarUxRestrictions.Builder(/* reqOpt= */ true,
+                CarUxRestrictions.UX_RESTRICTIONS_BASELINE, /* timestamp= */ 0).build();
+
+        PreferenceManager preferenceManager = new PreferenceManager(mContext);
+        PreferenceScreen screen = preferenceManager.createPreferenceScreen(mContext);
+        mPreferenceCategory = new PreferenceCategory(mContext);
+        screen.addPreference(mPreferenceCategory);
+
+        mPreferenceController = new RecentNotificationsAppsPreferenceController(mContext,
+                /* preferenceKey= */ "key", mFragmentController, mCarUxRestrictions);
+        PreferenceControllerTestUtil.assignPreference(mPreferenceController, mPreferenceCategory);
+
+        mPreferenceController.mNotificationManager = mMockManager;
+        mPreferenceController.mUsageStatsManager = mMockUsageStatsManager;
+        mPreferenceController.setApplicationsState(mMockApplicationsState);
+
+        mMaxEntryCount = mContext.getResources().getInteger(
+                R.integer.recent_notifications_apps_list_count);
+    }
+
+    @Test
+    public void noEvents_noPreferences() throws Exception {
+        when(mMockUsageStatsManager.queryEventsForUser(anyLong(),
+                anyLong(), anyInt(), anyString())).thenReturn(null);
+
+        mPreferenceController.onCreate(mLifecycleOwner);
+
+        assertThat(mPreferenceCategory.getPreferenceCount()).isEqualTo(0);
+    }
+
+    @Test
+    public void areEvents_showValidRecents() throws Exception {
+        List<UsageEvents.Event> events = new ArrayList<>();
+        UsageEvents.Event app1 = createUsageEvent("a", System.currentTimeMillis());
+        UsageEvents.Event app2 = createUsageEvent("com.android.settings",
+                System.currentTimeMillis());
+        UsageEvents.Event app3 = createUsageEvent("pkg.class2",
+                System.currentTimeMillis() - 1000);
+
+        events.add(app1);
+        events.add(app2);
+        events.add(app3);
+
+        ApplicationsState.AppEntry app1Entry = createAppEntry("app 1");
+        ApplicationsState.AppEntry app2Entry = createAppEntry("app 2");
+
+        // app1, app2 are valid apps. app3 is invalid.
+        when(mMockApplicationsState.getEntry(app1.getPackageName(), UserHandle.myUserId()))
+                .thenReturn(app1Entry);
+        when(mMockApplicationsState.getEntry(app2.getPackageName(), UserHandle.myUserId()))
+                .thenReturn(app2Entry);
+        when(mMockApplicationsState.getEntry(app3.getPackageName(), UserHandle.myUserId()))
+                .thenReturn(null);
+
+        UsageEvents usageEvents = getUsageEvents(
+                new String[] {app1.getPackageName(), app2.getPackageName(), app3.getPackageName()},
+                events);
+        when(mMockUsageStatsManager.queryEventsForUser(anyLong(), anyLong(), anyInt(), anyString()))
+                .thenReturn(usageEvents);
+
+        mPreferenceController.onCreate(mLifecycleOwner);
+        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+
+        // Only add app1 & app2. app3 skipped because it's invalid app.
+        verify(mMockApplicationsState, after(TIMEOUT).times(2))
+                .getEntry(eq(app1.getPackageName()), anyInt());
+        assertThat(mPreferenceCategory.getPreferenceCount()).isEqualTo(2);
+        assertThat(mPreferenceCategory.getPreference(0).getTitle()).isEqualTo(app1Entry.label);
+        assertThat(mPreferenceCategory.getPreference(1).getTitle()).isEqualTo(app2Entry.label);
+    }
+
+    @Test
+    public void areEvents_showMaximumRecents() throws Exception {
+        List<UsageEvents.Event> events = new ArrayList<>();
+        UsageEvents.Event app1 = createUsageEvent("a", System.currentTimeMillis());
+        UsageEvents.Event app2 = createUsageEvent("com.android.settings",
+                System.currentTimeMillis());
+        UsageEvents.Event app3 = createUsageEvent("pkg.class2",
+                System.currentTimeMillis() - 1000);
+        UsageEvents.Event app4 = createUsageEvent("pkg.class3",
+                System.currentTimeMillis() - 1000);
+
+        events.add(app1);
+        events.add(app2);
+        events.add(app3);
+        events.add(app4);
+
+        ApplicationsState.AppEntry app1Entry = createAppEntry("app 1");
+        ApplicationsState.AppEntry app2Entry = createAppEntry("app 2");
+        ApplicationsState.AppEntry app3Entry = createAppEntry("app 3");
+        ApplicationsState.AppEntry app4Entry = createAppEntry("app 4");
+
+        // Four valid apps found
+        when(mMockApplicationsState.getEntry(app1.getPackageName(), UserHandle.myUserId()))
+                .thenReturn(app1Entry);
+        when(mMockApplicationsState.getEntry(app2.getPackageName(), UserHandle.myUserId()))
+                .thenReturn(app2Entry);
+        when(mMockApplicationsState.getEntry(app3.getPackageName(), UserHandle.myUserId()))
+                .thenReturn(app3Entry);
+        when(mMockApplicationsState.getEntry(app4.getPackageName(), UserHandle.myUserId()))
+                .thenReturn(app4Entry);
+
+        UsageEvents usageEvents = getUsageEvents(
+                new String[] {app1.getPackageName(), app2.getPackageName(), app3.getPackageName(),
+                app4.getPackageName()}, events);
+        when(mMockUsageStatsManager.queryEventsForUser(anyLong(), anyLong(), anyInt(), anyString()))
+                .thenReturn(usageEvents);
+
+        mPreferenceController.onCreate(mLifecycleOwner);
+        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+
+        // Only max allowed apps displayed
+        verify(mMockApplicationsState, after(TIMEOUT).times(2))
+                .getEntry(eq(app1.getPackageName()), anyInt());
+        assertThat(mPreferenceCategory.getPreferenceCount()).isEqualTo(mMaxEntryCount);
+        assertThat(mPreferenceCategory.getPreference(0).getTitle()).isEqualTo(app1Entry.label);
+        assertThat(mPreferenceCategory.getPreference(1).getTitle()).isEqualTo(app2Entry.label);
+        assertThat(mPreferenceCategory.getPreference(2).getTitle()).isEqualTo(app3Entry.label);
+    }
+
+    private UsageEvents getUsageEvents(String[] pkgs, List<UsageEvents.Event> events) {
+        UsageEvents usageEvents = new UsageEvents(events, pkgs);
+        Parcel parcel = Parcel.obtain();
+        parcel.setDataPosition(0);
+        usageEvents.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        return UsageEvents.CREATOR.createFromParcel(parcel);
+    }
+
+    private UsageEvents.Event createUsageEvent(String packageName, long timestamp) {
+        UsageEvents.Event app = new UsageEvents.Event();
+        app.mEventType = UsageEvents.Event.NOTIFICATION_INTERRUPTION;
+        app.mPackage = packageName;
+        app.mTimeStamp = timestamp;
+        return app;
+    }
+
+    private ApplicationsState.AppEntry createAppEntry(String label) {
+        ApplicationsState.AppEntry appEntry = mock(ApplicationsState.AppEntry.class);
+        appEntry.info = mApplicationInfo;
+        appEntry.label = label;
+        return appEntry;
+    }
+}