Merge "Add guts to media player on long press" into rvc-qpr-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
994c26d04f
@@ -210,8 +210,98 @@
|
|||||||
android:layout_width="@dimen/qs_media_icon_size"
|
android:layout_width="@dimen/qs_media_icon_size"
|
||||||
android:layout_height="@dimen/qs_media_icon_size" />
|
android:layout_height="@dimen/qs_media_icon_size" />
|
||||||
|
|
||||||
<!-- Buttons to remove this view when no longer needed -->
|
<!-- Constraints are set here as they are the same regardless of host -->
|
||||||
<include
|
<TextView
|
||||||
layout="@layout/qs_media_panel_options"
|
android:layout_width="wrap_content"
|
||||||
android:visibility="gone" />
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="@dimen/qs_media_panel_outer_padding"
|
||||||
|
android:layout_marginStart="@dimen/qs_media_panel_outer_padding"
|
||||||
|
android:layout_marginEnd="@dimen/qs_media_panel_outer_padding"
|
||||||
|
android:id="@+id/media_text"
|
||||||
|
android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
|
||||||
|
android:textColor="@color/media_primary_text"
|
||||||
|
android:text="@string/controls_media_title"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintBottom_toTopOf="@id/remove_text"
|
||||||
|
app:layout_constraintVertical_chainStyle="spread_inside"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="@dimen/qs_media_panel_outer_padding"
|
||||||
|
android:layout_marginEnd="@dimen/qs_media_panel_outer_padding"
|
||||||
|
android:id="@+id/remove_text"
|
||||||
|
android:fontFamily="@*android:string/config_headlineFontFamily"
|
||||||
|
android:singleLine="true"
|
||||||
|
android:textColor="@color/media_primary_text"
|
||||||
|
android:text="@string/controls_media_close_session"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/media_text"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintBottom_toTopOf="@id/settings"/>
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:id="@+id/settings"
|
||||||
|
android:background="@drawable/qs_media_light_source"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="@dimen/qs_media_panel_outer_padding"
|
||||||
|
android:paddingBottom="@dimen/qs_media_panel_outer_padding"
|
||||||
|
android:minWidth="48dp"
|
||||||
|
android:minHeight="48dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/remove_text">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_gravity="bottom"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
|
||||||
|
android:textColor="@android:color/white"
|
||||||
|
android:text="@string/controls_media_settings_button" />
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:id="@+id/cancel"
|
||||||
|
android:background="@drawable/qs_media_light_source"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginEnd="@dimen/qs_media_panel_outer_padding"
|
||||||
|
android:paddingBottom="@dimen/qs_media_panel_outer_padding"
|
||||||
|
android:minWidth="48dp"
|
||||||
|
android:minHeight="48dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toStartOf="@id/dismiss" >
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_gravity="bottom"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
|
||||||
|
android:textColor="@android:color/white"
|
||||||
|
android:text="@string/cancel" />
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:id="@+id/dismiss"
|
||||||
|
android:background="@drawable/qs_media_light_source"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginEnd="@dimen/qs_media_panel_outer_padding"
|
||||||
|
android:paddingBottom="@dimen/qs_media_panel_outer_padding"
|
||||||
|
android:minWidth="48dp"
|
||||||
|
android:minHeight="48dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_gravity="bottom"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
|
||||||
|
android:textColor="@android:color/white"
|
||||||
|
android:text="@string/controls_media_dismiss_button"
|
||||||
|
/>
|
||||||
|
</FrameLayout>
|
||||||
</com.android.systemui.util.animation.TransitionLayout>
|
</com.android.systemui.util.animation.TransitionLayout>
|
||||||
|
|||||||
@@ -1,61 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<!--
|
|
||||||
~ Copyright (C) 2019 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
|
|
||||||
-->
|
|
||||||
<LinearLayout
|
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:id="@+id/qs_media_controls_options"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:gravity="center"
|
|
||||||
android:padding="16dp"
|
|
||||||
android:orientation="vertical">
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="48dp"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:minWidth="48dp"
|
|
||||||
android:layout_gravity="start|bottom"
|
|
||||||
android:gravity="bottom"
|
|
||||||
android:id="@+id/remove"
|
|
||||||
android:orientation="horizontal">
|
|
||||||
<ImageView
|
|
||||||
android:layout_width="18dp"
|
|
||||||
android:layout_height="18dp"
|
|
||||||
android:id="@+id/remove_icon"
|
|
||||||
android:layout_marginEnd="16dp"
|
|
||||||
android:tint="@color/media_primary_text"
|
|
||||||
android:src="@drawable/ic_clear"/>
|
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:id="@+id/remove_text"
|
|
||||||
android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
|
|
||||||
android:singleLine="true"
|
|
||||||
android:textColor="@color/media_primary_text"
|
|
||||||
android:text="@string/controls_media_close_session" />
|
|
||||||
</LinearLayout>
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/cancel"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="48dp"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:minWidth="48dp"
|
|
||||||
android:layout_gravity="end|bottom"
|
|
||||||
android:gravity="bottom"
|
|
||||||
android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
|
|
||||||
android:textColor="@android:color/white"
|
|
||||||
android:text="@string/cancel" />
|
|
||||||
</LinearLayout>
|
|
||||||
@@ -2822,7 +2822,7 @@
|
|||||||
<!-- Explanation for closing controls associated with a specific media session [CHAR_LIMIT=NONE] -->
|
<!-- Explanation for closing controls associated with a specific media session [CHAR_LIMIT=NONE] -->
|
||||||
<string name="controls_media_close_session">Hide the current session.</string>
|
<string name="controls_media_close_session">Hide the current session.</string>
|
||||||
<!-- Label for a button that will hide media controls [CHAR_LIMIT=30] -->
|
<!-- Label for a button that will hide media controls [CHAR_LIMIT=30] -->
|
||||||
<string name="controls_media_dismiss_button">Hide</string>
|
<string name="controls_media_dismiss_button">Dismiss</string>
|
||||||
<!-- Label for button to resume media playback [CHAR_LIMIT=NONE] -->
|
<!-- Label for button to resume media playback [CHAR_LIMIT=NONE] -->
|
||||||
<string name="controls_media_resume">Resume</string>
|
<string name="controls_media_resume">Resume</string>
|
||||||
<!-- Label for button to go to media control settings screen [CHAR_LIMIT=30] -->
|
<!-- Label for button to go to media control settings screen [CHAR_LIMIT=30] -->
|
||||||
|
|||||||
@@ -151,7 +151,7 @@ class MediaCarouselController @Inject constructor(
|
|||||||
pageIndicator = mediaFrame.requireViewById(R.id.media_page_indicator)
|
pageIndicator = mediaFrame.requireViewById(R.id.media_page_indicator)
|
||||||
mediaCarouselScrollHandler = MediaCarouselScrollHandler(mediaCarousel, pageIndicator,
|
mediaCarouselScrollHandler = MediaCarouselScrollHandler(mediaCarousel, pageIndicator,
|
||||||
executor, mediaManager::onSwipeToDismiss, this::updatePageIndicatorLocation,
|
executor, mediaManager::onSwipeToDismiss, this::updatePageIndicatorLocation,
|
||||||
falsingManager)
|
this::closeGuts, falsingManager)
|
||||||
isRtl = context.resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_RTL
|
isRtl = context.resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_RTL
|
||||||
inflateSettingsButton()
|
inflateSettingsButton()
|
||||||
mediaContent = mediaCarousel.requireViewById(R.id.media_carousel)
|
mediaContent = mediaCarousel.requireViewById(R.id.media_carousel)
|
||||||
@@ -470,6 +470,12 @@ class MediaCarouselController @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun closeGuts() {
|
||||||
|
mediaPlayers.values.forEach {
|
||||||
|
it.closeGuts(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update the size of the carousel, remeasuring it if necessary.
|
* Update the size of the carousel, remeasuring it if necessary.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ class MediaCarouselScrollHandler(
|
|||||||
private val mainExecutor: DelayableExecutor,
|
private val mainExecutor: DelayableExecutor,
|
||||||
private val dismissCallback: () -> Unit,
|
private val dismissCallback: () -> Unit,
|
||||||
private var translationChangedListener: () -> Unit,
|
private var translationChangedListener: () -> Unit,
|
||||||
|
private val closeGuts: () -> Unit,
|
||||||
private val falsingManager: FalsingManager
|
private val falsingManager: FalsingManager
|
||||||
) {
|
) {
|
||||||
/**
|
/**
|
||||||
@@ -452,6 +453,7 @@ class MediaCarouselScrollHandler(
|
|||||||
val nowScrolledIn = scrollIntoCurrentMedia != 0
|
val nowScrolledIn = scrollIntoCurrentMedia != 0
|
||||||
if (newIndex != activeMediaIndex || wasScrolledIn != nowScrolledIn) {
|
if (newIndex != activeMediaIndex || wasScrolledIn != nowScrolledIn) {
|
||||||
activeMediaIndex = newIndex
|
activeMediaIndex = newIndex
|
||||||
|
closeGuts()
|
||||||
updatePlayerVisibilities()
|
updatePlayerVisibilities()
|
||||||
}
|
}
|
||||||
val relativeLocation = activeMediaIndex.toFloat() + if (playerWidthPlusPadding > 0)
|
val relativeLocation = activeMediaIndex.toFloat() + if (playerWidthPlusPadding > 0)
|
||||||
|
|||||||
@@ -16,6 +16,8 @@
|
|||||||
|
|
||||||
package com.android.systemui.media;
|
package com.android.systemui.media;
|
||||||
|
|
||||||
|
import static android.provider.Settings.ACTION_MEDIA_CONTROLS_SETTINGS;
|
||||||
|
|
||||||
import android.app.PendingIntent;
|
import android.app.PendingIntent;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
@@ -45,6 +47,7 @@ import com.android.settingslib.widget.AdaptiveIcon;
|
|||||||
import com.android.systemui.R;
|
import com.android.systemui.R;
|
||||||
import com.android.systemui.dagger.qualifiers.Background;
|
import com.android.systemui.dagger.qualifiers.Background;
|
||||||
import com.android.systemui.plugins.ActivityStarter;
|
import com.android.systemui.plugins.ActivityStarter;
|
||||||
|
import com.android.systemui.statusbar.phone.KeyguardDismissUtil;
|
||||||
import com.android.systemui.util.animation.TransitionLayout;
|
import com.android.systemui.util.animation.TransitionLayout;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -52,6 +55,8 @@ import java.util.concurrent.Executor;
|
|||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import dagger.Lazy;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A view controller used for Media Playback.
|
* A view controller used for Media Playback.
|
||||||
*/
|
*/
|
||||||
@@ -59,6 +64,8 @@ public class MediaControlPanel {
|
|||||||
private static final String TAG = "MediaControlPanel";
|
private static final String TAG = "MediaControlPanel";
|
||||||
private static final float DISABLED_ALPHA = 0.38f;
|
private static final float DISABLED_ALPHA = 0.38f;
|
||||||
|
|
||||||
|
private static final Intent SETTINGS_INTENT = new Intent(ACTION_MEDIA_CONTROLS_SETTINGS);
|
||||||
|
|
||||||
// Button IDs for QS controls
|
// Button IDs for QS controls
|
||||||
static final int[] ACTION_IDS = {
|
static final int[] ACTION_IDS = {
|
||||||
R.id.action0,
|
R.id.action0,
|
||||||
@@ -78,6 +85,8 @@ public class MediaControlPanel {
|
|||||||
private MediaViewController mMediaViewController;
|
private MediaViewController mMediaViewController;
|
||||||
private MediaSession.Token mToken;
|
private MediaSession.Token mToken;
|
||||||
private MediaController mController;
|
private MediaController mController;
|
||||||
|
private KeyguardDismissUtil mKeyguardDismissUtil;
|
||||||
|
private Lazy<MediaDataManager> mMediaDataManagerLazy;
|
||||||
private int mBackgroundColor;
|
private int mBackgroundColor;
|
||||||
private int mAlbumArtSize;
|
private int mAlbumArtSize;
|
||||||
private int mAlbumArtRadius;
|
private int mAlbumArtRadius;
|
||||||
@@ -93,12 +102,15 @@ public class MediaControlPanel {
|
|||||||
@Inject
|
@Inject
|
||||||
public MediaControlPanel(Context context, @Background Executor backgroundExecutor,
|
public MediaControlPanel(Context context, @Background Executor backgroundExecutor,
|
||||||
ActivityStarter activityStarter, MediaViewController mediaViewController,
|
ActivityStarter activityStarter, MediaViewController mediaViewController,
|
||||||
SeekBarViewModel seekBarViewModel) {
|
SeekBarViewModel seekBarViewModel, Lazy<MediaDataManager> lazyMediaDataManager,
|
||||||
|
KeyguardDismissUtil keyguardDismissUtil) {
|
||||||
mContext = context;
|
mContext = context;
|
||||||
mBackgroundExecutor = backgroundExecutor;
|
mBackgroundExecutor = backgroundExecutor;
|
||||||
mActivityStarter = activityStarter;
|
mActivityStarter = activityStarter;
|
||||||
mSeekBarViewModel = seekBarViewModel;
|
mSeekBarViewModel = seekBarViewModel;
|
||||||
mMediaViewController = mediaViewController;
|
mMediaViewController = mediaViewController;
|
||||||
|
mMediaDataManagerLazy = lazyMediaDataManager;
|
||||||
|
mKeyguardDismissUtil = keyguardDismissUtil;
|
||||||
loadDimens();
|
loadDimens();
|
||||||
|
|
||||||
mViewOutlineProvider = new ViewOutlineProvider() {
|
mViewOutlineProvider = new ViewOutlineProvider() {
|
||||||
@@ -174,6 +186,21 @@ public class MediaControlPanel {
|
|||||||
mSeekBarViewModel.getProgress().observeForever(mSeekBarObserver);
|
mSeekBarViewModel.getProgress().observeForever(mSeekBarObserver);
|
||||||
mSeekBarViewModel.attachTouchHandlers(vh.getSeekBar());
|
mSeekBarViewModel.attachTouchHandlers(vh.getSeekBar());
|
||||||
mMediaViewController.attach(player);
|
mMediaViewController.attach(player);
|
||||||
|
|
||||||
|
mViewHolder.getPlayer().setOnLongClickListener(v -> {
|
||||||
|
if (!mMediaViewController.isGutsVisible()) {
|
||||||
|
mMediaViewController.openGuts();
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
mViewHolder.getCancel().setOnClickListener(v -> {
|
||||||
|
closeGuts();
|
||||||
|
});
|
||||||
|
mViewHolder.getSettings().setOnClickListener(v -> {
|
||||||
|
mActivityStarter.startActivity(SETTINGS_INTENT, true /* dismissShade */);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -205,6 +232,7 @@ public class MediaControlPanel {
|
|||||||
PendingIntent clickIntent = data.getClickIntent();
|
PendingIntent clickIntent = data.getClickIntent();
|
||||||
if (clickIntent != null) {
|
if (clickIntent != null) {
|
||||||
mViewHolder.getPlayer().setOnClickListener(v -> {
|
mViewHolder.getPlayer().setOnClickListener(v -> {
|
||||||
|
if (mMediaViewController.isGutsVisible()) return;
|
||||||
mActivityStarter.postStartActivityDismissingKeyguard(clickIntent);
|
mActivityStarter.postStartActivityDismissingKeyguard(clickIntent);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -329,14 +357,38 @@ public class MediaControlPanel {
|
|||||||
final MediaController controller = getController();
|
final MediaController controller = getController();
|
||||||
mBackgroundExecutor.execute(() -> mSeekBarViewModel.updateController(controller));
|
mBackgroundExecutor.execute(() -> mSeekBarViewModel.updateController(controller));
|
||||||
|
|
||||||
// Set up long press menu
|
// Dismiss
|
||||||
// TODO: b/156036025 bring back media guts
|
mViewHolder.getDismiss().setOnClickListener(v -> {
|
||||||
|
if (data.getNotificationKey() != null) {
|
||||||
|
closeGuts();
|
||||||
|
mKeyguardDismissUtil.executeWhenUnlocked(() -> {
|
||||||
|
mMediaDataManagerLazy.get().dismissMediaData(data.getNotificationKey(),
|
||||||
|
MediaViewController.GUTS_ANIMATION_DURATION + 100);
|
||||||
|
return true;
|
||||||
|
}, /* requiresShadeOpen */ true);
|
||||||
|
} else {
|
||||||
|
Log.w(TAG, "Dismiss media with null notification. Token uid="
|
||||||
|
+ data.getToken().getUid());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// TODO: We don't need to refresh this state constantly, only if the state actually changed
|
// TODO: We don't need to refresh this state constantly, only if the state actually changed
|
||||||
// to something which might impact the measurement
|
// to something which might impact the measurement
|
||||||
mMediaViewController.refreshState();
|
mMediaViewController.refreshState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Close the guts for this player.
|
||||||
|
* @param immediate {@code true} if it should be closed without animation
|
||||||
|
*/
|
||||||
|
public void closeGuts(boolean immediate) {
|
||||||
|
mMediaViewController.closeGuts(immediate);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void closeGuts() {
|
||||||
|
closeGuts(false);
|
||||||
|
}
|
||||||
|
|
||||||
@UiThread
|
@UiThread
|
||||||
private Drawable scaleDrawable(Icon icon) {
|
private Drawable scaleDrawable(Icon icon) {
|
||||||
if (icon == null) {
|
if (icon == null) {
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ import com.android.systemui.statusbar.notification.MediaNotificationProcessor
|
|||||||
import com.android.systemui.statusbar.notification.row.HybridGroupManager
|
import com.android.systemui.statusbar.notification.row.HybridGroupManager
|
||||||
import com.android.systemui.util.Assert
|
import com.android.systemui.util.Assert
|
||||||
import com.android.systemui.util.Utils
|
import com.android.systemui.util.Utils
|
||||||
|
import com.android.systemui.util.concurrency.DelayableExecutor
|
||||||
import java.io.FileDescriptor
|
import java.io.FileDescriptor
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.io.PrintWriter
|
import java.io.PrintWriter
|
||||||
@@ -89,7 +90,7 @@ fun isMediaNotification(sbn: StatusBarNotification): Boolean {
|
|||||||
class MediaDataManager(
|
class MediaDataManager(
|
||||||
private val context: Context,
|
private val context: Context,
|
||||||
@Background private val backgroundExecutor: Executor,
|
@Background private val backgroundExecutor: Executor,
|
||||||
@Main private val foregroundExecutor: Executor,
|
@Main private val foregroundExecutor: DelayableExecutor,
|
||||||
private val mediaControllerFactory: MediaControllerFactory,
|
private val mediaControllerFactory: MediaControllerFactory,
|
||||||
private val broadcastDispatcher: BroadcastDispatcher,
|
private val broadcastDispatcher: BroadcastDispatcher,
|
||||||
dumpManager: DumpManager,
|
dumpManager: DumpManager,
|
||||||
@@ -106,7 +107,7 @@ class MediaDataManager(
|
|||||||
constructor(
|
constructor(
|
||||||
context: Context,
|
context: Context,
|
||||||
@Background backgroundExecutor: Executor,
|
@Background backgroundExecutor: Executor,
|
||||||
@Main foregroundExecutor: Executor,
|
@Main foregroundExecutor: DelayableExecutor,
|
||||||
mediaControllerFactory: MediaControllerFactory,
|
mediaControllerFactory: MediaControllerFactory,
|
||||||
dumpManager: DumpManager,
|
dumpManager: DumpManager,
|
||||||
broadcastDispatcher: BroadcastDispatcher,
|
broadcastDispatcher: BroadcastDispatcher,
|
||||||
@@ -182,10 +183,7 @@ class MediaDataManager(
|
|||||||
val listenersCopy = listeners.toSet()
|
val listenersCopy = listeners.toSet()
|
||||||
val toRemove = mediaEntries.filter { it.value.packageName == packageName }
|
val toRemove = mediaEntries.filter { it.value.packageName == packageName }
|
||||||
toRemove.forEach {
|
toRemove.forEach {
|
||||||
mediaEntries.remove(it.key)
|
removeEntry(it.key, listenersCopy)
|
||||||
listenersCopy.forEach { listener ->
|
|
||||||
listener.onMediaDataRemoved(it.key)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -267,6 +265,18 @@ class MediaDataManager(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun removeEntry(key: String, listenersCopy: Set<Listener>) {
|
||||||
|
mediaEntries.remove(key)
|
||||||
|
listenersCopy.forEach {
|
||||||
|
it.onMediaDataRemoved(key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun dismissMediaData(key: String, delay: Long) {
|
||||||
|
val listenersCopy = listeners.toSet()
|
||||||
|
foregroundExecutor.executeDelayed({ removeEntry(key, listenersCopy) }, delay)
|
||||||
|
}
|
||||||
|
|
||||||
private fun loadMediaDataInBgForResumption(
|
private fun loadMediaDataInBgForResumption(
|
||||||
userId: Int,
|
userId: Int,
|
||||||
desc: MediaDescription,
|
desc: MediaDescription,
|
||||||
|
|||||||
@@ -293,6 +293,13 @@ class MediaHierarchyManager @Inject constructor(
|
|||||||
return viewHost
|
return viewHost
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Close the guts in all players in [MediaCarouselController].
|
||||||
|
*/
|
||||||
|
fun closeGuts() {
|
||||||
|
mediaCarouselController.closeGuts()
|
||||||
|
}
|
||||||
|
|
||||||
private fun createUniqueObjectHost(): UniqueObjectHostView {
|
private fun createUniqueObjectHost(): UniqueObjectHostView {
|
||||||
val viewHost = UniqueObjectHostView(context)
|
val viewHost = UniqueObjectHostView(context)
|
||||||
viewHost.addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener {
|
viewHost.addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener {
|
||||||
|
|||||||
@@ -37,6 +37,11 @@ class MediaViewController @Inject constructor(
|
|||||||
private val mediaHostStatesManager: MediaHostStatesManager
|
private val mediaHostStatesManager: MediaHostStatesManager
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
@JvmField
|
||||||
|
val GUTS_ANIMATION_DURATION = 500L
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A listener when the current dimensions of the player change
|
* A listener when the current dimensions of the player change
|
||||||
*/
|
*/
|
||||||
@@ -169,6 +174,12 @@ class MediaViewController @Inject constructor(
|
|||||||
*/
|
*/
|
||||||
val expandedLayout = ConstraintSet()
|
val expandedLayout = ConstraintSet()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the guts are visible for the associated player.
|
||||||
|
*/
|
||||||
|
var isGutsVisible = false
|
||||||
|
private set
|
||||||
|
|
||||||
init {
|
init {
|
||||||
collapsedLayout.load(context, R.xml.media_collapsed)
|
collapsedLayout.load(context, R.xml.media_collapsed)
|
||||||
expandedLayout.load(context, R.xml.media_expanded)
|
expandedLayout.load(context, R.xml.media_expanded)
|
||||||
@@ -189,6 +200,37 @@ class MediaViewController @Inject constructor(
|
|||||||
configurationController.removeCallback(configurationListener)
|
configurationController.removeCallback(configurationListener)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show guts with an animated transition.
|
||||||
|
*/
|
||||||
|
fun openGuts() {
|
||||||
|
if (isGutsVisible) return
|
||||||
|
isGutsVisible = true
|
||||||
|
animatePendingStateChange(GUTS_ANIMATION_DURATION, 0L)
|
||||||
|
setCurrentState(currentStartLocation,
|
||||||
|
currentEndLocation,
|
||||||
|
currentTransitionProgress,
|
||||||
|
applyImmediately = false)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Close the guts for the associated player.
|
||||||
|
*
|
||||||
|
* @param immediate if `false`, it will animate the transition.
|
||||||
|
*/
|
||||||
|
@JvmOverloads
|
||||||
|
fun closeGuts(immediate: Boolean = false) {
|
||||||
|
if (!isGutsVisible) return
|
||||||
|
isGutsVisible = false
|
||||||
|
if (!immediate) {
|
||||||
|
animatePendingStateChange(GUTS_ANIMATION_DURATION, 0L)
|
||||||
|
}
|
||||||
|
setCurrentState(currentStartLocation,
|
||||||
|
currentEndLocation,
|
||||||
|
currentTransitionProgress,
|
||||||
|
applyImmediately = immediate)
|
||||||
|
}
|
||||||
|
|
||||||
private fun ensureAllMeasurements() {
|
private fun ensureAllMeasurements() {
|
||||||
val mediaStates = mediaHostStatesManager.mediaHostStates
|
val mediaStates = mediaHostStatesManager.mediaHostStates
|
||||||
for (entry in mediaStates) {
|
for (entry in mediaStates) {
|
||||||
@@ -202,6 +244,24 @@ class MediaViewController @Inject constructor(
|
|||||||
private fun constraintSetForExpansion(expansion: Float): ConstraintSet =
|
private fun constraintSetForExpansion(expansion: Float): ConstraintSet =
|
||||||
if (expansion > 0) expandedLayout else collapsedLayout
|
if (expansion > 0) expandedLayout else collapsedLayout
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the views to be showing/hidden based on the [isGutsVisible] for a given
|
||||||
|
* [TransitionViewState].
|
||||||
|
*/
|
||||||
|
private fun setGutsViewState(viewState: TransitionViewState) {
|
||||||
|
PlayerViewHolder.controlsIds.forEach { id ->
|
||||||
|
viewState.widgetStates.get(id)?.let { state ->
|
||||||
|
// Make sure to use the unmodified state if guts are not visible
|
||||||
|
state.alpha = if (isGutsVisible) 0f else state.alpha
|
||||||
|
state.gone = if (isGutsVisible) true else state.gone
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PlayerViewHolder.gutsIds.forEach { id ->
|
||||||
|
viewState.widgetStates.get(id)?.alpha = if (isGutsVisible) 1f else 0f
|
||||||
|
viewState.widgetStates.get(id)?.gone = !isGutsVisible
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Obtain a new viewState for a given media state. This usually returns a cached state, but if
|
* Obtain a new viewState for a given media state. This usually returns a cached state, but if
|
||||||
* it's not available, it will recreate one by measuring, which may be expensive.
|
* it's not available, it will recreate one by measuring, which may be expensive.
|
||||||
@@ -211,7 +271,7 @@ class MediaViewController @Inject constructor(
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
// Only a subset of the state is relevant to get a valid viewState. Let's get the cachekey
|
// Only a subset of the state is relevant to get a valid viewState. Let's get the cachekey
|
||||||
var cacheKey = getKey(state, tmpKey)
|
var cacheKey = getKey(state, isGutsVisible, tmpKey)
|
||||||
val viewState = viewStates[cacheKey]
|
val viewState = viewStates[cacheKey]
|
||||||
if (viewState != null) {
|
if (viewState != null) {
|
||||||
// we already have cached this measurement, let's continue
|
// we already have cached this measurement, let's continue
|
||||||
@@ -228,6 +288,7 @@ class MediaViewController @Inject constructor(
|
|||||||
constraintSetForExpansion(state.expansion),
|
constraintSetForExpansion(state.expansion),
|
||||||
TransitionViewState())
|
TransitionViewState())
|
||||||
|
|
||||||
|
setGutsViewState(result)
|
||||||
// We don't want to cache interpolated or null states as this could quickly fill up
|
// We don't want to cache interpolated or null states as this could quickly fill up
|
||||||
// our cache. We only cache the start and the end states since the interpolation
|
// our cache. We only cache the start and the end states since the interpolation
|
||||||
// is cheap
|
// is cheap
|
||||||
@@ -252,11 +313,12 @@ class MediaViewController @Inject constructor(
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getKey(state: MediaHostState, result: CacheKey): CacheKey {
|
private fun getKey(state: MediaHostState, guts: Boolean, result: CacheKey): CacheKey {
|
||||||
result.apply {
|
result.apply {
|
||||||
heightMeasureSpec = state.measurementInput?.heightMeasureSpec ?: 0
|
heightMeasureSpec = state.measurementInput?.heightMeasureSpec ?: 0
|
||||||
widthMeasureSpec = state.measurementInput?.widthMeasureSpec ?: 0
|
widthMeasureSpec = state.measurementInput?.widthMeasureSpec ?: 0
|
||||||
expansion = state.expansion
|
expansion = state.expansion
|
||||||
|
gutsVisible = guts
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
@@ -432,5 +494,6 @@ class MediaViewController @Inject constructor(
|
|||||||
private data class CacheKey(
|
private data class CacheKey(
|
||||||
var widthMeasureSpec: Int = -1,
|
var widthMeasureSpec: Int = -1,
|
||||||
var heightMeasureSpec: Int = -1,
|
var heightMeasureSpec: Int = -1,
|
||||||
var expansion: Float = 0.0f
|
var expansion: Float = 0.0f,
|
||||||
|
var gutsVisible: Boolean = false
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -59,6 +59,11 @@ class PlayerViewHolder private constructor(itemView: View) {
|
|||||||
val action3 = itemView.requireViewById<ImageButton>(R.id.action3)
|
val action3 = itemView.requireViewById<ImageButton>(R.id.action3)
|
||||||
val action4 = itemView.requireViewById<ImageButton>(R.id.action4)
|
val action4 = itemView.requireViewById<ImageButton>(R.id.action4)
|
||||||
|
|
||||||
|
// Settings screen
|
||||||
|
val cancel = itemView.requireViewById<View>(R.id.cancel)
|
||||||
|
val dismiss = itemView.requireViewById<View>(R.id.dismiss)
|
||||||
|
val settings = itemView.requireViewById<View>(R.id.settings)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
(player.background as IlluminationDrawable).let {
|
(player.background as IlluminationDrawable).let {
|
||||||
it.registerLightSource(seamless)
|
it.registerLightSource(seamless)
|
||||||
@@ -67,6 +72,9 @@ class PlayerViewHolder private constructor(itemView: View) {
|
|||||||
it.registerLightSource(action2)
|
it.registerLightSource(action2)
|
||||||
it.registerLightSource(action3)
|
it.registerLightSource(action3)
|
||||||
it.registerLightSource(action4)
|
it.registerLightSource(action4)
|
||||||
|
it.registerLightSource(cancel)
|
||||||
|
it.registerLightSource(dismiss)
|
||||||
|
it.registerLightSource(settings)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,9 +91,6 @@ class PlayerViewHolder private constructor(itemView: View) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Settings screen
|
|
||||||
val options = itemView.requireViewById<View>(R.id.qs_media_controls_options)
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
/**
|
/**
|
||||||
* Creates a PlayerViewHolder.
|
* Creates a PlayerViewHolder.
|
||||||
@@ -105,5 +110,29 @@ class PlayerViewHolder private constructor(itemView: View) {
|
|||||||
progressTimes.layoutDirection = View.LAYOUT_DIRECTION_LTR
|
progressTimes.layoutDirection = View.LAYOUT_DIRECTION_LTR
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val controlsIds = setOf(
|
||||||
|
R.id.icon,
|
||||||
|
R.id.app_name,
|
||||||
|
R.id.album_art,
|
||||||
|
R.id.header_title,
|
||||||
|
R.id.header_artist,
|
||||||
|
R.id.media_seamless,
|
||||||
|
R.id.notification_media_progress_time,
|
||||||
|
R.id.media_progress_bar,
|
||||||
|
R.id.action0,
|
||||||
|
R.id.action1,
|
||||||
|
R.id.action2,
|
||||||
|
R.id.action3,
|
||||||
|
R.id.action4,
|
||||||
|
R.id.icon
|
||||||
|
)
|
||||||
|
val gutsIds = setOf(
|
||||||
|
R.id.media_text,
|
||||||
|
R.id.remove_text,
|
||||||
|
R.id.cancel,
|
||||||
|
R.id.dismiss,
|
||||||
|
R.id.settings
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2612,6 +2612,7 @@ public class NotificationPanelViewController extends PanelViewController {
|
|||||||
super.onClosingFinished();
|
super.onClosingFinished();
|
||||||
resetHorizontalPanelPosition();
|
resetHorizontalPanelPosition();
|
||||||
setClosingWithAlphaFadeout(false);
|
setClosingWithAlphaFadeout(false);
|
||||||
|
mMediaHierarchyManager.closeGuts();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setClosingWithAlphaFadeout(boolean closing) {
|
private void setClosingWithAlphaFadeout(boolean closing) {
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
package com.android.systemui.media
|
package com.android.systemui.media
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
import android.content.res.ColorStateList
|
import android.content.res.ColorStateList
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
import android.graphics.drawable.GradientDrawable
|
import android.graphics.drawable.GradientDrawable
|
||||||
@@ -23,6 +24,7 @@ import android.graphics.drawable.RippleDrawable
|
|||||||
import android.media.MediaMetadata
|
import android.media.MediaMetadata
|
||||||
import android.media.session.MediaSession
|
import android.media.session.MediaSession
|
||||||
import android.media.session.PlaybackState
|
import android.media.session.PlaybackState
|
||||||
|
import android.provider.Settings.ACTION_MEDIA_CONTROLS_SETTINGS
|
||||||
import android.testing.AndroidTestingRunner
|
import android.testing.AndroidTestingRunner
|
||||||
import android.testing.TestableLooper
|
import android.testing.TestableLooper
|
||||||
import android.view.View
|
import android.view.View
|
||||||
@@ -35,24 +37,31 @@ import android.widget.TextView
|
|||||||
import androidx.constraintlayout.widget.ConstraintSet
|
import androidx.constraintlayout.widget.ConstraintSet
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import androidx.test.filters.SmallTest
|
import androidx.test.filters.SmallTest
|
||||||
import com.android.systemui.R
|
|
||||||
import com.android.systemui.SysuiTestCase
|
import com.android.systemui.SysuiTestCase
|
||||||
import com.android.systemui.plugins.ActivityStarter
|
import com.android.systemui.plugins.ActivityStarter
|
||||||
|
import com.android.systemui.statusbar.phone.KeyguardDismissUtil
|
||||||
import com.android.systemui.util.animation.TransitionLayout
|
import com.android.systemui.util.animation.TransitionLayout
|
||||||
import com.android.systemui.util.concurrency.FakeExecutor
|
import com.android.systemui.util.concurrency.FakeExecutor
|
||||||
|
import com.android.systemui.util.mockito.eq
|
||||||
|
import com.android.systemui.util.mockito.any
|
||||||
import com.android.systemui.util.time.FakeSystemClock
|
import com.android.systemui.util.time.FakeSystemClock
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
|
import dagger.Lazy
|
||||||
import org.junit.After
|
import org.junit.After
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
import org.mockito.ArgumentCaptor
|
import org.mockito.ArgumentCaptor
|
||||||
|
import org.mockito.ArgumentMatchers
|
||||||
|
import org.mockito.ArgumentMatchers.anyLong
|
||||||
import org.mockito.Mock
|
import org.mockito.Mock
|
||||||
|
import org.mockito.Mockito.anyBoolean
|
||||||
import org.mockito.Mockito.mock
|
import org.mockito.Mockito.mock
|
||||||
|
import org.mockito.Mockito.never
|
||||||
import org.mockito.Mockito.verify
|
import org.mockito.Mockito.verify
|
||||||
import org.mockito.Mockito.`when` as whenever
|
|
||||||
import org.mockito.junit.MockitoJUnit
|
import org.mockito.junit.MockitoJUnit
|
||||||
|
import org.mockito.Mockito.`when` as whenever
|
||||||
|
|
||||||
private const val KEY = "TEST_KEY"
|
private const val KEY = "TEST_KEY"
|
||||||
private const val APP = "APP"
|
private const val APP = "APP"
|
||||||
@@ -81,6 +90,8 @@ public class MediaControlPanelTest : SysuiTestCase() {
|
|||||||
@Mock private lateinit var seekBarViewModel: SeekBarViewModel
|
@Mock private lateinit var seekBarViewModel: SeekBarViewModel
|
||||||
@Mock private lateinit var seekBarData: LiveData<SeekBarViewModel.Progress>
|
@Mock private lateinit var seekBarData: LiveData<SeekBarViewModel.Progress>
|
||||||
@Mock private lateinit var mediaViewController: MediaViewController
|
@Mock private lateinit var mediaViewController: MediaViewController
|
||||||
|
@Mock private lateinit var keyguardDismissUtil: KeyguardDismissUtil
|
||||||
|
@Mock private lateinit var mediaDataManager: MediaDataManager
|
||||||
@Mock private lateinit var expandedSet: ConstraintSet
|
@Mock private lateinit var expandedSet: ConstraintSet
|
||||||
@Mock private lateinit var collapsedSet: ConstraintSet
|
@Mock private lateinit var collapsedSet: ConstraintSet
|
||||||
private lateinit var appIcon: ImageView
|
private lateinit var appIcon: ImageView
|
||||||
@@ -100,6 +111,9 @@ public class MediaControlPanelTest : SysuiTestCase() {
|
|||||||
private lateinit var action2: ImageButton
|
private lateinit var action2: ImageButton
|
||||||
private lateinit var action3: ImageButton
|
private lateinit var action3: ImageButton
|
||||||
private lateinit var action4: ImageButton
|
private lateinit var action4: ImageButton
|
||||||
|
private lateinit var settings: View
|
||||||
|
private lateinit var cancel: View
|
||||||
|
private lateinit var dismiss: View
|
||||||
|
|
||||||
private lateinit var session: MediaSession
|
private lateinit var session: MediaSession
|
||||||
private val device = MediaDeviceData(true, null, DEVICE_NAME)
|
private val device = MediaDeviceData(true, null, DEVICE_NAME)
|
||||||
@@ -114,7 +128,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
|
|||||||
whenever(mediaViewController.collapsedLayout).thenReturn(collapsedSet)
|
whenever(mediaViewController.collapsedLayout).thenReturn(collapsedSet)
|
||||||
|
|
||||||
player = MediaControlPanel(context, bgExecutor, activityStarter, mediaViewController,
|
player = MediaControlPanel(context, bgExecutor, activityStarter, mediaViewController,
|
||||||
seekBarViewModel)
|
seekBarViewModel, Lazy { mediaDataManager }, keyguardDismissUtil)
|
||||||
whenever(seekBarViewModel.progress).thenReturn(seekBarData)
|
whenever(seekBarViewModel.progress).thenReturn(seekBarData)
|
||||||
|
|
||||||
// Mock out a view holder for the player to attach to.
|
// Mock out a view holder for the player to attach to.
|
||||||
@@ -156,6 +170,12 @@ public class MediaControlPanelTest : SysuiTestCase() {
|
|||||||
whenever(holder.action3).thenReturn(action3)
|
whenever(holder.action3).thenReturn(action3)
|
||||||
action4 = ImageButton(context)
|
action4 = ImageButton(context)
|
||||||
whenever(holder.action4).thenReturn(action4)
|
whenever(holder.action4).thenReturn(action4)
|
||||||
|
settings = View(context)
|
||||||
|
whenever(holder.settings).thenReturn(settings)
|
||||||
|
cancel = View(context)
|
||||||
|
whenever(holder.cancel).thenReturn(cancel)
|
||||||
|
dismiss = View(context)
|
||||||
|
whenever(holder.dismiss).thenReturn(dismiss)
|
||||||
|
|
||||||
// Create media session
|
// Create media session
|
||||||
val metadataBuilder = MediaMetadata.Builder().apply {
|
val metadataBuilder = MediaMetadata.Builder().apply {
|
||||||
@@ -254,4 +274,79 @@ public class MediaControlPanelTest : SysuiTestCase() {
|
|||||||
assertThat(seamlessText.getText()).isEqualTo(DEVICE_NAME)
|
assertThat(seamlessText.getText()).isEqualTo(DEVICE_NAME)
|
||||||
assertThat(seamless.isEnabled()).isFalse()
|
assertThat(seamless.isEnabled()).isFalse()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun longClick_gutsClosed() {
|
||||||
|
player.attach(holder)
|
||||||
|
whenever(mediaViewController.isGutsVisible).thenReturn(false)
|
||||||
|
|
||||||
|
val captor = ArgumentCaptor.forClass(View.OnLongClickListener::class.java)
|
||||||
|
verify(holder.player).setOnLongClickListener(captor.capture())
|
||||||
|
|
||||||
|
captor.value.onLongClick(holder.player)
|
||||||
|
verify(mediaViewController).openGuts()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun longClick_gutsOpen() {
|
||||||
|
player.attach(holder)
|
||||||
|
whenever(mediaViewController.isGutsVisible).thenReturn(true)
|
||||||
|
|
||||||
|
val captor = ArgumentCaptor.forClass(View.OnLongClickListener::class.java)
|
||||||
|
verify(holder.player).setOnLongClickListener(captor.capture())
|
||||||
|
|
||||||
|
captor.value.onLongClick(holder.player)
|
||||||
|
verify(mediaViewController, never()).openGuts()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun cancelButtonClick_animation() {
|
||||||
|
player.attach(holder)
|
||||||
|
|
||||||
|
cancel.callOnClick()
|
||||||
|
|
||||||
|
verify(mediaViewController).closeGuts(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun settingsButtonClick() {
|
||||||
|
player.attach(holder)
|
||||||
|
|
||||||
|
settings.callOnClick()
|
||||||
|
|
||||||
|
val captor = ArgumentCaptor.forClass(Intent::class.java)
|
||||||
|
verify(activityStarter).startActivity(captor.capture(), eq(true))
|
||||||
|
|
||||||
|
assertThat(captor.value.action).isEqualTo(ACTION_MEDIA_CONTROLS_SETTINGS)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun dismissButtonClick() {
|
||||||
|
player.attach(holder)
|
||||||
|
val state = MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
|
||||||
|
emptyList(), PACKAGE, session.getSessionToken(), null, null, true, null,
|
||||||
|
notificationKey = KEY)
|
||||||
|
player.bind(state)
|
||||||
|
|
||||||
|
dismiss.callOnClick()
|
||||||
|
val captor = ArgumentCaptor.forClass(ActivityStarter.OnDismissAction::class.java)
|
||||||
|
verify(keyguardDismissUtil).executeWhenUnlocked(captor.capture(), anyBoolean())
|
||||||
|
|
||||||
|
captor.value.onDismiss()
|
||||||
|
verify(mediaDataManager).dismissMediaData(eq(KEY), anyLong())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun dismissButtonClick_nullNotificationKey() {
|
||||||
|
player.attach(holder)
|
||||||
|
val state = MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
|
||||||
|
emptyList(), PACKAGE, session.getSessionToken(), null, null, true, null)
|
||||||
|
player.bind(state)
|
||||||
|
|
||||||
|
verify(keyguardDismissUtil, never())
|
||||||
|
.executeWhenUnlocked(
|
||||||
|
any(ActivityStarter.OnDismissAction::class.java),
|
||||||
|
ArgumentMatchers.anyBoolean()
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -217,6 +217,20 @@ class MediaDataManagerTest : SysuiTestCase() {
|
|||||||
assertThat(data.actions).hasSize(1)
|
assertThat(data.actions).hasSize(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testDismissMedia_listenerCalled() {
|
||||||
|
val listener = mock(MediaDataManager.Listener::class.java)
|
||||||
|
mediaDataManager.addListener(listener)
|
||||||
|
mediaDataManager.onNotificationAdded(KEY, mediaNotification)
|
||||||
|
mediaDataManager.onMediaDataLoaded(KEY, oldKey = null, data = mock(MediaData::class.java))
|
||||||
|
mediaDataManager.dismissMediaData(KEY, 0L)
|
||||||
|
|
||||||
|
foregroundExecutor.advanceClockToLast()
|
||||||
|
foregroundExecutor.runAllReady()
|
||||||
|
|
||||||
|
verify(listener).onMediaDataRemoved(eq(KEY))
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Simple implementation of [MediaDataManager.Listener] for the test.
|
* Simple implementation of [MediaDataManager.Listener] for the test.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -142,4 +142,11 @@ class MediaHierarchyManagerTest : SysuiTestCase() {
|
|||||||
verify(mediaCarouselController).onDesiredLocationChanged(ArgumentMatchers.anyInt(),
|
verify(mediaCarouselController).onDesiredLocationChanged(ArgumentMatchers.anyInt(),
|
||||||
any(MediaHostState::class.java), anyBoolean(), anyLong(), anyLong())
|
any(MediaHostState::class.java), anyBoolean(), anyLong(), anyLong())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testCloseGutsRelayToCarousel() {
|
||||||
|
mediaHiearchyManager.closeGuts()
|
||||||
|
|
||||||
|
verify(mediaCarouselController).closeGuts()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user