Merge changes from topic "media_output_dialog" into rvc-qpr-dev

* changes:
  Redirect to output switcher panel to the new dialog
  Update rule of launching media output dialog
  Update OutputSwitcher from MediaOutputSlice to MediaOutputDialog in Settings
  Add PairNewBluetoothReceiver to launch Bluetooth pairing page
This commit is contained in:
tim peng
2020-12-08 05:46:13 +00:00
committed by Android (Google) Code Review
12 changed files with 208 additions and 80 deletions

View File

@@ -3412,6 +3412,15 @@
android:value="com.android.settings.sound.MediaControlsSettings" />
</activity>
<receiver
android:name=".media.BluetoothPairingReceiver"
android:exported="true"
android:permission="android.permission.BLUETOOTH_ADMIN">
<intent-filter>
<action android:name="com.android.settings.action.LAUNCH_BLUETOOTH_PAIRING"/>
</intent-filter>/>
</receiver>
<!-- This is the longest AndroidManifest.xml ever. -->
</application>
</manifest>

View File

@@ -0,0 +1,46 @@
/*
* Copyright 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.media;
import android.app.settings.SettingsEnums;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.text.TextUtils;
import com.android.settings.R;
import com.android.settings.bluetooth.BluetoothPairingDetail;
import com.android.settings.core.SubSettingLauncher;
import com.android.settingslib.media.MediaOutputSliceConstants;
/**
* BroadcastReceiver for handling media output intent
*/
public class BluetoothPairingReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (TextUtils.equals(MediaOutputSliceConstants.ACTION_LAUNCH_BLUETOOTH_PAIRING,
intent.getAction())) {
context.startActivity(new SubSettingLauncher(context)
.setDestination(BluetoothPairingDetail.class.getName())
.setTitleRes(R.string.bluetooth_pairing_page_title)
.setSourceMetricsCategory(SettingsEnums.BLUETOOTH_PAIRING_RECEIVER)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK)
.toIntent());
}
}
}

View File

@@ -16,18 +16,15 @@
package com.android.settings.media;
import static android.app.PendingIntent.FLAG_UPDATE_CURRENT;
import static com.android.settings.slices.CustomSliceRegistry.MEDIA_OUTPUT_INDICATOR_SLICE_URI;
import android.annotation.ColorInt;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.media.session.MediaController;
import android.net.Uri;
import android.text.TextUtils;
import android.util.Log;
import androidx.annotation.VisibleForTesting;
import androidx.core.graphics.drawable.IconCompat;
@@ -63,14 +60,9 @@ public class MediaOutputIndicatorSlice implements CustomSliceable {
com.android.internal.R.drawable.ic_settings_bluetooth);
final CharSequence title = mContext.getString(R.string.media_output_label_title,
Utils.getApplicationLabel(mContext, getWorker().getPackageName()));
final int requestCode = TextUtils.isEmpty(getWorker().getPackageName())
? 0
: getWorker().getPackageName().hashCode();
final PendingIntent primaryActionIntent = PendingIntent.getActivity(mContext,
requestCode,
getMediaOutputSliceIntent(), FLAG_UPDATE_CURRENT);
final SliceAction primarySliceAction = SliceAction.createDeeplink(
primaryActionIntent, icon, ListBuilder.ICON_IMAGE, title);
final SliceAction primarySliceAction = SliceAction.create(
getBroadcastIntent(mContext), icon, ListBuilder.ICON_IMAGE, title);
@ColorInt final int color = Utils.getColorAccentDefaultColor(mContext);
// To set an empty icon to indent the row
final ListBuilder listBuilder = new ListBuilder(mContext, getUri(), ListBuilder.INFINITY)
@@ -83,22 +75,6 @@ public class MediaOutputIndicatorSlice implements CustomSliceable {
return listBuilder.build();
}
@VisibleForTesting
Intent getMediaOutputSliceIntent() {
final MediaController mediaController = getWorker().getActiveLocalMediaController();
final Intent intent = new Intent()
.setPackage(Utils.SETTINGS_PACKAGE_NAME)
.setAction(MediaOutputSliceConstants.ACTION_MEDIA_OUTPUT)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
if (mediaController != null) {
intent.putExtra(MediaOutputSliceConstants.KEY_MEDIA_SESSION_TOKEN,
mediaController.getSessionToken());
intent.putExtra(MediaOutputSliceConstants.EXTRA_PACKAGE_NAME,
mediaController.getPackageName());
}
return intent;
}
private IconCompat createEmptyIcon() {
final Bitmap bitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
return IconCompat.createWithBitmap(bitmap);
@@ -141,4 +117,26 @@ public class MediaOutputIndicatorSlice implements CustomSliceable {
&& getWorker().getMediaDevices().size() > 0
&& getWorker().getActiveLocalMediaController() != null;
}
@Override
public void onNotifyChange(Intent intent) {
final MediaController mediaController = getWorker().getActiveLocalMediaController();
if (mediaController == null) {
Log.d(TAG, "No active local media controller");
return;
}
// Launch media output dialog
mContext.sendBroadcast(new Intent()
.setPackage(MediaOutputSliceConstants.SYSTEMUI_PACKAGE_NAME)
.setAction(MediaOutputSliceConstants.ACTION_LAUNCH_MEDIA_OUTPUT_DIALOG)
.putExtra(MediaOutputSliceConstants.KEY_MEDIA_SESSION_TOKEN,
mediaController.getSessionToken())
.putExtra(MediaOutputSliceConstants.EXTRA_PACKAGE_NAME,
mediaController.getPackageName()));
// Dismiss volume panel
mContext.sendBroadcast(new Intent()
.setPackage(MediaOutputSliceConstants.SETTINGS_PACKAGE_NAME)
.setAction(MediaOutputSliceConstants.ACTION_CLOSE_PANEL));
}
}

