Add initial settings for summarization

Test: SummarizationGlobalPreferenceControllerTest
Test: SummarizationPreferenceControllerTest
Flag: android.app.nm_summarization
Bug: 390415383
Change-Id: I773d69c707ae5f902860196fb44821ede15261f5
This commit is contained in:
Julia Reynolds
2025-01-23 16:55:12 -05:00
parent 271fb3ea63
commit 50e720b03b
9 changed files with 446 additions and 4 deletions

View File

@@ -8847,6 +8847,13 @@
<!-- App Info > Notifications: Title for section where notifications bundles can be configured [CHAR LIMIT=80]-->
<string name="notification_bundles">Notification bundles</string>
<!-- App Info > Notifications: Title for section where notifications summaries can be configured [CHAR LIMIT=80]-->
<string name="notification_summarization_title">Notification summaries</string>
<string name="notification_summarization_on">On</string>
<string name="notification_summarization_off">Off</string>
<string name="notification_summarization_main_control_title">Use notification summaries</string>
<string name="notification_summarization_description">Automatically summarize conversation notifications from any app</string>
<!-- App Info > Notifications: Title for section controlling whether this app may show notifications in a promoted format [CHAR LIMIT=80] -->
<string name="live_notifications">Live notifications</string>
<!-- App Info > Notifications: Text on the switch to enable or disable an app from showing notifications in a live/promoted format [CHAR LIMIT=50] -->
@@ -9269,7 +9276,6 @@
<string name="notification_bundle_main_control_title">Use notification bundling</string>
<string name="notification_bundle_description">Notifications with similar themes will be silenced and grouped together for a quieter experience. Bundling will override an app\'s own notification settings.</string>
<!-- Title for managing VR (virtual reality) helper services. [CHAR LIMIT=50] -->
<string name="vr_listeners_title">VR helper services</string>

View File

@@ -25,7 +25,7 @@
<!-- See all apps button -->
<Preference
android:key="all_notifications"
android:order="10"
android:order="1"
android:title="@string/app_notification_field"
android:summary="@string/app_notification_field_summary"
android:fragment="com.android.settings.applications.manageapplications.ManageApplications">
@@ -35,7 +35,7 @@
</Preference>
<Preference
android:key="notification_history"
android:order="11"
android:order="2"
android:title="@string/notification_history"
android:summary="@string/notification_history_summary">
<intent
@@ -44,11 +44,19 @@
android:targetClass="com.android.settings.notification.history.NotificationHistoryActivity" />
</Preference>
<Preference
android:fragment="com.android.settings.notification.SummarizationPreferenceFragment"
android:key="summarization_notifications_preference"
android:order="3"
android:persistent="false"
android:title="@string/notification_summarization_title"
settings:controller="com.android.settings.notification.SummarizationPreferenceController" />
<Preference
android:fragment="com.android.settings.notification.BundlePreferenceFragment"
android:key="bundle_notifications_preference"
android:persistent="false"
android:order="12"
android:order="4"
android:title="@string/notification_bundle_title"
settings:controller="com.android.settings.notification.BundlePreferenceController" />
</PreferenceCategory>

View File

@@ -0,0 +1,39 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
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.
-->
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:settings="http://schemas.android.com/apk/res-auto"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:title="@string/notification_summarization_title">
<com.android.settingslib.widget.IllustrationPreference
android:key="illustration"
settings:searchable="false"
android:selectable="false"
app:lottie_cacheComposition="false"
settings:dynamicColor="true"/>
<com.android.settingslib.widget.TopIntroPreference
android:key="feature_description"
settings:searchable="false"
android:title="@string/notification_summarization_description"/>
<com.android.settingslib.widget.MainSwitchPreference
android:key="global_pref"
android:title="@string/notification_summarization_main_control_title"
settings:controller="com.android.settings.notification.SummarizationGlobalPreferenceController" />
</PreferenceScreen>

View File

