Merge "Add mode: Choose name and icon for new custom modes" into main

This commit is contained in:
Matías Hernández
2024-07-09 02:20:07 +00:00
committed by Android (Google) Code Review
18 changed files with 1154 additions and 69 deletions

View File

@@ -54,6 +54,16 @@ class TestModeBuilder {
mConfigZenRule.pkg = "some_package";
}
TestModeBuilder(ZenMode previous) {
mId = previous.getId();
mRule = previous.getRule();
mConfigZenRule = new ZenModeConfig.ZenRule();
mConfigZenRule.enabled = previous.getRule().isEnabled();
mConfigZenRule.pkg = previous.getRule().getPackageName();
setActive(previous.isActive());
}
TestModeBuilder setId(String id) {
mId = id;
return this;

View File

@@ -0,0 +1,93 @@
/*
* 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 com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import android.content.Context;
import android.widget.Button;
import androidx.preference.PreferenceManager;
import androidx.preference.PreferenceScreen;
import com.android.settings.R;
import com.android.settingslib.notification.modes.ZenMode;
import com.android.settingslib.widget.LayoutPreference;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
@RunWith(RobolectricTestRunner.class)
public class ZenModeEditDonePreferenceControllerTest {
private ZenModeEditDonePreferenceController mController;
private LayoutPreference mPreference;
private Button mButton;
@Mock private Runnable mConfirmSave;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
Context context = RuntimeEnvironment.application;
PreferenceManager preferenceManager = new PreferenceManager(context);
PreferenceScreen preferenceScreen = preferenceManager.inflateFromResource(context,
R.xml.modes_edit_name_icon, null);
mPreference = preferenceScreen.findPreference("done");
mController = new ZenModeEditDonePreferenceController(context, "done", mConfirmSave);
mController.displayPreference(preferenceScreen);
mButton = mPreference.findViewById(R.id.done);
assertThat(mButton).isNotNull();
}
@Test
public void updateState_nameNonEmpty_buttonEnabled() {
ZenMode mode = new TestModeBuilder().setName("Such a nice name").build();
mController.updateState(mPreference, mode);
assertThat(mButton.isEnabled()).isTrue();
verifyNoMoreInteractions(mConfirmSave);
}
@Test
public void updateState_nameEmpty_buttonDisabled() {
ZenMode aModeHasNoName = new TestModeBuilder().setName("").build();
mController.updateState(mPreference, aModeHasNoName);
assertThat(mButton.isEnabled()).isFalse();
verifyNoMoreInteractions(mConfirmSave);
}
@Test
public void onButtonClick_callsConfirmSave() {
mButton.performClick();
verify(mConfirmSave).run();
}
}

View File

@@ -0,0 +1,212 @@
/*
* 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.provider.Settings.EXTRA_AUTOMATIC_ZEN_RULE_ID;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.app.Activity;
import android.os.Bundle;
import androidx.annotation.Nullable;
import androidx.fragment.app.testing.FragmentScenario;
import androidx.lifecycle.Lifecycle;
import com.android.internal.R;
import com.android.settingslib.notification.modes.ZenMode;
import com.android.settingslib.notification.modes.ZenModesBackend;
import org.junit.After;
import org.junit.Before;
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 ZenModeEditNameIconFragmentTest {
private static final ZenMode MODE = new TestModeBuilder().setId("id").setName("Mode").build();
private Activity mActivity;
private ZenModeEditNameIconFragment mFragment;
private FragmentScenario<ZenModeEditNameIconFragment> mScenario;
@Mock
private ZenModesBackend mBackend;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
// Note: Each test should call startFragment() to set mScenario, mFragment and mActivity.
}
@After
public void tearDown() {
if (mScenario != null) {
mScenario.close();
}
}
@Test
public void onCreate_loadsMode() {
when(mBackend.getMode(MODE.getId())).thenReturn(MODE);
startFragment(MODE.getId());
assertThat(mFragment.getZenMode()).isEqualTo(MODE);
assertThat(mActivity.isFinishing()).isFalse();
}
@Test
public void onCreate_noModeId_exits() {
when(mBackend.getMode(any())).thenReturn(MODE);
startFragment(null);
assertThat(mActivity.isFinishing()).isTrue();
verifyNoMoreInteractions(mBackend);
}
@Test
public void onCreate_missingMode_exits() {
when(mBackend.getMode(any())).thenReturn(null);
startFragment(MODE.getId());
assertThat(mActivity.isFinishing()).isTrue();
verify(mBackend).getMode(MODE.getId());
}
@Test
public void saveMode_appliesChangesAndFinishes() {
when(mBackend.getMode(MODE.getId())).thenReturn(MODE);
startFragment(MODE.getId());
mFragment.setModeName("A new name");
mFragment.setModeIcon(R.drawable.ic_zen_mode_type_theater);
mFragment.setModeName("A newer name");
mFragment.saveMode();
ArgumentCaptor<ZenMode> captor = ArgumentCaptor.forClass(ZenMode.class);
verify(mBackend).updateMode(captor.capture());
ZenMode savedMode = captor.getValue();
assertThat(savedMode.getName()).isEqualTo("A newer name");
assertThat(savedMode.getRule().getIconResId()).isEqualTo(
R.drawable.ic_zen_mode_type_theater);
assertThat(mActivity.isFinishing()).isTrue();
}
@Test
public void saveMode_appliesOnyNameAndIconChanges() {
when(mBackend.getMode(MODE.getId())).thenReturn(MODE);
startFragment(MODE.getId());
mFragment.setModeName("A new name");
mFragment.setModeIcon(R.drawable.ic_zen_mode_type_theater);
// Before the user saves, something else about the mode was modified by someone else.
ZenMode newerMode = new TestModeBuilder(MODE).setTriggerDescription("Whenever").build();
when(mBackend.getMode(MODE.getId())).thenReturn(newerMode);
mFragment.saveMode();
// Verify that saving only wrote the mode name, and didn't accidentally stomp over
// unrelated fields of the mode.
ArgumentCaptor<ZenMode> captor = ArgumentCaptor.forClass(ZenMode.class);
verify(mBackend).updateMode(captor.capture());
ZenMode savedMode = captor.getValue();
assertThat(savedMode.getName()).isEqualTo("A new name");
assertThat(savedMode.getTriggerDescription()).isEqualTo("Whenever");
}
@Test
public void saveMode_forModeThatDisappeared_ignoresSave() {
when(mBackend.getMode(MODE.getId())).thenReturn(MODE);
startFragment(MODE.getId());
mFragment.setModeName("A new name");
mFragment.setModeIcon(R.drawable.ic_zen_mode_type_theater);
// Before the user saves, the mode was removed by someone else.
when(mBackend.getMode(MODE.getId())).thenReturn(null);
mFragment.saveMode();
verify(mBackend, never()).updateMode(any());
assertThat(mActivity.isFinishing()).isTrue();
}
@Test
public void setModeFields_withoutSaveMode_doesNotSaveChanges() {
when(mBackend.getMode(MODE.getId())).thenReturn(MODE);
startFragment(MODE.getId());
mFragment.setModeName("Not a good idea");
mFragment.setModeIcon(R.drawable.emergency_icon);
mActivity.finish();
verify(mBackend, never()).updateMode(any());
}
@Test
public void onCreate_whenRecreating_preservesEdits() {
when(mBackend.getMode(MODE.getId())).thenReturn(MODE);
startFragment(MODE.getId());
mFragment.setModeName("A better name");
mScenario.recreate().onFragment(newFragment -> {
assertThat(newFragment).isNotSameInstanceAs(mFragment);
newFragment.setBackend(mBackend);
mActivity = newFragment.getActivity();
mFragment = newFragment;
});
mFragment.saveMode();
ArgumentCaptor<ZenMode> captor = ArgumentCaptor.forClass(ZenMode.class);
verify(mBackend).updateMode(captor.capture());
ZenMode savedMode = captor.getValue();
assertThat(savedMode.getName()).isEqualTo("A better name");
assertThat(mActivity.isFinishing()).isTrue();
}
private void startFragment(@Nullable String modeId) {
Bundle fragmentArgs = null;
if (modeId != null) {
fragmentArgs = new Bundle();
fragmentArgs.putString(EXTRA_AUTOMATIC_ZEN_RULE_ID, modeId);
}
mScenario = FragmentScenario.launch(ZenModeEditNameIconFragment.class, fragmentArgs, 0,
Lifecycle.State.INITIALIZED);
mScenario.onFragment(fragment -> {
fragment.setBackend(mBackend); // Before onCreate().
mFragment = fragment;
});
mScenario.moveToState(Lifecycle.State.RESUMED).onFragment(fragment -> {
mActivity = fragment.requireActivity();
});
}
}

View File

@@ -0,0 +1,90 @@
/*
* 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 com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import android.content.Context;
import android.widget.EditText;
import androidx.preference.PreferenceManager;
import androidx.preference.PreferenceScreen;
import com.android.settings.R;
import com.android.settingslib.notification.modes.ZenMode;
import com.android.settingslib.widget.LayoutPreference;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import java.util.function.Consumer;
@RunWith(RobolectricTestRunner.class)
public class ZenModeEditNamePreferenceControllerTest {
private ZenModeEditNamePreferenceController mController;
private LayoutPreference mPreference;
private EditText mEditText;
@Mock private Consumer<String> mNameSetter;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
Context context = RuntimeEnvironment.application;
PreferenceManager preferenceManager = new PreferenceManager(context);
PreferenceScreen preferenceScreen = preferenceManager.inflateFromResource(context,
R.xml.modes_edit_name_icon, null);
mPreference = preferenceScreen.findPreference("name");
mController = new ZenModeEditNamePreferenceController(context, "name", mNameSetter);
mController.displayPreference(preferenceScreen);
mEditText = mPreference.findViewById(android.R.id.edit);
assertThat(mEditText).isNotNull();
}
@Test
public void updateState_showsName() {
ZenMode mode = new TestModeBuilder().setName("A fancy name").build();
mController.updateState(mPreference, mode);
assertThat(mEditText.getText().toString()).isEqualTo("A fancy name");
verifyNoMoreInteractions(mNameSetter);
}
@Test
public void onEditText_callsNameSetter() {
ZenMode mode = new TestModeBuilder().setName("A fancy name").build();
mController.updateState(mPreference, mode);
EditText editText = mPreference.findViewById(android.R.id.edit);
editText.setText("An even fancier name");
verify(mNameSetter).accept("An even fancier name");
verifyNoMoreInteractions(mNameSetter);
}
}

View File

@@ -0,0 +1,147 @@
/*
* 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.provider.Settings.EXTRA_AUTOMATIC_ZEN_RULE_ID;
import static com.android.settings.SettingsActivity.EXTRA_SHOW_FRAGMENT;
import static com.android.settings.SettingsActivity.EXTRA_SHOW_FRAGMENT_ARGUMENTS;
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.verify;
import static org.mockito.Mockito.when;
import static org.robolectric.Shadows.shadowOf;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import androidx.fragment.app.testing.EmptyFragmentActivity;
import androidx.fragment.app.testing.FragmentScenario;
import androidx.lifecycle.Lifecycle;
import androidx.test.ext.junit.rules.ActivityScenarioRule;
import com.android.internal.R;
import com.android.settingslib.notification.modes.ZenMode;
import com.android.settingslib.notification.modes.ZenModesBackend;
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.mockito.stubbing.Answer;
import org.robolectric.RobolectricTestRunner;
@RunWith(RobolectricTestRunner.class)
public class ZenModeNewCustomFragmentTest {
@Rule
public ActivityScenarioRule<EmptyFragmentActivity> mActivityScenario =
new ActivityScenarioRule<>(EmptyFragmentActivity.class);
private Activity mActivity;
private ZenModeNewCustomFragment mFragment;
@Mock
private ZenModesBackend mBackend;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mFragment = new ZenModeNewCustomFragment();
mFragment.setBackend(mBackend); // before onAttach()
mActivityScenario.getScenario().onActivity(activity -> {
mActivity = activity;
activity.getSupportFragmentManager().beginTransaction()
.add(mFragment, "tag").commitNow();
});
}
@Test
public void saveMode_addsCustomManualMode() {
mFragment.setModeName("The first name");
mFragment.setModeIcon(R.drawable.ic_zen_mode_type_theater);
mFragment.setModeName("Actually no, this name");
mFragment.saveMode();
verify(mBackend).addCustomManualMode("Actually no, this name",
R.drawable.ic_zen_mode_type_theater);
}
@Test
public void saveMode_withoutEdits_addsModeWithDefaultValues() {
mFragment.saveMode();
verify(mBackend).addCustomManualMode("Custom mode", 0);
}
@Test
public void saveMode_redirectsToModeView() {
when(mBackend.addCustomManualMode(any(), anyInt())).then(
(Answer<ZenMode>) invocationOnMock -> new TestModeBuilder()
.setId("Id of a mode named " + invocationOnMock.getArgument(0))
.setName(invocationOnMock.getArgument(0))
.setIconResId(invocationOnMock.getArgument(1))
.build());
mFragment.setModeName("something");
mFragment.setModeIcon(R.drawable.ic_zen_mode_type_immersive);
mFragment.saveMode();
Intent nextIntent = shadowOf(mActivity).getNextStartedActivity();
assertThat(nextIntent.getStringExtra(EXTRA_SHOW_FRAGMENT))
.isEqualTo(ZenModeFragment.class.getName());
Bundle fragmentArgs = nextIntent.getBundleExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS);
assertThat(fragmentArgs).isNotNull();
assertThat(fragmentArgs.getString(EXTRA_AUTOMATIC_ZEN_RULE_ID)).isEqualTo(
"Id of a mode named something");
}
@Test
public void onCreate_whenRecreating_preservesEdits() {
FragmentScenario<ZenModeNewCustomFragment> scenario =
FragmentScenario.launch(ZenModeNewCustomFragment.class, /* bundle= */ null, 0,
Lifecycle.State.INITIALIZED);
scenario.onFragment(first -> {
first.setBackend(mBackend);
mFragment = first;
});
scenario.moveToState(Lifecycle.State.RESUMED);
// Perform some edits in the first fragment.
mFragment.setModeName("Don't forget me!");
mFragment.setModeIcon(R.drawable.ic_zen_mode_type_immersive);
// Destroy the first fragment and creates a new one (which should restore state).
scenario.recreate().onFragment(second -> {
assertThat(second).isNotSameInstanceAs(mFragment);
second.setBackend(mBackend);
mFragment = second;
});
mFragment.saveMode();
verify(mBackend).addCustomManualMode("Don't forget me!",
R.drawable.ic_zen_mode_type_immersive);
scenario.close();
}
}

