Changes to the trigger segment

* Switch is always visible, even without a configuration activity
* Custom icon, title, and summary for some mode types (such as SCHEDULE_TIME, etc).
* Default texts in case of missing trigger description.
* Different icons for having/missing configuration activity.
* Move the section to the top of the screen.

Bug: 349376785
Test: atest ZenModeTriggerLinkPreferenceControllerTest
Flag: android.app.modes_ui
Change-Id: I960318899cf4da20ffc5765818429d5790d05067
This commit is contained in:
Matías Hernández
2024-07-23 17:50:35 +02:00
parent 51bb0fe4e4
commit 9e2ac046e4
6 changed files with 356 additions and 126 deletions

View File

@@ -0,0 +1,26 @@
<!--
Copyright (C) 2024 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.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?android:attr/colorControlNormal"
android:viewportHeight="960"
android:viewportWidth="960"
android:autoMirrored="true">
<path
android:fillColor="@android:color/white"
android:pathData="M200,840Q167,840 143.5,816.5Q120,793 120,760L120,600L200,600L200,760Q200,760 200,760Q200,760 200,760L760,760Q760,760 760,760Q760,760 760,760L760,200Q760,200 760,200Q760,200 760,200L200,200Q200,200 200,200Q200,200 200,200L200,360L120,360L120,200Q120,167 143.5,143.5Q167,120 200,120L760,120Q793,120 816.5,143.5Q840,167 840,200L840,760Q840,793 816.5,816.5Q793,840 760,840L200,840ZM420,680L364,622L466,520L120,520L120,440L466,440L364,338L420,280L620,480L420,680Z" />
</vector>

View File

@@ -0,0 +1,25 @@
<!--
Copyright (C) 2024 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.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?android:attr/colorControlNormal"
android:viewportHeight="960"
android:viewportWidth="960">
<path
android:fillColor="@android:color/white"
android:pathData="M352,840L200,840Q167,840 143.5,816.5Q120,793 120,760L120,608Q168,608 204,577.5Q240,547 240,500Q240,453 204,422.5Q168,392 120,392L120,240Q120,207 143.5,183.5Q167,160 200,160L360,160Q360,118 389,89Q418,60 460,60Q502,60 531,89Q560,118 560,160L720,160Q753,160 776.5,183.5Q800,207 800,240L800,400Q842,400 871,429Q900,458 900,500Q900,542 871,571Q842,600 800,600L800,760Q800,793 776.5,816.5Q753,840 720,840L568,840Q568,790 536.5,755Q505,720 460,720Q415,720 383.5,755Q352,790 352,840ZM200,760L285,760Q309,694 362,667Q415,640 460,640Q505,640 558,667Q611,694 635,760L720,760L720,520L800,520Q808,520 814,514Q820,508 820,500Q820,492 814,486Q808,480 800,480L720,480L720,240L480,240L480,160Q480,152 474,146Q468,140 460,140Q452,140 446,146Q440,152 440,160L440,240L200,240L200,328Q254,348 287,395Q320,442 320,500Q320,557 287,604Q254,651 200,672L200,760ZM460,500L460,500Q460,500 460,500Q460,500 460,500Q460,500 460,500Q460,500 460,500L460,500L460,500L460,500Q460,500 460,500Q460,500 460,500Q460,500 460,500Q460,500 460,500L460,500L460,500L460,500L460,500Q460,500 460,500Q460,500 460,500Q460,500 460,500Q460,500 460,500L460,500L460,500L460,500Q460,500 460,500Q460,500 460,500Q460,500 460,500Q460,500 460,500Z" />
</vector>

View File

@@ -8124,9 +8124,9 @@
<!-- Do not disturb: Subtitle for the Visual signals option to toggle on/off visual signals/alerts when the screen is on/when screen is off. [CHAR LIMIT=30] --> <!-- Do not disturb: Subtitle for the Visual signals option to toggle on/off visual signals/alerts when the screen is on/when screen is off. [CHAR LIMIT=30] -->
<string name="zen_mode_visual_signals_settings_subtitle">Allow visual signals</string> <string name="zen_mode_visual_signals_settings_subtitle">Allow visual signals</string>
<!-- Do not disturb: mode page section title [CHAR LIMIT=80] --> <!-- Priority Modes: mode page section title [CHAR LIMIT=80] -->
<string name="mode_interruption_filter_title">Stay focused</string> <string name="mode_interruption_filter_title">Stay focused</string>
<!-- Do not disturb: mode page section title [CHAR LIMIT=80] --> <!-- Priority Modes: mode page section title [CHAR LIMIT=80] -->
<string name="mode_device_effects_title">Additional actions</string> <string name="mode_device_effects_title">Additional actions</string>
<!-- Summary for the Sound Do not Disturb option when DND isn't currently on. [CHAR LIMIT=NONE]--> <!-- Summary for the Sound Do not Disturb option when DND isn't currently on. [CHAR LIMIT=NONE]-->
@@ -9483,6 +9483,19 @@
<!-- Priority Modes: Hint for the EditText for editing a mode's name [CHAR LIMIT=30] --> <!-- Priority Modes: Hint for the EditText for editing a mode's name [CHAR LIMIT=30] -->
<string name="zen_mode_edit_name_hint">Mode name</string> <string name="zen_mode_edit_name_hint">Mode name</string>
<!-- Priority Modes: Trigger title for modes of type SCHEDULE_CALENDAR. [CHAR LIMIT=30] -->
<string name="zen_mode_trigger_title_schedule_calendar">Calendar events</string>
<!-- Priority Modes: Trigger title for modes of type BEDTIME. [CHAR LIMIT=30] -->
<string name="zen_mode_trigger_title_bedtime">Sleep schedule</string>
<!-- Priority Modes: Trigger title for modes of type DRIVING. [CHAR LIMIT=30] -->
<string name="zen_mode_trigger_title_driving">While driving</string>
<!-- Priority Modes: Generic trigger title for modes of other types [CHAR LIMIT=30] -->
<string name="zen_mode_trigger_title_generic">Linked to app</string>
<!-- Priority Modes: Generic trigger summary for modes where the owner app did not provide a triggerDescription but did provide a configurationActivity to call [CHAR LIMIT=60] -->
<string name="zen_mode_trigger_summary_settings_in_app">Info and settings in <xliff:g id="app_name" example="The Awesome App">%1$s</xliff:g></string>
<!-- Priority Modes: Generic trigger summary for modes where the owner app did not provide neither a triggerDescription nor a configurationActivity to call [CHAR LIMIT=60] -->
<string name="zen_mode_trigger_summary_managed_by_app">Managed by <xliff:g id="app_name" example="The Awesome App">%1$s</xliff:g></string>
<!-- Content description for help icon button [CHAR LIMIT=20] --> <!-- Content description for help icon button [CHAR LIMIT=20] -->
<string name="warning_button_text">Warning</string> <string name="warning_button_text">Warning</string>

