Modify Summary for Mode's Apps settings page

Adds call to SummaryHelper to set Apps preference summary.

Bug: 308819928
Test: atest ZenModeAppsLinkPreferenceControllerTest
Flag: android.app.modes_ui
Change-Id: Iebec11afc62ecb79386e1866af57cd4e68461a95
This commit is contained in:
Alexander Roederer
2024-06-05 20:36:47 +00:00
parent a6b1d7cbbc
commit 41902bb0c5
4 changed files with 252 additions and 17 deletions

View File

@@ -357,6 +357,19 @@ public class NotificationBackend {
} }
} }
/**
* Returns all of a user's packages that have at least one channel that will bypass DND
*/
public List<String> getPackagesBypassingDnd(int userId,
boolean includeConversationChannels) {
try {
return sINM.getPackagesBypassingDnd(userId, includeConversationChannels);
} catch (Exception e) {
Log.w(TAG, "Error calling NoMan", e);
return new ArrayList<>();
}
}
public void updateChannel(String pkg, int uid, NotificationChannel channel) { public void updateChannel(String pkg, int uid, NotificationChannel channel) {
try { try {
sINM.updateNotificationChannelForPackage(pkg, uid, channel); sINM.updateNotificationChannelForPackage(pkg, uid, channel);

View File

@@ -20,23 +20,44 @@ import static com.android.settings.notification.modes.ZenModeFragmentBase.MODE_I
import android.content.Context; import android.content.Context;
import android.os.Bundle; import android.os.Bundle;
import android.util.ArraySet;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import androidx.core.text.BidiFormatter;
import androidx.fragment.app.Fragment;
import androidx.preference.Preference; import androidx.preference.Preference;
import com.android.settings.core.SubSettingLauncher; import com.android.settings.core.SubSettingLauncher;
import com.android.settings.notification.NotificationBackend;
import com.android.settingslib.applications.ApplicationsState;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
/** /**
* Preference with a link and summary about what apps can break through the mode * Preference with a link and summary about what apps can break through the mode
*/ */
public class ZenModeAppsLinkPreferenceController extends AbstractZenModePreferenceController { class ZenModeAppsLinkPreferenceController extends AbstractZenModePreferenceController {
private static final String TAG = "ZenModeAppsLinkPreferenceController";
private final ZenModeSummaryHelper mSummaryHelper; private final ZenModeSummaryHelper mSummaryHelper;
private ApplicationsState.Session mAppSession;
private NotificationBackend mNotificationBackend = new NotificationBackend();
private ZenMode mZenMode;
private Preference mPreference;
public ZenModeAppsLinkPreferenceController(Context context, String key, ZenModeAppsLinkPreferenceController(Context context, String key, Fragment host,
ZenModesBackend backend) { ApplicationsState applicationsState, ZenModesBackend backend) {
super(context, key, backend); super(context, key, backend);
mSummaryHelper = new ZenModeSummaryHelper(mContext, mBackend); mSummaryHelper = new ZenModeSummaryHelper(mContext, mBackend);
if (applicationsState != null && host != null) {
mAppSession = applicationsState.newSession(mAppSessionCallbacks, host.getLifecycle());
}
} }
@Override @Override
@@ -49,6 +70,84 @@ public class ZenModeAppsLinkPreferenceController extends AbstractZenModePreferen
.setSourceMetricsCategory(0) .setSourceMetricsCategory(0)
.setArguments(bundle) .setArguments(bundle)
.toIntent()); .toIntent());
preference.setSummary(mSummaryHelper.getAppsSummary(zenMode)); mZenMode = zenMode;
mPreference = preference;
triggerUpdateAppsBypassingDndSummaryText();
} }
private void triggerUpdateAppsBypassingDndSummaryText() {
if (mAppSession == null) {
return;
}
ApplicationsState.AppFilter filter = android.multiuser.Flags.enablePrivateSpaceFeatures()
&& android.multiuser.Flags.handleInterleavedSettingsForPrivateSpace()
? ApplicationsState.FILTER_ENABLED_NOT_QUIET
: ApplicationsState.FILTER_ALL_ENABLED;
// We initiate a rebuild in the background here. Once the rebuild is completed,
// the onRebuildComplete() callback will be invoked, which will trigger the summary text
// to be initialized.
mAppSession.rebuild(filter, ApplicationsState.ALPHA_COMPARATOR, false);
}
private void updateAppsBypassingDndSummaryText(List<ApplicationsState.AppEntry> apps) {
Set<String> appNames = getAppsBypassingDnd(apps);
mPreference.setSummary(mSummaryHelper.getAppsSummary(mZenMode, appNames));
}
@VisibleForTesting
ArraySet<String> getAppsBypassingDnd(@NonNull List<ApplicationsState.AppEntry> apps) {
ArraySet<String> appsBypassingDnd = new ArraySet<>();
Map<String, String> pkgLabelMap = new HashMap<String, String>();
for (ApplicationsState.AppEntry entry : apps) {
if (entry.info != null) {
pkgLabelMap.put(entry.info.packageName, entry.label);
}
}
for (String pkg : mNotificationBackend.getPackagesBypassingDnd(mContext.getUserId(),
/* includeConversationChannels= */ false)) {
// Settings may hide some packages from the user, so if they're not present here
// we skip displaying them, even if they bypass dnd.
if (pkgLabelMap.get(pkg) == null) {
continue;
}
appsBypassingDnd.add(BidiFormatter.getInstance().unicodeWrap(pkgLabelMap.get(pkg)));
}
return appsBypassingDnd;
}
@VisibleForTesting final ApplicationsState.Callbacks mAppSessionCallbacks =
new ApplicationsState.Callbacks() {
@Override
public void onRunningStateChanged(boolean running) { }
@Override
public void onPackageListChanged() {
triggerUpdateAppsBypassingDndSummaryText();
}
@Override
public void onRebuildComplete(ArrayList<ApplicationsState.AppEntry> apps) {
updateAppsBypassingDndSummaryText(apps);
}
@Override
public void onPackageIconChanged() { }
@Override
public void onPackageSizeChanged(String packageName) { }
@Override
public void onAllSizesComputed() { }
@Override
public void onLauncherInfoChanged() { }
@Override
public void onLoadEntriesCompleted() {
triggerUpdateAppsBypassingDndSummaryText();
}
};
} }

