diff --git a/packages/SystemUI/res/layout/keyguard_media_header.xml b/packages/SystemUI/res/layout/keyguard_media_header.xml
index 20ec10ca1e1bb..a520719566ab8 100644
--- a/packages/SystemUI/res/layout/keyguard_media_header.xml
+++ b/packages/SystemUI/res/layout/keyguard_media_header.xml
@@ -45,109 +45,4 @@
android:layout_height="match_parent"
/>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/packages/SystemUI/res/layout/media_carousel.xml b/packages/SystemUI/res/layout/media_carousel.xml
index 149446c55fc5a..f5fc67d6856c1 100644
--- a/packages/SystemUI/res/layout/media_carousel.xml
+++ b/packages/SystemUI/res/layout/media_carousel.xml
@@ -22,11 +22,10 @@
android:layout_height="wrap_content"
android:padding="@dimen/qs_media_padding"
android:scrollbars="none"
- android:visibility="gone"
>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardMediaPlayer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardMediaPlayer.java
deleted file mode 100644
index af5196f92bcba..0000000000000
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardMediaPlayer.java
+++ /dev/null
@@ -1,381 +0,0 @@
-/*
- * 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.keyguard;
-
-import android.app.Notification;
-import android.app.PendingIntent;
-import android.content.Context;
-import android.content.res.ColorStateList;
-import android.graphics.Bitmap;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.Icon;
-import android.media.MediaMetadata;
-import android.media.session.MediaController;
-import android.media.session.MediaSession;
-import android.util.Log;
-import android.view.View;
-import android.widget.ImageButton;
-import android.widget.ImageView;
-import android.widget.TextView;
-
-import androidx.core.graphics.drawable.RoundedBitmapDrawable;
-import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory;
-import androidx.lifecycle.LiveData;
-import androidx.lifecycle.MutableLiveData;
-import androidx.lifecycle.Observer;
-import androidx.palette.graphics.Palette;
-
-import com.android.internal.util.ContrastColorUtil;
-import com.android.systemui.R;
-import com.android.systemui.dagger.qualifiers.Background;
-import com.android.systemui.media.MediaControllerFactory;
-import com.android.systemui.statusbar.notification.MediaNotificationProcessor;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.stack.MediaHeaderView;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.Executor;
-
-import javax.inject.Inject;
-import javax.inject.Singleton;
-
-/**
- * Media controls to display on the lockscreen
- *
- * TODO: Should extend MediaControlPanel to avoid code duplication.
- * Unfortunately, it isn't currently possible because the ActivatableNotificationView background is
- * different.
- */
-@Singleton
-public class KeyguardMediaPlayer {
-
- private static final String TAG = "KeyguardMediaPlayer";
- // Buttons that can be displayed on lock screen media controls.
- private static final int[] ACTION_IDS = {R.id.action0, R.id.action1, R.id.action2};
-
- private final Context mContext;
- private final Executor mBackgroundExecutor;
- private final KeyguardMediaViewModel mViewModel;
- private KeyguardMediaObserver mObserver;
-
- @Inject
- public KeyguardMediaPlayer(Context context, MediaControllerFactory factory,
- @Background Executor backgroundExecutor) {
- mContext = context;
- mBackgroundExecutor = backgroundExecutor;
- mViewModel = new KeyguardMediaViewModel(context, factory);
- }
-
- /** Binds media controls to a view hierarchy. */
- public void bindView(View v) {
- if (mObserver != null) {
- throw new IllegalStateException("cannot bind views, already bound");
- }
- mViewModel.loadDimens();
- mObserver = new KeyguardMediaObserver(v);
- // Control buttons
- for (int i = 0; i < ACTION_IDS.length; i++) {
- ImageButton button = v.findViewById(ACTION_IDS[i]);
- if (button == null) {
- continue;
- }
- final int index = i;
- button.setOnClickListener(unused -> mViewModel.onActionClick(index));
- }
- mViewModel.getKeyguardMedia().observeForever(mObserver);
- }
-
- /** Unbinds media controls. */
- public void unbindView() {
- if (mObserver == null) {
- throw new IllegalStateException("cannot unbind views, nothing bound");
- }
- mViewModel.getKeyguardMedia().removeObserver(mObserver);
- mObserver = null;
- }
-
- /** Clear the media controls because there isn't an active session. */
- public void clearControls() {
- mBackgroundExecutor.execute(mViewModel::clearControls);
- }
-
- /**
- * Update the media player
- *
- * TODO: consider registering a MediaLister instead of exposing this update method.
- *
- * @param entry Media notification that will be used to update the player
- * @param appIcon Icon for the app playing the media
- * @param mediaMetadata Media metadata that will be used to update the player
- */
- public void updateControls(NotificationEntry entry, Icon appIcon,
- MediaMetadata mediaMetadata) {
- if (mObserver == null) {
- throw new IllegalStateException("cannot update controls, views not bound");
- }
- if (mediaMetadata == null) {
- Log.d(TAG, "media metadata was null, closing media controls");
- // Note that clearControls() executes on the same background executor, so there
- // shouldn't be an issue with an outdated update running after clear. However, if stale
- // controls are observed then consider removing any enqueued updates.
- clearControls();
- return;
- }
- mBackgroundExecutor.execute(() -> mViewModel.updateControls(entry, appIcon, mediaMetadata));
- }
-
- /** ViewModel for KeyguardMediaControls. */
- private static final class KeyguardMediaViewModel {
-
- private final Context mContext;
- private final MediaControllerFactory mMediaControllerFactory;
- private final MutableLiveData mMedia = new MutableLiveData<>();
- private final Object mActionsLock = new Object();
- private List mActions;
- private float mAlbumArtRadius;
- private int mAlbumArtSize;
-
- KeyguardMediaViewModel(Context context, MediaControllerFactory factory) {
- mContext = context;
- mMediaControllerFactory = factory;
- loadDimens();
- }
-
- /** Close the media player because there isn't an active session. */
- public void clearControls() {
- synchronized (mActionsLock) {
- mActions = null;
- }
- mMedia.postValue(null);
- }
-
- /** Update the media player with information about the active session. */
- public void updateControls(NotificationEntry entry, Icon appIcon,
- MediaMetadata mediaMetadata) {
-
- // Check the playback state of the media controller. If it is null, then the session was
- // probably destroyed. Don't update in this case.
- final MediaSession.Token token = entry.getSbn().getNotification().extras
- .getParcelable(Notification.EXTRA_MEDIA_SESSION);
- final MediaController controller = token != null
- ? mMediaControllerFactory.create(token) : null;
- if (controller != null && controller.getPlaybackState() == null) {
- clearControls();
- return;
- }
-
- // Foreground and Background colors computed from album art
- Notification notif = entry.getSbn().getNotification();
- int fgColor = notif.color;
- int bgColor = entry.getRow() == null ? -1 : entry.getRow().getCurrentBackgroundTint();
- Bitmap artworkBitmap = mediaMetadata.getBitmap(MediaMetadata.METADATA_KEY_ART);
- if (artworkBitmap == null) {
- artworkBitmap = mediaMetadata.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART);
- }
- if (artworkBitmap != null) {
- // If we have art, get colors from that
- Palette p = MediaNotificationProcessor.generateArtworkPaletteBuilder(artworkBitmap)
- .generate();
- Palette.Swatch swatch = MediaNotificationProcessor.findBackgroundSwatch(p);
- bgColor = swatch.getRgb();
- fgColor = MediaNotificationProcessor.selectForegroundColor(bgColor, p);
- }
- // Make sure colors will be legible
- boolean isDark = !ContrastColorUtil.isColorLight(bgColor);
- fgColor = ContrastColorUtil.resolveContrastColor(mContext, fgColor, bgColor,
- isDark);
- fgColor = ContrastColorUtil.ensureTextContrast(fgColor, bgColor, isDark);
-
- // Album art
- RoundedBitmapDrawable artwork = null;
- if (artworkBitmap != null) {
- Bitmap original = artworkBitmap.copy(Bitmap.Config.ARGB_8888, true);
- Bitmap scaled = Bitmap.createScaledBitmap(original, mAlbumArtSize, mAlbumArtSize,
- false);
- artwork = RoundedBitmapDrawableFactory.create(mContext.getResources(), scaled);
- artwork.setCornerRadius(mAlbumArtRadius);
- }
-
- // App name
- Notification.Builder builder = Notification.Builder.recoverBuilder(mContext, notif);
- String app = builder.loadHeaderAppName();
-
- // App Icon
- Drawable appIconDrawable = appIcon.loadDrawable(mContext);
-
- // Song name
- String song = mediaMetadata.getString(MediaMetadata.METADATA_KEY_TITLE);
-
- // Artist name
- String artist = mediaMetadata.getString(MediaMetadata.METADATA_KEY_ARTIST);
-
- // Control buttons
- List actionIcons = new ArrayList<>();
- final List intents = new ArrayList<>();
- Notification.Action[] actions = notif.actions;
- final int[] actionsToShow = notif.extras.getIntArray(
- Notification.EXTRA_COMPACT_ACTIONS);
-
- Context packageContext = entry.getSbn().getPackageContext(mContext);
- for (int i = 0; i < ACTION_IDS.length; i++) {
- if (actionsToShow != null && actions != null && i < actionsToShow.length
- && actionsToShow[i] < actions.length) {
- final int idx = actionsToShow[i];
- actionIcons.add(actions[idx].getIcon().loadDrawable(packageContext));
- intents.add(actions[idx].actionIntent);
- } else {
- actionIcons.add(null);
- intents.add(null);
- }
- }
- synchronized (mActionsLock) {
- mActions = intents;
- }
-
- KeyguardMedia data = new KeyguardMedia(fgColor, bgColor, app, appIconDrawable, artist,
- song, artwork, actionIcons);
- mMedia.postValue(data);
- }
-
- /** Gets state for the lock screen media controls. */
- public LiveData getKeyguardMedia() {
- return mMedia;
- }
-
- /**
- * Handle user clicks on media control buttons (actions).
- *
- * @param index position of the button that was clicked.
- */
- public void onActionClick(int index) {
- PendingIntent intent = null;
- // This might block the ui thread to wait for the lock. Currently, however, the
- // lock is held by the bg thread to assign a member, which should be fast. An
- // alternative could be to add the intents to the state and let the observer set
- // the onClick listeners.
- synchronized (mActionsLock) {
- if (mActions != null && index < mActions.size()) {
- intent = mActions.get(index);
- }
- }
- if (intent != null) {
- try {
- intent.send();
- } catch (PendingIntent.CanceledException e) {
- Log.d(TAG, "failed to send action intent", e);
- }
- }
- }
-
- void loadDimens() {
- mAlbumArtRadius = mContext.getResources().getDimension(R.dimen.qs_media_corner_radius);
- mAlbumArtSize = (int) mContext.getResources().getDimension(
- R.dimen.qs_media_album_size);
- }
- }
-
- /** Observer for state changes of lock screen media controls. */
- private static final class KeyguardMediaObserver implements Observer {
-
- private final View mRootView;
- private final MediaHeaderView mMediaHeaderView;
- private final ImageView mAlbumView;
- private final ImageView mAppIconView;
- private final TextView mAppNameView;
- private final TextView mTitleView;
- private final TextView mArtistView;
- private final List mButtonViews = new ArrayList<>();
-
- KeyguardMediaObserver(View v) {
- mRootView = v;
- mMediaHeaderView = v instanceof MediaHeaderView ? (MediaHeaderView) v : null;
- mAlbumView = v.findViewById(R.id.album_art);
- mAppIconView = v.findViewById(R.id.icon);
- mAppNameView = v.findViewById(R.id.app_name);
- mTitleView = v.findViewById(R.id.header_title);
- mArtistView = v.findViewById(R.id.header_artist);
- for (int i = 0; i < ACTION_IDS.length; i++) {
- mButtonViews.add(v.findViewById(ACTION_IDS[i]));
- }
- }
-
- /** Updates lock screen media player views when state changes. */
- @Override
- public void onChanged(KeyguardMedia data) {
- if (data == null) {
- mRootView.setVisibility(View.GONE);
- return;
- }
- mRootView.setVisibility(View.VISIBLE);
-
- // Background color
- if (mMediaHeaderView != null) {
- mMediaHeaderView.setBackgroundColor(data.getBackgroundColor());
- }
-
- // Album art
- if (mAlbumView != null) {
- mAlbumView.setImageDrawable(data.getArtwork());
- mAlbumView.setVisibility(data.getArtwork() == null ? View.GONE : View.VISIBLE);
- }
-
- // App icon
- if (mAppIconView != null) {
- Drawable iconDrawable = data.getAppIcon();
- iconDrawable.setTint(data.getForegroundColor());
- mAppIconView.setImageDrawable(iconDrawable);
- }
-
- // App name
- if (mAppNameView != null) {
- String appNameString = data.getApp();
- mAppNameView.setText(appNameString);
- mAppNameView.setTextColor(data.getForegroundColor());
- }
-
- // Song name
- if (mTitleView != null) {
- mTitleView.setText(data.getSong());
- mTitleView.setTextColor(data.getForegroundColor());
- }
-
- // Artist name
- if (mArtistView != null) {
- mArtistView.setText(data.getArtist());
- mArtistView.setTextColor(data.getForegroundColor());
- }
-
- // Control buttons
- for (int i = 0; i < ACTION_IDS.length; i++) {
- ImageButton button = mButtonViews.get(i);
- if (button == null) {
- continue;
- }
- Drawable icon = data.getActionIcons().get(i);
- if (icon == null) {
- button.setVisibility(View.GONE);
- button.setImageDrawable(null);
- } else {
- button.setVisibility(View.VISIBLE);
- button.setImageDrawable(icon);
- button.setImageTintList(ColorStateList.valueOf(data.getForegroundColor()));
- }
- }
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
index 96494cfe640f6..3a37c0fd4634d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
@@ -451,7 +451,8 @@ public class KeyguardSliceProvider extends SliceProvider implements
* @param metadata New metadata.
*/
@Override
- public void onMetadataOrStateChanged(MediaMetadata metadata, @PlaybackState.State int state) {
+ public void onPrimaryMetadataOrStateChanged(MediaMetadata metadata,
+ @PlaybackState.State int state) {
synchronized (this) {
boolean nextVisible = NotificationMediaManager.isPlayingState(state);
mMediaHandler.removeCallbacksAndMessages(null);
diff --git a/packages/SystemUI/src/com/android/systemui/media/KeyguardMediaController.kt b/packages/SystemUI/src/com/android/systemui/media/KeyguardMediaController.kt
new file mode 100644
index 0000000000000..f60c012c4c2c4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/KeyguardMediaController.kt
@@ -0,0 +1,67 @@
+/*
+ * 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.systemui.media
+
+import android.view.View
+import com.android.internal.util.ContrastColorUtil
+import com.android.systemui.statusbar.NotificationMediaManager
+import com.android.systemui.statusbar.notification.stack.MediaHeaderView
+import com.android.systemui.statusbar.phone.KeyguardBypassController
+import javax.inject.Inject
+import javax.inject.Singleton
+
+/**
+ * A class that controls the media notifications on the lock screen, handles its visibility and
+ * is responsible for the embedding of he media experience.
+ */
+@Singleton
+class KeyguardMediaController @Inject constructor(
+ private val mediaHierarchyManager: MediaHierarchyManager,
+ private val notifMediaManager: NotificationMediaManager,
+ private val bypassController: KeyguardBypassController
+) {
+ private var view: MediaHeaderView? = null
+
+ init {
+ notifMediaManager.addCallback(object : NotificationMediaManager.MediaListener {
+ override fun onMediaDataLoaded(key: String, data: MediaData) {
+ updateVisibility()
+ }
+
+ override fun onMediaDataRemoved(key: String) {
+ updateVisibility()
+ }
+ })
+ }
+
+ /**
+ * Attach this controller to a media view, initializing its state
+ */
+ fun attach(mediaControlsView: MediaHeaderView) {
+ view = mediaControlsView
+ val hostView = mediaHierarchyManager.createMediaHost(
+ MediaHierarchyManager.LOCATION_LOCKSCREEN)
+ mediaControlsView.setMediaHost(hostView)
+ updateVisibility()
+ }
+
+ private fun updateVisibility() {
+ val shouldBeVisible = notifMediaManager.hasActiveMedia() && !bypassController.bypassEnabled
+ view?.visibility = if (shouldBeVisible) View.VISIBLE else View.GONE
+ }
+}
+
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
index 9adfe7da44077..0297c90a29b9c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
@@ -16,6 +16,8 @@
package com.android.systemui.media;
+import static com.android.systemui.util.SysuiLifecycle.viewAttachLifecycle;
+
import android.annotation.LayoutRes;
import android.app.PendingIntent;
import android.content.ComponentName;
@@ -28,9 +30,9 @@ import android.content.pm.ResolveInfo;
import android.content.res.ColorStateList;
import android.graphics.Bitmap;
import android.graphics.ImageDecoder;
+import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
-import android.graphics.drawable.Icon;
import android.graphics.drawable.RippleDrawable;
import android.media.MediaDescription;
import android.media.MediaMetadata;
@@ -50,10 +52,12 @@ import android.view.ViewGroup;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
+import android.widget.SeekBar;
import android.widget.TextView;
import androidx.annotation.Nullable;
import androidx.constraintlayout.motion.widget.MotionLayout;
+import androidx.constraintlayout.widget.ConstraintSet;
import androidx.core.graphics.drawable.RoundedBitmapDrawable;
import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory;
@@ -65,6 +69,9 @@ import com.android.systemui.R;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.qs.QSMediaBrowser;
import com.android.systemui.util.Assert;
+import com.android.systemui.util.concurrency.DelayableExecutor;
+
+import org.jetbrains.annotations.NotNull;
import java.io.IOException;
import java.util.List;
@@ -76,6 +83,18 @@ import java.util.concurrent.Executor;
public class MediaControlPanel {
private static final String TAG = "MediaControlPanel";
@Nullable private final LocalMediaManager mLocalMediaManager;
+
+ // Button IDs for QS controls
+ static final int[] ACTION_IDS = {
+ R.id.action0,
+ R.id.action1,
+ R.id.action2,
+ R.id.action3,
+ R.id.action4
+ };
+
+ private final SeekBarViewModel mSeekBarViewModel;
+ private final SeekBarObserver mSeekBarObserver;
private final Executor mForegroundExecutor;
protected final Executor mBackgroundExecutor;
private final ActivityStarter mActivityStarter;
@@ -92,8 +111,6 @@ public class MediaControlPanel {
private boolean mIsRegistered = false;
private String mKey;
- private final int[] mActionIds;
-
public static final String MEDIA_PREFERENCES = "media_control_prefs";
public static final String MEDIA_PREFERENCE_KEY = "browser_components";
private SharedPreferences mSharedPrefs;
@@ -101,15 +118,6 @@ public class MediaControlPanel {
private boolean mIsRemotePlayback;
private QSMediaBrowser mQSMediaBrowser;
- // Button IDs used in notifications
- protected static final int[] NOTIF_ACTION_IDS = {
- com.android.internal.R.id.action0,
- com.android.internal.R.id.action1,
- com.android.internal.R.id.action2,
- com.android.internal.R.id.action3,
- com.android.internal.R.id.action4
- };
-
// URI fields to try loading album art from
private static final String[] ART_URIS = {
MediaMetadata.METADATA_KEY_ALBUM_ART_URI,
@@ -183,12 +191,11 @@ public class MediaControlPanel {
* @param activityStarter activity starter
*/
public MediaControlPanel(Context context, ViewGroup parent,
- @Nullable LocalMediaManager routeManager, @LayoutRes int layoutId, int[] actionIds,
- Executor foregroundExecutor, Executor backgroundExecutor,
- ActivityStarter activityStarter) {
+ @Nullable LocalMediaManager routeManager, Executor foregroundExecutor,
+ DelayableExecutor backgroundExecutor, ActivityStarter activityStarter) {
mContext = context;
LayoutInflater inflater = LayoutInflater.from(mContext);
- mMediaNotifView = (MotionLayout) inflater.inflate(layoutId, parent, false);
+ mMediaNotifView = (MotionLayout) inflater.inflate(R.layout.qs_media_panel, parent, false);
// TODO(b/150854549): removeOnAttachStateChangeListener when this doesn't inflate views
// mStateListener shouldn't need to be unregistered since this object shares the same
// lifecycle with the inflated view. It would be better, however, if this controller used an
@@ -196,20 +203,41 @@ public class MediaControlPanel {
// mStateListener to be unregistered in detach.
mMediaNotifView.addOnAttachStateChangeListener(mStateListener);
mLocalMediaManager = routeManager;
- mActionIds = actionIds;
mForegroundExecutor = foregroundExecutor;
mBackgroundExecutor = backgroundExecutor;
mActivityStarter = activityStarter;
+ mSeekBarViewModel = new SeekBarViewModel(backgroundExecutor);
+ mSeekBarObserver = new SeekBarObserver(getView());
+ // Can't use the viewAttachLifecycle of media player because remove/add is used to adjust
+ // priority of players. As soon as it is removed, the lifecycle will end and the seek bar
+ // will stop updating. So, use the lifecycle of the parent instead.
+ // TODO: this parent is also detached, need to fix that
+ mSeekBarViewModel.getProgress().observe(viewAttachLifecycle(parent), mSeekBarObserver);
+ SeekBar bar = getView().findViewById(R.id.media_progress_bar);
+ bar.setOnSeekBarChangeListener(mSeekBarViewModel.getSeekBarListener());
+ bar.setOnTouchListener(mSeekBarViewModel.getSeekBarTouchListener());
}
/**
* Get the view used to display media controls
* @return the view
*/
- public View getView() {
+ public MotionLayout getView() {
return mMediaNotifView;
}
+ /**
+ * Sets the listening state of the player.
+ *
+ * Should be set to true when the QS panel is open. Otherwise, false. This is a signal to avoid
+ * unnecessary work when the QS panel is closed.
+ *
+ * @param listening True when player should be active. Otherwise, false.
+ */
+ public void setListening(boolean listening) {
+ mSeekBarViewModel.setListening(listening);
+ }
+
/**
* Get the context
* @return context
@@ -219,20 +247,12 @@ public class MediaControlPanel {
}
/**
- * Update the media panel view for the given media session
- * @param token
- * @param iconDrawable
- * @param largeIcon
- * @param iconColor
- * @param bgColor
- * @param contentIntent
- * @param appNameString
- * @param key
+ * Bind this view based on the data given
*/
- public void setMediaSession(MediaSession.Token token, Drawable iconDrawable, Icon largeIcon,
- int iconColor, int bgColor, PendingIntent contentIntent, String appNameString,
- String key) {
- // Ensure that component names are updated if token has changed
+ public void bind(@NotNull MediaData data) {
+ mToken = data.getToken();
+ mForegroundColor = data.getForegroundColor();
+ mBackgroundColor = data.getBackgroundColor();
if (mToken == null || !mToken.equals(token)) {
if (mQSMediaBrowser != null) {
Log.d(TAG, "Disconnecting old media browser");
@@ -244,8 +264,6 @@ public class MediaControlPanel {
mCheckedForResumption = false;
}
- mForegroundColor = iconColor;
- mBackgroundColor = bgColor;
mController = new MediaController(mContext, mToken);
mKey = key;
@@ -258,6 +276,7 @@ public class MediaControlPanel {
PackageManager pm = mContext.getPackageManager();
Intent resumeIntent = new Intent(MediaBrowserService.SERVICE_INTERFACE);
List resumeInfo = pm.queryIntentServices(resumeIntent, 0);
+ // TODO: look into this resumption
if (resumeInfo != null) {
for (ResolveInfo inf : resumeInfo) {
if (inf.serviceInfo.packageName.equals(mController.getPackageName())) {
@@ -272,20 +291,33 @@ public class MediaControlPanel {
mController.registerCallback(mSessionCallback);
+ albumView.setImageDrawable(data.getArtwork());
+
mMediaNotifView.setBackgroundTintList(ColorStateList.valueOf(mBackgroundColor));
// Click action
- if (contentIntent != null) {
+ PendingIntent clickIntent = data.getClickIntent();
+ if (clickIntent != null) {
mMediaNotifView.setOnClickListener(v -> {
- mActivityStarter.postStartActivityDismissingKeyguard(contentIntent);
+ mActivityStarter.postStartActivityDismissingKeyguard(clickIntent);
});
}
// App icon
- ImageView appIcon = mMediaNotifView.findViewById(R.id.icon);
+ ImageView appIcon = mMediaNotifView.requireViewById(R.id.icon);
+ // TODO: look at iconDrawable
+ Drawable iconDrawable = data.getAppIcon();
iconDrawable.setTint(mForegroundColor);
appIcon.setImageDrawable(iconDrawable);
+ TextView titleText = mMediaNotifView.requireViewById(R.id.header_title);
+ titleText.setText(data.getSong());
+ TextView appName = mMediaNotifView.requireViewById(R.id.app_name);
+ appName.setText(data.getApp());
+ appName.setTextColor(mForegroundColor);
+ TextView artistText = mMediaNotifView.requireViewById(R.id.header_artist);
+ artistText.setText(data.getArtist());
+ artistText.setTextColor(mForegroundColor);
// Transfer chip
mSeamless = mMediaNotifView.findViewById(R.id.media_seamless);
if (mSeamless != null) {
@@ -313,6 +345,54 @@ public class MediaControlPanel {
mIsRemotePlayback = false;
}
+ ConstraintSet expandedSet = mMediaNotifView.getConstraintSet(R.id.expanded);
+ ConstraintSet collapsedSet = mMediaNotifView.getConstraintSet(R.id.collapsed);
+ List actionsWhenCollapsed = data.getActionsToShowInCompact();
+ // Media controls
+ int i = 0;
+ List actionIcons = data.getActions();
+ for (; i < actionIcons.size() && i < ACTION_IDS.length; i++) {
+ final ImageButton button = mMediaNotifView.findViewById(ACTION_IDS[i]);
+ MediaAction mediaAction = actionIcons.get(i);
+ button.setImageDrawable(mediaAction.getDrawable());
+ button.setContentDescription(mediaAction.getContentDescription());
+ button.setImageTintList(ColorStateList.valueOf(mForegroundColor));
+ PendingIntent actionIntent = mediaAction.getIntent();
+
+ if (mMediaNotifView.getBackground() instanceof IlluminationDrawable) {
+ ((IlluminationDrawable) mMediaNotifView.getBackground())
+ .setupTouch(button, mMediaNotifView);
+ }
+
+ button.setOnClickListener(v -> {
+ if (actionIntent != null) {
+ try {
+ actionIntent.send();
+ } catch (PendingIntent.CanceledException e) {
+ e.printStackTrace();
+ }
+ }
+ });
+ boolean visibleInCompat = actionsWhenCollapsed.contains(i);
+ collapsedSet.setVisibility(ACTION_IDS[i],
+ visibleInCompat ? ConstraintSet.VISIBLE : ConstraintSet.GONE);
+ expandedSet.setVisibility(ACTION_IDS[i], ConstraintSet.VISIBLE);
+ }
+
+ // Hide any unused buttons
+ for (; i < ACTION_IDS.length; i++) {
+ expandedSet.setVisibility(ACTION_IDS[i], ConstraintSet.GONE);
+ collapsedSet.setVisibility(ACTION_IDS[i], ConstraintSet.GONE);
+ }
+
+ // Seek Bar
+ final MediaController controller = new MediaController(getContext(), data.getToken());
+ mBackgroundExecutor.execute(
+ () -> mSeekBarViewModel.updateController(controller, data.getForegroundColor()));
+
+ // Set up long press menu
+ // TODO: b/156036025 bring back media guts
+
makeActive();
// App title (not in mini player)
@@ -614,15 +694,16 @@ public class MediaControlPanel {
*/
protected void resetButtons() {
// Hide all the old buttons
- for (int i = 0; i < mActionIds.length; i++) {
- ImageButton thisBtn = mMediaNotifView.findViewById(mActionIds[i]);
- if (thisBtn != null) {
- thisBtn.setVisibility(View.GONE);
- }
+
+ ConstraintSet expandedSet = mMediaNotifView.getConstraintSet(R.id.expanded);
+ ConstraintSet collapsedSet = mMediaNotifView.getConstraintSet(R.id.collapsed);
+ for (int i = 1; i < ACTION_IDS.length; i++) {
+ expandedSet.setVisibility(ACTION_IDS[i], ConstraintSet.GONE);
+ collapsedSet.setVisibility(ACTION_IDS[i], ConstraintSet.GONE);
}
// Add a restart button
- ImageButton btn = mMediaNotifView.findViewById(mActionIds[0]);
+ ImageButton btn = mMediaNotifView.findViewById(ACTION_IDS[0]);
btn.setOnClickListener(v -> {
Log.d(TAG, "Attempting to restart session");
if (mQSMediaBrowser != null) {
@@ -644,7 +725,20 @@ public class MediaControlPanel {
});
btn.setImageDrawable(mContext.getResources().getDrawable(R.drawable.lb_ic_play));
btn.setImageTintList(ColorStateList.valueOf(mForegroundColor));
- btn.setVisibility(View.VISIBLE);
+ expandedSet.setVisibility(ACTION_IDS[0], ConstraintSet.VISIBLE);
+ collapsedSet.setVisibility(ACTION_IDS[0], ConstraintSet.VISIBLE);
+
+ mSeekBarViewModel.clearController();
+ // TODO: fix guts
+ // View guts = mMediaNotifView.findViewById(R.id.media_guts);
+ View options = mMediaNotifView.findViewById(R.id.qs_media_controls_options);
+
+ mMediaNotifView.setOnLongClickListener(v -> {
+ // Replace player view with close/cancel view
+// guts.setVisibility(View.GONE);
+ options.setVisibility(View.VISIBLE);
+ return true; // consumed click
+ });
}
private void makeActive() {
@@ -668,7 +762,6 @@ public class MediaControlPanel {
mIsRegistered = false;
}
}
-
/**
* Verify that we can connect to the given component with a MediaBrowser, and if so, add that
* component to the list of resumption components
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardMedia.kt b/packages/SystemUI/src/com/android/systemui/media/MediaData.kt
similarity index 58%
rename from packages/SystemUI/src/com/android/keyguard/KeyguardMedia.kt
rename to packages/SystemUI/src/com/android/systemui/media/MediaData.kt
index 487c29573a146..8165362ef677c 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardMedia.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaData.kt
@@ -11,17 +11,18 @@
* 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.
+ * limitations under the License
*/
-package com.android.keyguard
+package com.android.systemui.media
+import android.app.PendingIntent
import android.graphics.drawable.Drawable
+import android.media.session.MediaSession
-import java.util.List
-
-/** State for lock screen media controls. */
-data class KeyguardMedia(
+/** State of a media view. */
+data class MediaData(
+ val initialized: Boolean = false,
val foregroundColor: Int,
val backgroundColor: Int,
val app: String?,
@@ -29,5 +30,16 @@ data class KeyguardMedia(
val artist: String?,
val song: String?,
val artwork: Drawable?,
- val actionIcons: List
+ val actions: List,
+ val actionsToShowInCompact: List,
+ val packageName: String?,
+ val token: MediaSession.Token?,
+ val clickIntent: PendingIntent?
+)
+
+/** State of a media action. */
+data class MediaAction(
+ val drawable: Drawable?,
+ val intent: PendingIntent?,
+ val contentDescription: CharSequence?
)
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
new file mode 100644
index 0000000000000..298a5fd60813a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
@@ -0,0 +1,206 @@
+/*
+ * 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.systemui.media
+
+import android.app.Notification
+import android.content.Context
+import android.graphics.Bitmap
+import android.graphics.drawable.Drawable
+import android.media.MediaMetadata
+import android.media.session.MediaSession
+import android.provider.Settings
+import android.service.notification.StatusBarNotification
+import androidx.core.graphics.drawable.RoundedBitmapDrawable
+import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory
+import com.android.internal.util.ContrastColorUtil
+import com.android.systemui.R
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.statusbar.NotificationMediaManager
+import com.android.systemui.statusbar.notification.MediaNotificationProcessor
+import java.util.*
+import java.util.concurrent.Executor
+import javax.inject.Inject
+import javax.inject.Singleton
+import kotlin.collections.LinkedHashMap
+
+/**
+ * A class that facilitates management and loading of Media Data, ready for binding.
+ */
+@Singleton
+class MediaDataManager @Inject constructor(
+ private val context: Context,
+ private val mediaControllerFactory: MediaControllerFactory,
+ @Background private val backgroundExecutor: Executor,
+ @Main private val foregroundExcecutor: Executor
+) {
+
+ lateinit var listener: NotificationMediaManager.MediaListener
+ private var albumArtSize: Int = 0
+ private var albumArtRadius: Int = 0
+ private val mediaEntries: LinkedHashMap = LinkedHashMap()
+
+ init {
+ loadDimens()
+ }
+
+ private fun loadDimens() {
+ albumArtRadius = context.resources.getDimensionPixelSize(R.dimen.qs_media_corner_radius)
+ albumArtSize = context.resources.getDimensionPixelSize(R.dimen.qs_media_album_size)
+ }
+
+ fun onNotificationAdded(key: String, sbn: StatusBarNotification) {
+ if (isMediaNotification(sbn)) {
+ if (!mediaEntries.containsKey(key)) {
+ mediaEntries.put(key, LOADING)
+ }
+ loadMediaData(key, sbn)
+ } else {
+ onNotificationRemoved(key)
+ }
+ }
+
+ private fun loadMediaData(key: String, sbn: StatusBarNotification) {
+ backgroundExecutor.execute {
+ loadMediaDataInBg(key, sbn)
+ }
+ }
+
+ private fun loadMediaDataInBg(key: String, sbn: StatusBarNotification) {
+ val token = sbn.notification.extras.getParcelable(Notification.EXTRA_MEDIA_SESSION)
+ as MediaSession.Token?
+ val metadata = mediaControllerFactory.create(token).metadata
+
+ if (metadata == null) {
+ // TODO: handle this better, removing media notification
+ return
+ }
+
+ // Foreground and Background colors computed from album art
+ val notif: Notification = sbn.notification
+ var fgColor = notif.color
+ var bgColor = -1
+ var artworkBitmap = metadata.getBitmap(MediaMetadata.METADATA_KEY_ART)
+ if (artworkBitmap == null) {
+ artworkBitmap = metadata.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART)
+ }
+ // TODO: load media data from uri
+ if (artworkBitmap != null) {
+ // If we have art, get colors from that
+ val p = MediaNotificationProcessor.generateArtworkPaletteBuilder(artworkBitmap)
+ .generate()
+ val swatch = MediaNotificationProcessor.findBackgroundSwatch(p)
+ bgColor = swatch.rgb
+ fgColor = MediaNotificationProcessor.selectForegroundColor(bgColor, p)
+ }
+ // Make sure colors will be legible
+ val isDark = !ContrastColorUtil.isColorLight(bgColor)
+ fgColor = ContrastColorUtil.resolveContrastColor(context, fgColor, bgColor,
+ isDark)
+ fgColor = ContrastColorUtil.ensureTextContrast(fgColor, bgColor, isDark)
+
+ // Album art
+ var artwork: RoundedBitmapDrawable? = null
+ if (artworkBitmap != null) {
+ val original = artworkBitmap.copy(Bitmap.Config.ARGB_8888, true)
+ val scaled = Bitmap.createScaledBitmap(original, albumArtSize, albumArtSize,
+ false)
+ artwork = RoundedBitmapDrawableFactory.create(context.resources, scaled)
+ artwork.cornerRadius = albumArtRadius.toFloat()
+ }
+
+ // App name
+ val builder = Notification.Builder.recoverBuilder(context, notif)
+ val app = builder.loadHeaderAppName()
+
+ // App Icon
+ val smallIconDrawable: Drawable = sbn.notification.smallIcon.loadDrawable(context)
+
+ // Song name
+ val song: String = metadata.getString(MediaMetadata.METADATA_KEY_TITLE)
+
+ // Artist name
+ val artist: String = metadata.getString(MediaMetadata.METADATA_KEY_ARTIST)
+
+ // Control buttons
+ val actionIcons: MutableList = ArrayList()
+ val actions = notif.actions
+ val actionsToShowCollapsed = notif.extras.getIntArray(
+ Notification.EXTRA_COMPACT_ACTIONS)?.toList() ?: emptyList()
+ // TODO: b/153736623 look into creating actions when this isn't a media style notification
+
+ val packageContext: Context = sbn.getPackageContext(context)
+ for (action in actions) {
+ val mediaAction = MediaAction(
+ action.getIcon().loadDrawable(packageContext),
+ action.actionIntent,
+ action.title)
+ actionIcons.add(mediaAction)
+ }
+
+ foregroundExcecutor.execute {
+ onMediaDataLoaded(key, MediaData(true, fgColor, bgColor, app, smallIconDrawable, artist,
+ song, artwork, actionIcons, actionsToShowCollapsed, sbn.packageName, token,
+ notif.contentIntent))
+ }
+
+ }
+
+ fun onMediaDataLoaded(key: String, data: MediaData) {
+ if (mediaEntries.containsKey(key)) {
+ // Otherwise this was removed already
+ mediaEntries.put(key, data)
+ listener.onMediaDataLoaded(key, data)
+ }
+ }
+
+ fun onNotificationRemoved(key: String) {
+ val removed = mediaEntries.remove(key)
+ if (removed != null) {
+ listener.onMediaDataRemoved(key)
+ }
+ }
+
+ private fun isMediaNotification(sbn: StatusBarNotification) : Boolean {
+ if (!useUniversalMediaPlayer()) {
+ return false
+ }
+ if (!sbn.notification.hasMediaSession()) {
+ return false
+ }
+ val notificationStyle = sbn.notification.notificationStyle
+ if (Notification.DecoratedMediaCustomViewStyle::class.java.equals(notificationStyle)
+ || Notification.MediaStyle::class.java.equals(notificationStyle)) {
+ return true
+ }
+ return false
+ }
+
+ /**
+ * are we using the universal media player
+ */
+ private fun useUniversalMediaPlayer()
+ = Settings.System.getInt(context.contentResolver, "qs_media_player", 1) > 0
+
+ /**
+ * Are there any media notifications active?
+ */
+ fun hasActiveMedia() = mediaEntries.size > 0
+}
+
+private val LOADING = MediaData(false, 0, 0, null, null, null, null, null,
+ emptyList(), emptyList(), null, null, null)
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
new file mode 100644
index 0000000000000..4a650ab828bc8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
@@ -0,0 +1,216 @@
+/*
+ * 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.systemui.media
+
+import android.annotation.IntDef
+import android.content.Context
+import android.view.LayoutInflater
+import android.view.ViewGroup
+import android.widget.LinearLayout
+
+import com.android.settingslib.bluetooth.LocalBluetoothManager
+import com.android.settingslib.media.InfoMediaManager
+import com.android.settingslib.media.LocalMediaManager
+import com.android.systemui.R
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.statusbar.NotificationMediaManager
+import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.notification.VisualStabilityManager
+import com.android.systemui.statusbar.phone.KeyguardBypassController
+import com.android.systemui.util.animation.UniqueObjectHost
+import com.android.systemui.util.concurrency.DelayableExecutor
+import java.util.concurrent.Executor
+import javax.inject.Inject
+import javax.inject.Singleton
+
+/**
+ * This manager is responsible for placement of the unique media view between the different hosts
+ * and animate the positions of the views to achieve seamless transitions.
+ */
+@Singleton
+class MediaHierarchyManager @Inject constructor(
+ private val context: Context,
+ @Main private val foregroundExecutor: Executor,
+ @Background private val backgroundExecutor: DelayableExecutor,
+ private val localBluetoothManager: LocalBluetoothManager?,
+ private val visualStabilityManager: VisualStabilityManager,
+ private val statusBarStateController: StatusBarStateController,
+ private val bypassController: KeyguardBypassController,
+ mediaManager: NotificationMediaManager
+) {
+ private val mediaCarousel: ViewGroup
+ private val mediaContent: ViewGroup
+ private val mediaPlayers: MutableMap = mutableMapOf()
+ private val mediaHosts = arrayOfNulls(LOCATION_LOCKSCREEN + 1)
+ private val visualStabilityCallback = ::reorderAllPlayers
+ private var currentAttachmentLocation = -1
+
+ var shouldListen = true
+ set(value) {
+ field = value
+ for (player in mediaPlayers.values) {
+ player.setListening(shouldListen)
+ }
+ }
+
+ init {
+ mediaCarousel = inflateMediaCarousel()
+ mediaContent = mediaCarousel.findViewById(R.id.media_carousel)
+ mediaManager.addCallback(object : NotificationMediaManager.MediaListener {
+ override fun onMediaDataLoaded(key: String, data: MediaData) {
+ updateView(key, data)
+ }
+
+ override fun onMediaDataRemoved(key: String) {
+ val removed = mediaPlayers.remove(key)
+ removed?.apply {
+ mediaContent.removeView(removed.view)
+ }
+ }
+ })
+ }
+
+ private fun inflateMediaCarousel(): ViewGroup {
+ return LayoutInflater.from(context).inflate(
+ R.layout.media_carousel, UniqueObjectHost(context), false) as ViewGroup
+ }
+
+ private fun reorderAllPlayers() {
+ for (mediaPlayer in mediaPlayers.values) {
+ val view = mediaPlayer.view
+ if (mediaPlayer.isPlaying && mediaContent.indexOfChild(view) != 0) {
+ mediaContent.removeView(view)
+ mediaContent.addView(view, 0)
+ }
+ }
+ }
+
+ private fun updateView(key: String, data: MediaData) {
+ var existingPlayer = mediaPlayers[key]
+ if (existingPlayer == null) {
+ // Set up listener for device changes
+ // TODO: integrate with MediaTransferManager?
+ val imm = InfoMediaManager(context, data.packageName,
+ null /* notification */, localBluetoothManager)
+ val routeManager = LocalMediaManager(context, localBluetoothManager,
+ imm, data.packageName)
+
+ existingPlayer = MediaControlPanel(context, mediaContent, routeManager,
+ foregroundExecutor, backgroundExecutor)
+ mediaPlayers[key] = existingPlayer
+ val padding = context.resources.getDimensionPixelSize(R.dimen.qs_media_padding)
+ val lp = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT)
+ lp.marginStart = padding
+ lp.marginEnd = padding
+ existingPlayer.view.setLayoutParams(lp)
+ existingPlayer.setListening(shouldListen)
+ if (existingPlayer.isPlaying) {
+ mediaContent.addView(existingPlayer.view, 0)
+ } else {
+ mediaContent.addView(existingPlayer.view)
+ }
+ } else if (existingPlayer.isPlaying &&
+ mediaContent.indexOfChild(existingPlayer.view) != 0) {
+ if (visualStabilityManager.isReorderingAllowed) {
+ mediaContent.removeView(existingPlayer.view)
+ mediaContent.addView(existingPlayer.view, 0)
+ } else {
+ visualStabilityManager.addReorderingAllowedCallback(visualStabilityCallback)
+ }
+ }
+ existingPlayer.bind(data)
+ }
+
+ /**
+ * Create a media host view which can be attached to a view hierarchy and where the player
+ * will be placed in when the host is the currently desired state.
+ *
+ * @return the hostView associated with this location
+ */
+ fun createMediaHost(@MediaLocation location: Int) : ViewGroup {
+ val viewHost = UniqueObjectHost(context)
+ mediaHosts[location] = viewHost
+ if (location == currentAttachmentLocation) {
+ // In case we are overriding a view that is already visible, make sure we attach it
+ // to this new host view in the below call
+ currentAttachmentLocation = -1
+ }
+ updateAttachmentLocation(animate = false)
+ return viewHost
+ }
+
+ private fun updateAttachmentLocation(animate: Boolean) {
+ var desiredLocation = calculateLocation()
+ if (desiredLocation != currentAttachmentLocation) {
+ val host = mediaHosts[desiredLocation]
+ host?.apply {
+ if (currentAttachmentLocation >= 0) {
+ val currentHost = mediaHosts[currentAttachmentLocation]
+ currentHost?.removeView(mediaCarousel)
+ }
+ host.addView(mediaCarousel)
+ // TODO: handle animation / repositioning etc
+ currentAttachmentLocation = desiredLocation
+ }
+ }
+ }
+
+ @MediaLocation
+ private fun calculateLocation() : Int {
+ val onLockscreen = (!bypassController.bypassEnabled
+ && (statusBarStateController.state == StatusBarState.KEYGUARD
+ || statusBarStateController.state == StatusBarState.FULLSCREEN_USER_SWITCHER))
+ return when {
+ qsExpansion > 0.0f -> LOCATION_QS
+ onLockscreen -> LOCATION_LOCKSCREEN
+ else -> LOCATION_QQS
+ }
+ }
+
+ /**
+ * The expansion of quick settings
+ */
+ var qsExpansion: Float = 0.0f
+ set(value) {
+ field = value
+ updateAttachmentLocation(animate = false)
+ }
+
+ @IntDef(prefix = ["LOCATION_"], value = [LOCATION_QS, LOCATION_QQS, LOCATION_LOCKSCREEN])
+ @Retention(AnnotationRetention.SOURCE)
+ annotation class MediaLocation
+
+ companion object {
+ /**
+ * Attached in expanded quick settings
+ */
+ const val LOCATION_QS = 0
+
+ /**
+ * Attached in the collapsed QS
+ */
+ const val LOCATION_QQS = 1
+
+ /**
+ * Attached on the lock screen
+ */
+ const val LOCATION_LOCKSCREEN = 2
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
index a0ea7fae493db..ce002297e1a1f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
@@ -269,14 +269,6 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha
count++;
}
-
- if (Utils.useQsMediaPlayer(mQsPanel.getContext())) {
- View qsMediaView = mQsPanel.getMediaPanel();
- View qqsMediaView = mQuickQsPanel.getMediaPlayer().getView();
- translationXBuilder.addFloat(qsMediaView, "alpha", 0, 1);
- translationXBuilder.addFloat(qqsMediaView, "alpha", 1, 0);
- }
-
if (mAllowFancy) {
// Make brightness appear static position and alpha in through second half.
View brightness = mQsPanel.getBrightnessView();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java b/packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java
deleted file mode 100644
index 0571c68fd7073..0000000000000
--- a/packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java
+++ /dev/null
@@ -1,262 +0,0 @@
-/*
- * 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.
- */
-
-package com.android.systemui.qs;
-
-import static com.android.systemui.util.SysuiLifecycle.viewAttachLifecycle;
-
-import android.app.PendingIntent;
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.content.res.ColorStateList;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.Icon;
-import android.media.MediaDescription;
-import android.media.session.MediaController;
-import android.media.session.MediaSession;
-import android.util.Log;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ImageButton;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-import android.widget.SeekBar;
-import android.widget.TextView;
-
-import androidx.constraintlayout.widget.ConstraintSet;
-import com.android.settingslib.media.LocalMediaManager;
-import com.android.internal.widget.MediaNotificationView;
-import com.android.systemui.R;
-import com.android.systemui.media.IlluminationDrawable;
-import com.android.systemui.media.MediaControlPanel;
-import com.android.systemui.media.SeekBarObserver;
-import com.android.systemui.media.SeekBarViewModel;
-import com.android.systemui.plugins.ActivityStarter;
-import com.android.systemui.util.concurrency.DelayableExecutor;
-
-import java.util.concurrent.Executor;
-
-/**
- * Single media player for carousel in QSPanel
- */
-public class QSMediaPlayer extends MediaControlPanel {
-
- private static final String TAG = "QSMediaPlayer";
-
- // Button IDs for QS controls
- static final int[] QS_ACTION_IDS = {
- R.id.action0,
- R.id.action1,
- R.id.action2,
- R.id.action3,
- R.id.action4
- };
-
- private final QSPanel mParent;
- private final Executor mForegroundExecutor;
- private final DelayableExecutor mBackgroundExecutor;
- private final SeekBarViewModel mSeekBarViewModel;
- private final SeekBarObserver mSeekBarObserver;
- private String mPackageName;
-
- /**
- * Initialize quick shade version of player
- * @param context
- * @param parent
- * @param routeManager Provides information about device
- * @param foregroundExecutor
- * @param backgroundExecutor
- * @param activityStarter
- */
- public QSMediaPlayer(Context context, ViewGroup parent, LocalMediaManager routeManager,
- Executor foregroundExecutor, DelayableExecutor backgroundExecutor,
- ActivityStarter activityStarter) {
- super(context, parent, routeManager, R.layout.qs_media_panel, QS_ACTION_IDS,
- foregroundExecutor, backgroundExecutor, activityStarter);
- mParent = (QSPanel) parent;
- mForegroundExecutor = foregroundExecutor;
- mBackgroundExecutor = backgroundExecutor;
- mSeekBarViewModel = new SeekBarViewModel(backgroundExecutor);
- mSeekBarObserver = new SeekBarObserver(getView());
- // Can't use the viewAttachLifecycle of media player because remove/add is used to adjust
- // priority of players. As soon as it is removed, the lifecycle will end and the seek bar
- // will stop updating. So, use the lifecycle of the parent instead.
- mSeekBarViewModel.getProgress().observe(viewAttachLifecycle(parent), mSeekBarObserver);
- SeekBar bar = getView().findViewById(R.id.media_progress_bar);
- bar.setOnSeekBarChangeListener(mSeekBarViewModel.getSeekBarListener());
- bar.setOnTouchListener(mSeekBarViewModel.getSeekBarTouchListener());
- }
-
- /**
- * Add a media panel view based on a media description. Used for resumption
- * @param description
- * @param iconColor
- * @param bgColor
- * @param contentIntent
- * @param pkgName
- */
- public void setMediaSession(MediaSession.Token token, MediaDescription description,
- int iconColor, int bgColor, PendingIntent contentIntent, String pkgName) {
- mPackageName = pkgName;
- PackageManager pm = getContext().getPackageManager();
- Drawable icon = null;
- CharSequence appName = pkgName.substring(pkgName.lastIndexOf("."));
- try {
- icon = pm.getApplicationIcon(pkgName);
- appName = pm.getApplicationLabel(pm.getApplicationInfo(pkgName, 0));
- } catch (PackageManager.NameNotFoundException e) {
- Log.e(TAG, "Error getting package information", e);
- }
-
- // Set what we can normally
- super.setMediaSession(token, icon, null, iconColor, bgColor, contentIntent,
- appName.toString(), null);
-
- // Then add info from MediaDescription
- ImageView albumView = mMediaNotifView.findViewById(R.id.album_art);
- if (albumView != null) {
- // Resize art in a background thread
- mBackgroundExecutor.execute(() -> processAlbumArt(description, albumView));
- }
-
- // Song name
- TextView titleText = mMediaNotifView.findViewById(R.id.header_title);
- CharSequence songName = description.getTitle();
- titleText.setText(songName);
- titleText.setTextColor(iconColor);
-
- // Artist name (not in mini player)
- TextView artistText = mMediaNotifView.findViewById(R.id.header_artist);
- if (artistText != null) {
- CharSequence artistName = description.getSubtitle();
- artistText.setText(artistName);
- artistText.setTextColor(iconColor);
- }
-
- initLongPressMenu(iconColor);
-
- // Set buttons to resume state
- resetButtons();
- }
-
- /**
- * Update media panel view for the given media session
- * @param token token for this media session
- * @param icon app notification icon
- * @param largeIcon notification's largeIcon, used as a fallback for album art
- * @param iconColor foreground color (for text, icons)
- * @param bgColor background color
- * @param actionsContainer a LinearLayout containing the media action buttons
- * @param contentIntent Intent to send when user taps on player
- * @param appName Application title
- * @param key original notification's key
- */
- public void setMediaSession(MediaSession.Token token, Drawable icon, Icon largeIcon,
- int iconColor, int bgColor, View actionsContainer, PendingIntent contentIntent,
- String appName, String key) {
-
- super.setMediaSession(token, icon, largeIcon, iconColor, bgColor, contentIntent, appName,
- key);
- ConstraintSet constraintSet = mMediaNotifView.getConstraintSet(R.id.expanded);
- // Media controls
- if (actionsContainer != null) {
- LinearLayout parentActionsLayout = (LinearLayout) actionsContainer;
- int i = 0;
- for (; i < parentActionsLayout.getChildCount() && i < QS_ACTION_IDS.length; i++) {
- final ImageButton thisBtn = mMediaNotifView.findViewById(QS_ACTION_IDS[i]);
- ImageButton thatBtn = parentActionsLayout.findViewById(NOTIF_ACTION_IDS[i]);
- if (thatBtn == null || thatBtn.getDrawable() == null
- || thatBtn.getVisibility() != View.VISIBLE) {
- constraintSet.setVisibility(QS_ACTION_IDS[i], ConstraintSet.GONE);
- continue;
- }
-
- if (mMediaNotifView.getBackground() instanceof IlluminationDrawable) {
- ((IlluminationDrawable) mMediaNotifView.getBackground())
- .setupTouch(thisBtn, mMediaNotifView);
- }
-
- Drawable thatIcon = thatBtn.getDrawable();
- thisBtn.setImageDrawable(thatIcon.mutate());
- constraintSet.setVisibility(QS_ACTION_IDS[i], ConstraintSet.VISIBLE);
- thisBtn.setOnClickListener(v -> {
- Log.d(TAG, "clicking on other button");
- thatBtn.performClick();
- });
- }
-
- // Hide any unused buttons
- for (; i < QS_ACTION_IDS.length; i++) {
- constraintSet.setVisibility(QS_ACTION_IDS[i], ConstraintSet.GONE);
- }
- }
-
- // Seek Bar
- final MediaController controller = new MediaController(getContext(), token);
- mBackgroundExecutor.execute(
- () -> mSeekBarViewModel.updateController(controller, iconColor));
-
- initLongPressMenu(iconColor);
- }
-
- private void initLongPressMenu(int iconColor) {
- // Set up long press menu
- // TODO: b/156036025 bring back media guts
- }
-
- @Override
- protected void resetButtons() {
- super.resetButtons();
- mSeekBarViewModel.clearController();
-// View guts = mMediaNotifView.findViewById(R.id.media_guts);
- View options = mMediaNotifView.findViewById(R.id.qs_media_controls_options);
-
- mMediaNotifView.setOnLongClickListener(v -> {
- // Replace player view with close/cancel view
- // TODO: b/156036025 make this work
- options.setVisibility(View.VISIBLE);
- return true; // consumed click
- });
- }
-
- /**
- * Sets the listening state of the player.
- *
- * Should be set to true when the QS panel is open. Otherwise, false. This is a signal to avoid
- * unnecessary work when the QS panel is closed.
- *
- * @param listening True when player should be active. Otherwise, false.
- */
- public void setListening(boolean listening) {
- mSeekBarViewModel.setListening(listening);
- }
-
- @Override
- public void removePlayer() {
- Log.d(TAG, "removing player from parent: " + mParent);
- // Ensure this happens on the main thread (could happen in QSMediaBrowser callback)
- mForegroundExecutor.execute(() -> mParent.removeMediaPlayer(QSMediaPlayer.this));
- }
-
- @Override
- public String getMediaPlayerPackage() {
- if (getController() == null) {
- return mPackageName;
- }
- return super.getMediaPlayerPackage();
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index e8f6c9668e9b3..128eee83e8fee 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -32,23 +32,19 @@ import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
-import android.graphics.drawable.Icon;
import android.media.MediaDescription;
-import android.media.session.MediaSession;
import android.metrics.LogMaker;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.UserHandle;
import android.os.UserManager;
-import android.service.notification.StatusBarNotification;
import android.service.quicksettings.Tile;
import android.util.AttributeSet;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
-import android.widget.HorizontalScrollView;
import android.widget.LinearLayout;
import com.android.internal.logging.MetricsLogger;
@@ -56,17 +52,13 @@ import com.android.internal.logging.UiEventLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.statusbar.NotificationVisibility;
import com.android.settingslib.Utils;
-import com.android.settingslib.bluetooth.LocalBluetoothManager;
-import com.android.settingslib.media.InfoMediaManager;
-import com.android.settingslib.media.LocalMediaManager;
import com.android.systemui.Dependency;
import com.android.systemui.Dumpable;
import com.android.systemui.R;
import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.dagger.qualifiers.Background;
-import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.media.MediaControlPanel;
+import com.android.systemui.media.MediaHierarchyManager;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.qs.DetailAdapter;
import com.android.systemui.plugins.qs.QSTile;
@@ -77,20 +69,17 @@ import com.android.systemui.qs.external.CustomTile;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.settings.BrightnessController;
import com.android.systemui.settings.ToggleSliderView;
-import com.android.systemui.statusbar.notification.NotificationEntryListener;
-import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.NotificationMediaManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.policy.BrightnessMirrorController;
import com.android.systemui.statusbar.policy.BrightnessMirrorController.BrightnessMirrorListener;
import com.android.systemui.tuner.TunerService;
import com.android.systemui.tuner.TunerService.Tunable;
-import com.android.systemui.util.concurrency.DelayableExecutor;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collection;
-import java.util.concurrent.Executor;
import java.util.stream.Collectors;
import javax.inject.Inject;
@@ -108,21 +97,14 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne
protected final Context mContext;
protected final ArrayList mRecords = new ArrayList<>();
private final BroadcastDispatcher mBroadcastDispatcher;
+ protected final MediaHierarchyManager mMediaHiearchyManager;
private String mCachedSpecs = "";
protected final View mBrightnessView;
private final H mHandler = new H();
private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class);
private final QSTileRevealController mQsTileRevealController;
- private final LinearLayout mMediaCarousel;
- private final ArrayList mMediaPlayers = new ArrayList<>();
- private final LocalBluetoothManager mLocalBluetoothManager;
- private final Executor mForegroundExecutor;
- private final DelayableExecutor mBackgroundExecutor;
- private boolean mUpdateCarousel = false;
private ActivityStarter mActivityStarter;
- private NotificationEntryManager mNotificationEntryManager;
-
protected boolean mExpanded;
protected boolean mListening;
@@ -158,15 +140,6 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne
}
};
- private final NotificationEntryListener mNotificationEntryListener =
- new NotificationEntryListener() {
- @Override
- public void onEntryRemoved(NotificationEntry entry, NotificationVisibility visibility,
- boolean removedByUser, int reason) {
- checkToRemoveMediaNotification(entry);
- }
- };
-
@Inject
public QSPanel(
@Named(VIEW_CONTEXT) Context context,
@@ -174,23 +147,17 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne
DumpManager dumpManager,
BroadcastDispatcher broadcastDispatcher,
QSLogger qsLogger,
- @Main Executor foregroundExecutor,
- @Background DelayableExecutor backgroundExecutor,
- @Nullable LocalBluetoothManager localBluetoothManager,
+ MediaHierarchyManager mediaHierarchyManager,
ActivityStarter activityStarter,
- NotificationEntryManager entryManager,
UiEventLogger uiEventLogger
) {
super(context, attrs);
+ mMediaHiearchyManager = mediaHierarchyManager;
mContext = context;
mQSLogger = qsLogger;
mDumpManager = dumpManager;
- mForegroundExecutor = foregroundExecutor;
- mBackgroundExecutor = backgroundExecutor;
- mLocalBluetoothManager = localBluetoothManager;
mBroadcastDispatcher = broadcastDispatcher;
mActivityStarter = activityStarter;
- mNotificationEntryManager = entryManager;
mUiEventLogger = uiEventLogger;
setOrientation(VERTICAL);
@@ -210,158 +177,27 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne
addDivider();
- // Add media carousel
- if (useQsMediaPlayer(context)) {
- HorizontalScrollView mediaScrollView = (HorizontalScrollView) LayoutInflater.from(
- mContext).inflate(R.layout.media_carousel, this, false);
- mMediaCarousel = mediaScrollView.findViewById(R.id.media_carousel);
- addView(mediaScrollView, 0);
- } else {
- mMediaCarousel = null;
- }
-
mFooter = new QSSecurityFooter(this, context);
addView(mFooter.getView());
+ // Add media carousel
+ if (useQsMediaPlayer(context)) {
+ addMediaHostView();
+ }
+
updateResources();
mBrightnessController = new BrightnessController(getContext(),
findViewById(R.id.brightness_slider), mBroadcastDispatcher);
}
- @Override
- public void onVisibilityAggregated(boolean isVisible) {
- super.onVisibilityAggregated(isVisible);
- if (!isVisible && mUpdateCarousel) {
- for (QSMediaPlayer player : mMediaPlayers) {
- if (player.isPlaying()) {
- LayoutParams lp = (LayoutParams) player.getView().getLayoutParams();
- mMediaCarousel.removeView(player.getView());
- mMediaCarousel.addView(player.getView(), 0, lp);
- ((HorizontalScrollView) mMediaCarousel.getParent()).fullScroll(View.FOCUS_LEFT);
- mUpdateCarousel = false;
- break;
- }
- }
- }
- }
-
- /**
- * Add or update a player for the associated media session
- * @param token
- * @param icon
- * @param largeIcon
- * @param iconColor
- * @param bgColor
- * @param actionsContainer
- * @param notif
- * @param key
- */
- public void addMediaSession(MediaSession.Token token, Drawable icon, Icon largeIcon,
- int iconColor, int bgColor, View actionsContainer, StatusBarNotification notif,
- String key) {
- if (!useQsMediaPlayer(mContext)) {
- // Shouldn't happen, but just in case
- Log.e(TAG, "Tried to add media session without player!");
- return;
- }
- if (token == null) {
- Log.e(TAG, "Media session token was null!");
- return;
- }
-
- String packageName = notif.getPackageName();
- QSMediaPlayer player = findMediaPlayer(packageName, token, key);
-
- int playerWidth = (int) getResources().getDimension(R.dimen.qs_media_width);
- int padding = (int) getResources().getDimension(R.dimen.qs_media_padding);
- LayoutParams lp = new LayoutParams(playerWidth, ViewGroup.LayoutParams.MATCH_PARENT);
- lp.setMarginStart(padding);
- lp.setMarginEnd(padding);
-
- if (player == null) {
- Log.d(TAG, "creating new player for " + packageName);
- // Set up listener for device changes
- // TODO: integrate with MediaTransferManager?
- InfoMediaManager imm = new InfoMediaManager(mContext, notif.getPackageName(),
- notif.getNotification(), mLocalBluetoothManager);
- LocalMediaManager routeManager = new LocalMediaManager(mContext, mLocalBluetoothManager,
- imm, notif.getPackageName());
-
- player = new QSMediaPlayer(mContext, this, routeManager, mForegroundExecutor,
- mBackgroundExecutor, mActivityStarter);
- player.setListening(mListening);
- if (player.isPlaying()) {
- mMediaCarousel.addView(player.getView(), 0, lp); // add in front
- } else {
- mMediaCarousel.addView(player.getView(), lp); // add at end
- }
- mMediaPlayers.add(player);
- } else if (player.isPlaying()) {
- mUpdateCarousel = true;
- }
-
- Log.d(TAG, "setting player session");
- String appName = Notification.Builder.recoverBuilder(getContext(), notif.getNotification())
- .loadHeaderAppName();
- player.setMediaSession(token, icon, largeIcon, iconColor, bgColor, actionsContainer,
- notif.getNotification().contentIntent, appName, key);
-
- if (mMediaPlayers.size() > 0) {
- ((View) mMediaCarousel.getParent()).setVisibility(View.VISIBLE);
- }
- }
-
- /**
- * Check for an existing media player using the given information
- * @param packageName
- * @param token
- * @param key
- * @return a player, or null if no match found
- */
- private QSMediaPlayer findMediaPlayer(String packageName, MediaSession.Token token,
- String key) {
- for (QSMediaPlayer player : mMediaPlayers) {
- if (player.getKey() == null || key == null) {
- // No notification key = loaded via mediabrowser, so just match on package
- if (packageName.equals(player.getMediaPlayerPackage())) {
- Log.d(TAG, "Found matching resume player by package: " + packageName);
- return player;
- }
- } else if (player.getMediaSessionToken().equals(token)) {
- Log.d(TAG, "Found matching player by token " + packageName);
- return player;
- } else if (packageName.equals(player.getMediaPlayerPackage())
- && key.equals(player.getKey())) {
- // Also match if it's the same package and notification key
- Log.d(TAG, "Found matching player by package " + packageName + ", " + key);
- return player;
- }
- }
- return null;
- }
-
- protected View getMediaPanel() {
- return mMediaCarousel;
- }
-
- /**
- * Remove the media player from the carousel
- * @param player Player to remove
- * @return true if removed, false if player was not found
- */
- protected boolean removeMediaPlayer(QSMediaPlayer player) {
- // Remove from list
- if (!mMediaPlayers.remove(player)) {
- return false;
- }
-
- // Check if we need to collapse the carousel now
- mMediaCarousel.removeView(player.getView());
- if (mMediaPlayers.size() == 0) {
- ((View) mMediaCarousel.getParent()).setVisibility(View.GONE);
- }
- return true;
+ protected void addMediaHostView() {
+ ViewGroup hostView = mMediaHiearchyManager.createMediaHost(
+ MediaHierarchyManager.LOCATION_QS);
+ addView(hostView);
+ ViewGroup.LayoutParams layoutParams = hostView.getLayoutParams();
+ layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT;
+ layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT;
}
private final QSMediaBrowser.Callback mMediaBrowserCallback = new QSMediaBrowser.Callback() {
@@ -441,27 +277,6 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne
mHasLoadedMediaControls = true;
}
- private void checkToRemoveMediaNotification(NotificationEntry entry) {
- if (!useQsMediaPlayer(mContext)) {
- return;
- }
-
- if (!entry.isMediaNotification()) {
- return;
- }
-
- // If this entry corresponds to an existing set of controls, clear the controls
- // This will handle apps that use an action to clear their notification
- for (QSMediaPlayer p : mMediaPlayers) {
- if (p.getKey() != null && p.getKey().equals(entry.getKey())) {
- Log.d(TAG, "Clearing controls since notification removed " + entry.getKey());
- p.clearControls();
- return;
- }
- }
- Log.d(TAG, "Media notification removed but no player found " + entry.getKey());
- }
-
protected void addDivider() {
mDivider = LayoutInflater.from(mContext).inflate(R.layout.qs_divider, this, false);
mDivider.setBackgroundColor(Utils.applyAlpha(mDivider.getAlpha(),
@@ -528,7 +343,6 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne
loadMediaResumptionControls();
}
}
- mNotificationEntryManager.addNotificationEntryListener(mNotificationEntryListener);
}
@Override
@@ -545,7 +359,6 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne
}
mDumpManager.unregisterDumpable(getDumpableTag());
mBroadcastDispatcher.unregisterReceiver(mUserChangeReceiver);
- mNotificationEntryManager.removeNotificationEntryListener(mNotificationEntryListener);
super.onDetachedFromWindow();
}
@@ -716,7 +529,7 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne
public void setListening(boolean listening) {
if (mListening == listening) return;
- mListening = listening;
+ mMediaHiearchyManager.setShouldListen(listening);
if (mTileLayout != null) {
mQSLogger.logAllTilesChangeListening(listening, getDumpableTag(), mCachedSpecs);
mTileLayout.setListening(listening);
@@ -724,9 +537,6 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne
if (mListening) {
refreshAllTiles();
}
- for (QSMediaPlayer player : mMediaPlayers) {
- player.setListening(mListening);
- }
}
private String getTilesSpecs() {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSMediaPlayer.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSMediaPlayer.java
deleted file mode 100644
index 7b53eed64ae6f..0000000000000
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSMediaPlayer.java
+++ /dev/null
@@ -1,128 +0,0 @@
-/*
- * 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.
- */
-
-package com.android.systemui.qs;
-
-import android.app.PendingIntent;
-import android.content.Context;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.Icon;
-import android.media.session.MediaController;
-import android.media.session.MediaSession;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ImageButton;
-import android.widget.LinearLayout;
-
-import com.android.systemui.R;
-import com.android.systemui.media.IlluminationDrawable;
-import com.android.systemui.media.MediaControlPanel;
-import com.android.systemui.plugins.ActivityStarter;
-
-import java.util.concurrent.Executor;
-
-/**
- * QQS mini media player
- */
-public class QuickQSMediaPlayer extends MediaControlPanel {
-
- private static final String TAG = "QQSMediaPlayer";
-
- // Button IDs for QS controls
- private static final int[] QQS_ACTION_IDS = {R.id.action0, R.id.action1, R.id.action2};
-
- /**
- * Initialize mini media player for QQS
- * @param context
- * @param parent
- * @param foregroundExecutor
- * @param backgroundExecutor
- * @param activityStarter
- */
- public QuickQSMediaPlayer(Context context, ViewGroup parent, Executor foregroundExecutor,
- Executor backgroundExecutor, ActivityStarter activityStarter) {
- super(context, parent, null, R.layout.qs_media_panel, QQS_ACTION_IDS,
- foregroundExecutor, backgroundExecutor, activityStarter);
- }
-
- /**
- * Update media panel view for the given media session
- * @param token token for this media session
- * @param icon app notification icon
- * @param largeIcon notification's largeIcon, used as a fallback for album art
- * @param iconColor foreground color (for text, icons)
- * @param bgColor background color
- * @param actionsContainer a LinearLayout containing the media action buttons
- * @param actionsToShow indices of which actions to display in the mini player
- * (max 3: Notification.MediaStyle.MAX_MEDIA_BUTTONS_IN_COMPACT)
- * @param contentIntent Intent to send when user taps on the view
- * @param key original notification's key
- */
- public void setMediaSession(MediaSession.Token token, Drawable icon, Icon largeIcon,
- int iconColor, int bgColor, View actionsContainer, int[] actionsToShow,
- PendingIntent contentIntent, String key) {
- // Only update if this is a different session and currently playing
- String oldPackage = "";
- if (getController() != null) {
- oldPackage = getController().getPackageName();
- }
- MediaController controller = new MediaController(getContext(), token);
- MediaSession.Token currentToken = getMediaSessionToken();
- boolean samePlayer = currentToken != null
- && currentToken.equals(token)
- && oldPackage.equals(controller.getPackageName());
- if (getController() != null && !samePlayer && !isPlaying(controller)) {
- return;
- }
-
- super.setMediaSession(token, icon, largeIcon, iconColor, bgColor, contentIntent, null, key);
-
- LinearLayout parentActionsLayout = (LinearLayout) actionsContainer;
- int i = 0;
- if (actionsToShow != null) {
- int maxButtons = Math.min(actionsToShow.length, parentActionsLayout.getChildCount());
- maxButtons = Math.min(maxButtons, QQS_ACTION_IDS.length);
- for (; i < maxButtons; i++) {
- ImageButton thisBtn = mMediaNotifView.findViewById(QQS_ACTION_IDS[i]);
- int thatId = NOTIF_ACTION_IDS[actionsToShow[i]];
- ImageButton thatBtn = parentActionsLayout.findViewById(thatId);
- if (thatBtn == null || thatBtn.getDrawable() == null
- || thatBtn.getVisibility() != View.VISIBLE) {
- thisBtn.setVisibility(View.GONE);
- continue;
- }
-
- if (mMediaNotifView.getBackground() instanceof IlluminationDrawable) {
- ((IlluminationDrawable) mMediaNotifView.getBackground())
- .setupTouch(thisBtn, mMediaNotifView);
- }
-
- Drawable thatIcon = thatBtn.getDrawable();
- thisBtn.setImageDrawable(thatIcon.mutate());
- thisBtn.setVisibility(View.VISIBLE);
- thisBtn.setOnClickListener(v -> {
- thatBtn.performClick();
- });
- }
- }
-
- // Hide any unused buttons
- for (; i < QQS_ACTION_IDS.length; i++) {
- ImageButton thisBtn = mMediaNotifView.findViewById(QQS_ACTION_IDS[i]);
- thisBtn.setVisibility(View.GONE);
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
index 6683a1ce4f4f0..8dfa22f115d7d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
@@ -18,38 +18,36 @@ package com.android.systemui.qs;
import static com.android.systemui.util.InjectionInflationController.VIEW_CONTEXT;
-import android.annotation.Nullable;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.View;
+import android.view.ViewGroup;
import android.widget.LinearLayout;
+import androidx.annotation.NonNull;
import com.android.internal.logging.UiEventLogger;
-import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.dagger.qualifiers.Background;
-import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.media.MediaData;
+import com.android.systemui.media.MediaHierarchyManager;
import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.plugins.qs.QSTile.SignalState;
import com.android.systemui.plugins.qs.QSTile.State;
import com.android.systemui.qs.customize.QSCustomizer;
import com.android.systemui.qs.logging.QSLogger;
-import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.NotificationMediaManager;
import com.android.systemui.tuner.TunerService;
import com.android.systemui.tuner.TunerService.Tunable;
import com.android.systemui.util.Utils;
-import com.android.systemui.util.concurrency.DelayableExecutor;
import java.util.ArrayList;
import java.util.Collection;
-import java.util.concurrent.Executor;
import javax.inject.Inject;
import javax.inject.Named;
@@ -63,20 +61,34 @@ public class QuickQSPanel extends QSPanel {
private static final String TAG = "QuickQSPanel";
// Start it at 6 so a non-zero value can be obtained statically.
private static int sDefaultMaxTiles = 6;
+ private final NotificationMediaManager mNotificationMediaManager;
private boolean mDisabledByPolicy;
private int mMaxTiles;
protected QSPanel mFullPanel;
- private QuickQSMediaPlayer mMediaPlayer;
/** Whether or not the QS media player feature is enabled. */
private boolean mUsingMediaPlayer;
/** Whether or not the QuickQSPanel currently contains a media player. */
- private boolean mHasMediaPlayer;
+ private boolean mShowHorizontalTileLayout;
private LinearLayout mHorizontalLinearLayout;
// Only used with media
- private QSTileLayout mMediaTileLayout;
+ private QSTileLayout mHorizontalTileLayout;
private QSTileLayout mRegularTileLayout;
+ private int mLastOrientation = -1;
+ private ViewGroup mMediaHost;
+ private final NotificationMediaManager.MediaListener mMediaCallback
+ = new NotificationMediaManager.MediaListener() {
+ @Override
+ public void onMediaDataLoaded(@NonNull String key, @NonNull MediaData data) {
+ switchTileLayout();
+ }
+
+ @Override
+ public void onMediaDataRemoved(@NonNull String key) {
+ switchTileLayout();
+ }
+ };
@Inject
public QuickQSPanel(
@@ -85,16 +97,14 @@ public class QuickQSPanel extends QSPanel {
DumpManager dumpManager,
BroadcastDispatcher broadcastDispatcher,
QSLogger qsLogger,
- @Main Executor foregroundExecutor,
- @Background DelayableExecutor backgroundExecutor,
- @Nullable LocalBluetoothManager localBluetoothManager,
+ MediaHierarchyManager mediaHierarchyManager,
+ NotificationMediaManager notificationMediaManager,
ActivityStarter activityStarter,
- NotificationEntryManager entryManager,
UiEventLogger uiEventLogger
) {
super(context, attrs, dumpManager, broadcastDispatcher, qsLogger,
- foregroundExecutor, backgroundExecutor, localBluetoothManager, activityStarter,
- entryManager, uiEventLogger);
+ mediaHierarchyManager, activityStarter, uiEventLogger);
+ mNotificationMediaManager = notificationMediaManager;
if (mFooter != null) {
removeView(mFooter.getView());
}
@@ -107,23 +117,17 @@ public class QuickQSPanel extends QSPanel {
mUsingMediaPlayer = Utils.useQsMediaPlayer(context);
if (mUsingMediaPlayer) {
+ mNotificationMediaManager.addCallback(mMediaCallback);
mHorizontalLinearLayout = new LinearLayout(mContext);
mHorizontalLinearLayout.setOrientation(LinearLayout.HORIZONTAL);
mHorizontalLinearLayout.setClipChildren(false);
mHorizontalLinearLayout.setClipToPadding(false);
- int marginSize = (int) mContext.getResources().getDimension(R.dimen.qqs_media_spacing);
- mMediaPlayer = new QuickQSMediaPlayer(mContext, mHorizontalLinearLayout,
- foregroundExecutor, backgroundExecutor, activityStarter);
- LayoutParams lp2 = new LayoutParams(0, LayoutParams.MATCH_PARENT, 1);
- lp2.setMarginEnd(marginSize);
- lp2.setMarginStart(0);
- mHorizontalLinearLayout.addView(mMediaPlayer.getView(), lp2);
-
mTileLayout = new DoubleLineTileLayout(context, mUiEventLogger);
- mMediaTileLayout = mTileLayout;
+ mHorizontalTileLayout = mTileLayout;
mRegularTileLayout = new HeaderTileLayout(context, mUiEventLogger);
LayoutParams lp = new LayoutParams(0, LayoutParams.MATCH_PARENT, 1);
+ int marginSize = (int) mContext.getResources().getDimension(R.dimen.qqs_media_spacing);
lp.setMarginEnd(0);
lp.setMarginStart(marginSize);
mHorizontalLinearLayout.addView((View) mTileLayout, lp);
@@ -135,6 +139,7 @@ public class QuickQSPanel extends QSPanel {
((View) mRegularTileLayout).setVisibility(View.GONE);
addView((View) mRegularTileLayout, 0);
super.setPadding(0, 0, 0, 0);
+ reAttachMediaHost();
} else {
sDefaultMaxTiles = getResources().getInteger(R.integer.quick_qs_panel_max_columns);
mTileLayout = new HeaderTileLayout(context, mUiEventLogger);
@@ -144,8 +149,24 @@ public class QuickQSPanel extends QSPanel {
}
}
- public QuickQSMediaPlayer getMediaPlayer() {
- return mMediaPlayer;
+ private void reAttachMediaHost() {
+ ViewGroup newParent = shouldUseHorizontalTileLayout() ? mHorizontalLinearLayout : this;
+ ViewGroup currentParent = (ViewGroup) mMediaHost.getParent();
+ if (currentParent != newParent) {
+ if (currentParent != null) {
+ currentParent.removeView(mMediaHost);
+ }
+ newParent.addView(mMediaHost);
+ ViewGroup.LayoutParams layoutParams = mMediaHost.getLayoutParams();
+ layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT;
+ layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT;
+ }
+ }
+
+ @Override
+ protected void addMediaHostView() {
+ mMediaHost = mMediaHiearchyManager.createMediaHost(
+ MediaHierarchyManager.LOCATION_QQS);
}
@Override
@@ -167,6 +188,7 @@ public class QuickQSPanel extends QSPanel {
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
Dependency.get(TunerService.class).removeTunable(mNumTiles);
+ mNotificationMediaManager.removeCallback(mMediaCallback);
}
@Override
@@ -191,10 +213,19 @@ public class QuickQSPanel extends QSPanel {
super.drawTile(r, state);
}
+ @Override
+ protected void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ if (newConfig.orientation != mLastOrientation) {
+ mLastOrientation = newConfig.orientation;
+ switchTileLayout();
+ }
+ }
+
boolean switchTileLayout() {
if (!mUsingMediaPlayer) return false;
- mHasMediaPlayer = mMediaPlayer.hasMediaSession();
- if (mHasMediaPlayer && mHorizontalLinearLayout.getVisibility() == View.GONE) {
+ mShowHorizontalTileLayout = shouldUseHorizontalTileLayout();
+ if (mShowHorizontalTileLayout && mHorizontalLinearLayout.getVisibility() == View.GONE) {
mHorizontalLinearLayout.setVisibility(View.VISIBLE);
((View) mRegularTileLayout).setVisibility(View.GONE);
mTileLayout.setListening(false);
@@ -202,11 +233,13 @@ public class QuickQSPanel extends QSPanel {
mTileLayout.removeTile(record);
record.tile.removeCallback(record.callback);
}
- mTileLayout = mMediaTileLayout;
+ mTileLayout = mHorizontalTileLayout;
if (mHost != null) setTiles(mHost.getTiles());
mTileLayout.setListening(mListening);
+ reAttachMediaHost();
return true;
- } else if (!mHasMediaPlayer && mHorizontalLinearLayout.getVisibility() == View.VISIBLE) {
+ } else if (!mShowHorizontalTileLayout
+ && mHorizontalLinearLayout.getVisibility() == View.VISIBLE) {
mHorizontalLinearLayout.setVisibility(View.GONE);
((View) mRegularTileLayout).setVisibility(View.VISIBLE);
mTileLayout.setListening(false);
@@ -217,14 +250,21 @@ public class QuickQSPanel extends QSPanel {
mTileLayout = mRegularTileLayout;
if (mHost != null) setTiles(mHost.getTiles());
mTileLayout.setListening(mListening);
+ reAttachMediaHost();
return true;
}
return false;
}
- /** Returns true if this panel currently contains a media player. */
- public boolean hasMediaPlayer() {
- return mHasMediaPlayer;
+ private boolean shouldUseHorizontalTileLayout() {
+ return mNotificationMediaManager.hasActiveMedia()
+ && getResources().getConfiguration().orientation
+ == Configuration.ORIENTATION_LANDSCAPE;
+ }
+
+ /** Returns true if this panel currently uses a horizontal tile layout. */
+ public boolean usesHorizontalLayout() {
+ return mShowHorizontalTileLayout;
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
index b15c6a3e3b59a..b14697d831ac0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
@@ -341,7 +341,7 @@ public class QuickStatusBarHeader extends RelativeLayout implements
if (mQsDisabled) {
lp.height = resources.getDimensionPixelSize(
com.android.internal.R.dimen.quick_qs_offset_height);
- } else if (useQsMediaPlayer(mContext) && mHeaderQsPanel.hasMediaPlayer()) {
+ } else if (useQsMediaPlayer(mContext) && mHeaderQsPanel.usesHorizontalLayout()) {
lp.height = Math.max(getMinimumHeight(),
resources.getDimensionPixelSize(
com.android.internal.R.dimen.quick_qs_total_height_with_media));
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
index 459c95b82ba48..63ba57ba12536 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
@@ -44,14 +44,17 @@ import android.util.Log;
import android.view.View;
import android.widget.ImageView;
+import androidx.annotation.NonNull;
+
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
import com.android.internal.statusbar.NotificationVisibility;
-import com.android.keyguard.KeyguardMediaPlayer;
import com.android.systemui.Dependency;
import com.android.systemui.Dumpable;
import com.android.systemui.Interpolators;
import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.media.MediaData;
+import com.android.systemui.media.MediaDataManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.dagger.StatusBarModule;
import com.android.systemui.statusbar.notification.NotificationEntryListener;
@@ -103,6 +106,7 @@ public class NotificationMediaManager implements Dumpable {
}
private final NotificationEntryManager mEntryManager;
+ private final MediaDataManager mMediaDataManager;
@Nullable
private Lazy mNotificationShadeWindowController;
@@ -113,7 +117,6 @@ public class NotificationMediaManager implements Dumpable {
private ScrimController mScrimController;
@Nullable
private LockscreenWallpaper mLockscreenWallpaper;
- private final KeyguardMediaPlayer mMediaPlayer;
private final Executor mMainExecutor;
@@ -187,13 +190,12 @@ public class NotificationMediaManager implements Dumpable {
NotificationEntryManager notificationEntryManager,
MediaArtworkProcessor mediaArtworkProcessor,
KeyguardBypassController keyguardBypassController,
- KeyguardMediaPlayer keyguardMediaPlayer,
@Main Executor mainExecutor,
- DeviceConfigProxy deviceConfig) {
+ DeviceConfigProxy deviceConfig,
+ MediaDataManager mediaDataManager) {
mContext = context;
mMediaArtworkProcessor = mediaArtworkProcessor;
mKeyguardBypassController = keyguardBypassController;
- mMediaPlayer = keyguardMediaPlayer;
mMediaListeners = new ArrayList<>();
// TODO: use MediaSessionManager.SessionListener to hook us up to future updates
// in session state
@@ -204,7 +206,37 @@ public class NotificationMediaManager implements Dumpable {
mNotificationShadeWindowController = notificationShadeWindowController;
mEntryManager = notificationEntryManager;
mMainExecutor = mainExecutor;
+ mMediaDataManager = mediaDataManager;
+ mediaDataManager.setListener(new MediaListener() {
+ @Override
+ public void onMediaDataLoaded(@NonNull String key, @NonNull MediaData metadata) {
+ ArrayList callbacks = new ArrayList<>(mMediaListeners);
+ for (int i = 0; i < callbacks.size(); i++) {
+ callbacks.get(i).onMediaDataLoaded(key, metadata);
+ }
+ }
+
+ @Override
+ public void onMediaDataRemoved(@NonNull String key) {
+ ArrayList callbacks = new ArrayList<>(mMediaListeners);
+ for (int i = 0; i < callbacks.size(); i++) {
+ callbacks.get(i).onMediaDataRemoved(key);
+ }
+ }
+ });
+
notificationEntryManager.addNotificationEntryListener(new NotificationEntryListener() {
+
+ @Override
+ public void onPendingEntryAdded(NotificationEntry entry) {
+ mMediaDataManager.onNotificationAdded(entry.getKey(), entry.getSbn());
+ }
+
+ @Override
+ public void onPreEntryUpdated(NotificationEntry entry) {
+ mMediaDataManager.onNotificationAdded(entry.getKey(), entry.getSbn());
+ }
+
@Override
public void onEntryInflated(NotificationEntry entry) {
findAndUpdateMediaNotifications();
@@ -222,6 +254,7 @@ public class NotificationMediaManager implements Dumpable {
boolean removedByUser,
int reason) {
onNotificationRemoved(entry.getKey());
+ mMediaDataManager.onNotificationRemoved(entry.getKey());
}
});
@@ -278,7 +311,7 @@ public class NotificationMediaManager implements Dumpable {
public void addCallback(MediaListener callback) {
mMediaListeners.add(callback);
- callback.onMetadataOrStateChanged(mMediaMetadata,
+ callback.onPrimaryMetadataOrStateChanged(mMediaMetadata,
getMediaControllerPlaybackState(mMediaController));
}
@@ -392,7 +425,7 @@ public class NotificationMediaManager implements Dumpable {
@PlaybackState.State int state = getMediaControllerPlaybackState(mMediaController);
ArrayList callbacks = new ArrayList<>(mMediaListeners);
for (int i = 0; i < callbacks.size(); i++) {
- callbacks.get(i).onMetadataOrStateChanged(mMediaMetadata, state);
+ callbacks.get(i).onPrimaryMetadataOrStateChanged(mMediaMetadata, state);
}
}
@@ -473,7 +506,6 @@ public class NotificationMediaManager implements Dumpable {
&& mBiometricUnlockController.isWakeAndUnlock();
if (mKeyguardStateController.isLaunchTransitionFadingAway() || wakeAndUnlock) {
mBackdrop.setVisibility(View.INVISIBLE);
- mMediaPlayer.clearControls();
Trace.endSection();
return;
}
@@ -496,14 +528,6 @@ public class NotificationMediaManager implements Dumpable {
}
}
- NotificationEntry entry = mEntryManager
- .getActiveNotificationUnfiltered(mMediaNotificationKey);
- if (entry != null) {
- mMediaPlayer.updateControls(entry, getMediaIcon(), mediaMetadata);
- } else {
- mMediaPlayer.clearControls();
- }
-
// Process artwork on a background thread and send the resulting bitmap to
// finishUpdateMediaMetaData.
if (metaDataChanged) {
@@ -626,7 +650,6 @@ public class NotificationMediaManager implements Dumpable {
// We are unlocking directly - no animation!
mBackdrop.setVisibility(View.GONE);
mBackdropBack.setImageDrawable(null);
- mMediaPlayer.clearControls();
if (windowController != null) {
windowController.setBackdropShowing(false);
}
@@ -643,7 +666,6 @@ public class NotificationMediaManager implements Dumpable {
mBackdrop.setVisibility(View.GONE);
mBackdropFront.animate().cancel();
mBackdropBack.setImageDrawable(null);
- mMediaPlayer.clearControls();
mMainExecutor.execute(mHideBackdropFront);
});
if (mKeyguardStateController.isKeyguardFadingAway()) {
@@ -697,6 +719,13 @@ public class NotificationMediaManager implements Dumpable {
mProcessArtworkTasks.remove(task);
}
+ /**
+ * Are there any media notifications active?
+ */
+ public boolean hasActiveMedia() {
+ return mMediaDataManager.hasActiveMedia();
+ }
+
/**
* {@link AsyncTask} to prepare album art for use as backdrop on lock screen.
*/
@@ -750,6 +779,17 @@ public class NotificationMediaManager implements Dumpable {
* @param state Current playback state
* @see PlaybackState.State
*/
- void onMetadataOrStateChanged(MediaMetadata metadata, @PlaybackState.State int state);
+ default void onPrimaryMetadataOrStateChanged(MediaMetadata metadata,
+ @PlaybackState.State int state) {}
+
+ /**
+ * Called whenever there's new MediaData Loaded for the consumption in views
+ */
+ default void onMediaDataLoaded(@NonNull String key, @NonNull MediaData data) {}
+
+ /**
+ * Called whenever a previously existing Media notification was removed
+ */
+ default void onMediaDataRemoved(@NonNull String key) {}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
index de7e36d97b228..f0fed13114ba8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
@@ -21,9 +21,9 @@ import android.content.Context;
import android.os.Handler;
import com.android.internal.statusbar.IStatusBarService;
-import com.android.keyguard.KeyguardMediaPlayer;
import com.android.systemui.bubbles.BubbleController;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.media.MediaDataManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.MediaArtworkProcessor;
@@ -95,9 +95,9 @@ public interface StatusBarDependenciesModule {
NotificationEntryManager notificationEntryManager,
MediaArtworkProcessor mediaArtworkProcessor,
KeyguardBypassController keyguardBypassController,
- KeyguardMediaPlayer keyguardMediaPlayer,
@Main Executor mainExecutor,
- DeviceConfigProxy deviceConfigProxy) {
+ DeviceConfigProxy deviceConfigProxy,
+ MediaDataManager mediaDataManager) {
return new NotificationMediaManager(
context,
statusBarLazy,
@@ -105,9 +105,9 @@ public interface StatusBarDependenciesModule {
notificationEntryManager,
mediaArtworkProcessor,
keyguardBypassController,
- keyguardMediaPlayer,
mainExecutor,
- deviceConfigProxy);
+ deviceConfigProxy,
+ mediaDataManager);
}
/** */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
index cb0c2838c24df..634872d9d761d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
@@ -209,7 +209,7 @@ public final class NotificationEntry extends ListEntry {
}
/** The key for this notification. Guaranteed to be immutable and unique */
- public String getKey() {
+ @NonNull public String getKey() {
return mKey;
}
@@ -217,7 +217,7 @@ public final class NotificationEntry extends ListEntry {
* The StatusBarNotification that represents one half of a NotificationEntry (the other half
* being the Ranking). This object is swapped out whenever a notification is updated.
*/
- public StatusBarNotification getSbn() {
+ @NonNull public StatusBarNotification getSbn() {
return mSbn;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionListener.java
index 59f119e987b4a..3fab6f7c38573 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionListener.java
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.notification.collection.notifcollection;
+import android.annotation.NonNull;
import android.service.notification.NotificationListenerService;
import android.service.notification.StatusBarNotification;
@@ -43,13 +44,13 @@ public interface NotifCollectionListener {
* there is no guarantee of order and they may not have had a chance to initialize yet. Instead,
* use {@link #onEntryAdded} which is called after all initialization.
*/
- default void onEntryInit(NotificationEntry entry) {
+ default void onEntryInit(@NonNull NotificationEntry entry) {
}
/**
* Called whenever a notification with a new key is posted.
*/
- default void onEntryAdded(NotificationEntry entry) {
+ default void onEntryAdded(@NonNull NotificationEntry entry) {
}
/**
@@ -64,7 +65,7 @@ public interface NotifCollectionListener {
* immediately after a user dismisses a notification: we wait until we receive confirmation from
* system server before considering the notification removed.
*/
- default void onEntryRemoved(NotificationEntry entry, @CancellationReason int reason) {
+ default void onEntryRemoved(@NonNull NotificationEntry entry, @CancellationReason int reason) {
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/BypassHeadsUpNotifier.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/BypassHeadsUpNotifier.kt
index 88888d10e283e..0fd865b603f81 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/BypassHeadsUpNotifier.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/BypassHeadsUpNotifier.kt
@@ -75,7 +75,7 @@ class BypassHeadsUpNotifier @Inject constructor(
mediaManager.addCallback(this)
}
- override fun onMetadataOrStateChanged(metadata: MediaMetadata?, state: Int) {
+ override fun onPrimaryMetadataOrStateChanged(metadata: MediaMetadata?, state: Int) {
val previous = currentMediaEntry
var newEntry = entryManager
.getActiveNotificationUnfiltered(mediaManager.mediaNotificationKey)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapper.java
index b96cff830f315..93d3f3bdbe96d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapper.java
@@ -178,38 +178,6 @@ public class NotificationMediaTemplateViewWrapper extends NotificationTemplateVi
final MediaSession.Token token = mRow.getEntry().getSbn().getNotification().extras
.getParcelable(Notification.EXTRA_MEDIA_SESSION);
- if (Utils.useQsMediaPlayer(mContext) && token != null) {
- final int[] compactActions = mRow.getEntry().getSbn().getNotification().extras
- .getIntArray(Notification.EXTRA_COMPACT_ACTIONS);
- int tintColor = getNotificationHeader().getOriginalIconColor();
- NotificationShadeWindowController ctrl = Dependency.get(
- NotificationShadeWindowController.class);
- QuickQSPanel panel = ctrl.getNotificationShadeView().findViewById(
- com.android.systemui.R.id.quick_qs_panel);
- StatusBarNotification sbn = mRow.getEntry().getSbn();
- Notification notif = sbn.getNotification();
- Drawable iconDrawable = notif.getSmallIcon().loadDrawable(mContext);
- panel.getMediaPlayer().setMediaSession(token,
- iconDrawable,
- notif.getLargeIcon(),
- tintColor,
- mBackgroundColor,
- mActions,
- compactActions,
- notif.contentIntent,
- sbn.getKey());
- QSPanel bigPanel = ctrl.getNotificationShadeView().findViewById(
- com.android.systemui.R.id.quick_settings_panel);
- bigPanel.addMediaSession(token,
- iconDrawable,
- notif.getLargeIcon(),
- tintColor,
- mBackgroundColor,
- mActions,
- sbn,
- sbn.getKey());
- }
-
boolean showCompactSeekbar = mMediaManager.getShowCompactMediaSeekbar();
if (token == null || (COMPACT_MEDIA_TAG.equals(mView.getTag()) && !showCompactSeekbar)) {
if (mSeekBarView != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaHeaderView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaHeaderView.java
index ab055e1bdc36f..82560f55509ae 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaHeaderView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaHeaderView.java
@@ -19,6 +19,7 @@ package com.android.systemui.statusbar.notification.stack;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
+import android.view.ViewGroup;
import com.android.systemui.R;
import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
@@ -37,7 +38,6 @@ public class MediaHeaderView extends ActivatableNotificationView {
@Override
protected void onFinishInflate() {
super.onFinishInflate();
- mContentView = findViewById(R.id.keyguard_media_view);
}
@Override
@@ -52,4 +52,12 @@ public class MediaHeaderView extends ActivatableNotificationView {
public void setBackgroundColor(int color) {
setTintColor(color);
}
+
+ public void setMediaHost(ViewGroup mediaHost) {
+ mContentView = mediaHost;
+ addView(mediaHost);
+ ViewGroup.LayoutParams layoutParams = mediaHost.getLayoutParams();
+ layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT;
+ layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.java
index 6eec1ca33e145..1d01db453da49 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.java
@@ -29,10 +29,12 @@ import android.content.Intent;
import android.provider.Settings;
import android.view.LayoutInflater;
import android.view.View;
+import android.view.ViewGroup;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.keyguard.KeyguardMediaPlayer;
import com.android.systemui.R;
+import com.android.systemui.media.KeyguardMediaController;
+import com.android.systemui.media.MediaHierarchyManager;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.StatusBarState;
@@ -74,8 +76,8 @@ public class NotificationSectionsManager implements StackScrollAlgorithm.Section
private final StatusBarStateController mStatusBarStateController;
private final ConfigurationController mConfigurationController;
private final PeopleHubViewAdapter mPeopleHubViewAdapter;
- private final KeyguardMediaPlayer mKeyguardMediaPlayer;
private final NotificationSectionsFeatureManager mSectionsFeatureManager;
+ private final KeyguardMediaController mKeyguardMediaController;
private final int mNumberOfSections;
private final PeopleHubViewBoundary mPeopleHubViewBoundary = new PeopleHubViewBoundary() {
@@ -123,15 +125,16 @@ public class NotificationSectionsManager implements StackScrollAlgorithm.Section
StatusBarStateController statusBarStateController,
ConfigurationController configurationController,
PeopleHubViewAdapter peopleHubViewAdapter,
- KeyguardMediaPlayer keyguardMediaPlayer,
+ MediaHierarchyManager mediaHiearchyManager,
+ KeyguardMediaController keyguardMediaController,
NotificationSectionsFeatureManager sectionsFeatureManager) {
mActivityStarter = activityStarter;
mStatusBarStateController = statusBarStateController;
mConfigurationController = configurationController;
mPeopleHubViewAdapter = peopleHubViewAdapter;
- mKeyguardMediaPlayer = keyguardMediaPlayer;
mSectionsFeatureManager = sectionsFeatureManager;
mNumberOfSections = mSectionsFeatureManager.getNumberOfBuckets();
+ mKeyguardMediaController = keyguardMediaController;
}
NotificationSection[] createSectionsForBuckets() {
@@ -205,12 +208,9 @@ public class NotificationSectionsManager implements StackScrollAlgorithm.Section
mIncomingHeader.setHeaderText(R.string.notification_section_header_incoming);
mIncomingHeader.setOnHeaderClickListener(this::onGentleHeaderClick);
- if (mMediaControlsView != null) {
- mKeyguardMediaPlayer.unbindView();
- }
mMediaControlsView = reinflateView(mMediaControlsView, layoutInflater,
R.layout.keyguard_media_header);
- mKeyguardMediaPlayer.bindView(mMediaControlsView);
+ mKeyguardMediaController.attach(mMediaControlsView);
}
/** Listener for when the "clear all" button is clicked on the gentle notification header. */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
index c9716d39590e0..aa6baffbeba40 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -69,6 +69,7 @@ import com.android.systemui.dagger.qualifiers.DisplayId;
import com.android.systemui.doze.DozeLog;
import com.android.systemui.fragments.FragmentHostManager;
import com.android.systemui.fragments.FragmentHostManager.FragmentListener;
+import com.android.systemui.media.MediaHierarchyManager;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.qs.QS;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -242,6 +243,7 @@ public class NotificationPanelViewController extends PanelViewController {
private final KeyguardBypassController mKeyguardBypassController;
private final KeyguardUpdateMonitor mUpdateMonitor;
private final ConversationNotificationManager mConversationNotificationManager;
+ private final MediaHierarchyManager mMediaHierarchyManager;
private KeyguardAffordanceHelper mAffordanceHelper;
private KeyguardUserSwitcher mKeyguardUserSwitcher;
@@ -456,7 +458,8 @@ public class NotificationPanelViewController extends PanelViewController {
ConfigurationController configurationController,
FlingAnimationUtils.Builder flingAnimationUtilsBuilder,
StatusBarTouchableRegionManager statusBarTouchableRegionManager,
- ConversationNotificationManager conversationNotificationManager) {
+ ConversationNotificationManager conversationNotificationManager,
+ MediaHierarchyManager mediaHierarchyManager) {
super(view, falsingManager, dozeLog, keyguardStateController,
(SysuiStatusBarStateController) statusBarStateController, vibratorHelper,
latencyTracker, flingAnimationUtilsBuilder, statusBarTouchableRegionManager);
@@ -466,6 +469,7 @@ public class NotificationPanelViewController extends PanelViewController {
mZenModeController = zenModeController;
mConfigurationController = configurationController;
mFlingAnimationUtilsBuilder = flingAnimationUtilsBuilder;
+ mMediaHierarchyManager = mediaHierarchyManager;
mView.setWillNotDraw(!DEBUG);
mInjectionInflationController = injectionInflationController;
mFalsingManager = falsingManager;
@@ -1609,7 +1613,7 @@ public class NotificationPanelViewController extends PanelViewController {
if (mQs == null) return;
float qsExpansionFraction = getQsExpansionFraction();
mQs.setQsExpansion(qsExpansionFraction, getHeaderTranslation());
- int heightDiff = mQs.getDesiredHeight() - mQs.getQsMinExpansionHeight();
+ mMediaHierarchyManager.setQsExpansion(qsExpansionFraction);
mNotificationStackScroller.setQsExpansionFraction(qsExpansionFraction);
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/animation/UniqueObjectHost.kt b/packages/SystemUI/src/com/android/systemui/util/animation/UniqueObjectHost.kt
new file mode 100644
index 0000000000000..63fc6c2481fbc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/animation/UniqueObjectHost.kt
@@ -0,0 +1,41 @@
+/*
+ * 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.systemui.util.animation
+
+import android.content.Context
+import android.widget.FrameLayout
+
+/**
+ * A special view that hosts a unique object, which only exists once, but can transition between
+ * different hosts. If the view currently hosts the unique object, it's measuring it normally,
+ * but if it's not attached, it will obtain the size by requesting a measure.
+ */
+class UniqueObjectHost(context: Context) : FrameLayout(context) {
+ private var cachedHeight: Int = 0
+ private var cachedWidth: Int = 0
+ override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec)
+ if (isCurrentHost()) {
+ cachedHeight = measuredHeight
+ cachedWidth = measuredWidth
+ } else {
+ setMeasuredDimension(cachedWidth, cachedHeight)
+ }
+ }
+
+ private fun isCurrentHost() = childCount != 0
+}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMediaPlayerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMediaPlayerTest.kt
deleted file mode 100644
index 4bcf917fa95d4..0000000000000
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMediaPlayerTest.kt
+++ /dev/null
@@ -1,176 +0,0 @@
-/*
- * 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.keyguard
-
-import android.app.Notification
-import android.graphics.drawable.Icon
-import android.media.MediaMetadata
-import android.media.session.MediaController
-import android.media.session.MediaSession
-import android.media.session.PlaybackState
-import android.testing.AndroidTestingRunner
-import android.testing.TestableLooper
-import android.view.View
-import android.widget.TextView
-import androidx.arch.core.executor.ArchTaskExecutor
-import androidx.arch.core.executor.TaskExecutor
-import androidx.test.filters.SmallTest
-
-import com.android.systemui.R
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.statusbar.notification.collection.NotificationEntry
-import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
-import com.android.systemui.media.MediaControllerFactory
-import com.android.systemui.util.concurrency.FakeExecutor
-import com.android.systemui.util.time.FakeSystemClock
-import com.google.common.truth.Truth.assertThat
-
-import org.junit.After
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.Mockito.any
-import org.mockito.Mockito.mock
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
-
-@SmallTest
-@RunWith(AndroidTestingRunner::class)
-@TestableLooper.RunWithLooper
-public class KeyguardMediaPlayerTest : SysuiTestCase() {
-
- private lateinit var keyguardMediaPlayer: KeyguardMediaPlayer
- @Mock private lateinit var mockMediaFactory: MediaControllerFactory
- @Mock private lateinit var mockMediaController: MediaController
- private lateinit var playbackState: PlaybackState
- private lateinit var fakeExecutor: FakeExecutor
- private lateinit var mediaMetadata: MediaMetadata.Builder
- private lateinit var entry: NotificationEntry
- @Mock private lateinit var mockView: View
- private lateinit var songView: TextView
- private lateinit var artistView: TextView
- @Mock private lateinit var mockIcon: Icon
-
- private val taskExecutor: TaskExecutor = object : TaskExecutor() {
- public override fun executeOnDiskIO(runnable: Runnable) {
- runnable.run()
- }
- public override fun postToMainThread(runnable: Runnable) {
- runnable.run()
- }
- public override fun isMainThread(): Boolean {
- return true
- }
- }
-
- @Before
- public fun setup() {
- playbackState = PlaybackState.Builder().run {
- build()
- }
- mockMediaController = mock(MediaController::class.java)
- whenever(mockMediaController.getPlaybackState()).thenReturn(playbackState)
- mockMediaFactory = mock(MediaControllerFactory::class.java)
- whenever(mockMediaFactory.create(any())).thenReturn(mockMediaController)
-
- fakeExecutor = FakeExecutor(FakeSystemClock())
- keyguardMediaPlayer = KeyguardMediaPlayer(context, mockMediaFactory, fakeExecutor)
- mockIcon = mock(Icon::class.java)
-
- mockView = mock(View::class.java)
- songView = TextView(context)
- artistView = TextView(context)
- whenever(mockView.findViewById(R.id.header_title)).thenReturn(songView)
- whenever(mockView.findViewById(R.id.header_artist)).thenReturn(artistView)
-
- mediaMetadata = MediaMetadata.Builder()
- entry = NotificationEntryBuilder().build()
- entry.getSbn().getNotification().extras.putParcelable(Notification.EXTRA_MEDIA_SESSION,
- MediaSession.Token(1, null))
-
- ArchTaskExecutor.getInstance().setDelegate(taskExecutor)
-
- keyguardMediaPlayer.bindView(mockView)
- }
-
- @After
- public fun tearDown() {
- keyguardMediaPlayer.unbindView()
- ArchTaskExecutor.getInstance().setDelegate(null)
- }
-
- @Test
- public fun testBind() {
- keyguardMediaPlayer.unbindView()
- keyguardMediaPlayer.bindView(mockView)
- }
-
- @Test
- public fun testUnboundClearControls() {
- keyguardMediaPlayer.unbindView()
- keyguardMediaPlayer.clearControls()
- keyguardMediaPlayer.bindView(mockView)
- }
-
- @Test
- public fun testUpdateControls() {
- keyguardMediaPlayer.updateControls(entry, mockIcon, mediaMetadata.build())
- FakeExecutor.exhaustExecutors(fakeExecutor)
- verify(mockView).setVisibility(View.VISIBLE)
- }
-
- @Test
- public fun testClearControls() {
- keyguardMediaPlayer.clearControls()
- FakeExecutor.exhaustExecutors(fakeExecutor)
- verify(mockView).setVisibility(View.GONE)
- }
-
- @Test
- public fun testUpdateControlsNullPlaybackState() {
- // GIVEN that the playback state is null (ie. the media session was destroyed)
- whenever(mockMediaController.getPlaybackState()).thenReturn(null)
- // WHEN updated
- keyguardMediaPlayer.updateControls(entry, mockIcon, mediaMetadata.build())
- FakeExecutor.exhaustExecutors(fakeExecutor)
- // THEN the controls are cleared (ie. visibility is set to GONE)
- verify(mockView).setVisibility(View.GONE)
- }
-
- @Test
- public fun testSongName() {
- val song: String = "Song"
- mediaMetadata.putText(MediaMetadata.METADATA_KEY_TITLE, song)
-
- keyguardMediaPlayer.updateControls(entry, mockIcon, mediaMetadata.build())
-
- assertThat(fakeExecutor.runAllReady()).isEqualTo(1)
- assertThat(songView.getText()).isEqualTo(song)
- }
-
- @Test
- public fun testArtistName() {
- val artist: String = "Artist"
- mediaMetadata.putText(MediaMetadata.METADATA_KEY_ARTIST, artist)
-
- keyguardMediaPlayer.updateControls(entry, mockIcon, mediaMetadata.build())
-
- assertThat(fakeExecutor.runAllReady()).isEqualTo(1)
- assertThat(artistView.getText()).isEqualTo(artist)
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java
index 92c1d76011063..f70fb4f55a8d3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java
@@ -131,7 +131,7 @@ public class KeyguardSliceProviderTest extends SysuiTestCase {
MediaMetadata metadata = mock(MediaMetadata.class);
when(metadata.getText(any())).thenReturn("metadata");
mProvider.onDozingChanged(true);
- mProvider.onMetadataOrStateChanged(metadata, PlaybackState.STATE_PLAYING);
+ mProvider.onPrimaryMetadataOrStateChanged(metadata, PlaybackState.STATE_PLAYING);
mProvider.onBindSlice(mProvider.getUri());
verify(metadata).getText(eq(MediaMetadata.METADATA_KEY_TITLE));
verify(metadata).getText(eq(MediaMetadata.METADATA_KEY_ARTIST));
@@ -144,7 +144,7 @@ public class KeyguardSliceProviderTest extends SysuiTestCase {
when(metadata.getText(any())).thenReturn("metadata");
when(mKeyguardBypassController.getBypassEnabled()).thenReturn(true);
when(mDozeParameters.getAlwaysOn()).thenReturn(true);
- mProvider.onMetadataOrStateChanged(metadata, PlaybackState.STATE_PLAYING);
+ mProvider.onPrimaryMetadataOrStateChanged(metadata, PlaybackState.STATE_PLAYING);
mProvider.onBindSlice(mProvider.getUri());
verify(metadata).getText(eq(MediaMetadata.METADATA_KEY_TITLE));
verify(metadata).getText(eq(MediaMetadata.METADATA_KEY_ARTIST));
@@ -210,7 +210,8 @@ public class KeyguardSliceProviderTest extends SysuiTestCase {
mProvider.onStateChanged(StatusBarState.KEYGUARD);
mProvider.onDozingChanged(true);
reset(mContentResolver);
- mProvider.onMetadataOrStateChanged(mock(MediaMetadata.class), PlaybackState.STATE_PLAYING);
+ mProvider.onPrimaryMetadataOrStateChanged(mock(MediaMetadata.class),
+ PlaybackState.STATE_PLAYING);
verify(mContentResolver).notifyChange(eq(mProvider.getUri()), eq(null));
// Hides after waking up
@@ -222,7 +223,8 @@ public class KeyguardSliceProviderTest extends SysuiTestCase {
@Test
public void onDozingChanged_updatesSliceIfMedia() {
mProvider.onStateChanged(StatusBarState.KEYGUARD);
- mProvider.onMetadataOrStateChanged(mock(MediaMetadata.class), PlaybackState.STATE_PLAYING);
+ mProvider.onPrimaryMetadataOrStateChanged(mock(MediaMetadata.class),
+ PlaybackState.STATE_PLAYING);
reset(mContentResolver);
// Show media when dozing
mProvider.onDozingChanged(true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java
index 646bc9699ff84..cdf2b6faf98c6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java
@@ -42,9 +42,9 @@ import android.view.ViewGroup;
import androidx.test.filters.SmallTest;
-import com.android.keyguard.KeyguardMediaPlayer;
import com.android.systemui.ActivityStarterDelegate;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.media.MediaHierarchyManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager;
@@ -53,6 +53,7 @@ import com.android.systemui.statusbar.notification.row.ActivatableNotificationVi
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.dagger.NotificationRowComponent;
import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.util.animation.UniqueObjectHost;
import org.junit.Before;
import org.junit.Rule;
@@ -74,7 +75,7 @@ public class NotificationSectionsManagerTest extends SysuiTestCase {
@Mock private StatusBarStateController mStatusBarStateController;
@Mock private ConfigurationController mConfigurationController;
@Mock private PeopleHubViewAdapter mPeopleHubAdapter;
- @Mock private KeyguardMediaPlayer mKeyguardMediaPlayer;
+ @Mock private MediaHierarchyManager mMediaHierarchyManager;
@Mock private NotificationSectionsFeatureManager mSectionsFeatureManager;
@Mock private NotificationRowComponent mNotificationRowComponent;
@Mock private ActivatableNotificationViewController mActivatableNotificationViewController;
@@ -87,13 +88,15 @@ public class NotificationSectionsManagerTest extends SysuiTestCase {
when(mNotificationRowComponent.getActivatableNotificationViewController()).thenReturn(
mActivatableNotificationViewController
);
+ when(mMediaHierarchyManager.createMediaHost(any())).thenReturn(
+ new UniqueObjectHost(getContext()));
mSectionsManager =
new NotificationSectionsManager(
mActivityStarterDelegate,
mStatusBarStateController,
mConfigurationController,
mPeopleHubAdapter,
- mKeyguardMediaPlayer,
+ mMediaHierarchyManager,
mSectionsFeatureManager
);
// Required in order for the header inflation to work properly
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
index 57ef05544e7ed..b5663d5dd19e1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
@@ -55,6 +55,7 @@ import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.classifier.FalsingManagerFake;
import com.android.systemui.doze.DozeLog;
+import com.android.systemui.media.MediaHierarchyManager;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.FlingAnimationUtils;
@@ -172,6 +173,8 @@ public class NotificationPanelViewTest extends SysuiTestCase {
@Mock
private ConfigurationController mConfigurationController;
@Mock
+ private MediaHierarchyManager mMediaHiearchyManager;
+ @Mock
private ConversationNotificationManager mConversationNotificationManager;
private FlingAnimationUtils.Builder mFlingAnimationUtilsBuilder;
@@ -228,7 +231,7 @@ public class NotificationPanelViewTest extends SysuiTestCase {
mLatencyTracker, mPowerManager, mAccessibilityManager, 0, mUpdateMonitor,
mMetricsLogger, mActivityManager, mZenModeController, mConfigurationController,
mFlingAnimationUtilsBuilder, mStatusBarTouchableRegionManager,
- mConversationNotificationManager);
+ mConversationNotificationManager, mMediaHiearchyManager);
mNotificationPanelViewController.initDependencies(mStatusBar, mGroupManager,
mNotificationShelf, mNotificationAreaController, mScrimController);
mNotificationPanelViewController.setHeadsUpManager(mHeadsUpManager);