@@ -687,6 +687,37 @@ public class NotificationBackend {
}
}
public boolean isNotificationSummarizationSupported() {
try {
return !sINM.getUnsupportedAdjustmentTypes().contains(Adjustment.KEY_SUMMARIZATION);
} catch (Exception e) {
Log.w(TAG, "Error calling NoMan", e);
}
return false;
}
public boolean isNotificationSummarizationEnabled(Context context) {
try {
return sINM.getAllowedAssistantAdjustments(context.getPackageName())
.contains(Adjustment.KEY_SUMMARIZATION);
} catch (Exception e) {
Log.w(TAG, "Error calling NoMan", e);
}
return false;
}
public void setNotificationSummarizationEnabled(boolean enabled) {
try {
if (enabled) {
sINM.allowAssistantAdjustment(Adjustment.KEY_SUMMARIZATION);
} else {
sINM.disallowAssistantAdjustment(Adjustment.KEY_SUMMARIZATION);
}
} catch (Exception e) {
Log.w(TAG, "Error calling NoMan", e);
}
}
public boolean isBundleTypeApproved(@Adjustment.Types int type) {
try {
int[] approved = sINM.getAllowedAdjustmentKeyTypes();

View File

@@ -0,0 +1,62 @@
/*
* 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.widget.SettingsMainSwitchPreferenceController;
public class SummarizationGlobalPreferenceController extends
SettingsMainSwitchPreferenceController {
NotificationBackend mBackend;
public SummarizationGlobalPreferenceController(@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;
}
@Override
public boolean isChecked() {
return mBackend.isNotificationSummarizationEnabled(mContext);
}
@Override
public boolean setChecked(boolean isChecked) {
mBackend.setNotificationSummarizationEnabled(isChecked);
return true;
}
@Override
public int getSliceHighlightMenuRes() {
// not needed since it's not sliceable
return NO_RES;
}
}

View File

@@ -0,0 +1,50 @@
/*
* 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 com.android.settings.R;
import com.android.settings.core.BasePreferenceController;
/**
* Controller for the summarized notifications settings page.
*/
public class SummarizationPreferenceController extends BasePreferenceController {
NotificationBackend mBackend;
public SummarizationPreferenceController(Context context, String preferenceKey) {
super(context, preferenceKey);
mBackend = new NotificationBackend();
}
@Override
public int getAvailabilityStatus() {
return (Flags.nmSummarization() || Flags.nmSummarizationUi())
&& mBackend.isNotificationSummarizationSupported()
? AVAILABLE : CONDITIONALLY_UNAVAILABLE;
}
@Override
public CharSequence getSummary() {
return mBackend.isNotificationSummarizationEnabled(mContext)
? mContext.getString(R.string.notification_summarization_on)
: mContext.getString(R.string.notification_summarization_off);
}
}

View File

@@ -0,0 +1,56 @@
/*
* 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.app.settings.SettingsEnums;
import android.content.Context;
import com.android.settings.R;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settingslib.search.SearchIndexable;
/**
* Fragment for summarized notifications.
*/
@SearchIndexable(forTarget = SearchIndexable.ALL & ~SearchIndexable.ARC)
public class SummarizationPreferenceFragment extends DashboardFragment {
@Override
public int getMetricsCategory() {
return SettingsEnums.SUMMARIZED_NOTIFICATIONS;
}
@Override
protected int getPreferenceScreenResId() {
return R.xml.summarization_notifications_settings;
}
@Override
protected String getLogTag() {
return "SummarizationPreferenceFragment";
}
public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
new BaseSearchIndexProvider(R.xml.summarization_notifications_settings) {
@Override
protected boolean isPageSearchEnabled(Context context) {
return Flags.notificationClassificationUi();
}
};
}

View File

@@ -0,0 +1,100 @@
/*
* 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.verify;
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 SummarizationGlobalPreferenceControllerTest {
@Rule
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
private static final String PREFERENCE_KEY = "preference_key";
private Context mContext;
SummarizationGlobalPreferenceController 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 SummarizationGlobalPreferenceController(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();
}
@Test
public void isChecked() throws Exception {
when(mInm.getAllowedAssistantAdjustments(any())).thenReturn(List.of(KEY_SUMMARIZATION));
assertThat(mController.isChecked()).isTrue();
when(mInm.getAllowedAssistantAdjustments(any())).thenReturn(List.of(KEY_IMPORTANCE));
assertThat(mController.isChecked()).isFalse();
}
@Test
public void setChecked() throws Exception {
mController.setChecked(false);
verify(mInm).disallowAssistantAdjustment(KEY_SUMMARIZATION);
mController.setChecked(true);
verify(mInm).allowAssistantAdjustment(KEY_SUMMARIZATION);
}
}

View File

@@ -0,0 +1,90 @@
/*
* 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 SummarizationPreferenceControllerTest {
@Rule
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
private static final String PREFERENCE_KEY = "preference_key";
private Context mContext;
SummarizationPreferenceController 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 SummarizationPreferenceController(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();
}
@Test
public void getSummary() throws Exception {
when(mInm.getAllowedAssistantAdjustments(any())).thenReturn(List.of(KEY_SUMMARIZATION));
assertThat(mController.getSummary()).isEqualTo("On");
when(mInm.getAllowedAssistantAdjustments(any())).thenReturn(List.of(KEY_IMPORTANCE));
assertThat(mController.getSummary()).isEqualTo("Off");
}
}