View File

@@ -28,6 +28,21 @@
android:selectable="false" android:selectable="false"
android:layout="@layout/modes_activation_button"/> android:layout="@layout/modes_activation_button"/>
<!-- automatic trigger section; preference changes programmatically depending on type -->
<PreferenceCategory
android:key="zen_automatic_trigger_category"
android:title="@string/zen_mode_automatic_trigger_title">
<!-- For configuring the trigger on tap and enabling/disabling the mode with the switch. -->
<com.android.settingslib.PrimarySwitchPreference
android:key="zen_automatic_trigger_settings" />
<!-- For adding a trigger for custom manual modes (no switch). -->
<Preference
android:key="zen_add_automatic_trigger"
android:title="@string/zen_mode_select_schedule"
android:icon="@drawable/ic_add_24dp"
settings:isPreferenceVisible="false" />
</PreferenceCategory>
<PreferenceCategory <PreferenceCategory
android:title="@string/mode_interruption_filter_title" android:title="@string/mode_interruption_filter_title"
android:key="modes_filters"> android:key="modes_filters">
@@ -49,14 +64,6 @@
android:title="@string/zen_category_exceptions" /> android:title="@string/zen_category_exceptions" />
</PreferenceCategory> </PreferenceCategory>
<!-- automatic trigger section; preference changes programmatically depending on type -->
<PreferenceCategory
android:key="zen_automatic_trigger_category"
android:title="@string/zen_mode_automatic_trigger_title">
<com.android.settingslib.PrimarySwitchPreference
android:key="zen_automatic_trigger_settings" />
</PreferenceCategory>
<PreferenceCategory <PreferenceCategory
android:title="@string/mode_device_effects_title" android:title="@string/mode_device_effects_title"
android:key="modes_additional_actions"> android:key="modes_additional_actions">

View File

