Merge "Add button for activating modes manually" into main

This commit is contained in:
Julia Reynolds
2024-05-28 20:08:25 +00:00
committed by Android (Google) Code Review
9 changed files with 318 additions and 7 deletions

View File

@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
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.
-->
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<Button
android:id="@+id/activate_mode"
style="@style/ActionPrimaryButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"/>
</FrameLayout>

View File

@@ -22,6 +22,11 @@
android:key="header"
android:layout="@layout/settings_entity_header" />
<com.android.settingslib.widget.LayoutPreference
android:key="activate"
android:selectable="false"
android:layout="@layout/modes_activation_button"/>
<PreferenceCategory
android:title="@string/mode_interruption_filter_title"
android:key="modes_filters">

View File

@@ -41,7 +41,7 @@ abstract class AbstractZenModePreferenceController extends AbstractPreferenceCon
@Nullable
protected ZenModesBackend mBackend;
@Nullable // only until updateZenMode() is called
@Nullable // only until setZenMode() is called
private ZenMode mZenMode;
@NonNull
@@ -65,7 +65,22 @@ abstract class AbstractZenModePreferenceController extends AbstractPreferenceCon
@Override
public boolean isAvailable() {
return Flags.modesUi();
if (mZenMode != null) {
return Flags.modesUi() && isAvailable(mZenMode);
} else {
return Flags.modesUi();
}
}
public boolean isAvailable(@NonNull ZenMode zenMode) {
return true;
}
// Called by parent Fragment onAttach, for any methods (such as isAvailable()) that need
// zen mode info before onStart. Most callers should use updateZenMode instead, which will
// do any further necessary propagation.
protected final void setZenMode(@NonNull ZenMode zenMode) {
mZenMode = zenMode;
}
// Called by the parent Fragment onStart, which means it will happen before resume.

View File

