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;
+ }
+}