@@ -16,14 +16,26 @@
package com.android.settings.notification.modes; package com.android.settings.notification.modes;
import static android.app.AutomaticZenRule.TYPE_BEDTIME;
import static android.app.AutomaticZenRule.TYPE_DRIVING;
import static android.app.AutomaticZenRule.TYPE_SCHEDULE_CALENDAR; import static android.app.AutomaticZenRule.TYPE_SCHEDULE_CALENDAR;
import static android.app.AutomaticZenRule.TYPE_SCHEDULE_TIME; import static android.app.AutomaticZenRule.TYPE_SCHEDULE_TIME;
import static android.service.notification.ZenModeConfig.tryParseScheduleConditionId;
import static com.google.common.base.Preconditions.checkNotNull;
import android.annotation.SuppressLint;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.service.notification.SystemZenRules;
import android.service.notification.ZenModeConfig;
import android.util.Log; import android.util.Log;
import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.StringRes;
import androidx.annotation.VisibleForTesting; import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference; import androidx.preference.Preference;
import androidx.preference.PreferenceCategory; import androidx.preference.PreferenceCategory;
@@ -35,6 +47,8 @@ import com.android.settingslib.PrimarySwitchPreference;
import com.android.settingslib.notification.modes.ZenMode; import com.android.settingslib.notification.modes.ZenMode;
import com.android.settingslib.notification.modes.ZenModesBackend; import com.android.settingslib.notification.modes.ZenModesBackend;
import com.google.common.base.Strings;
/** /**
* Preference controller for the link to an individual mode's configuration page. * Preference controller for the link to an individual mode's configuration page.
*/ */
@@ -42,26 +56,29 @@ class ZenModeSetTriggerLinkPreferenceController extends AbstractZenModePreferenc
private static final String TAG = "ZenModeSetTriggerLink"; private static final String TAG = "ZenModeSetTriggerLink";
@VisibleForTesting @VisibleForTesting
protected static final String AUTOMATIC_TRIGGER_PREF_KEY = "zen_automatic_trigger_settings"; static final String AUTOMATIC_TRIGGER_KEY = "zen_automatic_trigger_settings";
static final String ADD_TRIGGER_KEY = "zen_add_automatic_trigger";
private final DashboardFragment mFragment;
private final PackageManager mPackageManager;
private final ConfigurationActivityHelper mConfigurationActivityHelper; private final ConfigurationActivityHelper mConfigurationActivityHelper;
private final ZenServiceListing mServiceListing; private final ZenServiceListing mServiceListing;
private final DashboardFragment mFragment;
ZenModeSetTriggerLinkPreferenceController(Context context, String key, ZenModeSetTriggerLinkPreferenceController(Context context, String key,
DashboardFragment fragment, ZenModesBackend backend) { DashboardFragment fragment, ZenModesBackend backend) {
this(context, key, fragment, backend, this(context, key, fragment, backend, context.getPackageManager(),
new ConfigurationActivityHelper(context.getPackageManager()), new ConfigurationActivityHelper(context.getPackageManager()),
new ZenServiceListing(context)); new ZenServiceListing(context));
} }
@VisibleForTesting @VisibleForTesting
ZenModeSetTriggerLinkPreferenceController(Context context, String key, ZenModeSetTriggerLinkPreferenceController(Context context, String key,
DashboardFragment fragment, ZenModesBackend backend, DashboardFragment fragment, ZenModesBackend backend, PackageManager packageManager,
ConfigurationActivityHelper configurationActivityHelper, ConfigurationActivityHelper configurationActivityHelper,
ZenServiceListing serviceListing) { ZenServiceListing serviceListing) {
super(context, key, backend); super(context, key, backend);
mFragment = fragment; mFragment = fragment;
mPackageManager = packageManager;
mConfigurationActivityHelper = configurationActivityHelper; mConfigurationActivityHelper = configurationActivityHelper;
mServiceListing = serviceListing; mServiceListing = serviceListing;
} }
@@ -83,64 +100,137 @@ class ZenModeSetTriggerLinkPreferenceController extends AbstractZenModePreferenc
// This controller is expected to govern a preference category so that it controls the // This controller is expected to govern a preference category so that it controls the
// availability of the entire preference category if the mode doesn't have a way to // availability of the entire preference category if the mode doesn't have a way to
// automatically trigger (such as manual DND). // automatically trigger (such as manual DND).
PrimarySwitchPreference switchPref = ((PreferenceCategory) preference).findPreference( if (zenMode.isManualDnd()) {
AUTOMATIC_TRIGGER_PREF_KEY);
if (switchPref == null) {
return; return;
} }
switchPref.setChecked(zenMode.getRule().isEnabled()); PrimarySwitchPreference triggerPref = checkNotNull(
switchPref.setOnPreferenceChangeListener(mSwitchChangeListener); ((PreferenceCategory) preference).findPreference(AUTOMATIC_TRIGGER_KEY));
switchPref.setSummary(zenMode.getRule().getTriggerDescription()); Preference addTriggerPref = checkNotNull(
switchPref.setIcon(null); ((PreferenceCategory) preference).findPreference(ADD_TRIGGER_KEY));
switchPref.setOnPreferenceClickListener(null);
switchPref.setIntent(null);
if (zenMode.isSystemOwned()) { boolean isAddTrigger = zenMode.isSystemOwned() && zenMode.getType() != TYPE_SCHEDULE_TIME
if (zenMode.getType() == TYPE_SCHEDULE_TIME) { && zenMode.getType() != TYPE_SCHEDULE_CALENDAR;
switchPref.setTitle(R.string.zen_mode_set_schedule_link);
// TODO: b/332937635 - set correct metrics category if (isAddTrigger) {
switchPref.setIntent(ZenSubSettingLauncher.forModeFragment(mContext, triggerPref.setVisible(false);
ZenModeSetScheduleFragment.class, zenMode.getId(), 0).toIntent()); addTriggerPref.setVisible(true);
} else if (zenMode.getType() == TYPE_SCHEDULE_CALENDAR) { addTriggerPref.setOnPreferenceClickListener(unused -> {
switchPref.setTitle(R.string.zen_mode_set_calendar_link); ZenModeScheduleChooserDialog.show(mFragment, mOnScheduleOptionListener);
switchPref.setIcon(null); return true;
// TODO: b/332937635 - set correct metrics category });
switchPref.setIntent(ZenSubSettingLauncher.forModeFragment(mContext,
ZenModeSetCalendarFragment.class, zenMode.getId(), 0).toIntent());
} else {
switchPref.setTitle(R.string.zen_mode_select_schedule);
switchPref.setIcon(R.drawable.ic_add_24dp);
switchPref.setSummary("");
// TODO: b/342156843 - Hide the switch (needs support in SettingsLib).
switchPref.setOnPreferenceClickListener(clickedPreference -> {
ZenModeScheduleChooserDialog.show(mFragment, mOnScheduleOptionListener);
return true;
});
}
} else { } else {
Intent intent = mConfigurationActivityHelper.getConfigurationActivityIntentForMode( addTriggerPref.setVisible(false);
zenMode, mServiceListing::findService); triggerPref.setVisible(true);
if (intent != null) { triggerPref.setChecked(zenMode.getRule().isEnabled());
preference.setVisible(true); triggerPref.setOnPreferenceChangeListener(mSwitchChangeListener);
switchPref.setTitle(R.string.zen_mode_configuration_link_title);
switchPref.setSummary(zenMode.getRule().getTriggerDescription()); if (zenMode.isSystemOwned()) {
switchPref.setIntent(intent); setUpForSystemOwnedTrigger(triggerPref, zenMode);
} else { } else {
Log.i(TAG, "No intent found for " + zenMode.getRule().getName()); setUpForAppTrigger(triggerPref, zenMode);
preference.setVisible(false);
} }
} }
} }
private void setUpForSystemOwnedTrigger(Preference preference, ZenMode mode) {
if (mode.getType() == TYPE_SCHEDULE_TIME) {
// TODO: b/332937635 - set correct metrics category
preference.setIntent(ZenSubSettingLauncher.forModeFragment(mContext,
ZenModeSetScheduleFragment.class, mode.getId(), 0).toIntent());
// [Clock Icon] 9:00 - 17:00 / Sun-Mon
preference.setIcon(com.android.internal.R.drawable.ic_zen_mode_type_schedule_time);
ZenModeConfig.ScheduleInfo schedule =
tryParseScheduleConditionId(mode.getRule().getConditionId());
if (schedule != null) {
preference.setTitle(SystemZenRules.getTimeSummary(mContext, schedule));
preference.setSummary(SystemZenRules.getShortDaysSummary(mContext, schedule));
} else {
// Fallback, but shouldn't happen.
Log.wtf(TAG, "SCHEDULE_TIME mode without schedule: " + mode);
preference.setTitle(R.string.zen_mode_set_schedule_link);
preference.setSummary(null);
}
} else if (mode.getType() == TYPE_SCHEDULE_CALENDAR) {
// TODO: b/332937635 - set correct metrics category
preference.setIntent(ZenSubSettingLauncher.forModeFragment(mContext,
ZenModeSetCalendarFragment.class, mode.getId(), 0).toIntent());
// [Event Icon] Calendar Events / <Calendar name>
preference.setIcon(
com.android.internal.R.drawable.ic_zen_mode_type_schedule_calendar);
preference.setTitle(R.string.zen_mode_trigger_title_schedule_calendar);
preference.setSummary(mode.getTriggerDescription());
} else {
Log.wtf(TAG, "Unexpected type for system-owned mode: " + mode);
}
}
@SuppressLint("SwitchIntDef")
private void setUpForAppTrigger(Preference preference, ZenMode mode) {
// App-owned mode may have triggerDescription, configurationActivity, or both/neither.
Intent configurationIntent =
mConfigurationActivityHelper.getConfigurationActivityIntentForMode(
mode, mServiceListing::findService);
@StringRes int title = switch (mode.getType()) {
case TYPE_BEDTIME -> R.string.zen_mode_trigger_title_bedtime;
case TYPE_DRIVING -> R.string.zen_mode_trigger_title_driving;
default -> R.string.zen_mode_trigger_title_generic;
};
String summary;
if (!Strings.isNullOrEmpty(mode.getTriggerDescription())) {
summary = mode.getTriggerDescription();
} else if (!Strings.isNullOrEmpty(mode.getRule().getPackageName())) {
String appName = null;
try {
ApplicationInfo appInfo = mPackageManager.getApplicationInfo(
mode.getRule().getPackageName(), 0);
appName = appInfo.loadLabel(mPackageManager).toString();
} catch (PackageManager.NameNotFoundException e) {
Log.e(TAG, "Couldn't resolve owner for mode: " + mode);
}
if (appName != null) {
summary = mContext.getString(
configurationIntent != null
? R.string.zen_mode_trigger_summary_settings_in_app
: R.string.zen_mode_trigger_summary_managed_by_app,
appName);
} else {
summary = null;
}
} else {
Log.e(TAG, "Mode without package! " + mode);
summary = null;
}
@DrawableRes int icon;
if (mode.getType() == TYPE_BEDTIME) {
icon = com.android.internal.R.drawable.ic_zen_mode_type_schedule_time; // Clock
} else if (mode.getType() == TYPE_DRIVING) {
icon = com.android.internal.R.drawable.ic_zen_mode_type_driving; // Car
} else {
icon = configurationIntent != null ? R.drawable.ic_zen_mode_trigger_with_activity
: R.drawable.ic_zen_mode_trigger_without_activity;
}
preference.setTitle(title);
preference.setSummary(summary);
preference.setIcon(icon);
preference.setIntent(configurationIntent);
}
@VisibleForTesting @VisibleForTesting
final ZenModeScheduleChooserDialog.OnScheduleOptionListener mOnScheduleOptionListener = final ZenModeScheduleChooserDialog.OnScheduleOptionListener mOnScheduleOptionListener =
conditionId -> saveMode(mode -> { conditionId -> saveMode(mode -> {
mode.setCustomModeConditionId(mContext, conditionId); mode.setCustomModeConditionId(mContext, conditionId);
return mode; return mode;
// TODO: b/342156843 - Maybe jump to the corresponding schedule editing screen?
}); });
@VisibleForTesting private final Preference.OnPreferenceChangeListener mSwitchChangeListener = (p, newValue) -> {
protected Preference.OnPreferenceChangeListener mSwitchChangeListener = (p, newValue) -> {
final boolean newEnabled = (Boolean) newValue; final boolean newEnabled = (Boolean) newValue;
return saveMode((zenMode) -> { return saveMode((zenMode) -> {
if (newEnabled != zenMode.getRule().isEnabled()) { if (newEnabled != zenMode.getRule().isEnabled()) {
@@ -148,6 +238,5 @@ class ZenModeSetTriggerLinkPreferenceController extends AbstractZenModePreferenc
} }
return zenMode; return zenMode;
}); });
// TODO: b/342156843 - Do we want to jump to the corresponding schedule editing screen?
}; };
} }