View File

@@ -16,11 +16,13 @@
package com.android.settings.notification.modes; package com.android.settings.notification.modes;
import android.app.Application;
import android.app.AutomaticZenRule; import android.app.AutomaticZenRule;
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.settingslib.applications.ApplicationsState;
import com.android.settingslib.core.AbstractPreferenceController; import com.android.settingslib.core.AbstractPreferenceController;
import java.util.ArrayList; import java.util.ArrayList;
@@ -42,7 +44,9 @@ public class ZenModeFragment extends ZenModeFragmentBase {
prefControllers.add(new ZenModePeopleLinkPreferenceController( prefControllers.add(new ZenModePeopleLinkPreferenceController(
context, "zen_mode_people", mBackend)); context, "zen_mode_people", mBackend));
prefControllers.add(new ZenModeAppsLinkPreferenceController( prefControllers.add(new ZenModeAppsLinkPreferenceController(
context, "zen_mode_apps", mBackend)); context, "zen_mode_apps", this,
ApplicationsState.getInstance((Application) context.getApplicationContext()),
mBackend));
prefControllers.add(new ZenModeOtherLinkPreferenceController( prefControllers.add(new ZenModeOtherLinkPreferenceController(
context, "zen_other_settings", mBackend)); context, "zen_other_settings", mBackend));
prefControllers.add(new ZenModeDisplayLinkPreferenceController( prefControllers.add(new ZenModeDisplayLinkPreferenceController(

View File

@@ -16,28 +16,51 @@
package com.android.settings.notification.modes; package com.android.settings.notification.modes;
import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY;
import static com.android.settings.notification.modes.ZenModeFragmentBase.MODE_ID;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.AutomaticZenRule; import android.app.AutomaticZenRule;
import android.app.Flags; import android.app.Flags;
import android.content.Context; import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle;
import android.platform.test.annotations.EnableFlags; import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule; import android.platform.test.flag.junit.SetFlagsRule;
import android.service.notification.ZenPolicy; import android.service.notification.ZenPolicy;
import androidx.fragment.app.Fragment;
import androidx.preference.Preference; import androidx.preference.Preference;
import com.android.settings.SettingsActivity;
import com.android.settings.notification.NotificationBackend;
import com.android.settingslib.applications.ApplicationsState;
import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
import com.android.settingslib.widget.SelectorWithWidgetPreference;
import org.junit.Before; import org.junit.Before;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.MockitoAnnotations; import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner; import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment; import org.robolectric.RuntimeEnvironment;
import org.robolectric.util.ReflectionHelpers;
import java.util.ArrayList;
import java.util.List;
@RunWith(RobolectricTestRunner.class) @RunWith(RobolectricTestRunner.class)
@EnableFlags(Flags.FLAG_MODES_UI) @EnableFlags(Flags.FLAG_MODES_UI)
@@ -47,7 +70,15 @@ public final class ZenModeAppsLinkPreferenceControllerTest {
private Context mContext; private Context mContext;
@Mock @Mock
private ZenModesBackend mBackend; private ZenModesBackend mZenModesBackend;
@Mock
private NotificationBackend mNotificationBackend;
@Mock
private ApplicationsState mApplicationsState;
@Mock
private ApplicationsState.Session mSession;
@Rule @Rule
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@@ -56,21 +87,109 @@ public final class ZenModeAppsLinkPreferenceControllerTest {
public void setup() { public void setup() {
MockitoAnnotations.initMocks(this); MockitoAnnotations.initMocks(this);
mContext = RuntimeEnvironment.application; mContext = RuntimeEnvironment.application;
when(mApplicationsState.newSession(any(), any())).thenReturn(mSession);
mController = new ZenModeAppsLinkPreferenceController( mController = new ZenModeAppsLinkPreferenceController(
mContext, "controller_key", mBackend); mContext, "controller_key", mock(Fragment.class), mApplicationsState,
mZenModesBackend);
ReflectionHelpers.setField(mController, "mNotificationBackend", mNotificationBackend);
}
private ApplicationsState.AppEntry createAppEntry(String packageName, String label) {
ApplicationsState.AppEntry entry = mock(ApplicationsState.AppEntry.class);
entry.info = new ApplicationInfo();
entry.info.packageName = packageName;
entry.label = label;
entry.info.uid = 0;
return entry;
}
private ZenMode createPriorityChannelsZenMode() {
return new ZenMode("id", new AutomaticZenRule.Builder("Bedtime",
Uri.parse("bed"))
.setType(AutomaticZenRule.TYPE_BEDTIME)
.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
.setZenPolicy(new ZenPolicy.Builder()
.allowChannels(ZenPolicy.CHANNEL_POLICY_PRIORITY)
.build())
.build(), true);
} }
@Test @Test
@EnableFlags(Flags.FLAG_MODES_UI) public void testIsAvailable() {
public void testHasSummary() { assertThat(mController.isAvailable()).isTrue();
Preference pref = mock(Preference.class);
ZenMode zenMode = new ZenMode("id",
new AutomaticZenRule.Builder("Driving", Uri.parse("drive"))
.setType(AutomaticZenRule.TYPE_DRIVING)
.setZenPolicy(new ZenPolicy.Builder().allowAllSounds().build())
.build(), true);
mController.updateZenMode(pref, zenMode);
verify(pref).setSummary(any());
} }
@Test
public void testUpdateSetsIntent() {
// Creates the preference
SelectorWithWidgetPreference preference = mock(SelectorWithWidgetPreference.class);
// Create a zen mode that allows priority channels to breakthrough.
ZenMode zenMode = createPriorityChannelsZenMode();
// Capture the intent
ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
mController.updateState((Preference) preference, zenMode);
verify(preference).setIntent(captor.capture());
Intent launcherIntent = captor.getValue();
assertThat(launcherIntent.getStringExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT))
.isEqualTo("com.android.settings.notification.modes.ZenModeAppsFragment");
assertThat(launcherIntent.getIntExtra(MetricsFeatureProvider.EXTRA_SOURCE_METRICS_CATEGORY,
-1)).isEqualTo(0);
Bundle bundle = launcherIntent.getBundleExtra(
SettingsActivity.EXTRA_SHOW_FRAGMENT_ARGUMENTS);
assertThat(bundle).isNotNull();
assertThat(bundle.getString(MODE_ID)).isEqualTo("id");
}
@Test
public void testGetAppsBypassingDnd() {
ApplicationsState.AppEntry entry = createAppEntry("test", "testLabel");
ApplicationsState.AppEntry entryConv = createAppEntry("test_conv", "test_convLabel");
List<ApplicationsState.AppEntry> appEntries = List.of(entry, entryConv);
when(mNotificationBackend.getPackagesBypassingDnd(mContext.getUserId(),
false)).thenReturn(List.of("test"));
assertThat(mController.getAppsBypassingDnd(appEntries)).containsExactly("testLabel");
}
@Test
public void testUpdateTriggersRebuild() {
// Creates the preference
SelectorWithWidgetPreference preference = mock(SelectorWithWidgetPreference.class);
// Create a zen mode that allows priority channels to breakthrough.
ZenMode zenMode = createPriorityChannelsZenMode();
// Create some applications.
ArrayList<ApplicationsState.AppEntry> appEntries =
new ArrayList<ApplicationsState.AppEntry>();
appEntries.add(createAppEntry("test", "pkgLabel"));
when(mNotificationBackend.getPackagesBypassingDnd(
mContext.getUserId(), false))
.thenReturn(List.of("test"));
// Updates the preference with the zen mode. We expect that this causes the app session
// to trigger a rebuild.
mController.updateZenMode((Preference) preference, zenMode);
verify(mSession).rebuild(any(), any(), eq(false));
// Manually triggers the callback that will happen on rebuild.
mController.mAppSessionCallbacks.onRebuildComplete(appEntries);
verify(preference).setSummary("pkgLabel can interrupt");
}
@Test
public void testOnPackageListChangedTriggersRebuild() {
mController.mAppSessionCallbacks.onPackageListChanged();
verify(mSession).rebuild(any(), any(), eq(false));
}
@Test
public void testOnLoadEntriesCompletedTriggersRebuild() {
mController.mAppSessionCallbacks.onLoadEntriesCompleted();
verify(mSession).rebuild(any(), any(), eq(false));
}
} }