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

Commit 978b19ec authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Implement remote media preference in Sound Setting"

parents 59235bd8 b5455999
Loading
Loading
Loading
Loading
+183 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2020 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;

import android.content.Context;
import android.content.Intent;
import android.util.Log;

import androidx.annotation.VisibleForTesting;
import androidx.lifecycle.LifecycleObserver;
import androidx.lifecycle.OnLifecycleEvent;
import androidx.preference.Preference;
import androidx.preference.PreferenceCategory;
import androidx.preference.PreferenceScreen;

import com.android.settings.R;
import com.android.settings.core.BasePreferenceController;
import com.android.settingslib.core.lifecycle.Lifecycle;
import com.android.settingslib.media.LocalMediaManager;
import com.android.settingslib.media.MediaDevice;
import com.android.settingslib.media.MediaOutputSliceConstants;
import com.android.settingslib.utils.ThreadUtils;

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

/**
 * A group preference controller to add/remove/update preference
 * {@link com.android.settings.notification.RemoteVolumeSeekBarPreference}
 **/
public class RemoteVolumeGroupController extends BasePreferenceController implements
        Preference.OnPreferenceChangeListener, LifecycleObserver, LocalMediaManager.DeviceCallback {

    private static final String KEY_REMOTE_VOLUME_GROUP = "remote_media_group";
    private static final String TAG = "RemoteVolumePrefCtr";
    @VisibleForTesting
    static final String SWITCHER_PREFIX = "OUTPUT_SWITCHER";

    private PreferenceCategory mPreferenceCategory;
    private List<MediaDevice> mActiveRemoteMediaDevices = new ArrayList<>();

    @VisibleForTesting
    LocalMediaManager mLocalMediaManager;

    public RemoteVolumeGroupController(Context context, String preferenceKey) {
        super(context, preferenceKey);
        if (mLocalMediaManager == null) {
            mLocalMediaManager = new LocalMediaManager(mContext, null, null);
            mLocalMediaManager.registerCallback(this);
            mLocalMediaManager.startScan();
        }
    }

    @Override
    public int getAvailabilityStatus() {
        if (mActiveRemoteMediaDevices.isEmpty()) {
            return CONDITIONALLY_UNAVAILABLE;
        }
        return AVAILABLE_UNSEARCHABLE;
    }

    @Override
    public void displayPreference(PreferenceScreen screen) {
        super.displayPreference(screen);
        mPreferenceCategory = screen.findPreference(getPreferenceKey());
        mActiveRemoteMediaDevices.clear();
        mActiveRemoteMediaDevices.addAll(mLocalMediaManager.getActiveMediaDevice(
                MediaDevice.MediaDeviceType.TYPE_CAST_DEVICE));
        refreshPreference();
    }

    /**
     * onDestroy()
     * {@link androidx.lifecycle.OnLifecycleEvent}
     **/
    @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
    public void onDestroy() {
        mLocalMediaManager.unregisterCallback(this);
        mLocalMediaManager.stopScan();
    }

    private void refreshPreference() {
        mPreferenceCategory.removeAll();
        if (!isAvailable()) {
            mPreferenceCategory.setVisible(false);
            return;
        }
        final CharSequence outputTitle = mContext.getText(R.string.media_output_title);
        final CharSequence castVolume = mContext.getText(R.string.remote_media_volume_option_title);
        mPreferenceCategory.setVisible(true);
        int i = 0;
        for (MediaDevice device : mActiveRemoteMediaDevices) {
            if (mPreferenceCategory.findPreference(device.getId()) != null) {
                continue;
            }
            // Add slider
            final RemoteVolumeSeekBarPreference seekBarPreference =
                    new RemoteVolumeSeekBarPreference(mContext);
            seekBarPreference.setKey(device.getId());
            seekBarPreference.setTitle(castVolume + " (" + device.getClientAppLabel() + ")");
            seekBarPreference.setMax(device.getMaxVolume());
            seekBarPreference.setProgress(device.getCurrentVolume());
            seekBarPreference.setMin(0);
            seekBarPreference.setOnPreferenceChangeListener(this);
            seekBarPreference.setIcon(R.drawable.ic_volume_remote);
            mPreferenceCategory.addPreference(seekBarPreference);
            // Add output indicator
            final Preference preference = new Preference(mContext);
            preference.setKey(SWITCHER_PREFIX + device.getId());
            preference.setTitle(outputTitle);
            preference.setSummary(device.getName());
            mPreferenceCategory.addPreference(preference);
        }
    }

    @Override
    public boolean onPreferenceChange(Preference preference, Object newValue) {
        final MediaDevice device = mLocalMediaManager.getMediaDeviceById(preference.getKey());
        if (device == null) {
            Log.e(TAG, "Unable to find " + preference.getKey() + " to set volume");
            return false;
        }
        ThreadUtils.postOnBackgroundThread(() -> {
            device.requestSetVolume((int) newValue);
        });
        return true;
    }

    @Override
    public boolean handlePreferenceTreeClick(Preference preference) {
        if (!preference.getKey().startsWith(SWITCHER_PREFIX)) {
            return false;
        }
        final String key = preference.getKey().substring(SWITCHER_PREFIX.length());
        final MediaDevice device = mLocalMediaManager.getMediaDeviceById(key);
        if (device == null) {
            return false;
        }
        final Intent intent = new Intent()
                .setAction(MediaOutputSliceConstants.ACTION_MEDIA_OUTPUT)
                .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
                .putExtra(MediaOutputSliceConstants.EXTRA_PACKAGE_NAME,
                        device.getClientPackageName());
        mContext.startActivity(intent);
        return true;
    }

    @Override
    public String getPreferenceKey() {
        return KEY_REMOTE_VOLUME_GROUP;
    }

    @Override
    public void onDeviceListUpdate(List<MediaDevice> devices) {
        if (mPreferenceCategory == null) {
            // Preference group is not ready.
            return;
        }
        mActiveRemoteMediaDevices.clear();
        mActiveRemoteMediaDevices.addAll(mLocalMediaManager.getActiveMediaDevice(
                MediaDevice.MediaDeviceType.TYPE_CAST_DEVICE));
        refreshPreference();
    }

    @Override
    public void onSelectedDeviceStateChanged(MediaDevice device, int state) {

    }
}
+11 −0
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@ package com.android.settings.notification;