View File

@@ -22,11 +22,15 @@ import static android.app.AutomaticZenRule.TYPE_SCHEDULE_TIME;
import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY; import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY;
import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT; import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
import static com.android.settings.notification.modes.ZenModeSetTriggerLinkPreferenceController.AUTOMATIC_TRIGGER_PREF_KEY; import static com.android.settings.notification.modes.ZenModeSetTriggerLinkPreferenceController.ADD_TRIGGER_KEY;
import static com.android.settings.notification.modes.ZenModeSetTriggerLinkPreferenceController.AUTOMATIC_TRIGGER_KEY;
import static com.android.settings.notification.modes.ZenModeSetTriggerLinkPreferenceControllerTest.CharSequenceTruth.assertThat;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.truth.Truth.assertThat; 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.anyInt;
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 static org.mockito.Mockito.when;
@@ -35,6 +39,7 @@ 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.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.net.Uri; import android.net.Uri;
import android.platform.test.annotations.EnableFlags; import android.platform.test.annotations.EnableFlags;
@@ -42,7 +47,11 @@ import android.platform.test.flag.junit.SetFlagsRule;
import android.service.notification.SystemZenRules; import android.service.notification.SystemZenRules;
import android.service.notification.ZenModeConfig; import android.service.notification.ZenModeConfig;
import androidx.annotation.Nullable;
import androidx.preference.Preference;
import androidx.preference.PreferenceCategory; import androidx.preference.PreferenceCategory;
import androidx.preference.PreferenceManager;
import androidx.preference.PreferenceScreen;
import androidx.test.core.app.ApplicationProvider; import androidx.test.core.app.ApplicationProvider;
import com.android.settings.R; import com.android.settings.R;
@@ -53,6 +62,9 @@ import com.android.settingslib.notification.modes.TestModeBuilder;
import com.android.settingslib.notification.modes.ZenMode; import com.android.settingslib.notification.modes.ZenMode;
import com.android.settingslib.notification.modes.ZenModesBackend; import com.android.settingslib.notification.modes.ZenModesBackend;
import com.google.common.truth.StringSubject;
import com.google.common.truth.Truth;
import org.junit.Before; import org.junit.Before;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
@@ -60,6 +72,7 @@ import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor; import org.mockito.ArgumentCaptor;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.MockitoAnnotations; import org.mockito.MockitoAnnotations;
import org.mockito.stubbing.Answer;
import org.robolectric.RobolectricTestRunner; import org.robolectric.RobolectricTestRunner;
import java.util.Calendar; import java.util.Calendar;
@@ -74,32 +87,47 @@ public class ZenModeSetTriggerLinkPreferenceControllerTest {
private ZenModesBackend mBackend; private ZenModesBackend mBackend;
private Context mContext; private Context mContext;
private PrimarySwitchPreference mPreference;
@Mock @Mock
private PackageManager mPm; private PackageManager mPm;
@Mock @Mock
private ConfigurationActivityHelper mConfigurationActivityHelper; private ConfigurationActivityHelper mConfigurationActivityHelper;
@Mock
private PreferenceCategory mPrefCategory; private PreferenceCategory mPrefCategory;
private PrimarySwitchPreference mConfigPreference;
private Preference mAddPreference;
@Mock @Mock
private DashboardFragment mFragment; private DashboardFragment mFragment;
private ZenModeSetTriggerLinkPreferenceController mPrefController; private ZenModeSetTriggerLinkPreferenceController mController;
@Before @Before
public void setUp() { public void setUp() throws Exception {
MockitoAnnotations.initMocks(this); MockitoAnnotations.initMocks(this);
mContext = ApplicationProvider.getApplicationContext(); mContext = ApplicationProvider.getApplicationContext();
mPrefController = new ZenModeSetTriggerLinkPreferenceController(mContext, PreferenceManager preferenceManager = new PreferenceManager(mContext);
"zen_automatic_trigger_category", mFragment, mBackend, PreferenceScreen preferenceScreen = preferenceManager.inflateFromResource(mContext,
mConfigurationActivityHelper, R.xml.modes_rule_settings, null);
mock(ZenServiceListing.class));
mPreference = new PrimarySwitchPreference(mContext);
when(mPrefCategory.findPreference(AUTOMATIC_TRIGGER_PREF_KEY)).thenReturn(mPreference); mController = new ZenModeSetTriggerLinkPreferenceController(mContext,
"zen_automatic_trigger_category", mFragment, mBackend, mPm,
mConfigurationActivityHelper, mock(ZenServiceListing.class));
mPrefCategory = preferenceScreen.findPreference("zen_automatic_trigger_category");
mConfigPreference = checkNotNull(mPrefCategory).findPreference(AUTOMATIC_TRIGGER_KEY);
mAddPreference = checkNotNull(mPrefCategory).findPreference(ADD_TRIGGER_KEY);
when(mPm.getApplicationInfo(any(), anyInt())).then(
(Answer<ApplicationInfo>) invocationOnMock -> {
ApplicationInfo appInfo = new ApplicationInfo();
appInfo.packageName = invocationOnMock.getArgument(0);
appInfo.labelRes = 1; // Whatever, but != 0 so that loadLabel calls PM.getText()
return appInfo;
});
when(mPm.getText(any(), anyInt(), any())).then(
(Answer<CharSequence>) invocationOnMock ->
"App named " + invocationOnMock.getArgument(0));
} }
@Test @Test
@@ -110,37 +138,37 @@ public class ZenModeSetTriggerLinkPreferenceControllerTest {
.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY) .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
.build(), true); .build(), true);
mPrefController.updateZenMode(mPrefCategory, manualMode); mController.updateZenMode(mPrefCategory, manualMode);
assertThat(mPrefController.isAvailable()).isFalse(); assertThat(mController.isAvailable()).isFalse();
// should be available for other modes // should be available for other modes
mPrefController.updateZenMode(mPrefCategory, TestModeBuilder.EXAMPLE); mController.updateZenMode(mPrefCategory, TestModeBuilder.EXAMPLE);
assertThat(mPrefController.isAvailable()).isTrue(); assertThat(mController.isAvailable()).isTrue();
} }
@Test @Test
public void testUpdateState() { public void updateState_switchCheckedIfRuleEnabled() {
ZenMode zenMode = new TestModeBuilder().setEnabled(false).build(); ZenMode zenMode = new TestModeBuilder().setEnabled(false).build();
// Update preference controller with a zen mode that is not enabled // Update preference controller with a zen mode that is not enabled
mPrefController.updateZenMode(mPrefCategory, zenMode); mController.updateZenMode(mPrefCategory, zenMode);
assertThat(mPreference.getCheckedState()).isFalse(); assertThat(mConfigPreference.getCheckedState()).isFalse();
// Now with the rule enabled // Now with the rule enabled
zenMode.getRule().setEnabled(true); zenMode.getRule().setEnabled(true);
mPrefController.updateZenMode(mPrefCategory, zenMode); mController.updateZenMode(mPrefCategory, zenMode);
assertThat(mPreference.getCheckedState()).isTrue(); assertThat(mConfigPreference.getCheckedState()).isTrue();
} }
@Test @Test
public void testOnPreferenceChange() { public void onPreferenceChange_updatesMode() {
ZenMode zenMode = new TestModeBuilder().setEnabled(false).build(); ZenMode zenMode = new TestModeBuilder().setEnabled(false).build();
// start with disabled rule // start with disabled rule
mPrefController.updateZenMode(mPrefCategory, zenMode); mController.updateZenMode(mPrefCategory, zenMode);
// then update the preference to be checked // then flip the switch
mPrefController.mSwitchChangeListener.onPreferenceChange(mPreference, true); mConfigPreference.callChangeListener(true);
// verify the backend got asked to update the mode to be enabled // verify the backend got asked to update the mode to be enabled
ArgumentCaptor<ZenMode> captor = ArgumentCaptor.forClass(ZenMode.class); ArgumentCaptor<ZenMode> captor = ArgumentCaptor.forClass(ZenMode.class);
@@ -149,7 +177,7 @@ public class ZenModeSetTriggerLinkPreferenceControllerTest {
} }
@Test @Test
public void testRuleLink_calendar() { public void updateState_scheduleCalendarRule() {
ZenModeConfig.EventInfo eventInfo = new ZenModeConfig.EventInfo(); ZenModeConfig.EventInfo eventInfo = new ZenModeConfig.EventInfo();
eventInfo.calendarId = 1L; eventInfo.calendarId = 1L;
eventInfo.calName = "My events"; eventInfo.calName = "My events";
@@ -159,23 +187,21 @@ public class ZenModeSetTriggerLinkPreferenceControllerTest {
.setType(TYPE_SCHEDULE_CALENDAR) .setType(TYPE_SCHEDULE_CALENDAR)
.setTriggerDescription("My events") .setTriggerDescription("My events")
.build(); .build();
mPrefController.updateZenMode(mPrefCategory, mode);
assertThat(mPreference.getTitle()).isNotNull(); mController.updateState(mPrefCategory, mode);
assertThat(mPreference.getTitle().toString()).isEqualTo(
mContext.getString(R.string.zen_mode_set_calendar_link));
assertThat(mPreference.getSummary()).isNotNull();
assertThat(mPreference.getSummary().toString()).isEqualTo(
mode.getRule().getTriggerDescription());
assertThat(mPreference.getIcon()).isNull();
assertThat(mAddPreference.isVisible()).isFalse();
assertThat(mConfigPreference.isVisible()).isTrue();
assertThat(mConfigPreference.getTitle()).isEqualTo("Calendar events");
assertThat(mConfigPreference.getSummary()).isEqualTo("My events");
// Destination as written into the intent by SubSettingLauncher // Destination as written into the intent by SubSettingLauncher
assertThat(mPreference.getIntent().getStringExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT)) assertThat(
mConfigPreference.getIntent().getStringExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT))
.isEqualTo(ZenModeSetCalendarFragment.class.getName()); .isEqualTo(ZenModeSetCalendarFragment.class.getName());
} }
@Test @Test
public void testRuleLink_schedule() { public void updateState_scheduleTimeRule() {
ZenModeConfig.ScheduleInfo scheduleInfo = new ZenModeConfig.ScheduleInfo(); ZenModeConfig.ScheduleInfo scheduleInfo = new ZenModeConfig.ScheduleInfo();
scheduleInfo.days = new int[]{Calendar.MONDAY, Calendar.TUESDAY, Calendar.THURSDAY}; scheduleInfo.days = new int[]{Calendar.MONDAY, Calendar.TUESDAY, Calendar.THURSDAY};
scheduleInfo.startHour = 1; scheduleInfo.startHour = 1;
@@ -186,44 +212,41 @@ public class ZenModeSetTriggerLinkPreferenceControllerTest {
.setType(TYPE_SCHEDULE_TIME) .setType(TYPE_SCHEDULE_TIME)
.setTriggerDescription("some schedule") .setTriggerDescription("some schedule")
.build(); .build();
mPrefController.updateZenMode(mPrefCategory, mode);
assertThat(mPreference.getTitle()).isNotNull(); mController.updateState(mPrefCategory, mode);
assertThat(mPreference.getTitle().toString()).isEqualTo(
mContext.getString(R.string.zen_mode_set_schedule_link));
assertThat(mPreference.getSummary()).isNotNull();
assertThat(mPreference.getSummary().toString()).isEqualTo(
mode.getRule().getTriggerDescription());
assertThat(mPreference.getIcon()).isNull();
assertThat(mAddPreference.isVisible()).isFalse();
assertThat(mConfigPreference.isVisible()).isTrue();
assertThat(mConfigPreference.getTitle()).isEqualTo("1:00 AM - 3:00 PM");
assertThat(mConfigPreference.getSummary()).isEqualTo("Mon - Tue, Thu");
// Destination as written into the intent by SubSettingLauncher // Destination as written into the intent by SubSettingLauncher
assertThat(mPreference.getIntent().getStringExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT)) assertThat(
mConfigPreference.getIntent().getStringExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT))
.isEqualTo(ZenModeSetScheduleFragment.class.getName()); .isEqualTo(ZenModeSetScheduleFragment.class.getName());
} }
@Test @Test
public void testRuleLink_manual() { public void updateState_customManualRule() {
ZenMode mode = new TestModeBuilder() ZenMode mode = new TestModeBuilder()
.setConditionId(ZenModeConfig.toCustomManualConditionId()) .setConditionId(ZenModeConfig.toCustomManualConditionId())
.setPackage(SystemZenRules.PACKAGE_ANDROID) .setPackage(SystemZenRules.PACKAGE_ANDROID)
.setType(TYPE_OTHER) .setType(TYPE_OTHER)
.setTriggerDescription("Will not be shown") .setTriggerDescription("Will not be shown")
.build(); .build();
mPrefController.updateZenMode(mPrefCategory, mode);
assertThat(mPreference.getTitle()).isNotNull(); mController.updateState(mPrefCategory, mode);
assertThat(mPreference.getTitle().toString()).isEqualTo(
assertThat(mConfigPreference.isVisible()).isFalse();
assertThat(mAddPreference.isVisible()).isTrue();
assertThat(mAddPreference.getTitle()).isEqualTo(
mContext.getString(R.string.zen_mode_select_schedule)); mContext.getString(R.string.zen_mode_select_schedule));
assertThat(mPreference.getIcon()).isNotNull(); assertThat(mAddPreference.getSummary()).isNull();
assertThat(mPreference.getSummary()).isNotNull(); // Sets up a click listener to open the dialog.
assertThat(mPreference.getSummary().toString()).isEqualTo(""); assertThat(mAddPreference.getOnPreferenceClickListener()).isNotNull();
// Set up a click listener to open the dialog.
assertThat(mPreference.getOnPreferenceClickListener()).isNotNull();
} }
@Test @Test
public void testRuleLink_appWithConfigActivity_linksToConfigActivity() { public void updateState_appWithConfigActivity_showsLinkToConfigActivity() {
ZenMode mode = new TestModeBuilder() ZenMode mode = new TestModeBuilder()
.setPackage("some.package") .setPackage("some.package")
.setTriggerDescription("When The Music's Over") .setTriggerDescription("When The Music's Over")
@@ -232,28 +255,62 @@ public class ZenModeSetTriggerLinkPreferenceControllerTest {
when(mConfigurationActivityHelper.getConfigurationActivityIntentForMode(any(), any())) when(mConfigurationActivityHelper.getConfigurationActivityIntentForMode(any(), any()))
.thenReturn(configurationIntent); .thenReturn(configurationIntent);
mPrefController.updateZenMode(mPrefCategory, mode); mController.updateState(mPrefCategory, mode);
assertThat(mPreference.getTitle()).isNotNull(); assertThat(mConfigPreference.isVisible()).isTrue();
assertThat(mPreference.getTitle().toString()).isEqualTo( assertThat(mConfigPreference.getTitle()).isEqualTo("Linked to app");
mContext.getString(R.string.zen_mode_configuration_link_title)); assertThat(mConfigPreference.getSummary()).isEqualTo("When The Music's Over");
assertThat(mPreference.getSummary()).isNotNull(); assertThat(mConfigPreference.getIntent()).isEqualTo(configurationIntent);
assertThat(mPreference.getSummary().toString()).isEqualTo("When The Music's Over");
assertThat(mPreference.getIntent()).isEqualTo(configurationIntent);
} }
@Test @Test
public void testRuleLink_appWithoutConfigActivity_hidden() { public void updateState_appWithoutConfigActivity_showsWithoutLinkToConfigActivity() {
ZenMode mode = new TestModeBuilder() ZenMode mode = new TestModeBuilder()
.setPackage("some.package") .setPackage("some.package")
.setTriggerDescription("Will not be shown :(") .setTriggerDescription("When the saints go marching in")
.build(); .build();
when(mConfigurationActivityHelper.getConfigurationActivityIntentForMode(any(), any())) when(mConfigurationActivityHelper.getConfigurationActivityIntentForMode(any(), any()))
.thenReturn(null); .thenReturn(null);
mPrefController.updateZenMode(mPrefCategory, mode); mController.updateState(mPrefCategory, mode);
assertThat(mPrefCategory.isVisible()).isFalse(); assertThat(mConfigPreference.isVisible()).isTrue();
assertThat(mConfigPreference.getTitle()).isEqualTo("Linked to app");
assertThat(mConfigPreference.getSummary()).isEqualTo("When the saints go marching in");
assertThat(mConfigPreference.getIntent()).isNull();
}
@Test
public void updateState_appWithoutTriggerDescriptionWithConfigActivity_showsAppNameInSummary() {
ZenMode mode = new TestModeBuilder()
.setPackage("some.package")
.build();
Intent configurationIntent = new Intent("configure the mode");
when(mConfigurationActivityHelper.getConfigurationActivityIntentForMode(any(), any()))
.thenReturn(configurationIntent);
when(mPm.getText(any(), anyInt(), any())).thenReturn("The App Name");
mController.updateState(mPrefCategory, mode);
assertThat(mConfigPreference.isVisible()).isTrue();
assertThat(mConfigPreference.getTitle()).isEqualTo("Linked to app");
assertThat(mConfigPreference.getSummary()).isEqualTo("Info and settings in The App Name");
}
@Test
public void updateState_appWithoutTriggerDescriptionNorConfigActivity_showsAppNameInSummary() {
ZenMode mode = new TestModeBuilder()
.setPackage("some.package")
.build();
when(mConfigurationActivityHelper.getConfigurationActivityIntentForMode(any(), any()))
.thenReturn(null);
when(mPm.getText(any(), anyInt(), any())).thenReturn("The App Name");
mController.updateState(mPrefCategory, mode);
assertThat(mConfigPreference.isVisible()).isTrue();
assertThat(mConfigPreference.getTitle()).isEqualTo("Linked to app");
assertThat(mConfigPreference.getSummary()).isEqualTo("Managed by The App Name");
} }
@Test @Test
@@ -264,7 +321,7 @@ public class ZenModeSetTriggerLinkPreferenceControllerTest {
.setType(TYPE_OTHER) .setType(TYPE_OTHER)
.setTriggerDescription("") .setTriggerDescription("")
.build(); .build();
mPrefController.updateZenMode(mPrefCategory, originalMode); mController.updateZenMode(mPrefCategory, originalMode);
ZenModeConfig.ScheduleInfo scheduleInfo = new ZenModeConfig.ScheduleInfo(); ZenModeConfig.ScheduleInfo scheduleInfo = new ZenModeConfig.ScheduleInfo();
scheduleInfo.days = new int[] { Calendar.MONDAY }; scheduleInfo.days = new int[] { Calendar.MONDAY };
@@ -272,7 +329,7 @@ public class ZenModeSetTriggerLinkPreferenceControllerTest {
scheduleInfo.endHour = 15; scheduleInfo.endHour = 15;
Uri scheduleUri = ZenModeConfig.toScheduleConditionId(scheduleInfo); Uri scheduleUri = ZenModeConfig.toScheduleConditionId(scheduleInfo);
mPrefController.mOnScheduleOptionListener.onScheduleSelected(scheduleUri); mController.mOnScheduleOptionListener.onScheduleSelected(scheduleUri);
// verify the backend got asked to update the mode to be schedule-based. // verify the backend got asked to update the mode to be schedule-based.
ArgumentCaptor<ZenMode> captor = ArgumentCaptor.forClass(ZenMode.class); ArgumentCaptor<ZenMode> captor = ArgumentCaptor.forClass(ZenMode.class);
@@ -284,4 +341,17 @@ public class ZenModeSetTriggerLinkPreferenceControllerTest {
assertThat(updatedMode.getRule().getOwner()).isEqualTo( assertThat(updatedMode.getRule().getOwner()).isEqualTo(
ZenModeConfig.getScheduleConditionProvider()); ZenModeConfig.getScheduleConditionProvider());
} }
static class CharSequenceTruth {
/**
* Shortcut version of {@link Truth#assertThat(String)} suitable for {@link CharSequence}.
* {@link CharSequence} doesn't necessarily provide a good {@code equals()} implementation;
* however we don't care about formatting here, so we want to assert on the resulting
* string (without needing to worry that {@code assertThat(x.getText().toString())} can
* throw if the text is null).
*/
static StringSubject assertThat(@Nullable CharSequence actual) {
return Truth.assertThat((String) (actual != null ? actual.toString() : null));
}
}
} }