Add ability to exclude apps from adjustments

Specifically bundling and summarization

Test: BundleManageAppsPreferenceControllerTest
Test: AdjustmentExcludedAppsPreferenceControllerTest
Test: SummarizationManageAppsPreferenceControllerTest
Flag: android.app.nm_summarization
Flag: android.app.notification_classification_ui
Bug: 390415383
Bug: 377697346
Change-Id: Ica4b77212f4660624bfe12be7e6c9c584cd2c812
This commit is contained in:
Julia Reynolds
2025-01-28 15:16:09 -05:00
parent 17dc54c62c
commit 0762b81bcf
19 changed files with 890 additions and 30 deletions

View File

@@ -358,6 +358,8 @@ public class Settings extends SettingsActivity {
public static class AppBubbleNotificationSettingsActivity extends SettingsActivity { /* empty */ }
public static class NotificationAssistantSettingsActivity 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 */
public static class ClonedAppsListActivity extends SettingsActivity { /* empty */ }
/** Activity to manage Aspect Ratio app list page */

View File

@@ -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_TURN_SCREEN_ON = 19;
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.
public static final Set<Integer> LIST_TYPES_WITH_INSTANT = new ArraySet<>(Arrays.asList(

View File

@@ -31,6 +31,8 @@ import com.android.settings.Settings.ManageExternalSourcesActivity
import com.android.settings.Settings.ManageExternalStorageActivity
import com.android.settings.Settings.MediaManagementAppsActivity
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.OverlaySettingsActivity
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_BATTERY_OPTIMIZATION
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_HIGH_POWER
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,
TurnScreenOnSettingsActivity::class to LIST_TYPE_TURN_SCREEN_ON,
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
@@ -117,7 +124,7 @@ object ManageApplicationsUtil {
LIST_TYPE_MEDIA_MANAGEMENT_APPS -> MediaManagementAppsAppListProvider.getAppListRoute()
LIST_TYPE_ALARMS_AND_REMINDERS -> AlarmsAndRemindersAppListProvider.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_MAIN -> AllAppListPageProvider.name
LIST_TYPE_NFC_TAG_APPS -> NfcTagAppsSettingsProvider.getAppListRoute()
@@ -128,6 +135,8 @@ object ManageApplicationsUtil {
//LIST_TYPE_STORAGE -> StorageAppListPageProvider.Apps.name
//LIST_TYPE_GAMES -> StorageAppListPageProvider.Games.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
}
}

View File

@@ -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();
}
};
}

View File

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

View File

