Merge "Add stop casting button for output switch"

This commit is contained in:
TreeHugger Robot
2020-02-19 12:25:05 +00:00
committed by Android (Google) Code Review
6 changed files with 245 additions and 8 deletions

View File

@@ -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>

View File

@@ -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();
}
}

View File

@@ -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) {}
}

View File

@@ -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();
}

View File

@@ -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);
});
}
}
}

View File

@@ -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);