View File

@@ -59,6 +59,9 @@ public class RemoteMediaSlice implements CustomSliceable {
private static final String TAG = "RemoteMediaSlice";
private static final String MEDIA_ID = "media_id";
private static final String ACTION_LAUNCH_DIALOG = "action_launch_dialog";
private static final String SESSION_INFO = "RoutingSessionInfo";
private static final String CUSTOMIZED_ACTION = "customized_action";
private final Context mContext;
@@ -77,6 +80,20 @@ public class RemoteMediaSlice implements CustomSliceable {
final String id = intent.getStringExtra(MEDIA_ID);
if (!TextUtils.isEmpty(id)) {
getWorker().adjustSessionVolume(id, newPosition);
return;
}
if (TextUtils.equals(ACTION_LAUNCH_DIALOG, intent.getStringExtra(CUSTOMIZED_ACTION))) {
// Launch Media Output Dialog
final RoutingSessionInfo info = intent.getParcelableExtra(SESSION_INFO);
mContext.sendBroadcast(new Intent()
.setPackage(MediaOutputSliceConstants.SYSTEMUI_PACKAGE_NAME)
.setAction(MediaOutputSliceConstants.ACTION_LAUNCH_MEDIA_OUTPUT_DIALOG)
.putExtra(MediaOutputSliceConstants.EXTRA_PACKAGE_NAME,
info.getClientPackageName()));
// Dismiss volume panel
mContext.sendBroadcast(new Intent()
.setPackage(MediaOutputSliceConstants.SETTINGS_PACKAGE_NAME)
.setAction(MediaOutputSliceConstants.ACTION_CLOSE_PANEL));
}
}
@@ -133,8 +150,7 @@ public class RemoteMediaSlice implements CustomSliceable {
.setTitle(isMediaOutputDisabled ? spannableTitle : outputTitle)
.setSubtitle(info.getName())
.setTitleItem(emptyIcon, ListBuilder.ICON_IMAGE)
.setPrimaryAction(getMediaOutputSliceAction(
info.getClientPackageName(), isMediaOutputDisabled)));
.setPrimaryAction(getMediaOutputDialogAction(info, isMediaOutputDisabled)));
}
return listBuilder.build();
}
@@ -167,23 +183,22 @@ public class RemoteMediaSlice implements CustomSliceable {
return primarySliceAction;
}
private SliceAction getMediaOutputSliceAction(
String packageName, boolean isMediaOutputDisabled) {
final Intent intent = new Intent()
.setAction(isMediaOutputDisabled
? ""
: MediaOutputSliceConstants.ACTION_MEDIA_OUTPUT)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
.putExtra(MediaOutputSliceConstants.EXTRA_PACKAGE_NAME, packageName);
final IconCompat icon = IconCompat.createWithResource(mContext,
R.drawable.ic_volume_remote);
final int requestCode = TextUtils.isEmpty(packageName) ? 0 : packageName.hashCode();
final PendingIntent primaryActionIntent = PendingIntent.getActivity(mContext,
requestCode, intent, 0 /* flags */);
private SliceAction getMediaOutputDialogAction(RoutingSessionInfo info,
boolean isMediaOutputDisabled) {
final Intent intent = new Intent(getUri().toString())
.setData(getUri())
.setClass(mContext, SliceBroadcastReceiver.class)
.putExtra(CUSTOMIZED_ACTION, isMediaOutputDisabled ? "" : ACTION_LAUNCH_DIALOG)
.putExtra(SESSION_INFO, info)
.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
final PendingIntent primaryBroadcastIntent = PendingIntent.getBroadcast(mContext,
info.hashCode(), intent, PendingIntent.FLAG_UPDATE_CURRENT);
final SliceAction primarySliceAction = SliceAction.createDeeplink(
primaryActionIntent, icon, ListBuilder.ICON_IMAGE,
primaryBroadcastIntent,
IconCompat.createWithResource(mContext, R.drawable.ic_volume_remote),
ListBuilder.ICON_IMAGE,
mContext.getString(R.string.media_output_label_title,
Utils.getApplicationLabel(mContext, packageName)));
Utils.getApplicationLabel(mContext, info.getClientPackageName())));
return primarySliceAction;
}