@@ -16,6 +16,11 @@
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.content.Context;
import android.app.Flags;
@@ -25,6 +30,7 @@ import androidx.lifecycle.Lifecycle;
import com.android.settings.R;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settingslib.applications.ApplicationsState;
import com.android.settingslib.search.SearchIndexable;
import org.jetbrains.annotations.NotNull;
@@ -49,6 +55,26 @@ public class BundlePreferenceFragment extends DashboardFragment {
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 =
new BaseSearchIndexProvider(R.xml.bundle_notifications_settings) {

View File

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

View File

@@ -16,13 +16,19 @@
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.settings.SettingsEnums;
import android.content.Context;
import com.android.settings.R;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.notification.app.HeaderPreferenceController;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settingslib.applications.ApplicationsState;
import com.android.settingslib.search.SearchIndexable;
/**
@@ -45,6 +51,26 @@ public class SummarizationPreferenceFragment extends DashboardFragment {
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 =
new BaseSearchIndexProvider(R.xml.summarization_notifications_settings) {

View File

@@ -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.storage.StorageAppListPageProvider
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.compat.PlatformCompatAppListPageProvider
import com.android.settings.spa.home.HomePageProvider
@@ -106,7 +105,9 @@ open class SettingsSpaEnvironment(context: Context) : SpaEnvironment(context) {
AppInfoSettingsProvider,
SpecialAppAccessPageProvider,
NotificationMainPageProvider,
AppListNotificationsPageProvider,
AppListNotificationsPageProvider.AllApps,
AppListNotificationsPageProvider.ExcludeClassification,
AppListNotificationsPageProvider.ExcludeSummarization,
SystemMainPageProvider,
LanguageAndInputPageProvider,
AppLanguagesPageProvider,

View File

@@ -17,34 +17,72 @@
package com.android.settings.spa.notification
import android.os.Bundle
import androidx.annotation.StringRes
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import com.android.settings.R
import com.android.settingslib.spa.framework.common.SettingsPageProvider
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.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
object AppListNotificationsPageProvider : SettingsPageProvider {
override val name = "AppListNotifications"
sealed class AppListNotificationsPageProvider(private val type: ListType) : SettingsPageProvider {
@Composable
override fun Page(arguments: Bundle?) {
AppListPage(
title = stringResource(R.string.app_notifications_title),
listModel = rememberContext(::AppNotificationsListModel),
)
NotificationsAppListPage(type)
}
@Composable
fun EntryItem() {
val summary = stringResource(R.string.app_notification_field_summary)
Preference(object : PreferenceModel {
override val title = stringResource(R.string.app_notifications_title)
override val summary = { summary }
override val onClick = navigator(name)
})
object AllApps : AppListNotificationsPageProvider(ListType.Apps) {
override val name = "AppListNotifications"
@Composable
fun EntryItem() {
val summary = stringResource(R.string.app_notification_field_summary)
Preference(object : PreferenceModel {
override val title = stringResource(ListType.Apps.titleResource)
override val summary = { summary }
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,
)
}

View File

@@ -17,12 +17,16 @@
package com.android.settings.spa.notification
import android.content.pm.ApplicationInfo
import android.service.notification.Adjustment
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import com.android.settings.spa.app.storage.StorageAppListModel
import com.android.settings.spa.app.storage.StorageType
class AppNotificationController(
private val repository: AppNotificationRepository,
private val app: ApplicationInfo,
private val listType: ListType,
) {
val isEnabled: LiveData<Boolean>
get() = _isEnabled
@@ -47,4 +51,62 @@ class AppNotificationController(
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
}
}
}

View File

@@ -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 {
return try {
userManager.isUserUnlocked(user)

View File

@@ -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.userId
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.utils.StringUtil
import kotlinx.coroutines.Dispatchers
@@ -52,6 +53,7 @@ data class AppNotificationsRecord(
class AppNotificationsListModel(
private val context: Context,
private val listType: ListType
) : AppListModel<AppNotificationsRecord> {
private val repository = AppNotificationRepository(context)
private val now = System.currentTimeMillis()
@@ -64,7 +66,7 @@ class AppNotificationsListModel(
AppNotificationsRecord(
app = app,
sentState = usageEvents[app.packageName],
controller = AppNotificationController(repository, app),
controller = AppNotificationController(repository, app, listType),
)
}
}
@@ -129,17 +131,35 @@ class AppNotificationsListModel(
@Composable
override fun AppListItemModel<AppNotificationsRecord>.AppItem() {
val changeable by produceState(initialValue = false) {
withContext(Dispatchers.Default) {
value = repository.isChangeable(record.app)
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) {
withContext(Dispatchers.Default) {
value = repository.isChangeable(record.app)
}
}
AppListTwoTargetSwitchItem(
onClick = { navigateToAppNotificationSettings(app = record.app) },
checked = record.controller.isEnabled.observeAsCallback(),
changeable = { changeable },
onCheckedChange = record.controller::setEnabled,
)
}
}
AppListTwoTargetSwitchItem(
onClick = { navigateToAppNotificationSettings(app = record.app) },
checked = record.controller.isEnabled.observeAsCallback(),
changeable = { changeable },
onCheckedChange = record.controller::setEnabled,
)
}
private fun navigateToAppNotificationSettings(app: ApplicationInfo) {

View File

@@ -41,7 +41,7 @@ object NotificationMainPageProvider : SettingsPageProvider {
@Composable
override fun Page(arguments: Bundle?) {
RegularScaffold(title = getTitle(arguments)) {
AppListNotificationsPageProvider.EntryItem()
AppListNotificationsPageProvider.AllApps.EntryItem()
}
}