Merge "Add stop casting button for output switch"
This commit is contained in:
committed by
Android (Google) Code Review
commit
5f73b0cbcd
@@ -11749,4 +11749,6 @@
|
||||
<!-- Subtext for showing the option of RTT setting. [CHAR LIMIT=NONE] -->
|
||||
<string name="rtt_settings_always_visible"></string>
|
||||
|
||||
<!-- Button label to stop casting on media device. [CHAR LIMIT=40 -->
|
||||
<string name="media_output_panel_stop_casting_button">Stop casting</string>
|
||||
</resources>
|
||||
|
@@ -16,6 +16,9 @@
|
||||
|
||||
package com.android.settings.panel;
|
||||
|
||||
import static androidx.lifecycle.Lifecycle.Event.ON_START;
|
||||
import static androidx.lifecycle.Lifecycle.Event.ON_STOP;
|
||||
|
||||
import static com.android.settings.media.MediaOutputSlice.MEDIA_PACKAGE_NAME;
|
||||
import static com.android.settings.slices.CustomSliceRegistry.MEDIA_OUTPUT_SLICE_URI;
|
||||
|
||||
@@ -35,9 +38,15 @@ import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.core.graphics.drawable.IconCompat;
|
||||
import androidx.lifecycle.LifecycleObserver;
|
||||
import androidx.lifecycle.OnLifecycleEvent;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.Utils;
|
||||
import com.android.settingslib.media.InfoMediaDevice;
|
||||
import com.android.settingslib.media.LocalMediaManager;
|
||||
import com.android.settingslib.media.MediaDevice;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -49,13 +58,20 @@ import java.util.List;
|
||||
* Displays Media output item
|
||||
* </p>
|
||||
*/
|
||||
public class MediaOutputPanel implements PanelContent {
|
||||
public class MediaOutputPanel implements PanelContent, LocalMediaManager.DeviceCallback,
|
||||
LifecycleObserver {
|
||||
|
||||
private static final String TAG = "MediaOutputPanel";
|
||||
|
||||
private final Context mContext;
|
||||
private final String mPackageName;
|
||||
|
||||
private PanelCustomizedButtonCallback mCallback;
|
||||
private boolean mIsCustomizedButtonUsed = true;
|
||||
|
||||
@VisibleForTesting
|
||||
LocalMediaManager mLocalMediaManager;
|
||||
|
||||
private MediaSessionManager mMediaSessionManager;
|
||||
private MediaController mMediaController;
|
||||
|
||||
@@ -65,8 +81,9 @@ public class MediaOutputPanel implements PanelContent {
|
||||
|
||||
private MediaOutputPanel(Context context, String packageName) {
|
||||
mContext = context.getApplicationContext();
|
||||
mPackageName = packageName;
|
||||
if (mPackageName != null) {
|
||||
mPackageName = TextUtils.isEmpty(packageName) ? "" : packageName;
|
||||
|
||||
if (!TextUtils.isEmpty(mPackageName)) {
|
||||
mMediaSessionManager = mContext.getSystemService(MediaSessionManager.class);
|
||||
for (MediaController controller : mMediaSessionManager.getActiveSessions(null)) {
|
||||
if (TextUtils.equals(controller.getPackageName(), mPackageName)) {
|
||||
@@ -75,6 +92,7 @@ public class MediaOutputPanel implements PanelContent {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (mMediaController == null) {
|
||||
Log.e(TAG, "Unable to find " + mPackageName + " media controller");
|
||||
}
|
||||
@@ -156,8 +174,69 @@ public class MediaOutputPanel implements PanelContent {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCustomizedButtonUsed() {
|
||||
return mIsCustomizedButtonUsed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence getCustomButtonTitle() {
|
||||
return mContext.getText(R.string.media_output_panel_stop_casting_button);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClickCustomizedButton() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerCallback(PanelCustomizedButtonCallback callback) {
|
||||
mCallback = callback;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMetricsCategory() {
|
||||
return SettingsEnums.PANEL_MEDIA_OUTPUT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSelectedDeviceStateChanged(MediaDevice device, int state) {
|
||||
dispatchCustomButtonStateChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDeviceListUpdate(List<MediaDevice> devices) {
|
||||
dispatchCustomButtonStateChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDeviceAttributesChanged() {
|
||||
dispatchCustomButtonStateChanged();
|
||||
}
|
||||
|
||||
private void dispatchCustomButtonStateChanged() {
|
||||
hideCustomButtonIfNecessary();
|
||||
if (mCallback != null) {
|
||||
mCallback.onCustomizedButtonStateChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private void hideCustomButtonIfNecessary() {
|
||||
final MediaDevice device = mLocalMediaManager.getCurrentConnectedDevice();
|
||||
mIsCustomizedButtonUsed = device instanceof InfoMediaDevice;
|
||||
}
|
||||
|
||||
@OnLifecycleEvent(ON_START)
|
||||
public void onStart() {
|
||||
if (mLocalMediaManager == null) {
|
||||
mLocalMediaManager = new LocalMediaManager(mContext, mPackageName, null);
|
||||
}
|
||||
mLocalMediaManager.registerCallback(this);
|
||||
mLocalMediaManager.startScan();
|
||||
}
|
||||
|
||||
@OnLifecycleEvent(ON_STOP)
|
||||
public void onStop() {
|
||||
mLocalMediaManager.unregisterCallback(this);
|
||||
mLocalMediaManager.stopScan();
|
||||
}
|
||||
}
|
||||
|
@@ -74,4 +74,31 @@ public interface PanelContent extends Instrumentable {
|
||||
default Intent getHeaderIconIntent() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {@code true} to enable custom button to replace see more button,
|
||||
* {@code false} otherwise.
|
||||
*/
|
||||
default boolean isCustomizedButtonUsed() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a string for the title of the custom button.
|
||||
*/
|
||||
default CharSequence getCustomButtonTitle() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implement the click event for custom button.
|
||||
*/
|
||||
default void onClickCustomizedButton() {}
|
||||
|
||||
/**
|
||||
* Register to start receiving callbacks for custom button events.
|
||||
*
|
||||
* @param callback the callback to add.
|
||||
*/
|
||||
default void registerCallback(PanelCustomizedButtonCallback callback) {}
|
||||
}
|
||||
|
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* 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.panel;
|
||||
|
||||
/**
|
||||
* PanelCustomizedButtonCallback provides a callback interface for {@link PanelFragment} to receive
|
||||
* events from {@link PanelContent}.
|
||||
*/
|
||||
public interface PanelCustomizedButtonCallback {
|
||||
|
||||
/**
|
||||
* It will be called when customized button state is changed. For example, custom button
|
||||
* would be hidden for specific behavior.
|
||||
*/
|
||||
void onCustomizedButtonStateChanged();
|
||||
}
|
@@ -41,6 +41,7 @@ import androidx.annotation.Nullable;
|
||||
import androidx.core.graphics.drawable.IconCompat;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
import androidx.lifecycle.LifecycleObserver;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
@@ -53,6 +54,7 @@ import com.android.settings.R;
|
||||
import com.android.settings.overlay.FeatureFactory;
|
||||
import com.android.settings.panel.PanelLoggingContract.PanelClosedKeys;
|
||||
import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
|
||||
import com.android.settingslib.utils.ThreadUtils;
|
||||
|
||||
import com.google.android.setupdesign.DividerItemDecoration;
|
||||
|
||||
@@ -183,6 +185,11 @@ public class PanelFragment extends Fragment {
|
||||
activity.finish();
|
||||
}
|
||||
|
||||
mPanel.registerCallback(new LocalPanelCallback());
|
||||
if (mPanel instanceof LifecycleObserver) {
|
||||
getLifecycle().addObserver((LifecycleObserver) mPanel);
|
||||
}
|
||||
|
||||
mMetricsProvider = FeatureFactory.getFactory(activity).getMetricsFeatureProvider();
|
||||
|
||||
mPanelSlices.setLayoutManager(new LinearLayoutManager((activity)));
|
||||
@@ -208,8 +215,15 @@ public class PanelFragment extends Fragment {
|
||||
mSeeMoreButton.setOnClickListener(getSeeMoreListener());
|
||||
mDoneButton.setOnClickListener(getCloseListener());
|
||||
|
||||
// If getSeeMoreIntent() is null, hide the mSeeMoreButton.
|
||||
if (mPanel.getSeeMoreIntent() == null) {
|
||||
if (mPanel.isCustomizedButtonUsed()) {
|
||||
final CharSequence customTitle = mPanel.getCustomButtonTitle();
|
||||
if (TextUtils.isEmpty(customTitle)) {
|
||||
mSeeMoreButton.setVisibility(View.GONE);
|
||||
} else {
|
||||
mSeeMoreButton.setText(customTitle);
|
||||
}
|
||||
} else if (mPanel.getSeeMoreIntent() == null) {
|
||||
// If getSeeMoreIntent() is null hide the mSeeMoreButton.
|
||||
mSeeMoreButton.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
@@ -371,9 +385,13 @@ public class PanelFragment extends Fragment {
|
||||
View.OnClickListener getSeeMoreListener() {
|
||||
return (v) -> {
|
||||
mPanelClosedKey = PanelClosedKeys.KEY_SEE_MORE;
|
||||
final FragmentActivity activity = getActivity();
|
||||
activity.startActivityForResult(mPanel.getSeeMoreIntent(), 0);
|
||||
activity.finish();
|
||||
if (mPanel.isCustomizedButtonUsed()) {
|
||||
mPanel.onClickCustomizedButton();
|
||||
} else {
|
||||
final FragmentActivity activity = getActivity();
|
||||
activity.startActivityForResult(mPanel.getSeeMoreIntent(), 0);
|
||||
activity.finish();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -392,4 +410,15 @@ public class PanelFragment extends Fragment {
|
||||
activity.startActivity(mPanel.getHeaderIconIntent());
|
||||
};
|
||||
}
|
||||
|
||||
class LocalPanelCallback implements PanelCustomizedButtonCallback {
|
||||
|
||||
@Override
|
||||
public void onCustomizedButtonStateChanged() {
|
||||
ThreadUtils.postOnMainThread(() -> {
|
||||
mSeeMoreButton.setVisibility(
|
||||
mPanel.isCustomizedButtonUsed() ? View.VISIBLE : View.GONE);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -21,7 +21,9 @@ import static com.android.settings.media.MediaOutputSlice.MEDIA_PACKAGE_NAME;
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.content.Context;
|
||||
@@ -32,6 +34,9 @@ import android.net.Uri;
|
||||
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.slices.CustomSliceRegistry;
|
||||
import com.android.settingslib.media.InfoMediaDevice;
|
||||
import com.android.settingslib.media.LocalMediaManager;
|
||||
import com.android.settingslib.media.PhoneMediaDevice;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
@@ -58,6 +63,11 @@ public class MediaOutputPanelTest {
|
||||
@Mock
|
||||
private MediaMetadata mMediaMetadata;
|
||||
|
||||
@Mock
|
||||
private LocalMediaManager mLocalMediaManager;
|
||||
@Mock
|
||||
private PanelCustomizedButtonCallback mCallback;
|
||||
|
||||
private MediaOutputPanel mPanel;
|
||||
private Context mContext;
|
||||
private List<MediaController> mMediaControllers = new ArrayList<>();
|
||||
@@ -67,12 +77,16 @@ public class MediaOutputPanelTest {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
|
||||
mContext = spy(RuntimeEnvironment.application);
|
||||
|
||||
mMediaControllers.add(mMediaController);
|
||||
when(mMediaController.getPackageName()).thenReturn(TEST_PACKAGENAME);
|
||||
when(mMediaSessionManager.getActiveSessions(any())).thenReturn(mMediaControllers);
|
||||
when(mContext.getApplicationContext()).thenReturn(mContext);
|
||||
when(mContext.getSystemService(MediaSessionManager.class)).thenReturn(mMediaSessionManager);
|
||||
|
||||
mPanel = MediaOutputPanel.create(mContext, TEST_PACKAGENAME);
|
||||
mPanel.mLocalMediaManager = mLocalMediaManager;
|
||||
mPanel.registerCallback(mCallback);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -94,6 +108,63 @@ public class MediaOutputPanelTest {
|
||||
assertThat(mPanel.getSeeMoreIntent()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onStart_shouldRegisterCallback() {
|
||||
mPanel.onStart();
|
||||
|
||||
verify(mLocalMediaManager).registerCallback(any());
|
||||
verify(mLocalMediaManager).startScan();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onStop_shouldUnregisterCallback() {
|
||||
mPanel.onStop();
|
||||
|
||||
verify(mLocalMediaManager).unregisterCallback(any());
|
||||
verify(mLocalMediaManager).stopScan();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onSelectedDeviceStateChanged_shouldDispatchCustomButtonStateChanged() {
|
||||
mPanel.onSelectedDeviceStateChanged(null, 0);
|
||||
|
||||
verify(mCallback).onCustomizedButtonStateChanged();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onDeviceListUpdate_shouldDispatchCustomButtonStateChanged() {
|
||||
mPanel.onDeviceListUpdate(null);
|
||||
|
||||
verify(mCallback).onCustomizedButtonStateChanged();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onDeviceAttributesChanged_shouldDispatchCustomButtonStateChanged() {
|
||||
mPanel.onDeviceAttributesChanged();
|
||||
|
||||
verify(mCallback).onCustomizedButtonStateChanged();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void currentConnectDeviceIsInfoDevice_useCustomButtonIsTrue() {
|
||||
final InfoMediaDevice infoMediaDevice = mock(InfoMediaDevice.class);
|
||||
when(mLocalMediaManager.getCurrentConnectedDevice()).thenReturn(infoMediaDevice);
|
||||
|
||||
mPanel.onDeviceAttributesChanged();
|
||||
|
||||
assertThat(mPanel.isCustomizedButtonUsed()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void currentConnectDeviceIsNotInfoDevice_useCustomButtonIsFalse() {
|
||||
final PhoneMediaDevice phoneMediaDevice = mock(PhoneMediaDevice.class);
|
||||
when(mLocalMediaManager.getCurrentConnectedDevice()).thenReturn(phoneMediaDevice);
|
||||
|
||||
mPanel.onDeviceAttributesChanged();
|
||||
|
||||
assertThat(mPanel.isCustomizedButtonUsed()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getTitle_withMetadata_returnArtistName() {
|
||||
when(mMediaMetadata.getString(MediaMetadata.METADATA_KEY_ARTIST)).thenReturn(TEST_ARTIST);
|
||||
|
Reference in New Issue
Block a user