import android.content.Context;
import android.util.AttributeSet;
import android.widget.SeekBar;

/**
 * A slider preference that controls remote volume, which doesn't go through
@@ -50,7 +51,17 @@ public class RemoteVolumeSeekBarPreference extends VolumeSeekBarPreference {
    @Override
    protected void init() {
        if (mSeekBar == null) return;
        setContinuousUpdates(true);
        updateIconView();
        updateSuppressionText();
        notifyHierarchyChanged();
    }

    @Override
    public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
        super.onProgressChanged(seekBar, progress, fromUser);
        if (fromUser) {
            notifyChanged();
        }
    }
}
+180 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2020 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;

import static com.android.settings.core.BasePreferenceController.AVAILABLE_UNSEARCHABLE;
import static com.android.settings.core.BasePreferenceController.CONDITIONALLY_UNAVAILABLE;

import static com.google.common.truth.Truth.assertThat;

import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;

import android.content.Context;
import android.content.SharedPreferences;

import androidx.preference.Preference;
import androidx.preference.PreferenceCategory;
import androidx.preference.PreferenceManager;
import androidx.preference.PreferenceScreen;

import com.android.settings.R;
import com.android.settings.testutils.shadow.ShadowBluetoothAdapter;
import com.android.settings.widget.SeekBarPreference;
import com.android.settingslib.media.LocalMediaManager;
import com.android.settingslib.media.MediaDevice;

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 org.robolectric.annotation.Config;

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

@RunWith(RobolectricTestRunner.class)
@Config(shadows = {ShadowBluetoothAdapter.class})
public class RemoteVolumeGroupControllerTest {

    private static final String KEY_REMOTE_VOLUME_GROUP = "remote_media_group";
    private static final String TEST_PACKAGE_LABEL = "music";
    private static final String TEST_DEVICE_1_ID = "test_device_1_id";
    private static final String TEST_DEVICE_1_NAME = "test_device_1_name";
    private static final int CURRENT_VOLUME = 30;
    private static final int MAX_VOLUME = 100;

    @Mock
    private LocalMediaManager mLocalMediaManager;
    @Mock
    private MediaDevice mDevice;
    @Mock
    private PreferenceScreen mScreen;
    @Mock
    private PreferenceManager mPreferenceManager;
    @Mock
    private SharedPreferences mSharedPreferences;

    private final List<MediaDevice> mDevices = new ArrayList<>();

    private Context mContext;
    private RemoteVolumeGroupController mController;
    private PreferenceCategory mPreferenceCategory;

    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);
        mContext = RuntimeEnvironment.application;
        mController = new RemoteVolumeGroupController(mContext, KEY_REMOTE_VOLUME_GROUP);
        mController.mLocalMediaManager = mLocalMediaManager;
        mPreferenceCategory = spy(new PreferenceCategory(mContext));
        mPreferenceCategory.setKey(mController.getPreferenceKey());

        when(mPreferenceCategory.getPreferenceManager()).thenReturn(mPreferenceManager);
        when(mPreferenceManager.getSharedPreferences()).thenReturn(mSharedPreferences);
        when(mLocalMediaManager.getActiveMediaDevice(
                MediaDevice.MediaDeviceType.TYPE_CAST_DEVICE)).thenReturn(mDevices);
        when(mDevice.getId()).thenReturn(TEST_DEVICE_1_ID);
        when(mDevice.getName()).thenReturn(TEST_DEVICE_1_NAME);
        when(mDevice.getMaxVolume()).thenReturn(MAX_VOLUME);
        when(mDevice.getCurrentVolume()).thenReturn(CURRENT_VOLUME);
        when(mDevice.getClientAppLabel()).thenReturn(TEST_PACKAGE_LABEL);
        when(mScreen.findPreference(mController.getPreferenceKey())).thenReturn(
                mPreferenceCategory);
    }

    @Test
    public void getAvailabilityStatus_withActiveDevice_returnAvailableUnsearchable() {
        mDevices.add(mDevice);
        mController.displayPreference(mScreen);

        assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE_UNSEARCHABLE);
    }

    @Test
    public void getAvailabilityStatus_noActiveDevice_returnConditionallyUnavailable() {
        mController.displayPreference(mScreen);

        assertThat(mController.getAvailabilityStatus()).isEqualTo(CONDITIONALLY_UNAVAILABLE);
    }

    @Test
    public void displayPreference_noActiveDevice_checkPreferenceCount() {
        mController.displayPreference(mScreen);

        assertThat(mPreferenceCategory.getPreferenceCount()).isEqualTo(0);
    }

    @Test
    public void displayPreference_withActiveDevice_checkPreferenceCount() {
        mDevices.add(mDevice);
        mController.displayPreference(mScreen);

        assertThat(mPreferenceCategory.getPreferenceCount()).isEqualTo(2);
    }

    @Test
    public void displayPreference_withActiveDevice_checkSeekBarTitle() {
        mDevices.add(mDevice);
        mController.displayPreference(mScreen);
        final Preference preference = mPreferenceCategory.findPreference(TEST_DEVICE_1_ID);

        assertThat(preference.getTitle()).isEqualTo(mContext.getText(
                R.string.remote_media_volume_option_title) + " (" + TEST_PACKAGE_LABEL + ")");
    }

    @Test
    public void displayPreference_withActiveDevice_checkSeekBarMaxVolume() {
        mDevices.add(mDevice);
        mController.displayPreference(mScreen);
        final SeekBarPreference preference = mPreferenceCategory.findPreference(TEST_DEVICE_1_ID);

        assertThat(preference.getMax()).isEqualTo(MAX_VOLUME);
    }

    @Test
    public void displayPreference_withActiveDevice_checkSeekBarCurrentVolume() {
        mDevices.add(mDevice);
        mController.displayPreference(mScreen);
        final SeekBarPreference preference = mPreferenceCategory.findPreference(TEST_DEVICE_1_ID);

        assertThat(preference.getProgress()).isEqualTo(CURRENT_VOLUME);
    }

    @Test
    public void displayPreference_withActiveDevice_checkSwitcherPreferenceTitle() {
        mDevices.add(mDevice);
        mController.displayPreference(mScreen);
        final Preference preference = mPreferenceCategory.findPreference(
                RemoteVolumeGroupController.SWITCHER_PREFIX + TEST_DEVICE_1_ID);

        assertThat(preference.getTitle()).isEqualTo(mContext.getText(R.string.media_output_title));
    }

    @Test
    public void displayPreference_withActiveDevice_checkSwitcherPreferenceSummary() {
        mDevices.add(mDevice);
        mController.displayPreference(mScreen);
        final Preference preference = mPreferenceCategory.findPreference(
                RemoteVolumeGroupController.SWITCHER_PREFIX + TEST_DEVICE_1_ID);

        assertThat(preference.getSummary()).isEqualTo(TEST_DEVICE_1_NAME);
    }
}