Merge "Add ability to exclude apps from adjustments" into main
This commit is contained in:
committed by
Android (Google) Code Review
commit
6191772de7
@@ -56,4 +56,27 @@
|
|||||||
android:key="recs"
|
android:key="recs"
|
||||||
android:title="@*android:string/recs_notification_channel_label"
|
android:title="@*android:string/recs_notification_channel_label"
|
||||||
settings:controller="com.android.settings.notification.BundleTypePreferenceController"/>
|
settings:controller="com.android.settings.notification.BundleTypePreferenceController"/>
|
||||||
|
|
||||||
|
<PreferenceCategory
|
||||||
|
android:key="notification_bundle_excluded_apps_list"
|
||||||
|
android:title="@string/notification_excluded_apps"
|
||||||
|
settings:controller="com.android.settings.notification.AdjustmentExcludedAppsPreferenceController">
|
||||||
|
<com.android.settingslib.widget.TopIntroPreference
|
||||||
|
android:key="excluded_description"
|
||||||
|
android:title="@string/notification_bundle_excluded_description"/>
|
||||||
|
|
||||||
|
<!-- apps are added here -->
|
||||||
|
|
||||||
|
<Preference
|
||||||
|
android:key="notification_bundle_manage_apps"
|
||||||
|
android:order="1000"
|
||||||
|
android:icon="@drawable/ic_chevron_right_24dp"
|
||||||
|
android:title="@string/notification_summarization_manage_excluded_apps"
|
||||||
|
settings:controller="com.android.settings.notification.BundleManageAppsPreferenceController"
|
||||||
|
android:fragment="com.android.settings.applications.manageapplications.ManageApplications">
|
||||||
|
<extra
|
||||||
|
android:name="classname"
|
||||||
|
android:value="com.android.settings.Settings$NotificationExcludeClassificationActivity"/>
|
||||||
|
</Preference>
|
||||||
|
</PreferenceCategory>
|
||||||
</PreferenceScreen>
|
</PreferenceScreen>
|
||||||
|
@@ -36,4 +36,29 @@
|
|||||||
android:key="global_pref"
|
android:key="global_pref"
|
||||||
android:title="@string/notification_summarization_main_control_title"
|
android:title="@string/notification_summarization_main_control_title"
|
||||||
settings:controller="com.android.settings.notification.SummarizationGlobalPreferenceController" />
|
settings:controller="com.android.settings.notification.SummarizationGlobalPreferenceController" />
|
||||||
|
|
||||||
|
<PreferenceCategory
|
||||||
|
android:key="notification_summarization_excluded_apps_list"
|
||||||
|
android:title="@string/notification_excluded_apps"
|
||||||
|
settings:controller="com.android.settings.notification.AdjustmentExcludedAppsPreferenceController">
|
||||||
|
<com.android.settingslib.widget.TopIntroPreference
|
||||||
|
android:key="excluded_description"
|
||||||
|
android:title="@string/notification_summarization_excluded_description"/>
|
||||||
|
|
||||||
|
<!-- apps are added here -->
|
||||||
|
|
||||||
|
<Preference
|
||||||
|
android:key="notification_summarization_manage_apps"
|
||||||
|
android:order="1000"
|
||||||
|
android:icon="@drawable/ic_chevron_right_24dp"
|
||||||
|
android:title="@string/notification_summarization_manage_excluded_apps"
|
||||||
|
settings:controller="com.android.settings.notification.SummarizationManageAppsPreferenceController"
|
||||||
|
android:fragment="com.android.settings.applications.manageapplications.ManageApplications">
|
||||||
|
<extra
|
||||||
|
android:name="classname"
|
||||||
|
android:value="com.android.settings.Settings$NotificationExcludeSummarizationActivity"/>
|
||||||
|
</Preference>
|
||||||
|
</PreferenceCategory>
|
||||||
|
|
||||||
|
|
||||||
</PreferenceScreen>
|
</PreferenceScreen>
|
||||||
|
@@ -358,6 +358,8 @@ public class Settings extends SettingsActivity {
|
|||||||
public static class AppBubbleNotificationSettingsActivity extends SettingsActivity { /* empty */ }
|
public static class AppBubbleNotificationSettingsActivity extends SettingsActivity { /* empty */ }
|
||||||
public static class NotificationAssistantSettingsActivity extends SettingsActivity{ /* empty */ }
|
public static class NotificationAssistantSettingsActivity extends SettingsActivity{ /* empty */ }
|
||||||
public static class NotificationAppListActivity extends SettingsActivity { /* empty */ }
|
public static class NotificationAppListActivity extends SettingsActivity { /* empty */ }
|
||||||
|
public static class NotificationExcludeSummarizationActivity extends SettingsActivity { /* empty */ }
|
||||||
|
public static class NotificationExcludeClassificationActivity extends SettingsActivity { /* empty */ }
|
||||||
/** Activity to manage Cloned Apps page */
|
/** Activity to manage Cloned Apps page */
|
||||||
public static class ClonedAppsListActivity extends SettingsActivity { /* empty */ }
|
public static class ClonedAppsListActivity extends SettingsActivity { /* empty */ }
|
||||||
/** Activity to manage Aspect Ratio app list page */
|
/** Activity to manage Aspect Ratio app list page */
|
||||||
|
@@ -270,6 +270,8 @@ public class ManageApplications extends InstrumentedFragment
|
|||||||
public static final int LIST_TYPE_NFC_TAG_APPS = 18;
|
public static final int LIST_TYPE_NFC_TAG_APPS = 18;
|
||||||
public static final int LIST_TYPE_TURN_SCREEN_ON = 19;
|
public static final int LIST_TYPE_TURN_SCREEN_ON = 19;
|
||||||
public static final int LIST_TYPE_USER_ASPECT_RATIO_APPS = 20;
|
public static final int LIST_TYPE_USER_ASPECT_RATIO_APPS = 20;
|
||||||
|
public static final int LIST_TYPE_NOTIFICATION_EXCLUDE_SUMMARIZATION = 21;
|
||||||
|
public static final int LIST_TYPE_NOTIFICATION_EXCLUDE_CLASSIFICATION = 22;
|
||||||
|
|
||||||
// List types that should show instant apps.
|
// List types that should show instant apps.
|
||||||
public static final Set<Integer> LIST_TYPES_WITH_INSTANT = new ArraySet<>(Arrays.asList(
|
public static final Set<Integer> LIST_TYPES_WITH_INSTANT = new ArraySet<>(Arrays.asList(
|
||||||
|
@@ -31,6 +31,8 @@ import com.android.settings.Settings.ManageExternalSourcesActivity
|
|||||||
import com.android.settings.Settings.ManageExternalStorageActivity
|
import com.android.settings.Settings.ManageExternalStorageActivity
|
||||||
import com.android.settings.Settings.MediaManagementAppsActivity
|
import com.android.settings.Settings.MediaManagementAppsActivity
|
||||||
import com.android.settings.Settings.NotificationAppListActivity
|
import com.android.settings.Settings.NotificationAppListActivity
|
||||||
|
import com.android.settings.Settings.NotificationExcludeClassificationActivity
|
||||||
|
import com.android.settings.Settings.NotificationExcludeSummarizationActivity
|
||||||
import com.android.settings.Settings.NotificationReviewPermissionsActivity
|
import com.android.settings.Settings.NotificationReviewPermissionsActivity
|
||||||
import com.android.settings.Settings.OverlaySettingsActivity
|
import com.android.settings.Settings.OverlaySettingsActivity
|
||||||
import com.android.settings.Settings.StorageUseActivity
|
import com.android.settings.Settings.StorageUseActivity
|
||||||
@@ -44,6 +46,8 @@ import com.android.settings.applications.manageapplications.ManageApplications.L
|
|||||||
import com.android.settings.applications.manageapplications.ManageApplications.LIST_TYPE_USER_ASPECT_RATIO_APPS
|
import com.android.settings.applications.manageapplications.ManageApplications.LIST_TYPE_USER_ASPECT_RATIO_APPS
|
||||||
import com.android.settings.applications.manageapplications.ManageApplications.LIST_TYPE_BATTERY_OPTIMIZATION
|
import com.android.settings.applications.manageapplications.ManageApplications.LIST_TYPE_BATTERY_OPTIMIZATION
|
||||||
import com.android.settings.applications.manageapplications.ManageApplications.LIST_TYPE_CLONED_APPS
|
import com.android.settings.applications.manageapplications.ManageApplications.LIST_TYPE_CLONED_APPS
|
||||||
|
import com.android.settings.applications.manageapplications.ManageApplications.LIST_TYPE_NOTIFICATION_EXCLUDE_CLASSIFICATION
|
||||||
|
import com.android.settings.applications.manageapplications.ManageApplications.LIST_TYPE_NOTIFICATION_EXCLUDE_SUMMARIZATION
|
||||||
import com.android.settings.applications.manageapplications.ManageApplications.LIST_TYPE_GAMES
|
import com.android.settings.applications.manageapplications.ManageApplications.LIST_TYPE_GAMES
|
||||||
import com.android.settings.applications.manageapplications.ManageApplications.LIST_TYPE_HIGH_POWER
|
import com.android.settings.applications.manageapplications.ManageApplications.LIST_TYPE_HIGH_POWER
|
||||||
import com.android.settings.applications.manageapplications.ManageApplications.LIST_TYPE_LONG_BACKGROUND_TASKS
|
import com.android.settings.applications.manageapplications.ManageApplications.LIST_TYPE_LONG_BACKGROUND_TASKS
|
||||||
@@ -99,6 +103,9 @@ object ManageApplicationsUtil {
|
|||||||
ChangeNfcTagAppsActivity::class to LIST_TYPE_NFC_TAG_APPS,
|
ChangeNfcTagAppsActivity::class to LIST_TYPE_NFC_TAG_APPS,
|
||||||
TurnScreenOnSettingsActivity::class to LIST_TYPE_TURN_SCREEN_ON,
|
TurnScreenOnSettingsActivity::class to LIST_TYPE_TURN_SCREEN_ON,
|
||||||
UserAspectRatioAppListActivity::class to LIST_TYPE_USER_ASPECT_RATIO_APPS,
|
UserAspectRatioAppListActivity::class to LIST_TYPE_USER_ASPECT_RATIO_APPS,
|
||||||
|
NotificationExcludeSummarizationActivity::class to LIST_TYPE_NOTIFICATION_EXCLUDE_SUMMARIZATION,
|
||||||
|
NotificationExcludeClassificationActivity::class to LIST_TYPE_NOTIFICATION_EXCLUDE_CLASSIFICATION,
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|
||||||
@JvmField
|
@JvmField
|
||||||
@@ -117,7 +124,7 @@ object ManageApplicationsUtil {
|
|||||||
LIST_TYPE_MEDIA_MANAGEMENT_APPS -> MediaManagementAppsAppListProvider.getAppListRoute()
|
LIST_TYPE_MEDIA_MANAGEMENT_APPS -> MediaManagementAppsAppListProvider.getAppListRoute()
|
||||||
LIST_TYPE_ALARMS_AND_REMINDERS -> AlarmsAndRemindersAppListProvider.getAppListRoute()
|
LIST_TYPE_ALARMS_AND_REMINDERS -> AlarmsAndRemindersAppListProvider.getAppListRoute()
|
||||||
LIST_TYPE_WIFI_ACCESS -> WifiControlAppListProvider.getAppListRoute()
|
LIST_TYPE_WIFI_ACCESS -> WifiControlAppListProvider.getAppListRoute()
|
||||||
LIST_TYPE_NOTIFICATION -> AppListNotificationsPageProvider.name
|
LIST_TYPE_NOTIFICATION -> AppListNotificationsPageProvider.AllApps.name
|
||||||
LIST_TYPE_APPS_LOCALE -> AppLanguagesPageProvider.name
|
LIST_TYPE_APPS_LOCALE -> AppLanguagesPageProvider.name
|
||||||
LIST_TYPE_MAIN -> AllAppListPageProvider.name
|
LIST_TYPE_MAIN -> AllAppListPageProvider.name
|
||||||
LIST_TYPE_NFC_TAG_APPS -> NfcTagAppsSettingsProvider.getAppListRoute()
|
LIST_TYPE_NFC_TAG_APPS -> NfcTagAppsSettingsProvider.getAppListRoute()
|
||||||
@@ -128,6 +135,8 @@ object ManageApplicationsUtil {
|
|||||||
//LIST_TYPE_STORAGE -> StorageAppListPageProvider.Apps.name
|
//LIST_TYPE_STORAGE -> StorageAppListPageProvider.Apps.name
|
||||||
//LIST_TYPE_GAMES -> StorageAppListPageProvider.Games.name
|
//LIST_TYPE_GAMES -> StorageAppListPageProvider.Games.name
|
||||||
LIST_TYPE_BATTERY_OPTIMIZATION -> BatteryOptimizationModeAppListPageProvider.name
|
LIST_TYPE_BATTERY_OPTIMIZATION -> BatteryOptimizationModeAppListPageProvider.name
|
||||||
|
LIST_TYPE_NOTIFICATION_EXCLUDE_SUMMARIZATION -> AppListNotificationsPageProvider.ExcludeSummarization.name
|
||||||
|
LIST_TYPE_NOTIFICATION_EXCLUDE_CLASSIFICATION -> AppListNotificationsPageProvider.ExcludeClassification.name
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,212 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2025 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.settings.notification;
|
||||||
|
|
||||||
|
import static android.service.notification.Adjustment.KEY_SUMMARIZATION;
|
||||||
|
import static android.service.notification.Adjustment.KEY_TYPE;
|
||||||
|
|
||||||
|
import android.app.Flags;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.service.notification.Adjustment;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.annotation.VisibleForTesting;
|
||||||
|
import androidx.core.text.BidiFormatter;
|
||||||
|
import androidx.fragment.app.Fragment;
|
||||||
|
import androidx.preference.Preference;
|
||||||
|
import androidx.preference.PreferenceCategory;
|
||||||
|
import androidx.preference.PreferenceScreen;
|
||||||
|
|
||||||
|
import com.android.settings.core.BasePreferenceController;
|
||||||
|
import com.android.settings.core.PreferenceControllerMixin;
|
||||||
|
import com.android.settingslib.applications.AppUtils;
|
||||||
|
import com.android.settingslib.applications.ApplicationsState;
|
||||||
|
import com.android.settingslib.utils.ThreadUtils;
|
||||||
|
|
||||||
|
import com.google.common.util.concurrent.ListenableFuture;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a preference to the PreferenceCategory for every app excluded from an adjustment key
|
||||||
|
*/
|
||||||
|
public class AdjustmentExcludedAppsPreferenceController extends BasePreferenceController
|
||||||
|
implements PreferenceControllerMixin {
|
||||||
|
|
||||||
|
@NonNull private NotificationBackend mBackend;
|
||||||
|
|
||||||
|
@Nullable String mAdjustmentKey;
|
||||||
|
@Nullable @VisibleForTesting ApplicationsState mApplicationsState;
|
||||||
|
@VisibleForTesting PreferenceCategory mPreferenceCategory;
|
||||||
|
@VisibleForTesting Context mPrefContext;
|
||||||
|
|
||||||
|
private ApplicationsState.Session mAppSession;
|
||||||
|
|
||||||
|
public AdjustmentExcludedAppsPreferenceController(@NonNull Context context,
|
||||||
|
@NonNull String preferenceKey) {
|
||||||
|
super(context, preferenceKey);
|
||||||
|
mBackend = new NotificationBackend();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void onAttach(@Nullable ApplicationsState appState, @Nullable Fragment host,
|
||||||
|
@NonNull NotificationBackend helperBackend, @Adjustment.Keys String adjustment) {
|
||||||
|
mApplicationsState = appState;
|
||||||
|
mBackend = helperBackend;
|
||||||
|
mAdjustmentKey = adjustment;
|
||||||
|
|
||||||
|
if (mApplicationsState != null && host != null) {
|
||||||
|
mAppSession = mApplicationsState.newSession(mAppSessionCallbacks, host.getLifecycle());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void displayPreference(@NonNull PreferenceScreen screen) {
|
||||||
|
mPreferenceCategory = screen.findPreference(getPreferenceKey());
|
||||||
|
mPrefContext = screen.getContext();
|
||||||
|
updateAppList();
|
||||||
|
super.displayPreference(screen);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getAvailabilityStatus() {
|
||||||
|
if (!(Flags.nmSummarization() || Flags.nmSummarizationUi()
|
||||||
|
|| Flags.notificationClassificationUi())) {
|
||||||
|
return CONDITIONALLY_UNAVAILABLE;
|
||||||
|
}
|
||||||
|
if (KEY_SUMMARIZATION.equals(mAdjustmentKey)
|
||||||
|
&& mBackend.isNotificationSummarizationSupported()) {
|
||||||
|
return AVAILABLE;
|
||||||
|
}
|
||||||
|
if (KEY_TYPE.equals(mAdjustmentKey) && mBackend.isNotificationBundlingSupported()) {
|
||||||
|
return AVAILABLE;
|
||||||
|
}
|
||||||
|
return CONDITIONALLY_UNAVAILABLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call this method to trigger the app list to refresh.
|
||||||
|
*/
|
||||||
|
public void updateAppList() {
|
||||||
|
if (mAppSession == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ApplicationsState.AppFilter filter = android.multiuser.Flags.enablePrivateSpaceFeatures()
|
||||||
|
&& android.multiuser.Flags.handleInterleavedSettingsForPrivateSpace()
|
||||||
|
? ApplicationsState.FILTER_ENABLED_NOT_QUIET
|
||||||
|
: ApplicationsState.FILTER_ALL_ENABLED;
|
||||||
|
mAppSession.rebuild(filter, ApplicationsState.ALPHA_COMPARATOR);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the icon for the given preference to the entry icon from cache if available, or look
|
||||||
|
// it up.
|
||||||
|
private void updateIcon(Preference pref, ApplicationsState.AppEntry entry) {
|
||||||
|
synchronized (entry) {
|
||||||
|
final Drawable cachedIcon = AppUtils.getIconFromCache(entry);
|
||||||
|
if (cachedIcon != null && entry.mounted) {
|
||||||
|
pref.setIcon(cachedIcon);
|
||||||
|
} else {
|
||||||
|
ListenableFuture unused = ThreadUtils.postOnBackgroundThread(() -> {
|
||||||
|
final Drawable icon = AppUtils.getIcon(mPrefContext, entry);
|
||||||
|
if (icon != null) {
|
||||||
|
ThreadUtils.postOnMainThread(() -> pref.setIcon(icon));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
void updateAppList(List<ApplicationsState.AppEntry> apps) {
|
||||||
|
if (mPreferenceCategory == null || apps == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> excludedApps = List.of(mBackend.getAdjustmentDeniedPackages(mAdjustmentKey));
|
||||||
|
|
||||||
|
for (ApplicationsState.AppEntry app : apps) {
|
||||||
|
String pkg = app.info.packageName;
|
||||||
|
final String key = getKey(pkg, app.info.uid);
|
||||||
|
boolean doesAppPassCriteria = false;
|
||||||
|
|
||||||
|
if (excludedApps.contains(pkg)) {
|
||||||
|
doesAppPassCriteria = true;
|
||||||
|
}
|
||||||
|
Preference pref = mPreferenceCategory.findPreference(key);
|
||||||
|
if (pref == null) {
|
||||||
|
if (doesAppPassCriteria) {
|
||||||
|
// does not exist but should
|
||||||
|
pref = new Preference(mPrefContext);
|
||||||
|
pref.setKey(key);
|
||||||
|
pref.setTitle(BidiFormatter.getInstance().unicodeWrap(app.label));
|
||||||
|
updateIcon(pref, app);
|
||||||
|
mPreferenceCategory.addPreference(pref);
|
||||||
|
}
|
||||||
|
} else if (!doesAppPassCriteria) {
|
||||||
|
// exists but shouldn't anymore
|
||||||
|
mPreferenceCategory.removePreference(pref);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a unique key to identify an AppPreference
|
||||||
|
*/
|
||||||
|
static String getKey(String pkg, int uid) {
|
||||||
|
return "all|" + pkg + "|" + uid;
|
||||||
|
}
|
||||||
|
|
||||||
|
private final ApplicationsState.Callbacks mAppSessionCallbacks =
|
||||||
|
new ApplicationsState.Callbacks() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRunningStateChanged(boolean running) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPackageListChanged() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRebuildComplete(@NonNull ArrayList<ApplicationsState.AppEntry> apps) {
|
||||||
|
updateAppList(apps);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPackageIconChanged() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPackageSizeChanged(@NonNull String packageName) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAllSizesComputed() { }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLauncherInfoChanged() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLoadEntriesCompleted() {
|
||||||
|
updateAppList();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
@@ -0,0 +1,44 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2025 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.settings.notification;
|
||||||
|
|
||||||
|
import android.app.Flags;
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import com.android.settings.core.BasePreferenceController;
|
||||||
|
|
||||||
|
public class BundleManageAppsPreferenceController extends
|
||||||
|
BasePreferenceController {
|
||||||
|
|
||||||
|
NotificationBackend mBackend;
|
||||||
|
|
||||||
|
public BundleManageAppsPreferenceController(@NonNull Context context,
|
||||||
|
@NonNull String preferenceKey) {
|
||||||
|
super(context, preferenceKey);
|
||||||
|
mBackend = new NotificationBackend();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getAvailabilityStatus() {
|
||||||
|
if (Flags.notificationClassificationUi() && mBackend.isNotificationBundlingSupported()) {
|
||||||
|
return AVAILABLE;
|
||||||
|
}
|
||||||
|
return CONDITIONALLY_UNAVAILABLE;
|
||||||
|
}
|
||||||
|
}
|
@@ -16,6 +16,11 @@
|
|||||||
|
|
||||||
package com.android.settings.notification;
|
package com.android.settings.notification;
|
||||||
|
|
||||||
|
import static android.service.notification.Adjustment.KEY_SUMMARIZATION;
|
||||||
|
import static android.service.notification.Adjustment.KEY_TYPE;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.app.Application;
|
||||||
import android.app.settings.SettingsEnums;
|
import android.app.settings.SettingsEnums;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.app.Flags;
|
import android.app.Flags;
|
||||||
@@ -25,6 +30,7 @@ import androidx.lifecycle.Lifecycle;
|
|||||||
import com.android.settings.R;
|
import com.android.settings.R;
|
||||||
import com.android.settings.dashboard.DashboardFragment;
|
import com.android.settings.dashboard.DashboardFragment;
|
||||||
import com.android.settings.search.BaseSearchIndexProvider;
|
import com.android.settings.search.BaseSearchIndexProvider;
|
||||||
|
import com.android.settingslib.applications.ApplicationsState;
|
||||||
import com.android.settingslib.search.SearchIndexable;
|
import com.android.settingslib.search.SearchIndexable;
|
||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
@@ -49,6 +55,26 @@ public class BundlePreferenceFragment extends DashboardFragment {
|
|||||||
return "BundlePreferenceFragment";
|
return "BundlePreferenceFragment";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAttach(Context context) {
|
||||||
|
super.onAttach(context);
|
||||||
|
if (use(AdjustmentExcludedAppsPreferenceController.class) != null) {
|
||||||
|
final Activity activity = getActivity();
|
||||||
|
Application app = null;
|
||||||
|
ApplicationsState appState = null;
|
||||||
|
if (activity != null) {
|
||||||
|
app = activity.getApplication();
|
||||||
|
} else {
|
||||||
|
app = null;
|
||||||
|
}
|
||||||
|
if (app != null) {
|
||||||
|
appState = ApplicationsState.getInstance(app);
|
||||||
|
}
|
||||||
|
use(AdjustmentExcludedAppsPreferenceController.class).onAttach(
|
||||||
|
appState, this, new NotificationBackend(), KEY_TYPE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
|
public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
|
||||||
new BaseSearchIndexProvider(R.xml.bundle_notifications_settings) {
|
new BaseSearchIndexProvider(R.xml.bundle_notifications_settings) {
|
||||||
|
|
||||||
|
@@ -0,0 +1,45 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2025 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.settings.notification;
|
||||||
|
|
||||||
|
import android.app.Flags;
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import com.android.settings.core.BasePreferenceController;
|
||||||
|
|
||||||
|
public class SummarizationManageAppsPreferenceController extends
|
||||||
|
BasePreferenceController {
|
||||||
|
|
||||||
|
NotificationBackend mBackend;
|
||||||
|
|
||||||
|
public SummarizationManageAppsPreferenceController(@NonNull Context context,
|
||||||
|
@NonNull String preferenceKey) {
|
||||||
|
super(context, preferenceKey);
|
||||||
|
mBackend = new NotificationBackend();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getAvailabilityStatus() {
|
||||||
|
if ((Flags.nmSummarization() || Flags.nmSummarizationUi())
|
||||||
|
&& mBackend.isNotificationSummarizationSupported()) {
|
||||||
|
return AVAILABLE;
|
||||||
|
}
|
||||||
|
return CONDITIONALLY_UNAVAILABLE;
|
||||||
|
}
|
||||||
|
}
|
@@ -16,13 +16,19 @@
|
|||||||
|
|
||||||
package com.android.settings.notification;
|
package com.android.settings.notification;
|
||||||
|
|
||||||
|
import static android.service.notification.Adjustment.KEY_SUMMARIZATION;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.app.Application;
|
||||||
import android.app.Flags;
|
import android.app.Flags;
|
||||||
import android.app.settings.SettingsEnums;
|
import android.app.settings.SettingsEnums;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
|
||||||
import com.android.settings.R;
|
import com.android.settings.R;
|
||||||
import com.android.settings.dashboard.DashboardFragment;
|
import com.android.settings.dashboard.DashboardFragment;
|
||||||
|
import com.android.settings.notification.app.HeaderPreferenceController;
|
||||||
import com.android.settings.search.BaseSearchIndexProvider;
|
import com.android.settings.search.BaseSearchIndexProvider;
|
||||||
|
import com.android.settingslib.applications.ApplicationsState;
|
||||||
import com.android.settingslib.search.SearchIndexable;
|
import com.android.settingslib.search.SearchIndexable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -45,6 +51,26 @@ public class SummarizationPreferenceFragment extends DashboardFragment {
|
|||||||
return "SummarizationPreferenceFragment";
|
return "SummarizationPreferenceFragment";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAttach(Context context) {
|
||||||
|
super.onAttach(context);
|
||||||
|
if (use(AdjustmentExcludedAppsPreferenceController.class) != null) {
|
||||||
|
final Activity activity = getActivity();
|
||||||
|
Application app = null;
|
||||||
|
ApplicationsState appState = null;
|
||||||
|
if (activity != null) {
|
||||||
|
app = activity.getApplication();
|
||||||
|
} else {
|
||||||
|
app = null;
|
||||||
|
}
|
||||||
|
if (app != null) {
|
||||||
|
appState = ApplicationsState.getInstance(app);
|
||||||
|
}
|
||||||
|
use(AdjustmentExcludedAppsPreferenceController.class).onAttach(
|
||||||
|
appState, this, new NotificationBackend(), KEY_SUMMARIZATION);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
|
public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
|
||||||
new BaseSearchIndexProvider(R.xml.summarization_notifications_settings) {
|
new BaseSearchIndexProvider(R.xml.summarization_notifications_settings) {
|
||||||
|
|
||||||
|
@@ -45,7 +45,6 @@ import com.android.settings.spa.app.specialaccess.WifiControlAppListProvider
|
|||||||
import com.android.settings.spa.app.specialaccess.WriteSystemPreferencesAppListProvider
|
import com.android.settings.spa.app.specialaccess.WriteSystemPreferencesAppListProvider
|
||||||
import com.android.settings.spa.app.storage.StorageAppListPageProvider
|
import com.android.settings.spa.app.storage.StorageAppListPageProvider
|
||||||
import com.android.settings.spa.core.instrumentation.SpaLogMetricsProvider
|
import com.android.settings.spa.core.instrumentation.SpaLogMetricsProvider
|
||||||
import com.android.settings.spa.core.instrumentation.SpaLogProvider
|
|
||||||
import com.android.settings.spa.development.UsageStatsPageProvider
|
import com.android.settings.spa.development.UsageStatsPageProvider
|
||||||
import com.android.settings.spa.development.compat.PlatformCompatAppListPageProvider
|
import com.android.settings.spa.development.compat.PlatformCompatAppListPageProvider
|
||||||
import com.android.settings.spa.home.HomePageProvider
|
import com.android.settings.spa.home.HomePageProvider
|
||||||
@@ -107,7 +106,9 @@ open class SettingsSpaEnvironment(context: Context) : SpaEnvironment(context) {
|
|||||||
AppInfoSettingsProvider,
|
AppInfoSettingsProvider,
|
||||||
SpecialAppAccessPageProvider,
|
SpecialAppAccessPageProvider,
|
||||||
NotificationMainPageProvider,
|
NotificationMainPageProvider,
|
||||||
AppListNotificationsPageProvider,
|
AppListNotificationsPageProvider.AllApps,
|
||||||
|
AppListNotificationsPageProvider.ExcludeClassification,
|
||||||
|
AppListNotificationsPageProvider.ExcludeSummarization,
|
||||||
SystemMainPageProvider,
|
SystemMainPageProvider,
|
||||||
LanguageAndInputPageProvider,
|
LanguageAndInputPageProvider,
|
||||||
AppLanguagesPageProvider,
|
AppLanguagesPageProvider,
|
||||||
|
@@ -17,34 +17,72 @@
|
|||||||
package com.android.settings.spa.notification
|
package com.android.settings.spa.notification
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import androidx.annotation.StringRes
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import com.android.settings.R
|
import com.android.settings.R
|
||||||
import com.android.settingslib.spa.framework.common.SettingsPageProvider
|
import com.android.settingslib.spa.framework.common.SettingsPageProvider
|
||||||
import com.android.settingslib.spa.framework.compose.navigator
|
import com.android.settingslib.spa.framework.compose.navigator
|
||||||
import com.android.settingslib.spa.framework.compose.rememberContext
|
|
||||||
import com.android.settingslib.spa.widget.preference.Preference
|
import com.android.settingslib.spa.widget.preference.Preference
|
||||||
import com.android.settingslib.spa.widget.preference.PreferenceModel
|
import com.android.settingslib.spa.widget.preference.PreferenceModel
|
||||||
|
import com.android.settingslib.spaprivileged.template.app.AppList
|
||||||
|
import com.android.settingslib.spaprivileged.template.app.AppListInput
|
||||||
import com.android.settingslib.spaprivileged.template.app.AppListPage
|
import com.android.settingslib.spaprivileged.template.app.AppListPage
|
||||||
|
|
||||||
object AppListNotificationsPageProvider : SettingsPageProvider {
|
sealed class AppListNotificationsPageProvider(private val type: ListType) : SettingsPageProvider {
|
||||||
override val name = "AppListNotifications"
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun Page(arguments: Bundle?) {
|
override fun Page(arguments: Bundle?) {
|
||||||
AppListPage(
|
NotificationsAppListPage(type)
|
||||||
title = stringResource(R.string.app_notifications_title),
|
|
||||||
listModel = rememberContext(::AppNotificationsListModel),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
object AllApps : AppListNotificationsPageProvider(ListType.Apps) {
|
||||||
|
override val name = "AppListNotifications"
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun EntryItem() {
|
fun EntryItem() {
|
||||||
val summary = stringResource(R.string.app_notification_field_summary)
|
val summary = stringResource(R.string.app_notification_field_summary)
|
||||||
Preference(object : PreferenceModel {
|
Preference(object : PreferenceModel {
|
||||||
override val title = stringResource(R.string.app_notifications_title)
|
override val title = stringResource(ListType.Apps.titleResource)
|
||||||
override val summary = { summary }
|
override val summary = { summary }
|
||||||
override val onClick = navigator(name)
|
override val onClick = navigator(name)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
object ExcludeSummarization : AppListNotificationsPageProvider(ListType.ExcludeSummarization) {
|
||||||
|
override val name = "NotificationsExcludeSummarization"
|
||||||
|
}
|
||||||
|
|
||||||
|
object ExcludeClassification : AppListNotificationsPageProvider(ListType.ExcludeClassification) {
|
||||||
|
override val name = "NotificationsExcludeClassification"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun NotificationsAppListPage(
|
||||||
|
type: ListType,
|
||||||
|
appList: @Composable AppListInput<AppNotificationsRecord>.() -> Unit = { AppList() }
|
||||||
|
) {
|
||||||
|
val context = LocalContext.current
|
||||||
|
AppListPage(
|
||||||
|
title = stringResource(type.titleResource),
|
||||||
|
listModel = remember(context) { AppNotificationsListModel(context, type) },
|
||||||
|
appList = appList,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed class ListType(
|
||||||
|
@StringRes val titleResource: Int
|
||||||
|
) {
|
||||||
|
object Apps : ListType(
|
||||||
|
titleResource = R.string.app_notifications_title,
|
||||||
|
)
|
||||||
|
object ExcludeSummarization : ListType(
|
||||||
|
titleResource = R.string.notification_summarization_manage_excluded_apps_title,
|
||||||
|
)
|
||||||
|
object ExcludeClassification : ListType(
|
||||||
|
titleResource = R.string.notification_bundle_manage_excluded_apps_title,
|
||||||
|
)
|
||||||
|
}
|
@@ -17,12 +17,16 @@
|
|||||||
package com.android.settings.spa.notification
|
package com.android.settings.spa.notification
|
||||||
|
|
||||||
import android.content.pm.ApplicationInfo
|
import android.content.pm.ApplicationInfo
|
||||||
|
import android.service.notification.Adjustment
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
|
import com.android.settings.spa.app.storage.StorageAppListModel
|
||||||
|
import com.android.settings.spa.app.storage.StorageType
|
||||||
|
|
||||||
class AppNotificationController(
|
class AppNotificationController(
|
||||||
private val repository: AppNotificationRepository,
|
private val repository: AppNotificationRepository,
|
||||||
private val app: ApplicationInfo,
|
private val app: ApplicationInfo,
|
||||||
|
private val listType: ListType,
|
||||||
) {
|
) {
|
||||||
val isEnabled: LiveData<Boolean>
|
val isEnabled: LiveData<Boolean>
|
||||||
get() = _isEnabled
|
get() = _isEnabled
|
||||||
@@ -47,4 +51,62 @@ class AppNotificationController(
|
|||||||
postValue(it)
|
postValue(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val isAllowed: LiveData<Boolean>
|
||||||
|
get() = _isAllowed
|
||||||
|
|
||||||
|
fun getAllowed() = _isAllowed.get()
|
||||||
|
|
||||||
|
fun setAllowed(enabled: Boolean) {
|
||||||
|
when (listType) {
|
||||||
|
ListType.ExcludeSummarization -> {
|
||||||
|
if (repository.setAdjustmentSupportedForPackage(
|
||||||
|
app, Adjustment.KEY_SUMMARIZATION, enabled)) {
|
||||||
|
_isAllowed.postValue(enabled)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ListType.ExcludeClassification -> {
|
||||||
|
if (repository.setAdjustmentSupportedForPackage(
|
||||||
|
app, Adjustment.KEY_TYPE, enabled)) {
|
||||||
|
_isAllowed.postValue(enabled)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val _isAllowed = object : MutableLiveData<Boolean>() {
|
||||||
|
override fun onActive() {
|
||||||
|
when (listType) {
|
||||||
|
ListType.ExcludeSummarization -> {
|
||||||
|
postValue(repository.isAdjustmentSupportedForPackage(
|
||||||
|
app, Adjustment.KEY_SUMMARIZATION))
|
||||||
|
}
|
||||||
|
ListType.ExcludeClassification -> {
|
||||||
|
postValue(repository.isAdjustmentSupportedForPackage(
|
||||||
|
app, Adjustment.KEY_TYPE))
|
||||||
|
}
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onInactive() {
|
||||||
|
}
|
||||||
|
|
||||||
|
fun get(): Boolean = when (listType) {
|
||||||
|
ListType.ExcludeSummarization -> {
|
||||||
|
value ?: repository.isAdjustmentSupportedForPackage(
|
||||||
|
app, Adjustment.KEY_SUMMARIZATION).also {
|
||||||
|
postValue(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ListType.ExcludeClassification -> {
|
||||||
|
value ?: repository.isAdjustmentSupportedForPackage(
|
||||||
|
app, Adjustment.KEY_TYPE).also {
|
||||||
|
postValue(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -126,6 +126,20 @@ class AppNotificationRepository(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun isAdjustmentSupportedForPackage(app: ApplicationInfo, key: String): Boolean =
|
||||||
|
notificationManager.isAdjustmentSupportedForPackage(key, app.packageName)
|
||||||
|
|
||||||
|
fun setAdjustmentSupportedForPackage(app: ApplicationInfo, key: String, enabled: Boolean):
|
||||||
|
Boolean {
|
||||||
|
return try {
|
||||||
|
notificationManager.setAdjustmentSupportedForPackage(key, app.packageName, enabled)
|
||||||
|
true
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.w(TAG, "Error calling INotificationManager", e)
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun isUserUnlocked(user: Int): Boolean {
|
fun isUserUnlocked(user: Int): Boolean {
|
||||||
return try {
|
return try {
|
||||||
userManager.isUserUnlocked(user)
|
userManager.isUserUnlocked(user)
|
||||||
|
@@ -36,6 +36,7 @@ import com.android.settingslib.spaprivileged.model.app.AppListModel
|
|||||||
import com.android.settingslib.spaprivileged.model.app.AppRecord
|
import com.android.settingslib.spaprivileged.model.app.AppRecord
|
||||||
import com.android.settingslib.spaprivileged.model.app.userId
|
import com.android.settingslib.spaprivileged.model.app.userId
|
||||||
import com.android.settingslib.spaprivileged.template.app.AppListItemModel
|
import com.android.settingslib.spaprivileged.template.app.AppListItemModel
|
||||||
|
import com.android.settingslib.spaprivileged.template.app.AppListSwitchItem
|
||||||
import com.android.settingslib.spaprivileged.template.app.AppListTwoTargetSwitchItem
|
import com.android.settingslib.spaprivileged.template.app.AppListTwoTargetSwitchItem
|
||||||
import com.android.settingslib.utils.StringUtil
|
import com.android.settingslib.utils.StringUtil
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
@@ -52,6 +53,7 @@ data class AppNotificationsRecord(
|
|||||||
|
|
||||||
class AppNotificationsListModel(
|
class AppNotificationsListModel(
|
||||||
private val context: Context,
|
private val context: Context,
|
||||||
|
private val listType: ListType
|
||||||
) : AppListModel<AppNotificationsRecord> {
|
) : AppListModel<AppNotificationsRecord> {
|
||||||
private val repository = AppNotificationRepository(context)
|
private val repository = AppNotificationRepository(context)
|
||||||
private val now = System.currentTimeMillis()
|
private val now = System.currentTimeMillis()
|
||||||
@@ -64,7 +66,7 @@ class AppNotificationsListModel(
|
|||||||
AppNotificationsRecord(
|
AppNotificationsRecord(
|
||||||
app = app,
|
app = app,
|
||||||
sentState = usageEvents[app.packageName],
|
sentState = usageEvents[app.packageName],
|
||||||
controller = AppNotificationController(repository, app),
|
controller = AppNotificationController(repository, app, listType),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -129,6 +131,22 @@ class AppNotificationsListModel(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun AppListItemModel<AppNotificationsRecord>.AppItem() {
|
override fun AppListItemModel<AppNotificationsRecord>.AppItem() {
|
||||||
|
when (listType) {
|
||||||
|
ListType.ExcludeSummarization -> {
|
||||||
|
AppListSwitchItem(
|
||||||
|
checked = record.controller.isAllowed.observeAsCallback(),
|
||||||
|
changeable = { true },
|
||||||
|
onCheckedChange = record.controller::setAllowed,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
ListType.ExcludeClassification -> {
|
||||||
|
AppListSwitchItem(
|
||||||
|
checked = record.controller.isAllowed.observeAsCallback(),
|
||||||
|
changeable = { true },
|
||||||
|
onCheckedChange = record.controller::setAllowed,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
val changeable by produceState(initialValue = false) {
|
val changeable by produceState(initialValue = false) {
|
||||||
withContext(Dispatchers.Default) {
|
withContext(Dispatchers.Default) {
|
||||||
value = repository.isChangeable(record.app)
|
value = repository.isChangeable(record.app)
|
||||||
@@ -141,6 +159,8 @@ class AppNotificationsListModel(
|
|||||||
onCheckedChange = record.controller::setEnabled,
|
onCheckedChange = record.controller::setEnabled,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun navigateToAppNotificationSettings(app: ApplicationInfo) {
|
private fun navigateToAppNotificationSettings(app: ApplicationInfo) {
|
||||||
AppInfoBase.startAppInfoFragment(
|
AppInfoBase.startAppInfoFragment(
|
||||||
|
@@ -41,7 +41,7 @@ object NotificationMainPageProvider : SettingsPageProvider {
|
|||||||
@Composable
|
@Composable
|
||||||
override fun Page(arguments: Bundle?) {
|
override fun Page(arguments: Bundle?) {
|
||||||
RegularScaffold(title = getTitle(arguments)) {
|
RegularScaffold(title = getTitle(arguments)) {
|
||||||
AppListNotificationsPageProvider.EntryItem()
|
AppListNotificationsPageProvider.AllApps.EntryItem()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -0,0 +1,151 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2025 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.settings.notification;
|
||||||
|
|
||||||
|
import static android.service.notification.Adjustment.KEY_SUMMARIZATION;
|
||||||
|
|
||||||
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
|
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
import android.app.Flags;
|
||||||
|
import android.app.INotificationManager;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.pm.ApplicationInfo;
|
||||||
|
import android.platform.test.annotations.EnableFlags;
|
||||||
|
import android.platform.test.flag.junit.SetFlagsRule;
|
||||||
|
|
||||||
|
import androidx.fragment.app.Fragment;
|
||||||
|
import androidx.preference.Preference;
|
||||||
|
import androidx.preference.PreferenceCategory;
|
||||||
|
import androidx.preference.PreferenceManager;
|
||||||
|
import androidx.preference.PreferenceScreen;
|
||||||
|
|
||||||
|
import com.android.settingslib.applications.ApplicationsState;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Rule;
|
||||||
|
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;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@RunWith(RobolectricTestRunner.class)
|
||||||
|
@EnableFlags({Flags.FLAG_NM_SUMMARIZATION_UI, Flags.FLAG_NM_SUMMARIZATION,
|
||||||
|
Flags.FLAG_NOTIFICATION_CLASSIFICATION_UI})
|
||||||
|
public class AdjustmentExcludedAppsPreferenceControllerTest {
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private NotificationBackend mBackend;
|
||||||
|
@Mock
|
||||||
|
private ApplicationsState mApplicationState;
|
||||||
|
private AdjustmentExcludedAppsPreferenceController mController;
|
||||||
|
private Context mContext;
|
||||||
|
@Mock
|
||||||
|
INotificationManager mInm;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setup() {
|
||||||
|
MockitoAnnotations.initMocks(this);
|
||||||
|
mContext = RuntimeEnvironment.application;
|
||||||
|
|
||||||
|
mController = new AdjustmentExcludedAppsPreferenceController(mContext, "key");
|
||||||
|
mController.onAttach(null, mock(Fragment.class), mBackend, KEY_SUMMARIZATION);
|
||||||
|
PreferenceScreen screen = new PreferenceManager(mContext).createPreferenceScreen(mContext);
|
||||||
|
mController.mPreferenceCategory = new PreferenceCategory(mContext);
|
||||||
|
screen.addPreference(mController.mPreferenceCategory);
|
||||||
|
|
||||||
|
mController.mApplicationsState = mApplicationState;
|
||||||
|
mController.mPrefContext = mContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testIsAvailable() {
|
||||||
|
when(mBackend.isNotificationBundlingSupported()).thenReturn(true);
|
||||||
|
when(mBackend.isNotificationSummarizationSupported()).thenReturn(true);
|
||||||
|
assertThat(mController.isAvailable()).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void isAvailable_flagEnabledNasDoesNotSupport_shouldReturnFalse() throws Exception {
|
||||||
|
when(mInm.getUnsupportedAdjustmentTypes()).thenReturn(List.of(KEY_SUMMARIZATION));
|
||||||
|
assertThat(mController.isAvailable()).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testUpdateAppList() throws Exception {
|
||||||
|
when(mBackend.getAdjustmentDeniedPackages(KEY_SUMMARIZATION)).thenReturn(
|
||||||
|
new String[] {"cannot", "cannot2"});
|
||||||
|
|
||||||
|
// GIVEN there are four apps, and two have KEY_SUMMARIZATION off
|
||||||
|
ApplicationsState.AppEntry canSummarize =
|
||||||
|
mock(ApplicationsState.AppEntry.class);
|
||||||
|
canSummarize.info = new ApplicationInfo();
|
||||||
|
canSummarize.info.packageName = "canSummarize";
|
||||||
|
canSummarize.info.uid = 0;
|
||||||
|
|
||||||
|
ApplicationsState.AppEntry canSummarize2 = mock(ApplicationsState.AppEntry.class);
|
||||||
|
canSummarize2.info = new ApplicationInfo();
|
||||||
|
canSummarize2.info.packageName = "canSummarizeTwo";
|
||||||
|
canSummarize2.info.uid = 0;
|
||||||
|
|
||||||
|
ApplicationsState.AppEntry cannot =
|
||||||
|
mock(ApplicationsState.AppEntry.class);
|
||||||
|
cannot.info = new ApplicationInfo();
|
||||||
|
cannot.info.packageName = "cannot";
|
||||||
|
cannot.info.uid = 0;
|
||||||
|
|
||||||
|
ApplicationsState.AppEntry cannot2 =
|
||||||
|
mock(ApplicationsState.AppEntry.class);
|
||||||
|
cannot2.info = new ApplicationInfo();
|
||||||
|
cannot2.info.packageName = "cannot2";
|
||||||
|
cannot2.info.uid = 0;
|
||||||
|
|
||||||
|
List<ApplicationsState.AppEntry> appEntries = new ArrayList<>();
|
||||||
|
appEntries.add(canSummarize);
|
||||||
|
appEntries.add(canSummarize2);
|
||||||
|
appEntries.add(cannot);
|
||||||
|
appEntries.add(cannot2);
|
||||||
|
|
||||||
|
// WHEN the controller updates the app list with the app entries
|
||||||
|
mController.updateAppList(appEntries);
|
||||||
|
|
||||||
|
// THEN only the 'cannot' entries make it to the app list
|
||||||
|
assertThat(mController.mPreferenceCategory.getPreferenceCount()).isEqualTo(2);
|
||||||
|
assertThat((Preference) mController.mPreferenceCategory.findPreference(
|
||||||
|
AdjustmentExcludedAppsPreferenceController.getKey(
|
||||||
|
cannot.info.packageName,cannot.info.uid))).isNotNull();
|
||||||
|
assertThat((Preference) mController.mPreferenceCategory.findPreference(
|
||||||
|
AdjustmentExcludedAppsPreferenceController.getKey(
|
||||||
|
cannot2.info.packageName,cannot2.info.uid))).isNotNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testUpdateAppList_nullApps() {
|
||||||
|
mController.updateAppList(null);
|
||||||
|
assertThat(mController.mPreferenceCategory.getPreferenceCount()).isEqualTo(0);
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,79 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2025 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.settings.notification;
|
||||||
|
|
||||||
|
import static android.service.notification.Adjustment.KEY_SUMMARIZATION;
|
||||||
|
|
||||||
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
|
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
import android.app.Flags;
|
||||||
|
import android.app.INotificationManager;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.platform.test.flag.junit.SetFlagsRule;
|
||||||
|
import android.service.notification.Adjustment;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Rule;
|
||||||
|
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;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@RunWith(RobolectricTestRunner.class)
|
||||||
|
public class BundleManageAppsPreferenceControllerTest {
|
||||||
|
@Rule
|
||||||
|
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
|
||||||
|
|
||||||
|
private static final String PREFERENCE_KEY = "preference_key";
|
||||||
|
|
||||||
|
private Context mContext;
|
||||||
|
BundleManageAppsPreferenceController mController;
|
||||||
|
@Mock
|
||||||
|
INotificationManager mInm;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() {
|
||||||
|
MockitoAnnotations.initMocks(this);
|
||||||
|
mContext = RuntimeEnvironment.application;
|
||||||
|
mSetFlagsRule.enableFlags(Flags.FLAG_NOTIFICATION_CLASSIFICATION_UI);
|
||||||
|
mController = new BundleManageAppsPreferenceController(mContext, PREFERENCE_KEY);
|
||||||
|
mController.mBackend.setNm(mInm);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void isAvailable_flagEnabledNasSupports_shouldReturnTrue() {
|
||||||
|
assertThat(mController.isAvailable()).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void isAvailable_flagEnabledNasDoesNotSupport_shouldReturnFalse() throws Exception {
|
||||||
|
when(mInm.getUnsupportedAdjustmentTypes()).thenReturn(List.of(Adjustment.KEY_TYPE));
|
||||||
|
assertThat(mController.isAvailable()).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void isAvailable_flagDisabledNasSupports_shouldReturnFalse() {
|
||||||
|
mSetFlagsRule.disableFlags(Flags.FLAG_NOTIFICATION_CLASSIFICATION_UI);
|
||||||
|
assertThat(mController.isAvailable()).isFalse();
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,81 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2025 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.settings.notification;
|
||||||
|
|
||||||
|
import static android.service.notification.Adjustment.KEY_IMPORTANCE;
|
||||||
|
import static android.service.notification.Adjustment.KEY_SUMMARIZATION;
|
||||||
|
|
||||||
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
|
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
import android.app.Flags;
|
||||||
|
import android.app.INotificationManager;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.platform.test.flag.junit.SetFlagsRule;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Rule;
|
||||||
|
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;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@RunWith(RobolectricTestRunner.class)
|
||||||
|
public class SummarizationManageAppsPreferenceControllerTest {
|
||||||
|
@Rule
|
||||||
|
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
|
||||||
|
|
||||||
|
private static final String PREFERENCE_KEY = "preference_key";
|
||||||
|
|
||||||
|
private Context mContext;
|
||||||
|
SummarizationManageAppsPreferenceController mController;
|
||||||
|
@Mock
|
||||||
|
INotificationManager mInm;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() {
|
||||||
|
MockitoAnnotations.initMocks(this);
|
||||||
|
mContext = RuntimeEnvironment.application;
|
||||||
|
mSetFlagsRule.enableFlags(Flags.FLAG_NM_SUMMARIZATION, Flags.FLAG_NM_SUMMARIZATION_UI);
|
||||||
|
mController = new SummarizationManageAppsPreferenceController(mContext, PREFERENCE_KEY);
|
||||||
|
mController.mBackend.setNm(mInm);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void isAvailable_flagEnabledNasSupports_shouldReturnTrue() {
|
||||||
|
assertThat(mController.isAvailable()).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void isAvailable_flagEnabledNasDoesNotSupport_shouldReturnFalse() throws Exception {
|
||||||
|
when(mInm.getUnsupportedAdjustmentTypes()).thenReturn(List.of(KEY_SUMMARIZATION));
|
||||||
|
assertThat(mController.isAvailable()).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void isAvailable_flagDisabledNasSupports_shouldReturnFalse() {
|
||||||
|
mSetFlagsRule.disableFlags(Flags.FLAG_NM_SUMMARIZATION);
|
||||||
|
mSetFlagsRule.disableFlags(Flags.FLAG_NM_SUMMARIZATION_UI);
|
||||||
|
assertThat(mController.isAvailable()).isFalse();
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user