View File

@@ -196,11 +196,11 @@ public class RemoteVolumeGroupController extends BasePreferenceController implem
if (TextUtils.equals(info.getId(),
preference.getKey().substring(SWITCHER_PREFIX.length()))) {
final Intent intent = new Intent()
.setAction(MediaOutputSliceConstants.ACTION_MEDIA_OUTPUT)
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
.setAction(MediaOutputSliceConstants.ACTION_LAUNCH_MEDIA_OUTPUT_DIALOG)
.setPackage(MediaOutputSliceConstants.SYSTEMUI_PACKAGE_NAME)
.putExtra(MediaOutputSliceConstants.EXTRA_PACKAGE_NAME,
info.getClientPackageName());
mContext.startActivity(intent);
mContext.sendBroadcast(intent);
return true;
}
}

View File

@@ -44,6 +44,7 @@ import com.android.settings.Utils;
import com.android.settingslib.media.InfoMediaDevice;
import com.android.settingslib.media.LocalMediaManager;
import com.android.settingslib.media.MediaDevice;
import com.android.settingslib.media.MediaOutputSliceConstants;
import java.util.ArrayList;
import java.util.List;
@@ -72,7 +73,12 @@ public class MediaOutputPanel implements PanelContent, LocalMediaManager.DeviceC
private MediaController mMediaController;
public static MediaOutputPanel create(Context context, String packageName) {
return new MediaOutputPanel(context, packageName);
// Redirect to new media output dialog
context.sendBroadcast(new Intent()
.setPackage(MediaOutputSliceConstants.SYSTEMUI_PACKAGE_NAME)
.setAction(MediaOutputSliceConstants.ACTION_LAUNCH_MEDIA_OUTPUT_DIALOG)
.putExtra(MediaOutputSliceConstants.EXTRA_PACKAGE_NAME, packageName));
return null;
}
private MediaOutputPanel(Context context, String packageName) {

View File

@@ -436,6 +436,7 @@ public class PanelFragment extends Fragment {
if (mLayoutView != null) {
mLayoutView.getViewTreeObserver().removeOnGlobalLayoutListener(mPanelLayoutListener);
}
if (mPanel != null) {
mMetricsProvider.action(
0 /* attribution */,
SettingsEnums.PAGE_HIDE,
@@ -443,6 +444,7 @@ public class PanelFragment extends Fragment {
mPanelClosedKey,
0 /* value */);
}
}
@VisibleForTesting
View.OnClickListener getSeeMoreListener() {

View File

@@ -16,6 +16,9 @@
package com.android.settings.panel;
import static androidx.lifecycle.Lifecycle.Event.ON_PAUSE;
import static androidx.lifecycle.Lifecycle.Event.ON_RESUME;
import static com.android.settings.slices.CustomSliceRegistry.MEDIA_OUTPUT_INDICATOR_SLICE_URI;
import static com.android.settings.slices.CustomSliceRegistry.REMOTE_MEDIA_SLICE_URI;
import static com.android.settings.slices.CustomSliceRegistry.VOLUME_ALARM_URI;
@@ -24,20 +27,40 @@ import static com.android.settings.slices.CustomSliceRegistry.VOLUME_MEDIA_URI;
import static com.android.settings.slices.CustomSliceRegistry.VOLUME_RINGER_URI;
import android.app.settings.SettingsEnums;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.Uri;
import android.provider.Settings;
import androidx.lifecycle.LifecycleObserver;
import androidx.lifecycle.OnLifecycleEvent;
import com.android.settings.R;
import com.android.settingslib.media.MediaOutputSliceConstants;
import java.util.ArrayList;
import java.util.List;
public class VolumePanel implements PanelContent {
/**
* Panel data class for Volume settings.
*/
public class VolumePanel implements PanelContent, LifecycleObserver {
private final Context mContext;
private PanelContentCallback mCallback;
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (MediaOutputSliceConstants.ACTION_CLOSE_PANEL.equals(intent.getAction())) {
mCallback.forceClose();
}
}
};
public static VolumePanel create(Context context) {
return new VolumePanel(context);
}
@@ -46,6 +69,20 @@ public class VolumePanel implements PanelContent {
mContext = context.getApplicationContext();
}
/** Invoked when the panel is resumed. */
@OnLifecycleEvent(ON_RESUME)
public void onResume() {
final IntentFilter filter = new IntentFilter();
filter.addAction(MediaOutputSliceConstants.ACTION_CLOSE_PANEL);
mContext.registerReceiver(mReceiver, filter);
}
/** Invoked when the panel is paused. */
@OnLifecycleEvent(ON_PAUSE)
public void onPause() {
mContext.unregisterReceiver(mReceiver);
}
@Override
public CharSequence getTitle() {
return mContext.getText(R.string.sound_settings);
@@ -78,4 +115,9 @@ public class VolumePanel implements PanelContent {
public int getViewType() {
return PanelContent.VIEW_TYPE_SLIDER;
}
@Override
public void registerCallback(PanelContentCallback callback) {
mCallback = callback;
}
}

View File

@@ -133,10 +133,13 @@ public class MediaOutputPreferenceController extends AudioSwitchPreferenceContro
@Override
public boolean handlePreferenceTreeClick(Preference preference) {
if (TextUtils.equals(preference.getKey(), getPreferenceKey())) {
final Intent intent = new Intent()
.setAction(MediaOutputSliceConstants.ACTION_MEDIA_OUTPUT)
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mContext.startActivity(intent);
mContext.sendBroadcast(new Intent()
.setAction(MediaOutputSliceConstants.ACTION_LAUNCH_MEDIA_OUTPUT_DIALOG)
.setPackage(MediaOutputSliceConstants.SYSTEMUI_PACKAGE_NAME)
.putExtra(MediaOutputSliceConstants.EXTRA_PACKAGE_NAME,
mMediaController.getPackageName())
.putExtra(MediaOutputSliceConstants.KEY_MEDIA_SESSION_TOKEN,
mMediaController.getSessionToken()));
return true;
}
return false;

View File

@@ -23,6 +23,8 @@ import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.Context;
@@ -54,6 +56,7 @@ import com.android.settingslib.media.MediaOutputSliceConstants;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
@@ -203,31 +206,45 @@ public class MediaOutputIndicatorSliceTest {
}
@Test
public void getMediaOutputSliceIntent_withActiveLocalMedia_verifyIntentExtra() {
public void onNotifyChange_withActiveLocalMedia_verifyIntentExtra() {
when(mMediaController.getSessionToken()).thenReturn(mToken);
when(mMediaController.getPackageName()).thenReturn(TEST_PACKAGE_NAME);
doReturn(mMediaController).when(sMediaOutputIndicatorWorker)
.getActiveLocalMediaController();
final Intent intent = mMediaOutputIndicatorSlice.getMediaOutputSliceIntent();
ArgumentCaptor<Intent> argument = ArgumentCaptor.forClass(Intent.class);
mMediaOutputIndicatorSlice.onNotifyChange(null);
verify(mContext, times(2)).sendBroadcast(argument.capture());
List<Intent> intentList = argument.getAllValues();
Intent intent = intentList.get(0);
assertThat(TextUtils.equals(TEST_PACKAGE_NAME, intent.getStringExtra(
MediaOutputSliceConstants.EXTRA_PACKAGE_NAME))).isTrue();
assertThat(MediaOutputSliceConstants.ACTION_MEDIA_OUTPUT).isEqualTo(intent.getAction());
assertThat(TextUtils.equals(Utils.SETTINGS_PACKAGE_NAME, intent.getPackage())).isTrue();
assertThat(MediaOutputSliceConstants.ACTION_LAUNCH_MEDIA_OUTPUT_DIALOG).isEqualTo(
intent.getAction());
assertThat(TextUtils.equals(MediaOutputSliceConstants.SYSTEMUI_PACKAGE_NAME,
intent.getPackage())).isTrue();
assertThat(mToken == intent.getExtras().getParcelable(
MediaOutputSliceConstants.KEY_MEDIA_SESSION_TOKEN)).isTrue();
}
@Test
public void getMediaOutputSliceIntent_withoutActiveLocalMedia_verifyIntentExtra() {
public void onNotifyChange_withoutActiveLocalMedia_verifyIntentExtra() {
doReturn(mMediaController).when(sMediaOutputIndicatorWorker)
.getActiveLocalMediaController();
final Intent intent = mMediaOutputIndicatorSlice.getMediaOutputSliceIntent();
ArgumentCaptor<Intent> argument = ArgumentCaptor.forClass(Intent.class);
mMediaOutputIndicatorSlice.onNotifyChange(null);
verify(mContext, times(2)).sendBroadcast(argument.capture());
List<Intent> intentList = argument.getAllValues();
Intent intent = intentList.get(0);
assertThat(TextUtils.isEmpty(intent.getStringExtra(
MediaOutputSliceConstants.EXTRA_PACKAGE_NAME))).isTrue();
assertThat(MediaOutputSliceConstants.ACTION_MEDIA_OUTPUT).isEqualTo(intent.getAction());
assertThat(TextUtils.equals(Utils.SETTINGS_PACKAGE_NAME, intent.getPackage())).isTrue();
assertThat(MediaOutputSliceConstants.ACTION_LAUNCH_MEDIA_OUTPUT_DIALOG).isEqualTo(
intent.getAction());
assertThat(TextUtils.equals(MediaOutputSliceConstants.SYSTEMUI_PACKAGE_NAME,
intent.getPackage())).isTrue();
assertThat(intent.getExtras().getParcelable(
MediaOutputSliceConstants.KEY_MEDIA_SESSION_TOKEN) == null).isTrue();
}

View File

@@ -19,7 +19,6 @@ package com.android.settings.panel;
import static com.android.settings.panel.SettingsPanelActivity.KEY_MEDIA_PACKAGE_NAME;
import static com.android.settings.panel.SettingsPanelActivity.KEY_PANEL_TYPE_ARGUMENT;
import static com.android.settingslib.media.MediaOutputSliceConstants.ACTION_MEDIA_OUTPUT;
import static com.google.common.truth.Truth.assertThat;
@@ -67,13 +66,4 @@ public class PanelFeatureProviderImplTest {
assertThat(panel).isInstanceOf(VolumePanel.class);
}
@Test
public void getPanel_mediaOutputKey_returnsCorrectPanel() {
mBundle.putString(KEY_PANEL_TYPE_ARGUMENT, ACTION_MEDIA_OUTPUT);
final PanelContent panel = mProvider.getPanel(mContext, mBundle);
assertThat(panel).isInstanceOf(MediaOutputPanel.class);
}
}

View File

@@ -299,9 +299,9 @@ public class MediaOutputPreferenceControllerTest {
mPreference.setKey(TEST_KEY);
mController.handlePreferenceTreeClick(mPreference);
verify(mContext).startActivity(intentCaptor.capture());
verify(mContext).sendBroadcast(intentCaptor.capture());
assertThat(intentCaptor.getValue().getAction())
.isEqualTo(MediaOutputSliceConstants.ACTION_MEDIA_OUTPUT);
.isEqualTo(MediaOutputSliceConstants.ACTION_LAUNCH_MEDIA_OUTPUT_DIALOG);
}
/**