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
diff --git a/res/drawable/ic_zen_mode_trigger_with_activity.xml b/res/drawable/ic_zen_mode_trigger_with_activity.xml
new file mode 100644
index 0000000..567f01a
--- /dev/null
+++ b/res/drawable/ic_zen_mode_trigger_with_activity.xml
@@ -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>
\ No newline at end of file
diff --git a/res/drawable/ic_zen_mode_trigger_without_activity.xml b/res/drawable/ic_zen_mode_trigger_without_activity.xml
new file mode 100644
index 0000000..11a97f1
--- /dev/null
+++ b/res/drawable/ic_zen_mode_trigger_without_activity.xml
@@ -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>
\ No newline at end of file
diff --git a/res/values/strings.xml b/res/values/strings.xml
index f994fbc..08d29ea 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -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] -->
<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>
- <!-- 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>
<!-- 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] -->
<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] -->
<string name="warning_button_text">Warning</string>
diff --git a/res/xml/modes_rule_settings.xml b/res/xml/modes_rule_settings.xml
index a8ba553..2464c25 100644
--- a/res/xml/modes_rule_settings.xml
+++ b/res/xml/modes_rule_settings.xml
@@ -28,6 +28,21 @@
android:selectable="false"
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
android:title="@string/mode_interruption_filter_title"
android:key="modes_filters">
@@ -49,14 +64,6 @@
android:title="@string/zen_category_exceptions" />
</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
android:title="@string/mode_device_effects_title"
android:key="modes_additional_actions">
diff --git a/src/com/android/settings/notification/modes/ZenModeSetTriggerLinkPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeSetTriggerLinkPreferenceController.java
index 86135a9..1f97902 100644
--- a/src/com/android/settings/notification/modes/ZenModeSetTriggerLinkPreferenceController.java
+++ b/src/com/android/settings/notification/modes/ZenModeSetTriggerLinkPreferenceController.java
@@ -16,14 +16,26 @@
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_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.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 androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
+import androidx.annotation.StringRes;
import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
import androidx.preference.PreferenceCategory;
@@ -35,6 +47,8 @@
import com.android.settingslib.notification.modes.ZenMode;
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.
*/
@@ -42,26 +56,29 @@
private static final String TAG = "ZenModeSetTriggerLink";
@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 ZenServiceListing mServiceListing;
- private final DashboardFragment mFragment;
ZenModeSetTriggerLinkPreferenceController(Context context, String key,
DashboardFragment fragment, ZenModesBackend backend) {
- this(context, key, fragment, backend,
+ this(context, key, fragment, backend, context.getPackageManager(),
new ConfigurationActivityHelper(context.getPackageManager()),
new ZenServiceListing(context));
}
@VisibleForTesting
ZenModeSetTriggerLinkPreferenceController(Context context, String key,
- DashboardFragment fragment, ZenModesBackend backend,
+ DashboardFragment fragment, ZenModesBackend backend, PackageManager packageManager,
ConfigurationActivityHelper configurationActivityHelper,
ZenServiceListing serviceListing) {
super(context, key, backend);
mFragment = fragment;
+ mPackageManager = packageManager;
mConfigurationActivityHelper = configurationActivityHelper;
mServiceListing = serviceListing;
}
@@ -83,64 +100,137 @@
// 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
// automatically trigger (such as manual DND).
- PrimarySwitchPreference switchPref = ((PreferenceCategory) preference).findPreference(
- AUTOMATIC_TRIGGER_PREF_KEY);
- if (switchPref == null) {
+ if (zenMode.isManualDnd()) {
return;
}
- switchPref.setChecked(zenMode.getRule().isEnabled());
- switchPref.setOnPreferenceChangeListener(mSwitchChangeListener);
- switchPref.setSummary(zenMode.getRule().getTriggerDescription());
- switchPref.setIcon(null);
- switchPref.setOnPreferenceClickListener(null);
- switchPref.setIntent(null);
+ PrimarySwitchPreference triggerPref = checkNotNull(
+ ((PreferenceCategory) preference).findPreference(AUTOMATIC_TRIGGER_KEY));
+ Preference addTriggerPref = checkNotNull(
+ ((PreferenceCategory) preference).findPreference(ADD_TRIGGER_KEY));
- if (zenMode.isSystemOwned()) {
- if (zenMode.getType() == TYPE_SCHEDULE_TIME) {
- switchPref.setTitle(R.string.zen_mode_set_schedule_link);
- // TODO: b/332937635 - set correct metrics category
- switchPref.setIntent(ZenSubSettingLauncher.forModeFragment(mContext,
- ZenModeSetScheduleFragment.class, zenMode.getId(), 0).toIntent());
- } else if (zenMode.getType() == TYPE_SCHEDULE_CALENDAR) {
- switchPref.setTitle(R.string.zen_mode_set_calendar_link);
- switchPref.setIcon(null);
- // 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;
- });
- }
+ boolean isAddTrigger = zenMode.isSystemOwned() && zenMode.getType() != TYPE_SCHEDULE_TIME
+ && zenMode.getType() != TYPE_SCHEDULE_CALENDAR;
+
+ if (isAddTrigger) {
+ triggerPref.setVisible(false);
+ addTriggerPref.setVisible(true);
+ addTriggerPref.setOnPreferenceClickListener(unused -> {
+ ZenModeScheduleChooserDialog.show(mFragment, mOnScheduleOptionListener);
+ return true;
+ });
} else {
- Intent intent = mConfigurationActivityHelper.getConfigurationActivityIntentForMode(
- zenMode, mServiceListing::findService);
- if (intent != null) {
- preference.setVisible(true);
- switchPref.setTitle(R.string.zen_mode_configuration_link_title);
- switchPref.setSummary(zenMode.getRule().getTriggerDescription());
- switchPref.setIntent(intent);
+ addTriggerPref.setVisible(false);
+ triggerPref.setVisible(true);
+ triggerPref.setChecked(zenMode.getRule().isEnabled());
+ triggerPref.setOnPreferenceChangeListener(mSwitchChangeListener);
+
+ if (zenMode.isSystemOwned()) {
+ setUpForSystemOwnedTrigger(triggerPref, zenMode);
} else {
- Log.i(TAG, "No intent found for " + zenMode.getRule().getName());
- preference.setVisible(false);
+ setUpForAppTrigger(triggerPref, zenMode);
}
}
}
+ 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
final ZenModeScheduleChooserDialog.OnScheduleOptionListener mOnScheduleOptionListener =
conditionId -> saveMode(mode -> {
mode.setCustomModeConditionId(mContext, conditionId);
return mode;
+ // TODO: b/342156843 - Maybe jump to the corresponding schedule editing screen?
});
- @VisibleForTesting
- protected Preference.OnPreferenceChangeListener mSwitchChangeListener = (p, newValue) -> {
+ private final Preference.OnPreferenceChangeListener mSwitchChangeListener = (p, newValue) -> {
final boolean newEnabled = (Boolean) newValue;
return saveMode((zenMode) -> {
if (newEnabled != zenMode.getRule().isEnabled()) {
@@ -148,6 +238,5 @@
}
return zenMode;
});
- // TODO: b/342156843 - Do we want to jump to the corresponding schedule editing screen?
};
}
diff --git a/tests/robotests/src/com/android/settings/notification/modes/ZenModeSetTriggerLinkPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/modes/ZenModeSetTriggerLinkPreferenceControllerTest.java
index fc3cef1..61ca4d8 100644
--- a/tests/robotests/src/com/android/settings/notification/modes/ZenModeSetTriggerLinkPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/notification/modes/ZenModeSetTriggerLinkPreferenceControllerTest.java
@@ -22,11 +22,15 @@
import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY;
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 org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -35,6 +39,7 @@
import android.app.Flags;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.platform.test.annotations.EnableFlags;
@@ -42,7 +47,11 @@
import android.service.notification.SystemZenRules;
import android.service.notification.ZenModeConfig;
+import androidx.annotation.Nullable;
+import androidx.preference.Preference;
import androidx.preference.PreferenceCategory;
+import androidx.preference.PreferenceManager;
+import androidx.preference.PreferenceScreen;
import androidx.test.core.app.ApplicationProvider;
import com.android.settings.R;
@@ -53,6 +62,9 @@
import com.android.settingslib.notification.modes.ZenMode;
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.Rule;
import org.junit.Test;
@@ -60,6 +72,7 @@
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import org.mockito.stubbing.Answer;
import org.robolectric.RobolectricTestRunner;
import java.util.Calendar;
@@ -74,32 +87,47 @@
private ZenModesBackend mBackend;
private Context mContext;
- private PrimarySwitchPreference mPreference;
-
@Mock
private PackageManager mPm;
@Mock
private ConfigurationActivityHelper mConfigurationActivityHelper;
- @Mock
private PreferenceCategory mPrefCategory;
+ private PrimarySwitchPreference mConfigPreference;
+ private Preference mAddPreference;
+
@Mock
private DashboardFragment mFragment;
- private ZenModeSetTriggerLinkPreferenceController mPrefController;
+ private ZenModeSetTriggerLinkPreferenceController mController;
@Before
- public void setUp() {
+ public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
mContext = ApplicationProvider.getApplicationContext();
- mPrefController = new ZenModeSetTriggerLinkPreferenceController(mContext,
- "zen_automatic_trigger_category", mFragment, mBackend,
- mConfigurationActivityHelper,
- mock(ZenServiceListing.class));
- mPreference = new PrimarySwitchPreference(mContext);
+ PreferenceManager preferenceManager = new PreferenceManager(mContext);
+ PreferenceScreen preferenceScreen = preferenceManager.inflateFromResource(mContext,
+ R.xml.modes_rule_settings, null);
- 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
@@ -110,37 +138,37 @@
.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
.build(), true);
- mPrefController.updateZenMode(mPrefCategory, manualMode);
- assertThat(mPrefController.isAvailable()).isFalse();
+ mController.updateZenMode(mPrefCategory, manualMode);
+ assertThat(mController.isAvailable()).isFalse();
// should be available for other modes
- mPrefController.updateZenMode(mPrefCategory, TestModeBuilder.EXAMPLE);
- assertThat(mPrefController.isAvailable()).isTrue();
+ mController.updateZenMode(mPrefCategory, TestModeBuilder.EXAMPLE);
+ assertThat(mController.isAvailable()).isTrue();
}
@Test
- public void testUpdateState() {
+ public void updateState_switchCheckedIfRuleEnabled() {
ZenMode zenMode = new TestModeBuilder().setEnabled(false).build();
// Update preference controller with a zen mode that is not enabled
- mPrefController.updateZenMode(mPrefCategory, zenMode);
- assertThat(mPreference.getCheckedState()).isFalse();
+ mController.updateZenMode(mPrefCategory, zenMode);
+ assertThat(mConfigPreference.getCheckedState()).isFalse();
// Now with the rule enabled
zenMode.getRule().setEnabled(true);
- mPrefController.updateZenMode(mPrefCategory, zenMode);
- assertThat(mPreference.getCheckedState()).isTrue();
+ mController.updateZenMode(mPrefCategory, zenMode);
+ assertThat(mConfigPreference.getCheckedState()).isTrue();
}
@Test
- public void testOnPreferenceChange() {
+ public void onPreferenceChange_updatesMode() {
ZenMode zenMode = new TestModeBuilder().setEnabled(false).build();
// start with disabled rule
- mPrefController.updateZenMode(mPrefCategory, zenMode);
+ mController.updateZenMode(mPrefCategory, zenMode);
- // then update the preference to be checked
- mPrefController.mSwitchChangeListener.onPreferenceChange(mPreference, true);
+ // then flip the switch
+ mConfigPreference.callChangeListener(true);
// verify the backend got asked to update the mode to be enabled
ArgumentCaptor<ZenMode> captor = ArgumentCaptor.forClass(ZenMode.class);
@@ -149,7 +177,7 @@
}
@Test
- public void testRuleLink_calendar() {
+ public void updateState_scheduleCalendarRule() {
ZenModeConfig.EventInfo eventInfo = new ZenModeConfig.EventInfo();
eventInfo.calendarId = 1L;
eventInfo.calName = "My events";
@@ -159,23 +187,21 @@
.setType(TYPE_SCHEDULE_CALENDAR)
.setTriggerDescription("My events")
.build();
- mPrefController.updateZenMode(mPrefCategory, mode);
- assertThat(mPreference.getTitle()).isNotNull();
- 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();
+ mController.updateState(mPrefCategory, mode);
+ 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
- assertThat(mPreference.getIntent().getStringExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT))
+ assertThat(
+ mConfigPreference.getIntent().getStringExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT))
.isEqualTo(ZenModeSetCalendarFragment.class.getName());
}
@Test
- public void testRuleLink_schedule() {
+ public void updateState_scheduleTimeRule() {
ZenModeConfig.ScheduleInfo scheduleInfo = new ZenModeConfig.ScheduleInfo();
scheduleInfo.days = new int[]{Calendar.MONDAY, Calendar.TUESDAY, Calendar.THURSDAY};
scheduleInfo.startHour = 1;
@@ -186,44 +212,41 @@
.setType(TYPE_SCHEDULE_TIME)
.setTriggerDescription("some schedule")
.build();
- mPrefController.updateZenMode(mPrefCategory, mode);
- assertThat(mPreference.getTitle()).isNotNull();
- 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();
+ mController.updateState(mPrefCategory, mode);
+ 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
- assertThat(mPreference.getIntent().getStringExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT))
+ assertThat(
+ mConfigPreference.getIntent().getStringExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT))
.isEqualTo(ZenModeSetScheduleFragment.class.getName());
}
@Test
- public void testRuleLink_manual() {
+ public void updateState_customManualRule() {
ZenMode mode = new TestModeBuilder()
.setConditionId(ZenModeConfig.toCustomManualConditionId())
.setPackage(SystemZenRules.PACKAGE_ANDROID)
.setType(TYPE_OTHER)
.setTriggerDescription("Will not be shown")
.build();
- mPrefController.updateZenMode(mPrefCategory, mode);
- assertThat(mPreference.getTitle()).isNotNull();
- assertThat(mPreference.getTitle().toString()).isEqualTo(
+ mController.updateState(mPrefCategory, mode);
+
+ assertThat(mConfigPreference.isVisible()).isFalse();
+ assertThat(mAddPreference.isVisible()).isTrue();
+ assertThat(mAddPreference.getTitle()).isEqualTo(
mContext.getString(R.string.zen_mode_select_schedule));
- assertThat(mPreference.getIcon()).isNotNull();
- assertThat(mPreference.getSummary()).isNotNull();
- assertThat(mPreference.getSummary().toString()).isEqualTo("");
-
- // Set up a click listener to open the dialog.
- assertThat(mPreference.getOnPreferenceClickListener()).isNotNull();
+ assertThat(mAddPreference.getSummary()).isNull();
+ // Sets up a click listener to open the dialog.
+ assertThat(mAddPreference.getOnPreferenceClickListener()).isNotNull();
}
@Test
- public void testRuleLink_appWithConfigActivity_linksToConfigActivity() {
+ public void updateState_appWithConfigActivity_showsLinkToConfigActivity() {
ZenMode mode = new TestModeBuilder()
.setPackage("some.package")
.setTriggerDescription("When The Music's Over")
@@ -232,28 +255,62 @@
when(mConfigurationActivityHelper.getConfigurationActivityIntentForMode(any(), any()))
.thenReturn(configurationIntent);
- mPrefController.updateZenMode(mPrefCategory, mode);
+ mController.updateState(mPrefCategory, mode);
- assertThat(mPreference.getTitle()).isNotNull();
- assertThat(mPreference.getTitle().toString()).isEqualTo(
- mContext.getString(R.string.zen_mode_configuration_link_title));
- assertThat(mPreference.getSummary()).isNotNull();
- assertThat(mPreference.getSummary().toString()).isEqualTo("When The Music's Over");
- assertThat(mPreference.getIntent()).isEqualTo(configurationIntent);
+ assertThat(mConfigPreference.isVisible()).isTrue();
+ assertThat(mConfigPreference.getTitle()).isEqualTo("Linked to app");
+ assertThat(mConfigPreference.getSummary()).isEqualTo("When The Music's Over");
+ assertThat(mConfigPreference.getIntent()).isEqualTo(configurationIntent);
}
@Test
- public void testRuleLink_appWithoutConfigActivity_hidden() {
+ public void updateState_appWithoutConfigActivity_showsWithoutLinkToConfigActivity() {
ZenMode mode = new TestModeBuilder()
.setPackage("some.package")
- .setTriggerDescription("Will not be shown :(")
+ .setTriggerDescription("When the saints go marching in")
.build();
when(mConfigurationActivityHelper.getConfigurationActivityIntentForMode(any(), any()))
.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
@@ -264,7 +321,7 @@
.setType(TYPE_OTHER)
.setTriggerDescription("")
.build();
- mPrefController.updateZenMode(mPrefCategory, originalMode);
+ mController.updateZenMode(mPrefCategory, originalMode);
ZenModeConfig.ScheduleInfo scheduleInfo = new ZenModeConfig.ScheduleInfo();
scheduleInfo.days = new int[] { Calendar.MONDAY };
@@ -272,7 +329,7 @@
scheduleInfo.endHour = 15;
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.
ArgumentCaptor<ZenMode> captor = ArgumentCaptor.forClass(ZenMode.class);
@@ -284,4 +341,17 @@
assertThat(updatedMode.getRule().getOwner()).isEqualTo(
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));
+ }
+ }
}