@@ -0,0 +1,59 @@
/*
* 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.annotation.NonNull;
import android.content.Context;
import android.widget.Button;
import androidx.preference.Preference;
import com.android.settings.R;
import com.android.settingslib.widget.LayoutPreference;
public class ZenModeButtonPreferenceController extends AbstractZenModePreferenceController {
private Button mZenButton;
public ZenModeButtonPreferenceController(Context context, String key, ZenModesBackend backend) {
super(context, key, backend);
}
@Override
public boolean isAvailable(ZenMode zenMode) {
return zenMode.getRule().isManualInvocationAllowed() && zenMode.getRule().isEnabled();
}
@Override
public void updateState(Preference preference, @NonNull ZenMode zenMode) {
if (mZenButton == null) {
mZenButton = ((LayoutPreference) preference).findViewById(R.id.activate_mode);
}
mZenButton.setOnClickListener(v -> {
if (zenMode.isActive()) {
mBackend.deactivateMode(zenMode);
} else {
mBackend.activateMode(zenMode, null);
}
});
if (zenMode.isActive()) {
mZenButton.setText(R.string.zen_mode_button_turn_off);
} else {
mZenButton.setText(R.string.zen_mode_button_turn_on);
}
}
}

View File

@@ -37,6 +37,7 @@ public class ZenModeFragment extends ZenModeFragmentBase {
protected List<AbstractPreferenceController> createPreferenceControllers(Context context) {
List<AbstractPreferenceController> prefControllers = new ArrayList<>();
prefControllers.add(new ZenModeHeaderController(context, "header", this, mBackend));
prefControllers.add(new ZenModeButtonPreferenceController(context, "activate", mBackend));
prefControllers.add(new ZenModePeopleLinkPreferenceController(
context, "zen_mode_people", mBackend));
prefControllers.add(new ZenModeOtherLinkPreferenceController(

View File

@@ -53,10 +53,25 @@ abstract class ZenModeFragmentBase extends ZenModesFragmentBase {
if (!reloadMode(id)) {
Log.d(TAG, "Mode id " + id + " not found");
toastAndFinish();
return;
}
} else {
Log.d(TAG, "Mode id required to set mode config settings");
toastAndFinish();
return;
}
if (mZenMode != null) {
// Propagate mode info through to controllers.
for (List<AbstractPreferenceController> list : getPreferenceControllers()) {
try {
for (AbstractPreferenceController controller : list) {
// mZenMode guaranteed non-null from reloadMode() above
((AbstractZenModePreferenceController) controller).setZenMode(mZenMode);
}
} catch (ClassCastException e) {
// ignore controllers that aren't AbstractZenModePreferenceController
}
}
}
}

View File

@@ -51,6 +51,7 @@ class ZenModeHeaderController extends AbstractZenModePreferenceController {
if (mFragment == null) {
return;
}
preference.setSelectable(false);
if (mHeaderController == null) {
final LayoutPreference pref = (LayoutPreference) preference;

View File

@@ -60,17 +60,15 @@ abstract class ZenModesFragmentBase extends RestrictedDashboardFragment {
mContext = context;
mBackend = ZenModesBackend.getInstance(context);
super.onAttach(context);
mSettingsObserver.register();
}
@Override
public void onStart() {
super.onStart();
updateZenModeState();
mSettingsObserver.register();
if (isUiRestricted()) {
if (isUiRestrictedByOnlyAdmin()) {
getPreferenceScreen().removeAll();
return;
} else {
finish();
}
@@ -84,8 +82,8 @@ abstract class ZenModesFragmentBase extends RestrictedDashboardFragment {
}
@Override
public void onStop() {
super.onStop();
public void onDetach() {
super.onDetach();
mSettingsObserver.unregister();
}

View File

@@ -0,0 +1,187 @@
/*
* 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.NotificationManager.INTERRUPTION_FILTER_PRIORITY;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.mock;
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.net.Uri;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
import android.service.notification.ZenPolicy;
import android.widget.Button;
import com.android.settingslib.widget.LayoutPreference;
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 org.robolectric.RuntimeEnvironment;
@EnableFlags(Flags.FLAG_MODES_UI)
@RunWith(RobolectricTestRunner.class)
public final class ZenModeButtonPreferenceControllerTest {
private ZenModeButtonPreferenceController mController;
@Rule
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
private Context mContext;
@Mock
private ZenModesBackend mBackend;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
mContext = RuntimeEnvironment.application;
mController = new ZenModeButtonPreferenceController(
mContext, "something", mBackend);
}
@Test
public void isAvailable_notIfAppOptsOut() {
ZenMode zenMode = new ZenMode("id",
new AutomaticZenRule.Builder("Driving", Uri.parse("drive"))
.setType(AutomaticZenRule.TYPE_DRIVING)
.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
.setZenPolicy(new ZenPolicy.Builder().allowAllSounds().build())
.setManualInvocationAllowed(false)
.setEnabled(true)
.build(), false);
mController.setZenMode(zenMode);
assertThat(mController.isAvailable()).isFalse();
}
@Test
public void isAvailable_notIfModeDisabled() {
ZenMode zenMode = new ZenMode("id",
new AutomaticZenRule.Builder("Driving", Uri.parse("drive"))
.setType(AutomaticZenRule.TYPE_DRIVING)
.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
.setZenPolicy(new ZenPolicy.Builder().allowAllSounds().build())
.setManualInvocationAllowed(true)
.setEnabled(false)
.build(), false);
mController.setZenMode(zenMode);
assertThat(mController.isAvailable()).isFalse();
}
@Test
public void isAvailable_appOptedIn_modeEnabled() {
ZenMode zenMode = new ZenMode("id",
new AutomaticZenRule.Builder("Driving", Uri.parse("drive"))
.setType(AutomaticZenRule.TYPE_DRIVING)
.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
.setZenPolicy(new ZenPolicy.Builder().allowAllSounds().build())
.setManualInvocationAllowed(true)
.setEnabled(true)
.build(), false);
mController.setZenMode(zenMode);
assertThat(mController.isAvailable()).isTrue();
}
@Test
public void updateState_ruleActive() {
Button button = new Button(mContext);
LayoutPreference pref = mock(LayoutPreference.class);
when(pref.findViewById(anyInt())).thenReturn(button);
ZenMode zenMode = new ZenMode("id",
new AutomaticZenRule.Builder("Driving", Uri.parse("drive"))
.setType(AutomaticZenRule.TYPE_DRIVING)
.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
.setZenPolicy(new ZenPolicy.Builder().allowAllSounds().build())
.setManualInvocationAllowed(true)
.setEnabled(true)
.build(), true);
mController.updateZenMode(pref, zenMode);
assertThat(button.getText().toString()).contains("off");
assertThat(button.hasOnClickListeners()).isTrue();
}
@Test
public void updateState_ruleNotActive() {
Button button = new Button(mContext);
LayoutPreference pref = mock(LayoutPreference.class);
when(pref.findViewById(anyInt())).thenReturn(button);
ZenMode zenMode = new ZenMode("id",
new AutomaticZenRule.Builder("Driving", Uri.parse("drive"))
.setType(AutomaticZenRule.TYPE_DRIVING)
.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
.setZenPolicy(new ZenPolicy.Builder().allowAllSounds().build())
.setManualInvocationAllowed(true)
.setEnabled(true)
.build(), false);
mController.updateZenMode(pref, zenMode);
assertThat(button.getText().toString()).contains("on");
assertThat(button.hasOnClickListeners()).isTrue();
}
@Test
public void updateStateThenClick_ruleActive() {
Button button = new Button(mContext);
LayoutPreference pref = mock(LayoutPreference.class);
when(pref.findViewById(anyInt())).thenReturn(button);
ZenMode zenMode = new ZenMode("id",
new AutomaticZenRule.Builder("Driving", Uri.parse("drive"))
.setType(AutomaticZenRule.TYPE_DRIVING)
.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
.setZenPolicy(new ZenPolicy.Builder().allowAllSounds().build())
.setManualInvocationAllowed(true)
.setEnabled(true)
.build(), true);
mController.updateZenMode(pref, zenMode);
button.callOnClick();
verify(mBackend).deactivateMode(zenMode);
}
@Test
public void updateStateThenClick_ruleNotActive() {
Button button = new Button(mContext);
LayoutPreference pref = mock(LayoutPreference.class);
when(pref.findViewById(anyInt())).thenReturn(button);
ZenMode zenMode = new ZenMode("id",
new AutomaticZenRule.Builder("Driving", Uri.parse("drive"))
.setType(AutomaticZenRule.TYPE_DRIVING)
.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
.setZenPolicy(new ZenPolicy.Builder().allowAllSounds().build())
.setManualInvocationAllowed(true)
.setEnabled(true)
.build(), false);
mController.updateZenMode(pref, zenMode);
button.callOnClick();
verify(mBackend).activateMode(zenMode, null);
}
}