Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit fdb948df authored by Yuri Lin's avatar Yuri Lin
Browse files

Add mode names to search results dynamically.

The resulting search result takes the user to the modes aggregator page.

Flag: android.app.modes_ui
Bug: 332937523
Test: ZenModesListPreferenceControllerTest, manual with modes_ui on and off
Change-Id: I7372307cb995667f6f7234530a3140f5295ab06a
parent 1b1c159d
Loading
Loading
Loading
Loading
+6 −0
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package com.android.settings.notification.modes;

import android.app.AutomaticZenRule;
import android.app.Flags;
import android.content.Context;

import androidx.annotation.NonNull;
@@ -55,6 +56,11 @@ abstract class AbstractZenModePreferenceController extends AbstractPreferenceCon
        return mKey;
    }

    @Override
    public boolean isAvailable() {
        return Flags.modesUi();
    }

    // Called by the parent Fragment onStart, which means it will happen before resume.
    public void updateZenMode(@NonNull Preference preference, @NonNull ZenMode zenMode) {
        mZenMode = zenMode;
+33 −5
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@ package com.android.settings.notification.modes;
import android.app.AutomaticZenRule;
import android.app.Flags;
import android.content.Context;
import android.content.res.Resources;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -25,9 +26,12 @@ import androidx.fragment.app.Fragment;
import androidx.preference.Preference;
import androidx.preference.PreferenceCategory;

import com.android.settingslib.core.AbstractPreferenceController;
import com.android.settings.R;
import com.android.settings.core.BasePreferenceController;
import com.android.settingslib.search.SearchIndexableRaw;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
@@ -35,7 +39,7 @@ import java.util.Map;
 * containing links to each individual mode. This is a central controller that populates and updates
 * all the preferences that then lead to a mode configuration page.
 */
public class ZenModesListPreferenceController extends AbstractPreferenceController {
public class ZenModesListPreferenceController extends BasePreferenceController {
    protected static final String KEY = "zen_modes_list";

    @Nullable
@@ -44,7 +48,7 @@ public class ZenModesListPreferenceController extends AbstractPreferenceControll

    public ZenModesListPreferenceController(Context context, @Nullable Fragment parent,
            @NonNull ZenModesBackend backend) {
        super(context);
        super(context, KEY);
        mParent = parent;
        mBackend = backend;
    }
@@ -55,8 +59,9 @@ public class ZenModesListPreferenceController extends AbstractPreferenceControll
    }

    @Override
    public boolean isAvailable() {
        return Flags.modesUi();
    @AvailabilityStatus
    public int getAvailabilityStatus() {
        return Flags.modesUi() ? AVAILABLE_UNSEARCHABLE : UNSUPPORTED_ON_DEVICE;
    }

    @Override
@@ -95,4 +100,27 @@ public class ZenModesListPreferenceController extends AbstractPreferenceControll
            category.removePreferenceRecursively(key);
        }
    }

    // Provide search data for the modes, which will allow users to reach the modes page if they
    // search for a mode name.
    @Override
    public void updateDynamicRawDataToIndex(List<SearchIndexableRaw> rawData) {
        // Don't add anything if flag is off. In theory this preference controller itself shouldn't
        // be available in that case, but we check anyway to be sure.
        if (!Flags.modesUi()) {
            return;
        }
        if (mBackend == null) {
            return;
        }

        final Resources res = mContext.getResources();
        for (ZenMode mode : mBackend.getModes()) {
            SearchIndexableRaw data = new SearchIndexableRaw(mContext);
            data.key = mode.getId();
            data.title = mode.getRule().getName();
            data.screenTitle = res.getString(R.string.zen_modes_list_title);
            rawData.add(data);
        }
    }
}
+154 −0
Original line number Diff line number Diff line
/*
 * 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.Mockito.when;

import android.app.AutomaticZenRule;
import android.app.Flags;
import android.content.Context;
import android.net.Uri;
import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
import android.service.notification.ZenPolicy;

import com.android.settingslib.search.SearchIndexableRaw;

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;

import java.util.ArrayList;
import java.util.List;

@RunWith(RobolectricTestRunner.class)
public class ZenModesListPreferenceControllerTest {
    private static final String TEST_MODE_ID = "test_mode";
    private static final String TEST_MODE_NAME = "Test Mode";
    private static final ZenMode TEST_MODE = new ZenMode(
            TEST_MODE_ID,
            new AutomaticZenRule.Builder(TEST_MODE_NAME, Uri.parse("test_uri"))
                    .setType(AutomaticZenRule.TYPE_BEDTIME)
                    .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
                    .setZenPolicy(new ZenPolicy.Builder().allowAllSounds().build())
                    .build(),
            false);

    private static final ZenMode TEST_MANUAL_MODE = ZenMode.manualDndMode(
            new AutomaticZenRule.Builder("Do Not Disturb", Uri.EMPTY)
                    .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
                    .setZenPolicy(new ZenPolicy.Builder().allowAllSounds().build())
                    .build(),
            false);

    @Rule
    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(
            SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT);

    private Context mContext;

    @Mock
    private ZenModesBackend mBackend;

    private ZenModesListPreferenceController mPrefController;

    @Before
    public void setup() {
        MockitoAnnotations.initMocks(this);
        mContext = RuntimeEnvironment.application;

        mPrefController = new ZenModesListPreferenceController(mContext, null, mBackend);
    }

    @Test
    @DisableFlags(Flags.FLAG_MODES_UI)
    public void testModesUiOff_notAvailableAndNoSearchData() {
        // There exist modes
        when(mBackend.getModes()).thenReturn(List.of(TEST_MANUAL_MODE, TEST_MODE));

        assertThat(mPrefController.isAvailable()).isFalse();
        List<SearchIndexableRaw> data = new ArrayList<>();
        mPrefController.updateDynamicRawDataToIndex(data);
        assertThat(data).isEmpty();  // despite existence of modes
    }

    @Test
    @EnableFlags(Flags.FLAG_MODES_UI)
    public void testUpdateDynamicRawDataToIndex_empty() {
        // Case of no modes.
        when(mBackend.getModes()).thenReturn(new ArrayList<>());

        List<SearchIndexableRaw> data = new ArrayList<>();
        mPrefController.updateDynamicRawDataToIndex(data);
        assertThat(data).isEmpty();
    }

    @Test
    @EnableFlags(Flags.FLAG_MODES_UI)
    public void testUpdateDynamicRawDataToIndex_oneMode() {
        // One mode present, confirm it's the correct one
        when(mBackend.getModes()).thenReturn(List.of(TEST_MODE));

        List<SearchIndexableRaw> data = new ArrayList<>();
        mPrefController.updateDynamicRawDataToIndex(data);
        assertThat(data).hasSize(1);

        SearchIndexableRaw item = data.get(0);
        assertThat(item.key).isEqualTo(TEST_MODE_ID);
        assertThat(item.title).isEqualTo(TEST_MODE_NAME);

        // Changing mode data so there's a different one mode doesn't keep any previous data
        // (and setting that state up in the caller)
        when(mBackend.getModes()).thenReturn(List.of(TEST_MANUAL_MODE));
        List<SearchIndexableRaw> newData = new ArrayList<>();
        mPrefController.updateDynamicRawDataToIndex(newData);
        assertThat(newData).hasSize(1);

        SearchIndexableRaw newItem = newData.get(0);
        assertThat(newItem.key).isEqualTo(ZenMode.MANUAL_DND_MODE_ID);
        assertThat(newItem.title).isEqualTo("Do Not Disturb");  // set above
    }

    @Test
    @EnableFlags(Flags.FLAG_MODES_UI)
    public void testUpdateDynamicRawDataToIndex_multipleModes() {
        when(mBackend.getModes()).thenReturn(List.of(TEST_MANUAL_MODE, TEST_MODE));

        List<SearchIndexableRaw> data = new ArrayList<>();
        mPrefController.updateDynamicRawDataToIndex(data);
        assertThat(data).hasSize(2);

        // Should keep the order presented by getModes()
        SearchIndexableRaw item0 = data.get(0);
        assertThat(item0.key).isEqualTo(ZenMode.MANUAL_DND_MODE_ID);
        assertThat(item0.title).isEqualTo("Do Not Disturb");  // set above

        SearchIndexableRaw item1 = data.get(1);
        assertThat(item1.key).isEqualTo(TEST_MODE_ID);
        assertThat(item1.title).isEqualTo(TEST_MODE_NAME);
    }
}