View File

@@ -57,6 +57,9 @@ public class ZenModesListFragmentTest {
private static final ModeType APP_PROVIDED_MODE_TYPE = new ModeType("Mode", new ColorDrawable(),
"Details", new Intent().setComponent(new ComponentName("pkg", "configActivity")));
private static final ModeType CUSTOM_MANUAL_TYPE = new ModeType("Custom", new ColorDrawable(),
null, null); // null creationActivityIntent means custom_manual.
private static final ImmutableList<ZenMode> EXISTING_MODES = ImmutableList.of(
new TestModeBuilder().setId("A").build(),
new TestModeBuilder().setId("B").build(),
@@ -94,6 +97,16 @@ public class ZenModesListFragmentTest {
assertThat(intent.intent).isEqualTo(APP_PROVIDED_MODE_TYPE.creationActivityIntent());
}
@Test
public void onChosenModeTypeForAdd_customManualMode_startsNameAndIconPicker() {
mFragment.onChosenModeTypeForAdd(CUSTOM_MANUAL_TYPE);
Intent nextIntent = shadowOf(mActivity).getNextStartedActivity();
assertThat(nextIntent).isNotNull();
assertThat(nextIntent.getStringExtra(EXTRA_SHOW_FRAGMENT))
.isEqualTo(ZenModeNewCustomFragment.class.getName());
}
@Test
public void onActivityResult_modeWasCreated_opensIt() {
when(mBackend.getModes()).thenReturn(EXISTING_MODES);