diff --git a/res/values/strings.xml b/res/values/strings.xml index bc573228030..3b1bec85b43 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -7947,6 +7947,18 @@ Schedule + + Turn on automatically + + + Add a calendar + + + Use your calendar + + + Schedule + Schedule diff --git a/res/xml/modes_rule_settings.xml b/res/xml/modes_rule_settings.xml index df560957036..f2822741bc7 100644 --- a/res/xml/modes_rule_settings.xml +++ b/res/xml/modes_rule_settings.xml @@ -16,6 +16,7 @@ --> + + + + + diff --git a/res/xml/modes_set_calendar.xml b/res/xml/modes_set_calendar.xml new file mode 100644 index 00000000000..02eb26e33af --- /dev/null +++ b/res/xml/modes_set_calendar.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/com/android/settings/notification/modes/ZenModeFragment.java b/src/com/android/settings/notification/modes/ZenModeFragment.java index 1f6ae45c462..7084f51a922 100644 --- a/src/com/android/settings/notification/modes/ZenModeFragment.java +++ b/src/com/android/settings/notification/modes/ZenModeFragment.java @@ -46,6 +46,8 @@ public class ZenModeFragment extends ZenModeFragmentBase { context, "zen_other_settings", mBackend)); prefControllers.add(new ZenModeDisplayLinkPreferenceController( context, "mode_display_settings", mBackend)); + prefControllers.add(new ZenModeSetTriggerLinkPreferenceController(context, + "zen_automatic_trigger_category", mBackend)); return prefControllers; } diff --git a/src/com/android/settings/notification/modes/ZenModeFragmentBase.java b/src/com/android/settings/notification/modes/ZenModeFragmentBase.java index ff75afc756b..5e6cfa5084e 100644 --- a/src/com/android/settings/notification/modes/ZenModeFragmentBase.java +++ b/src/com/android/settings/notification/modes/ZenModeFragmentBase.java @@ -103,6 +103,7 @@ abstract class ZenModeFragmentBase extends ZenModesFragmentBase { if (!reloadMode(id)) { Log.d(TAG, "Mode id=" + id + " not found"); toastAndFinish(); + return; } updateControllers(); } diff --git a/src/com/android/settings/notification/modes/ZenModeListPreference.java b/src/com/android/settings/notification/modes/ZenModeListPreference.java index 0f4728f05de..78bae81481c 100644 --- a/src/com/android/settings/notification/modes/ZenModeListPreference.java +++ b/src/com/android/settings/notification/modes/ZenModeListPreference.java @@ -22,7 +22,6 @@ import android.content.Context; import android.os.Bundle; import com.android.settings.core.SubSettingLauncher; -import com.android.settings.notification.zen.ZenModeSettings; import com.android.settingslib.RestrictedPreference; /** @@ -42,22 +41,13 @@ class ZenModeListPreference extends RestrictedPreference { @Override public void onClick() { - // TODO: b/322373473 - This implementation is a hack that just leads to the old DND page - // for manual only; remove this in favor of the real implementation. - if (mZenMode.isManualDnd()) { - new SubSettingLauncher(mContext) - .setDestination(ZenModeSettings.class.getName()) - .setSourceMetricsCategory(SettingsEnums.NOTIFICATION_ZEN_MODE) - .launch(); - } else { - Bundle bundle = new Bundle(); - bundle.putString(MODE_ID, mZenMode.getId()); - new SubSettingLauncher(mContext) - .setDestination(ZenModeFragment.class.getName()) - .setArguments(bundle) - .setSourceMetricsCategory(SettingsEnums.NOTIFICATION_ZEN_MODE_AUTOMATION) - .launch(); - } + Bundle bundle = new Bundle(); + bundle.putString(MODE_ID, mZenMode.getId()); + new SubSettingLauncher(mContext) + .setDestination(ZenModeFragment.class.getName()) + .setArguments(bundle) + .setSourceMetricsCategory(SettingsEnums.NOTIFICATION_ZEN_MODE_AUTOMATION) + .launch(); } public void setZenMode(ZenMode zenMode) { diff --git a/src/com/android/settings/notification/modes/ZenModeSetCalendarFragment.java b/src/com/android/settings/notification/modes/ZenModeSetCalendarFragment.java new file mode 100644 index 00000000000..f0206ef5dad --- /dev/null +++ b/src/com/android/settings/notification/modes/ZenModeSetCalendarFragment.java @@ -0,0 +1,52 @@ +/* + * 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. + */ + +package com.android.settings.notification.modes; + +import android.app.settings.SettingsEnums; +import android.content.Context; + +import com.android.settings.R; +import com.android.settingslib.core.AbstractPreferenceController; + +import java.util.ArrayList; +import java.util.List; + +/** + * Page for choosing calendar and reply type for a scheduled mode that triggers on events. + */ +public class ZenModeSetCalendarFragment extends ZenModeFragmentBase { + + @Override + protected List createPreferenceControllers(Context context) { + List controllers = new ArrayList<>(); + controllers.add( + new ZenModeSetCalendarPreferenceController(context, "zen_mode_event_category", + mBackend)); + return controllers; + } + + @Override + protected int getPreferenceScreenResId() { + return R.xml.modes_set_calendar; + } + + @Override + public int getMetricsCategory() { + // TODO: b/332937635 - make this the correct metrics category + return SettingsEnums.NOTIFICATION_ZEN_MODE_EVENT_RULE; + } +} diff --git a/src/com/android/settings/notification/modes/ZenModeSetCalendarPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeSetCalendarPreferenceController.java new file mode 100644 index 00000000000..28413091a37 --- /dev/null +++ b/src/com/android/settings/notification/modes/ZenModeSetCalendarPreferenceController.java @@ -0,0 +1,264 @@ +/* + * 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. + */ + +package com.android.settings.notification.modes; + +import android.app.Flags; +import android.content.Context; +import android.content.pm.PackageManager; +import android.database.Cursor; +import android.os.UserHandle; +import android.os.UserManager; +import android.provider.CalendarContract; +import android.service.notification.SystemZenRules; +import android.service.notification.ZenModeConfig; + +import androidx.annotation.NonNull; +import androidx.preference.DropDownPreference; +import androidx.preference.Preference; +import androidx.preference.PreferenceCategory; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.settings.R; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Objects; +import java.util.function.Function; + +public class ZenModeSetCalendarPreferenceController extends AbstractZenModePreferenceController { + @VisibleForTesting + protected static final String KEY_CALENDAR = "calendar"; + @VisibleForTesting + protected static final String KEY_REPLY = "reply"; + + private DropDownPreference mCalendar; + private DropDownPreference mReply; + + private ZenModeConfig.EventInfo mEvent; + + public ZenModeSetCalendarPreferenceController(Context context, String key, + ZenModesBackend backend) { + super(context, key, backend); + } + + @Override + public void updateState(Preference preference, @NonNull ZenMode zenMode) { + PreferenceCategory cat = (PreferenceCategory) preference; + + // Refresh our understanding of local preferences + mCalendar = cat.findPreference(KEY_CALENDAR); + mReply = cat.findPreference(KEY_REPLY); + + if (mCalendar == null || mReply == null) { + return; + } + + mCalendar.setOnPreferenceChangeListener(mCalendarChangeListener); + + mReply.setEntries(new CharSequence[] { + mContext.getString(R.string.zen_mode_event_rule_reply_any_except_no), + mContext.getString(R.string.zen_mode_event_rule_reply_yes_or_maybe), + mContext.getString(R.string.zen_mode_event_rule_reply_yes), + }); + mReply.setEntryValues(new CharSequence[] { + Integer.toString(ZenModeConfig.EventInfo.REPLY_ANY_EXCEPT_NO), + Integer.toString(ZenModeConfig.EventInfo.REPLY_YES_OR_MAYBE), + Integer.toString(ZenModeConfig.EventInfo.REPLY_YES), + }); + mReply.setOnPreferenceChangeListener(mReplyChangeListener); + + // Parse the zen mode's condition to update our EventInfo object. + mEvent = ZenModeConfig.tryParseEventConditionId(zenMode.getRule().getConditionId()); + if (mEvent != null) { + reloadCalendar(); + updatePrefValues(); + } + } + + private void reloadCalendar() { + List calendars = getCalendars(mContext); + ArrayList entries = new ArrayList<>(); + ArrayList values = new ArrayList<>(); + entries.add(mContext.getString(R.string.zen_mode_event_rule_calendar_any)); + values.add(key(0, null, "")); + final String eventCalendar = mEvent != null ? mEvent.calName : null; + for (CalendarInfo calendar : calendars) { + entries.add(calendar.name); + values.add(key(calendar)); + if (eventCalendar != null && (mEvent.calendarId == null + && eventCalendar.equals(calendar.name))) { + mEvent.calendarId = calendar.calendarId; + } + } + + CharSequence[] entriesArr = entries.toArray(new CharSequence[entries.size()]); + CharSequence[] valuesArr = values.toArray(new CharSequence[values.size()]); + if (!Arrays.equals(mCalendar.getEntries(), entriesArr)) { + mCalendar.setEntries(entriesArr); + } + + if (!Arrays.equals(mCalendar.getEntryValues(), valuesArr)) { + mCalendar.setEntryValues(valuesArr); + } + } + + @VisibleForTesting + protected Function updateEventMode(ZenModeConfig.EventInfo event) { + return (zenMode) -> { + zenMode.getRule().setConditionId(ZenModeConfig.toEventConditionId(event)); + if (Flags.modesApi() && Flags.modesUi()) { + zenMode.getRule().setTriggerDescription( + SystemZenRules.getTriggerDescriptionForScheduleEvent(mContext, event)); + } + return zenMode; + }; + } + + Preference.OnPreferenceChangeListener mCalendarChangeListener = + new Preference.OnPreferenceChangeListener() { + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + final String calendarKey = (String) newValue; + if (calendarKey.equals(key(mEvent))) return false; + String[] key = calendarKey.split(":", 3); + mEvent.userId = Integer.parseInt(key[0]); + mEvent.calendarId = key[1].equals("") ? null : Long.parseLong(key[1]); + mEvent.calName = key[2].equals("") ? null : key[2]; + saveMode(updateEventMode(mEvent)); + return true; + } + }; + + Preference.OnPreferenceChangeListener mReplyChangeListener = + new Preference.OnPreferenceChangeListener() { + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + final int reply = Integer.parseInt((String) newValue); + if (reply == mEvent.reply) return false; + mEvent.reply = reply; + saveMode(updateEventMode(mEvent)); + return true; + } + }; + + private void updatePrefValues() { + if (!Objects.equals(mCalendar.getValue(), key(mEvent))) { + mCalendar.setValue(key(mEvent)); + } + if (!Objects.equals(mReply.getValue(), Integer.toString(mEvent.reply))) { + mReply.setValue(Integer.toString(mEvent.reply)); + } + } + + private List getCalendars(Context context) { + final List calendars = new ArrayList<>(); + for (UserHandle user : UserManager.get(context).getUserProfiles()) { + final Context userContext = getContextForUser(context, user); + if (userContext != null) { + addCalendars(userContext, calendars); + } + } + Collections.sort(calendars, CALENDAR_NAME); + return calendars; + } + + private static Context getContextForUser(Context context, UserHandle user) { + try { + return context.createPackageContextAsUser(context.getPackageName(), 0, user); + } catch (PackageManager.NameNotFoundException e) { + return null; + } + } + + private void addCalendars(Context context, List outCalendars) { + final String[] projection = + {CalendarContract.Calendars._ID, CalendarContract.Calendars.CALENDAR_DISPLAY_NAME}; + final String selection = CalendarContract.Calendars.CALENDAR_ACCESS_LEVEL + " >= " + + CalendarContract.Calendars.CAL_ACCESS_CONTRIBUTOR + + " AND " + CalendarContract.Calendars.SYNC_EVENTS + " = 1"; + Cursor cursor = null; + try { + cursor = context.getContentResolver().query(CalendarContract.Calendars.CONTENT_URI, + projection, selection, null, null); + if (cursor == null) { + return; + } + while (cursor.moveToNext()) { + addCalendar(cursor.getLong(0), cursor.getString(1), + context.getUserId(), outCalendars); + } + } finally { + if (cursor != null) { + cursor.close(); + } + } + } + + @VisibleForTesting + protected static void addCalendar(long calendarId, String calName, int userId, + List outCalendars) { + final CalendarInfo ci = new CalendarInfo(); + ci.calendarId = calendarId; + ci.name = calName; + ci.userId = userId; + if (!outCalendars.contains(ci)) { + outCalendars.add(ci); + } + } + + private static String key(CalendarInfo calendar) { + return key(calendar.userId, calendar.calendarId, calendar.name); + } + + private static String key(ZenModeConfig.EventInfo event) { + return key(event.userId, event.calendarId, event.calName); + } + + @VisibleForTesting + protected static String key(int userId, Long calendarId, String displayName) { + return ZenModeConfig.EventInfo.resolveUserId(userId) + ":" + + (calendarId == null ? "" : calendarId) + + ":" + (displayName == null ? "" : displayName); + } + + @VisibleForTesting + protected static final Comparator CALENDAR_NAME = Comparator.comparing( + lhs -> lhs.name); + + public static class CalendarInfo { + public String name; + public int userId; + public Long calendarId; + + @Override + public boolean equals(Object o) { + if (!(o instanceof CalendarInfo)) return false; + if (o == this) return true; + final CalendarInfo other = (CalendarInfo) o; + return Objects.equals(other.name, name) + && Objects.equals(other.calendarId, calendarId); + } + + @Override + public int hashCode() { + return Objects.hash(name, calendarId); + } + } +} diff --git a/src/com/android/settings/notification/modes/ZenModeSetTriggerLinkPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeSetTriggerLinkPreferenceController.java new file mode 100644 index 00000000000..a3bc508cfbb --- /dev/null +++ b/src/com/android/settings/notification/modes/ZenModeSetTriggerLinkPreferenceController.java @@ -0,0 +1,96 @@ +/* + * 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. + */ +package com.android.settings.notification.modes; + +import static android.app.AutomaticZenRule.TYPE_SCHEDULE_CALENDAR; + +import static com.android.settings.notification.modes.ZenModeFragmentBase.MODE_ID; + +import android.content.Context; +import android.os.Bundle; + +import androidx.annotation.NonNull; +import androidx.annotation.VisibleForTesting; +import androidx.preference.Preference; +import androidx.preference.PreferenceCategory; + +import com.android.settings.R; +import com.android.settings.core.SubSettingLauncher; +import com.android.settingslib.PrimarySwitchPreference; + +/** + * Preference controller for the link + */ +public class ZenModeSetTriggerLinkPreferenceController extends AbstractZenModePreferenceController { + @VisibleForTesting + protected static final String AUTOMATIC_TRIGGER_PREF_KEY = "zen_automatic_trigger_settings"; + + public ZenModeSetTriggerLinkPreferenceController(Context context, String key, + ZenModesBackend backend) { + super(context, key, backend); + } + + @Override + public boolean isAvailable(@NonNull ZenMode zenMode) { + return !zenMode.isManualDnd(); + } + + @Override + public void updateState(Preference preference, @NonNull ZenMode zenMode) { + // 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). + Preference switchPref = ((PreferenceCategory) preference).findPreference( + AUTOMATIC_TRIGGER_PREF_KEY); + if (switchPref == null) { + return; + } + ((PrimarySwitchPreference) switchPref).setChecked(zenMode.getRule().isEnabled()); + switchPref.setOnPreferenceChangeListener(mSwitchChangeListener); + + Bundle bundle = new Bundle(); + bundle.putString(MODE_ID, zenMode.getId()); + + // TODO: b/341961712 - direct preference to app-owned intent if available + switch (zenMode.getRule().getType()) { + case TYPE_SCHEDULE_CALENDAR: + switchPref.setTitle(R.string.zen_mode_set_calendar_link); + switchPref.setSummary(zenMode.getRule().getTriggerDescription()); + switchPref.setIntent(new SubSettingLauncher(mContext) + .setDestination(ZenModeSetCalendarFragment.class.getName()) + // TODO: b/332937635 - set correct metrics category + .setSourceMetricsCategory(0) + .setArguments(bundle) + .toIntent()); + break; + default: + // TODO: b/342156843 - change this to allow adding a trigger condition for system + // rules that don't yet have a type selected + switchPref.setTitle("not implemented"); + } + } + + @VisibleForTesting + protected Preference.OnPreferenceChangeListener mSwitchChangeListener = (p, newValue) -> { + final boolean newEnabled = (Boolean) newValue; + return saveMode((zenMode) -> { + if (newEnabled != zenMode.getRule().isEnabled()) { + zenMode.getRule().setEnabled(newEnabled); + } + return zenMode; + }); + }; +} diff --git a/tests/robotests/src/com/android/settings/notification/modes/ZenModeSetCalendarPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/modes/ZenModeSetCalendarPreferenceControllerTest.java new file mode 100644 index 00000000000..6b24fa21832 --- /dev/null +++ b/tests/robotests/src/com/android/settings/notification/modes/ZenModeSetCalendarPreferenceControllerTest.java @@ -0,0 +1,151 @@ +/* + * 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. + */ + +package com.android.settings.notification.modes; + +import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT; +import static android.service.notification.ZenModeConfig.EventInfo.REPLY_YES; + +import static com.android.settings.notification.modes.ZenModeSetCalendarPreferenceController.CALENDAR_NAME; +import static com.android.settings.notification.modes.ZenModeSetCalendarPreferenceController.KEY_CALENDAR; +import static com.android.settings.notification.modes.ZenModeSetCalendarPreferenceController.KEY_REPLY; +import static com.android.settings.notification.modes.ZenModeSetCalendarPreferenceController.addCalendar; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.when; + +import android.app.AutomaticZenRule; +import android.app.Flags; +import android.content.Context; +import android.net.Uri; +import android.platform.test.annotations.EnableFlags; +import android.platform.test.flag.junit.SetFlagsRule; +import android.service.notification.ZenModeConfig; + +import androidx.preference.DropDownPreference; +import androidx.preference.PreferenceCategory; +import androidx.test.core.app.ApplicationProvider; + +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 java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +@RunWith(RobolectricTestRunner.class) +public class ZenModeSetCalendarPreferenceControllerTest { + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT); + + @Mock + private ZenModesBackend mBackend; + private Context mContext; + + @Mock + private PreferenceCategory mPrefCategory; + private DropDownPreference mCalendar, mReply; + + private ZenModeSetCalendarPreferenceController mPrefController; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mContext = ApplicationProvider.getApplicationContext(); + + mCalendar = new DropDownPreference(mContext); + mReply = new DropDownPreference(mContext); + when(mPrefCategory.findPreference(KEY_CALENDAR)).thenReturn(mCalendar); + when(mPrefCategory.findPreference(KEY_REPLY)).thenReturn(mReply); + + mPrefController = new ZenModeSetCalendarPreferenceController(mContext, + "zen_mode_event_category", mBackend); + } + + @Test + @EnableFlags({Flags.FLAG_MODES_API, Flags.FLAG_MODES_UI}) + public void updateEventMode_updatesConditionAndTriggerDescription() { + ZenMode mode = new ZenMode("id", + new AutomaticZenRule.Builder("name", Uri.parse("condition")).build(), + true); // is active + + // Explicitly update preference controller with mode info first, which will also call + // updateState() + mPrefController.updateZenMode(mPrefCategory, mode); + + ZenModeConfig.EventInfo eventInfo = new ZenModeConfig.EventInfo(); + eventInfo.calendarId = 1L; + eventInfo.calName = "My events"; + + // apply event mode updater to existing mode + ZenMode out = mPrefController.updateEventMode(eventInfo).apply(mode); + + assertThat(out.getRule().getConditionId()).isEqualTo( + ZenModeConfig.toEventConditionId(eventInfo)); + assertThat(out.getRule().getTriggerDescription()).isEqualTo("My events"); + } + + @Test + public void updateState_setsPreferenceValues() { + ZenModeConfig.EventInfo eventInfo = new ZenModeConfig.EventInfo(); + eventInfo.calendarId = 1L; + eventInfo.calName = "Definitely A Calendar"; + eventInfo.reply = REPLY_YES; + + ZenMode mode = new ZenMode("id", + new AutomaticZenRule.Builder("name", + ZenModeConfig.toEventConditionId(eventInfo)).build(), + true); // is active + mPrefController.updateZenMode(mPrefCategory, mode); + + // We should see mCalendar, mReply have their values set + assertThat(mCalendar.getValue()).isEqualTo( + ZenModeSetCalendarPreferenceController.key(eventInfo.userId, eventInfo.calendarId, + eventInfo.calName)); + assertThat(mReply.getValue()).isEqualTo(Integer.toString(eventInfo.reply)); + } + + @Test + public void testNoDuplicateCalendars() { + List calendarsList = new ArrayList<>(); + addCalendar(1234, "calName", 1, calendarsList); + addCalendar(1234, "calName", 2, calendarsList); + addCalendar(1234, "calName", 3, calendarsList); + assertThat(calendarsList).hasSize(1); + } + + @Test + public void testCalendarInfoSortByName() { + List calendarsList = new ArrayList<>(); + addCalendar(123, "zyx", 1, calendarsList); + addCalendar(456, "wvu", 2, calendarsList); + addCalendar(789, "abc", 3, calendarsList); + Collections.sort(calendarsList, CALENDAR_NAME); + + List sortedList = new ArrayList<>(); + addCalendar(789, "abc", 3, sortedList); + addCalendar(456, "wvu", 2, sortedList); + addCalendar(123, "zyx", 1, sortedList); + + assertThat(calendarsList).containsExactlyElementsIn(sortedList).inOrder(); + } +} diff --git a/tests/robotests/src/com/android/settings/notification/modes/ZenModeSetTriggerLinkPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/modes/ZenModeSetTriggerLinkPreferenceControllerTest.java new file mode 100644 index 00000000000..7dcec1cfeed --- /dev/null +++ b/tests/robotests/src/com/android/settings/notification/modes/ZenModeSetTriggerLinkPreferenceControllerTest.java @@ -0,0 +1,170 @@ +/* + * 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. + */ + +package com.android.settings.notification.modes; + +import static android.app.AutomaticZenRule.TYPE_SCHEDULE_CALENDAR; +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.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.AutomaticZenRule; +import android.app.Flags; +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.platform.test.annotations.EnableFlags; +import android.platform.test.flag.junit.SetFlagsRule; +import android.service.notification.ZenModeConfig; +import android.service.notification.ZenPolicy; + +import androidx.preference.PreferenceCategory; +import androidx.test.core.app.ApplicationProvider; + +import com.android.settings.R; +import com.android.settings.SettingsActivity; +import com.android.settingslib.PrimarySwitchPreference; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RobolectricTestRunner; + +@RunWith(RobolectricTestRunner.class) +public class ZenModeSetTriggerLinkPreferenceControllerTest { + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT); + + @Mock + private ZenModesBackend mBackend; + private Context mContext; + + @Mock + private PreferenceCategory mPrefCategory; + @Mock + private PrimarySwitchPreference mPreference; + private ZenModeSetTriggerLinkPreferenceController mPrefController; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mContext = ApplicationProvider.getApplicationContext(); + + mPrefController = new ZenModeSetTriggerLinkPreferenceController(mContext, + "zen_automatic_trigger_category", mBackend); + when(mPrefCategory.findPreference(AUTOMATIC_TRIGGER_PREF_KEY)).thenReturn(mPreference); + } + + @Test + @EnableFlags(Flags.FLAG_MODES_UI) + public void testIsAvailable() { + // should not be available for manual DND + ZenMode manualMode = ZenMode.manualDndMode(new AutomaticZenRule.Builder("Do Not Disturb", + Uri.parse("manual")) + .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY) + .build(), true); + + mPrefController.updateZenMode(mPrefCategory, manualMode); + assertThat(mPrefController.isAvailable()).isFalse(); + + // should be available for other modes + ZenMode zenMode = new ZenMode("id", + new AutomaticZenRule.Builder("Driving", Uri.parse("drive")) + .setType(AutomaticZenRule.TYPE_DRIVING) + .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY) + .setZenPolicy(new ZenPolicy.Builder().allowAlarms(true).build()) + .setEnabled(false) + .build(), false); + mPrefController.updateZenMode(mPrefCategory, zenMode); + assertThat(mPrefController.isAvailable()).isTrue(); + } + + @Test + public void testUpdateState() { + ZenMode zenMode = new ZenMode("id", + new AutomaticZenRule.Builder("Driving", Uri.parse("drive")) + .setType(AutomaticZenRule.TYPE_DRIVING) + .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY) + .setZenPolicy(new ZenPolicy.Builder().allowAlarms(true).build()) + .setEnabled(false) + .build(), false); + + // Update preference controller with a zen mode that is not enabled + mPrefController.updateZenMode(mPrefCategory, zenMode); + verify(mPreference).setChecked(false); + + // Now with the rule enabled + zenMode.getRule().setEnabled(true); + mPrefController.updateZenMode(mPrefCategory, zenMode); + verify(mPreference).setChecked(true); + } + + @Test + public void testOnPreferenceChange() { + ZenMode zenMode = new ZenMode("id", + new AutomaticZenRule.Builder("Driving", Uri.parse("drive")) + .setType(AutomaticZenRule.TYPE_DRIVING) + .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY) + .setZenPolicy(new ZenPolicy.Builder().allowAlarms(true).build()) + .setEnabled(false) + .build(), false); + + // start with disabled rule + mPrefController.updateZenMode(mPrefCategory, zenMode); + + // then update the preference to be checked + mPrefController.mSwitchChangeListener.onPreferenceChange(mPreference, true); + + // verify the backend got asked to update the mode to be enabled + ArgumentCaptor captor = ArgumentCaptor.forClass(ZenMode.class); + verify(mBackend).updateMode(captor.capture()); + assertThat(captor.getValue().getRule().isEnabled()).isTrue(); + } + + @Test + public void testRuleLink_calendar() { + ZenModeConfig.EventInfo eventInfo = new ZenModeConfig.EventInfo(); + eventInfo.calendarId = 1L; + eventInfo.calName = "My events"; + ZenMode mode = new ZenMode("id", new AutomaticZenRule.Builder("name", + ZenModeConfig.toEventConditionId(eventInfo)) + .setType(TYPE_SCHEDULE_CALENDAR) + .setTriggerDescription("My events") + .build(), + true); // is active + mPrefController.updateZenMode(mPrefCategory, mode); + + verify(mPreference).setTitle(R.string.zen_mode_set_calendar_link); + verify(mPreference).setSummary(mode.getRule().getTriggerDescription()); + + ArgumentCaptor captor = ArgumentCaptor.forClass(Intent.class); + verify(mPreference).setIntent(captor.capture()); + // Destination as written into the intent by SubSettingLauncher + assertThat( + captor.getValue().getStringExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT)).isEqualTo( + ZenModeSetCalendarFragment.class.getName()); + } +}