Loading packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java +99 −84 Original line number Diff line number Diff line Loading @@ -37,9 +37,7 @@ import android.media.session.MediaSession; import android.media.session.PlaybackState; import android.service.media.MediaBrowserService; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ImageButton; import android.widget.ImageView; import android.widget.LinearLayout; Loading Loading @@ -90,16 +88,14 @@ public class MediaControlPanel { }; private final SeekBarViewModel mSeekBarViewModel; private final SeekBarObserver mSeekBarObserver; private SeekBarObserver mSeekBarObserver; private final Executor mForegroundExecutor; protected final Executor mBackgroundExecutor; private final ActivityStarter mActivityStarter; private final LayoutAnimationHelper mLayoutAnimationHelper; private LayoutAnimationHelper mLayoutAnimationHelper; private Context mContext; private MotionLayout mMediaNotifView; private final View mBackground; private View mSeamless; private PlayerViewHolder mViewHolder; private MediaSession.Token mToken; private MediaController mController; private int mForegroundColor; Loading @@ -107,7 +103,7 @@ public class MediaControlPanel { private MediaDevice mDevice; protected ComponentName mServiceComponent; private boolean mIsRegistered = false; private final List<KeyFrames> mKeyFrames; private List<KeyFrames> mKeyFrames; private String mKey; private int mAlbumArtSize; private int mAlbumArtRadius; Loading Loading @@ -166,37 +162,27 @@ public class MediaControlPanel { /** * Initialize a new control panel * @param context * @param parent * @param routeManager Manager used to listen for device change events. * @param foregroundExecutor foreground executor * @param backgroundExecutor background executor, used for processing artwork * @param activityStarter activity starter */ public MediaControlPanel(Context context, ViewGroup parent, @Nullable LocalMediaManager routeManager, Executor foregroundExecutor, DelayableExecutor backgroundExecutor, ActivityStarter activityStarter) { public MediaControlPanel(Context context, @Nullable LocalMediaManager routeManager, Executor foregroundExecutor, DelayableExecutor backgroundExecutor, ActivityStarter activityStarter) { mContext = context; LayoutInflater inflater = LayoutInflater.from(mContext); mMediaNotifView = (MotionLayout) inflater.inflate(R.layout.qs_media_panel, parent, false); mBackground = mMediaNotifView.findViewById(R.id.media_background); mLayoutAnimationHelper = new LayoutAnimationHelper(mMediaNotifView); GoneChildrenHideHelper.clipGoneChildrenOnLayout(mMediaNotifView); mKeyFrames = mMediaNotifView.getDefinedTransitions().get(0).getKeyFrameList(); mLocalMediaManager = routeManager; mForegroundExecutor = foregroundExecutor; mBackgroundExecutor = backgroundExecutor; mActivityStarter = activityStarter; mSeekBarViewModel = new SeekBarViewModel(backgroundExecutor); mSeekBarObserver = new SeekBarObserver(getView()); mSeekBarViewModel.getProgress().observeForever(mSeekBarObserver); SeekBar bar = getView().findViewById(R.id.media_progress_bar); bar.setOnSeekBarChangeListener(mSeekBarViewModel.getSeekBarListener()); bar.setOnTouchListener(mSeekBarViewModel.getSeekBarTouchListener()); loadDimens(); } public void onDestroy() { if (mSeekBarObserver != null) { mSeekBarViewModel.getProgress().removeObserver(mSeekBarObserver); } makeInactive(); } Loading @@ -207,11 +193,12 @@ public class MediaControlPanel { } /** * Get the view used to display media controls * @return the view * Get the view holder used to display media controls * @return the view holder */ public MotionLayout getView() { return mMediaNotifView; @Nullable public PlayerViewHolder getView() { return mViewHolder; } /** Loading @@ -234,10 +221,27 @@ public class MediaControlPanel { return mContext; } /** Attaches the player to the view holder. */ public void attach(PlayerViewHolder vh) { mViewHolder = vh; MotionLayout motionView = vh.getPlayer(); mLayoutAnimationHelper = new LayoutAnimationHelper(motionView); GoneChildrenHideHelper.clipGoneChildrenOnLayout(motionView); mKeyFrames = motionView.getDefinedTransitions().get(0).getKeyFrameList(); mSeekBarObserver = new SeekBarObserver(motionView); mSeekBarViewModel.getProgress().observeForever(mSeekBarObserver); SeekBar bar = vh.getSeekBar(); bar.setOnSeekBarChangeListener(mSeekBarViewModel.getSeekBarListener()); bar.setOnTouchListener(mSeekBarViewModel.getSeekBarTouchListener()); } /** * Bind this view based on the data given */ public void bind(@NotNull MediaData data) { if (mViewHolder == null) { return; } MediaSession.Token token = data.getToken(); mForegroundColor = data.getForegroundColor(); mBackgroundColor = data.getBackgroundColor(); Loading @@ -254,8 +258,8 @@ public class MediaControlPanel { mController = new MediaController(mContext, mToken); ConstraintSet expandedSet = mMediaNotifView.getConstraintSet(R.id.expanded); ConstraintSet collapsedSet = mMediaNotifView.getConstraintSet(R.id.collapsed); ConstraintSet expandedSet = mViewHolder.getPlayer().getConstraintSet(R.id.expanded); ConstraintSet collapsedSet = mViewHolder.getPlayer().getConstraintSet(R.id.collapsed); // Try to find a browser service component for this app // TODO also check for a media button receiver intended for restarting (b/154127084) Loading @@ -281,53 +285,51 @@ public class MediaControlPanel { mController.registerCallback(mSessionCallback); mMediaNotifView.requireViewById(R.id.media_background).setBackgroundTintList( mViewHolder.getBackground().setBackgroundTintList( ColorStateList.valueOf(mBackgroundColor)); // Click action PendingIntent clickIntent = data.getClickIntent(); if (clickIntent != null) { mMediaNotifView.setOnClickListener(v -> { mViewHolder.getPlayer().setOnClickListener(v -> { mActivityStarter.postStartActivityDismissingKeyguard(clickIntent); }); } ImageView albumView = mMediaNotifView.findViewById(R.id.album_art); ImageView albumView = mViewHolder.getAlbumView(); // TODO: migrate this to a view with rounded corners instead of baking the rounding // into the bitmap Drawable artwork = createRoundedBitmap(data.getArtwork()); albumView.setImageDrawable(artwork); // App icon ImageView appIcon = mMediaNotifView.requireViewById(R.id.icon); ImageView appIcon = mViewHolder.getAppIcon(); Drawable iconDrawable = data.getAppIcon().mutate(); iconDrawable.setTint(mForegroundColor); appIcon.setImageDrawable(iconDrawable); // Song name TextView titleText = mMediaNotifView.requireViewById(R.id.header_title); TextView titleText = mViewHolder.getTitleText(); titleText.setText(data.getSong()); titleText.setTextColor(data.getForegroundColor()); // App title TextView appName = mMediaNotifView.requireViewById(R.id.app_name); TextView appName = mViewHolder.getAppName(); appName.setText(data.getApp()); appName.setTextColor(mForegroundColor); // Artist name TextView artistText = mMediaNotifView.requireViewById(R.id.header_artist); TextView artistText = mViewHolder.getArtistText(); artistText.setText(data.getArtist()); artistText.setTextColor(mForegroundColor); // Transfer chip mSeamless = mMediaNotifView.findViewById(R.id.media_seamless); if (mSeamless != null) { if (mLocalMediaManager != null) { mSeamless.setVisibility(View.VISIBLE); mViewHolder.getSeamless().setVisibility(View.VISIBLE); setVisibleAndAlpha(collapsedSet, R.id.media_seamless, true /*visible */); setVisibleAndAlpha(expandedSet, R.id.media_seamless, true /*visible */); updateDevice(mLocalMediaManager.getCurrentConnectedDevice()); mSeamless.setOnClickListener(v -> { mViewHolder.getSeamless().setOnClickListener(v -> { final Intent intent = new Intent() .setAction(MediaOutputSliceConstants.ACTION_MEDIA_OUTPUT) .putExtra(MediaOutputSliceConstants.EXTRA_PACKAGE_NAME, Loading @@ -339,7 +341,6 @@ public class MediaControlPanel { } else { Log.d(TAG, "LocalMediaManager is null. Not binding output chip for pkg=" + pkgName); } } PlaybackInfo playbackInfo = mController.getPlaybackInfo(); if (playbackInfo != null) { mIsRemotePlayback = playbackInfo.getPlaybackType() == PlaybackInfo.PLAYBACK_TYPE_REMOTE; Loading @@ -353,16 +354,16 @@ public class MediaControlPanel { List<MediaAction> actionIcons = data.getActions(); for (; i < actionIcons.size() && i < ACTION_IDS.length; i++) { int actionId = ACTION_IDS[i]; final ImageButton button = mMediaNotifView.findViewById(actionId); final ImageButton button = mViewHolder.getAction(actionId); MediaAction mediaAction = actionIcons.get(i); button.setImageDrawable(mediaAction.getDrawable()); button.setContentDescription(mediaAction.getContentDescription()); button.setImageTintList(ColorStateList.valueOf(mForegroundColor)); PendingIntent actionIntent = mediaAction.getIntent(); if (mBackground.getBackground() instanceof IlluminationDrawable) { ((IlluminationDrawable) mBackground.getBackground()) .setupTouch(button, mMediaNotifView); if (mViewHolder.getBackground().getBackground() instanceof IlluminationDrawable) { ((IlluminationDrawable) mViewHolder.getBackground().getBackground()) .setupTouch(button, mViewHolder.getPlayer()); } button.setOnClickListener(v -> { Loading Loading @@ -397,8 +398,8 @@ public class MediaControlPanel { makeActive(); // Update both constraint sets to regenerate the animation. mMediaNotifView.updateState(R.id.collapsed, collapsedSet); mMediaNotifView.updateState(R.id.expanded, expandedSet); mViewHolder.getPlayer().updateState(R.id.collapsed, collapsedSet); mViewHolder.getPlayer().updateState(R.id.expanded, expandedSet); } @UiThread Loading Loading @@ -441,6 +442,9 @@ public class MediaControlPanel { * @param visible is the view visible */ private void updateKeyFrameVisibility(int actionId, boolean visible) { if (mKeyFrames == null) { return; } for (int i = 0; i < mKeyFrames.size(); i++) { KeyFrames keyframe = mKeyFrames.get(i); ArrayList<Key> viewKeyFrames = keyframe.getKeyFramesForView(actionId); Loading Loading @@ -528,38 +532,38 @@ public class MediaControlPanel { * @param device device information to display */ private void updateDevice(MediaDevice device) { if (mSeamless == null) { return; } mForegroundExecutor.execute(() -> { updateChipInternal(device); }); } private void updateChipInternal(MediaDevice device) { if (mViewHolder == null) { return; } ColorStateList fgTintList = ColorStateList.valueOf(mForegroundColor); // Update the outline color LinearLayout viewLayout = (LinearLayout) mSeamless; LinearLayout viewLayout = (LinearLayout) mViewHolder.getSeamless(); RippleDrawable bkgDrawable = (RippleDrawable) viewLayout.getBackground(); GradientDrawable rect = (GradientDrawable) bkgDrawable.getDrawable(0); rect.setStroke(2, mForegroundColor); rect.setColor(mBackgroundColor); ImageView iconView = mSeamless.findViewById(R.id.media_seamless_image); TextView deviceName = mSeamless.findViewById(R.id.media_seamless_text); ImageView iconView = mViewHolder.getSeamlessIcon(); TextView deviceName = mViewHolder.getSeamlessText(); deviceName.setTextColor(fgTintList); if (mIsRemotePlayback) { mSeamless.setEnabled(false); mSeamless.setAlpha(0.38f); mViewHolder.getSeamless().setEnabled(false); mViewHolder.getSeamless().setAlpha(0.38f); iconView.setImageResource(R.drawable.ic_hardware_speaker); iconView.setVisibility(View.VISIBLE); iconView.setImageTintList(fgTintList); deviceName.setText(R.string.media_seamless_remote_device); } else if (device != null) { mSeamless.setEnabled(true); mSeamless.setAlpha(1f); mViewHolder.getSeamless().setEnabled(true); mViewHolder.getSeamless().setAlpha(1f); Drawable icon = device.getIcon(); iconView.setVisibility(View.VISIBLE); iconView.setImageTintList(fgTintList); Loading @@ -575,8 +579,8 @@ public class MediaControlPanel { } else { // Reset to default Log.d(TAG, "device is null. Not binding output chip."); mSeamless.setEnabled(true); mSeamless.setAlpha(1f); mViewHolder.getSeamless().setEnabled(true); mViewHolder.getSeamless().setAlpha(1f); iconView.setVisibility(View.GONE); deviceName.setText(com.android.internal.R.string.ext_media_seamless_action); } Loading @@ -601,17 +605,20 @@ public class MediaControlPanel { * Hide the media buttons and show only a restart button */ protected void resetButtons() { if (mViewHolder == null) { return; } // Hide all the old buttons ConstraintSet expandedSet = mMediaNotifView.getConstraintSet(R.id.expanded); ConstraintSet collapsedSet = mMediaNotifView.getConstraintSet(R.id.collapsed); ConstraintSet expandedSet = mViewHolder.getPlayer().getConstraintSet(R.id.expanded); ConstraintSet collapsedSet = mViewHolder.getPlayer().getConstraintSet(R.id.collapsed); for (int i = 1; i < ACTION_IDS.length; i++) { setVisibleAndAlpha(expandedSet, ACTION_IDS[i], false /*visible */); setVisibleAndAlpha(collapsedSet, ACTION_IDS[i], false /*visible */); } // Add a restart button ImageButton btn = mMediaNotifView.findViewById(ACTION_IDS[0]); ImageButton btn = mViewHolder.getAction0(); btn.setOnClickListener(v -> { Log.d(TAG, "Attempting to restart session"); if (mQSMediaBrowser != null) { Loading Loading @@ -639,9 +646,9 @@ public class MediaControlPanel { mSeekBarViewModel.clearController(); // TODO: fix guts // View guts = mMediaNotifView.findViewById(R.id.media_guts); View options = mMediaNotifView.findViewById(R.id.qs_media_controls_options); View options = mViewHolder.getOptions(); mMediaNotifView.setOnLongClickListener(v -> { mViewHolder.getPlayer().setOnLongClickListener(v -> { // Replace player view with close/cancel view // guts.setVisibility(View.GONE); options.setVisibility(View.VISIBLE); Loading Loading @@ -748,20 +755,28 @@ public class MediaControlPanel { protected void removePlayer() { } public void measure(@Nullable MediaMeasurementInput input) { if (mViewHolder == null) { return; } if (input != null) { int width = input.getWidth(); setPlayerWidth(width); mMediaNotifView.measure(input.getWidthMeasureSpec(), input.getHeightMeasureSpec()); mViewHolder.getPlayer().measure(input.getWidthMeasureSpec(), input.getHeightMeasureSpec()); } } public void setPlayerWidth(int width) { ConstraintSet expandedSet = mMediaNotifView.getConstraintSet(R.id.expanded); ConstraintSet collapsedSet = mMediaNotifView.getConstraintSet(R.id.collapsed); if (mViewHolder == null) { return; } MotionLayout view = mViewHolder.getPlayer(); ConstraintSet expandedSet = view.getConstraintSet(R.id.expanded); ConstraintSet collapsedSet = view.getConstraintSet(R.id.collapsed); collapsedSet.setGuidelineBegin(R.id.view_width, width); expandedSet.setGuidelineBegin(R.id.view_width, width); mMediaNotifView.updateState(R.id.collapsed, collapsedSet); mMediaNotifView.updateState(R.id.expanded, expandedSet); view.updateState(R.id.collapsed, collapsedSet); view.updateState(R.id.expanded, expandedSet); } public void animatePendingSizeChange(long duration, long startDelay) { Loading packages/SystemUI/src/com/android/systemui/media/MediaViewManager.kt +53 −41 Original line number Diff line number Diff line Loading @@ -56,8 +56,13 @@ class MediaViewManager @Inject constructor( } } private val scrollChangedListener = object : View.OnScrollChangeListener { override fun onScrollChange(v: View?, scrollX: Int, scrollY: Int, oldScrollX: Int, oldScrollY: Int) { override fun onScrollChange( v: View?, scrollX: Int, scrollY: Int, oldScrollX: Int, oldScrollY: Int ) { if (playerWidthPlusPadding == 0) { return } Loading @@ -79,16 +84,17 @@ class MediaViewManager @Inject constructor( override fun onMediaDataRemoved(key: String) { val removed = mediaPlayers.remove(key) removed?.apply { val beforeActive = mediaContent.indexOfChild(removed.view) <= activeMediaIndex mediaContent.removeView(removed.view) val beforeActive = mediaContent.indexOfChild(removed.view?.player) <= activeMediaIndex mediaContent.removeView(removed.view?.player) removed.onDestroy() updateMediaPaddings() if (beforeActive) { // also update the index here since the scroll below might not always lead // to a scrolling changed activeMediaIndex = Math.max(0, activeMediaIndex - 1) mediaCarousel.scrollX = Math.max(mediaCarousel.scrollX - playerWidthPlusPadding, 0) mediaCarousel.scrollX = Math.max(mediaCarousel.scrollX - playerWidthPlusPadding, 0) } updatePlayerVisibilities() } Loading @@ -103,7 +109,7 @@ class MediaViewManager @Inject constructor( private fun reorderAllPlayers() { for (mediaPlayer in mediaPlayers.values) { val view = mediaPlayer.view val view = mediaPlayer.view?.player if (mediaPlayer.isPlaying && mediaContent.indexOfChild(view) != 0) { mediaContent.removeView(view) mediaContent.addView(view, 0) Loading Loading @@ -142,24 +148,26 @@ class MediaViewManager @Inject constructor( val routeManager = LocalMediaManager(context, localBluetoothManager, imm, data.packageName) existingPlayer = MediaControlPanel(context, mediaContent, routeManager, foregroundExecutor, backgroundExecutor, activityStarter) existingPlayer = MediaControlPanel(context, routeManager, foregroundExecutor, backgroundExecutor, activityStarter) existingPlayer.attach(PlayerViewHolder.create(LayoutInflater.from(context), mediaContent)) mediaPlayers[key] = existingPlayer val lp = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) existingPlayer.view.setLayoutParams(lp) existingPlayer.view?.player?.setLayoutParams(lp) existingPlayer.setListening(currentlyExpanded) if (existingPlayer.isPlaying) { mediaContent.addView(existingPlayer.view, 0) mediaContent.addView(existingPlayer.view?.player, 0) } else { mediaContent.addView(existingPlayer.view) mediaContent.addView(existingPlayer.view?.player) } updatePlayerToCurrentState(existingPlayer) } else if (existingPlayer.isPlaying && mediaContent.indexOfChild(existingPlayer.view) != 0) { mediaContent.indexOfChild(existingPlayer.view?.player) != 0) { if (visualStabilityManager.isReorderingAllowed) { mediaContent.removeView(existingPlayer.view) mediaContent.addView(existingPlayer.view, 0) mediaContent.removeView(existingPlayer.view?.player) mediaContent.addView(existingPlayer.view?.player, 0) } else { visualStabilityManager.addReorderingAllowedCallback(visualStabilityCallback) } Loading @@ -167,7 +175,7 @@ class MediaViewManager @Inject constructor( existingPlayer.bind(data) // Resetting the progress to make sure it's taken into account for the latest // motion model existingPlayer.view.progress = currentState?.expansion ?: 0.0f existingPlayer.view?.player?.progress = currentState?.expansion ?: 0.0f updateMediaPaddings() } Loading @@ -190,7 +198,6 @@ class MediaViewManager @Inject constructor( mediaView.layoutParams = layoutParams } } } /** Loading @@ -201,8 +208,8 @@ class MediaViewManager @Inject constructor( currentState = state currentlyExpanded = state.expansion > 0 for (mediaPlayer in mediaPlayers.values) { val view = mediaPlayer.view view.progress = state.expansion val view = mediaPlayer.view?.player view?.progress = state.expansion } } Loading @@ -215,8 +222,12 @@ class MediaViewManager @Inject constructor( * @param desiredState the target state we're transitioning to * @param animate should this be animated */ fun onDesiredLocationChanged(desiredState: MediaState?, animate: Boolean, duration: Long, startDelay: Long) { fun onDesiredLocationChanged( desiredState: MediaState?, animate: Boolean, duration: Long, startDelay: Long ) { if (desiredState is MediaHost.MediaHostState) { // This is a hosting view, let's remeasure our players this.desiredState = desiredState Loading @@ -224,7 +235,7 @@ class MediaViewManager @Inject constructor( if (playerWidth != width) { setPlayerWidth(width) for (mediaPlayer in mediaPlayers.values) { if (animate && mediaPlayer.view.visibility == View.VISIBLE) { if (animate && mediaPlayer.view?.player?.visibility == View.VISIBLE) { mediaPlayer.animatePendingSizeChange(duration, startDelay) } } Loading Loading @@ -268,22 +279,23 @@ class MediaViewManager @Inject constructor( */ fun obtainMeasurement(input: MediaMeasurementInput): MeasurementOutput? { val firstPlayer = mediaPlayers.values.firstOrNull() ?: return null var result: MeasurementOutput? = null firstPlayer.view?.player?.let { // Let's measure the size of the first player and return its height val previousProgress = firstPlayer.view.progress val previousRight = firstPlayer.view.right val previousBottom = firstPlayer.view.bottom firstPlayer.view.progress = input.expansion val previousProgress = it.progress val previousRight = it.right val previousBottom = it.bottom it.progress = input.expansion firstPlayer.measure(input) // Relayouting is necessary in motionlayout to obtain its size properly .... firstPlayer.view.layout(0, 0, firstPlayer.view.measuredWidth, firstPlayer.view.measuredHeight) val result = MeasurementOutput(firstPlayer.view.measuredWidth, firstPlayer.view.measuredHeight) firstPlayer.view.progress = previousProgress it.layout(0, 0, it.measuredWidth, it.measuredHeight) val result = MeasurementOutput(it.measuredWidth, it.measuredHeight) it.progress = previousProgress if (desiredState != null) { // remeasure it to the old size again! firstPlayer.measure(desiredState!!.measurementInput) firstPlayer.view.layout(0, 0, previousRight, previousBottom) it.layout(0, 0, previousRight, previousBottom) } } return result } Loading @@ -295,7 +307,7 @@ class MediaViewManager @Inject constructor( val widthSpec = desiredState!!.measurementInput?.widthMeasureSpec ?: 0 val heightSpec = desiredState!!.measurementInput?.heightMeasureSpec ?: 0 for (mediaPlayer in mediaPlayers.values) { mediaPlayer.view.measure(widthSpec, heightSpec) mediaPlayer.view?.player?.measure(widthSpec, heightSpec) } } } Loading packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt 0 → 100644 +91 −0 File added.Preview size limit exceeded, changes collapsed. Show changes Loading
packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java +99 −84 Original line number Diff line number Diff line Loading @@ -37,9 +37,7 @@ import android.media.session.MediaSession; import android.media.session.PlaybackState; import android.service.media.MediaBrowserService; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ImageButton; import android.widget.ImageView; import android.widget.LinearLayout; Loading Loading @@ -90,16 +88,14 @@ public class MediaControlPanel { }; private final SeekBarViewModel mSeekBarViewModel; private final SeekBarObserver mSeekBarObserver; private SeekBarObserver mSeekBarObserver; private final Executor mForegroundExecutor; protected final Executor mBackgroundExecutor; private final ActivityStarter mActivityStarter; private final LayoutAnimationHelper mLayoutAnimationHelper; private LayoutAnimationHelper mLayoutAnimationHelper; private Context mContext; private MotionLayout mMediaNotifView; private final View mBackground; private View mSeamless; private PlayerViewHolder mViewHolder; private MediaSession.Token mToken; private MediaController mController; private int mForegroundColor; Loading @@ -107,7 +103,7 @@ public class MediaControlPanel { private MediaDevice mDevice; protected ComponentName mServiceComponent; private boolean mIsRegistered = false; private final List<KeyFrames> mKeyFrames; private List<KeyFrames> mKeyFrames; private String mKey; private int mAlbumArtSize; private int mAlbumArtRadius; Loading Loading @@ -166,37 +162,27 @@ public class MediaControlPanel { /** * Initialize a new control panel * @param context * @param parent * @param routeManager Manager used to listen for device change events. * @param foregroundExecutor foreground executor * @param backgroundExecutor background executor, used for processing artwork * @param activityStarter activity starter */ public MediaControlPanel(Context context, ViewGroup parent, @Nullable LocalMediaManager routeManager, Executor foregroundExecutor, DelayableExecutor backgroundExecutor, ActivityStarter activityStarter) { public MediaControlPanel(Context context, @Nullable LocalMediaManager routeManager, Executor foregroundExecutor, DelayableExecutor backgroundExecutor, ActivityStarter activityStarter) { mContext = context; LayoutInflater inflater = LayoutInflater.from(mContext); mMediaNotifView = (MotionLayout) inflater.inflate(R.layout.qs_media_panel, parent, false); mBackground = mMediaNotifView.findViewById(R.id.media_background); mLayoutAnimationHelper = new LayoutAnimationHelper(mMediaNotifView); GoneChildrenHideHelper.clipGoneChildrenOnLayout(mMediaNotifView); mKeyFrames = mMediaNotifView.getDefinedTransitions().get(0).getKeyFrameList(); mLocalMediaManager = routeManager; mForegroundExecutor = foregroundExecutor; mBackgroundExecutor = backgroundExecutor; mActivityStarter = activityStarter; mSeekBarViewModel = new SeekBarViewModel(backgroundExecutor); mSeekBarObserver = new SeekBarObserver(getView()); mSeekBarViewModel.getProgress().observeForever(mSeekBarObserver); SeekBar bar = getView().findViewById(R.id.media_progress_bar); bar.setOnSeekBarChangeListener(mSeekBarViewModel.getSeekBarListener()); bar.setOnTouchListener(mSeekBarViewModel.getSeekBarTouchListener()); loadDimens(); } public void onDestroy() { if (mSeekBarObserver != null) { mSeekBarViewModel.getProgress().removeObserver(mSeekBarObserver); } makeInactive(); } Loading @@ -207,11 +193,12 @@ public class MediaControlPanel { } /** * Get the view used to display media controls * @return the view * Get the view holder used to display media controls * @return the view holder */ public MotionLayout getView() { return mMediaNotifView; @Nullable public PlayerViewHolder getView() { return mViewHolder; } /** Loading @@ -234,10 +221,27 @@ public class MediaControlPanel { return mContext; } /** Attaches the player to the view holder. */ public void attach(PlayerViewHolder vh) { mViewHolder = vh; MotionLayout motionView = vh.getPlayer(); mLayoutAnimationHelper = new LayoutAnimationHelper(motionView); GoneChildrenHideHelper.clipGoneChildrenOnLayout(motionView); mKeyFrames = motionView.getDefinedTransitions().get(0).getKeyFrameList(); mSeekBarObserver = new SeekBarObserver(motionView); mSeekBarViewModel.getProgress().observeForever(mSeekBarObserver); SeekBar bar = vh.getSeekBar(); bar.setOnSeekBarChangeListener(mSeekBarViewModel.getSeekBarListener()); bar.setOnTouchListener(mSeekBarViewModel.getSeekBarTouchListener()); } /** * Bind this view based on the data given */ public void bind(@NotNull MediaData data) { if (mViewHolder == null) { return; } MediaSession.Token token = data.getToken(); mForegroundColor = data.getForegroundColor(); mBackgroundColor = data.getBackgroundColor(); Loading @@ -254,8 +258,8 @@ public class MediaControlPanel { mController = new MediaController(mContext, mToken); ConstraintSet expandedSet = mMediaNotifView.getConstraintSet(R.id.expanded); ConstraintSet collapsedSet = mMediaNotifView.getConstraintSet(R.id.collapsed); ConstraintSet expandedSet = mViewHolder.getPlayer().getConstraintSet(R.id.expanded); ConstraintSet collapsedSet = mViewHolder.getPlayer().getConstraintSet(R.id.collapsed); // Try to find a browser service component for this app // TODO also check for a media button receiver intended for restarting (b/154127084) Loading @@ -281,53 +285,51 @@ public class MediaControlPanel { mController.registerCallback(mSessionCallback); mMediaNotifView.requireViewById(R.id.media_background).setBackgroundTintList( mViewHolder.getBackground().setBackgroundTintList( ColorStateList.valueOf(mBackgroundColor)); // Click action PendingIntent clickIntent = data.getClickIntent(); if (clickIntent != null) { mMediaNotifView.setOnClickListener(v -> { mViewHolder.getPlayer().setOnClickListener(v -> { mActivityStarter.postStartActivityDismissingKeyguard(clickIntent); }); } ImageView albumView = mMediaNotifView.findViewById(R.id.album_art); ImageView albumView = mViewHolder.getAlbumView(); // TODO: migrate this to a view with rounded corners instead of baking the rounding // into the bitmap Drawable artwork = createRoundedBitmap(data.getArtwork()); albumView.setImageDrawable(artwork); // App icon ImageView appIcon = mMediaNotifView.requireViewById(R.id.icon); ImageView appIcon = mViewHolder.getAppIcon(); Drawable iconDrawable = data.getAppIcon().mutate(); iconDrawable.setTint(mForegroundColor); appIcon.setImageDrawable(iconDrawable); // Song name TextView titleText = mMediaNotifView.requireViewById(R.id.header_title); TextView titleText = mViewHolder.getTitleText(); titleText.setText(data.getSong()); titleText.setTextColor(data.getForegroundColor()); // App title TextView appName = mMediaNotifView.requireViewById(R.id.app_name); TextView appName = mViewHolder.getAppName(); appName.setText(data.getApp()); appName.setTextColor(mForegroundColor); // Artist name TextView artistText = mMediaNotifView.requireViewById(R.id.header_artist); TextView artistText = mViewHolder.getArtistText(); artistText.setText(data.getArtist()); artistText.setTextColor(mForegroundColor); // Transfer chip mSeamless = mMediaNotifView.findViewById(R.id.media_seamless); if (mSeamless != null) { if (mLocalMediaManager != null) { mSeamless.setVisibility(View.VISIBLE); mViewHolder.getSeamless().setVisibility(View.VISIBLE); setVisibleAndAlpha(collapsedSet, R.id.media_seamless, true /*visible */); setVisibleAndAlpha(expandedSet, R.id.media_seamless, true /*visible */); updateDevice(mLocalMediaManager.getCurrentConnectedDevice()); mSeamless.setOnClickListener(v -> { mViewHolder.getSeamless().setOnClickListener(v -> { final Intent intent = new Intent() .setAction(MediaOutputSliceConstants.ACTION_MEDIA_OUTPUT) .putExtra(MediaOutputSliceConstants.EXTRA_PACKAGE_NAME, Loading @@ -339,7 +341,6 @@ public class MediaControlPanel { } else { Log.d(TAG, "LocalMediaManager is null. Not binding output chip for pkg=" + pkgName); } } PlaybackInfo playbackInfo = mController.getPlaybackInfo(); if (playbackInfo != null) { mIsRemotePlayback = playbackInfo.getPlaybackType() == PlaybackInfo.PLAYBACK_TYPE_REMOTE; Loading @@ -353,16 +354,16 @@ public class MediaControlPanel { List<MediaAction> actionIcons = data.getActions(); for (; i < actionIcons.size() && i < ACTION_IDS.length; i++) { int actionId = ACTION_IDS[i]; final ImageButton button = mMediaNotifView.findViewById(actionId); final ImageButton button = mViewHolder.getAction(actionId); MediaAction mediaAction = actionIcons.get(i); button.setImageDrawable(mediaAction.getDrawable()); button.setContentDescription(mediaAction.getContentDescription()); button.setImageTintList(ColorStateList.valueOf(mForegroundColor)); PendingIntent actionIntent = mediaAction.getIntent(); if (mBackground.getBackground() instanceof IlluminationDrawable) { ((IlluminationDrawable) mBackground.getBackground()) .setupTouch(button, mMediaNotifView); if (mViewHolder.getBackground().getBackground() instanceof IlluminationDrawable) { ((IlluminationDrawable) mViewHolder.getBackground().getBackground()) .setupTouch(button, mViewHolder.getPlayer()); } button.setOnClickListener(v -> { Loading Loading @@ -397,8 +398,8 @@ public class MediaControlPanel { makeActive(); // Update both constraint sets to regenerate the animation. mMediaNotifView.updateState(R.id.collapsed, collapsedSet); mMediaNotifView.updateState(R.id.expanded, expandedSet); mViewHolder.getPlayer().updateState(R.id.collapsed, collapsedSet); mViewHolder.getPlayer().updateState(R.id.expanded, expandedSet); } @UiThread Loading Loading @@ -441,6 +442,9 @@ public class MediaControlPanel { * @param visible is the view visible */ private void updateKeyFrameVisibility(int actionId, boolean visible) { if (mKeyFrames == null) { return; } for (int i = 0; i < mKeyFrames.size(); i++) { KeyFrames keyframe = mKeyFrames.get(i); ArrayList<Key> viewKeyFrames = keyframe.getKeyFramesForView(actionId); Loading Loading @@ -528,38 +532,38 @@ public class MediaControlPanel { * @param device device information to display */ private void updateDevice(MediaDevice device) { if (mSeamless == null) { return; } mForegroundExecutor.execute(() -> { updateChipInternal(device); }); } private void updateChipInternal(MediaDevice device) { if (mViewHolder == null) { return; } ColorStateList fgTintList = ColorStateList.valueOf(mForegroundColor); // Update the outline color LinearLayout viewLayout = (LinearLayout) mSeamless; LinearLayout viewLayout = (LinearLayout) mViewHolder.getSeamless(); RippleDrawable bkgDrawable = (RippleDrawable) viewLayout.getBackground(); GradientDrawable rect = (GradientDrawable) bkgDrawable.getDrawable(0); rect.setStroke(2, mForegroundColor); rect.setColor(mBackgroundColor); ImageView iconView = mSeamless.findViewById(R.id.media_seamless_image); TextView deviceName = mSeamless.findViewById(R.id.media_seamless_text); ImageView iconView = mViewHolder.getSeamlessIcon(); TextView deviceName = mViewHolder.getSeamlessText(); deviceName.setTextColor(fgTintList); if (mIsRemotePlayback) { mSeamless.setEnabled(false); mSeamless.setAlpha(0.38f); mViewHolder.getSeamless().setEnabled(false); mViewHolder.getSeamless().setAlpha(0.38f); iconView.setImageResource(R.drawable.ic_hardware_speaker); iconView.setVisibility(View.VISIBLE); iconView.setImageTintList(fgTintList); deviceName.setText(R.string.media_seamless_remote_device); } else if (device != null) { mSeamless.setEnabled(true); mSeamless.setAlpha(1f); mViewHolder.getSeamless().setEnabled(true); mViewHolder.getSeamless().setAlpha(1f); Drawable icon = device.getIcon(); iconView.setVisibility(View.VISIBLE); iconView.setImageTintList(fgTintList); Loading @@ -575,8 +579,8 @@ public class MediaControlPanel { } else { // Reset to default Log.d(TAG, "device is null. Not binding output chip."); mSeamless.setEnabled(true); mSeamless.setAlpha(1f); mViewHolder.getSeamless().setEnabled(true); mViewHolder.getSeamless().setAlpha(1f); iconView.setVisibility(View.GONE); deviceName.setText(com.android.internal.R.string.ext_media_seamless_action); } Loading @@ -601,17 +605,20 @@ public class MediaControlPanel { * Hide the media buttons and show only a restart button */ protected void resetButtons() { if (mViewHolder == null) { return; } // Hide all the old buttons ConstraintSet expandedSet = mMediaNotifView.getConstraintSet(R.id.expanded); ConstraintSet collapsedSet = mMediaNotifView.getConstraintSet(R.id.collapsed); ConstraintSet expandedSet = mViewHolder.getPlayer().getConstraintSet(R.id.expanded); ConstraintSet collapsedSet = mViewHolder.getPlayer().getConstraintSet(R.id.collapsed); for (int i = 1; i < ACTION_IDS.length; i++) { setVisibleAndAlpha(expandedSet, ACTION_IDS[i], false /*visible */); setVisibleAndAlpha(collapsedSet, ACTION_IDS[i], false /*visible */); } // Add a restart button ImageButton btn = mMediaNotifView.findViewById(ACTION_IDS[0]); ImageButton btn = mViewHolder.getAction0(); btn.setOnClickListener(v -> { Log.d(TAG, "Attempting to restart session"); if (mQSMediaBrowser != null) { Loading Loading @@ -639,9 +646,9 @@ public class MediaControlPanel { mSeekBarViewModel.clearController(); // TODO: fix guts // View guts = mMediaNotifView.findViewById(R.id.media_guts); View options = mMediaNotifView.findViewById(R.id.qs_media_controls_options); View options = mViewHolder.getOptions(); mMediaNotifView.setOnLongClickListener(v -> { mViewHolder.getPlayer().setOnLongClickListener(v -> { // Replace player view with close/cancel view // guts.setVisibility(View.GONE); options.setVisibility(View.VISIBLE); Loading Loading @@ -748,20 +755,28 @@ public class MediaControlPanel { protected void removePlayer() { } public void measure(@Nullable MediaMeasurementInput input) { if (mViewHolder == null) { return; } if (input != null) { int width = input.getWidth(); setPlayerWidth(width); mMediaNotifView.measure(input.getWidthMeasureSpec(), input.getHeightMeasureSpec()); mViewHolder.getPlayer().measure(input.getWidthMeasureSpec(), input.getHeightMeasureSpec()); } } public void setPlayerWidth(int width) { ConstraintSet expandedSet = mMediaNotifView.getConstraintSet(R.id.expanded); ConstraintSet collapsedSet = mMediaNotifView.getConstraintSet(R.id.collapsed); if (mViewHolder == null) { return; } MotionLayout view = mViewHolder.getPlayer(); ConstraintSet expandedSet = view.getConstraintSet(R.id.expanded); ConstraintSet collapsedSet = view.getConstraintSet(R.id.collapsed); collapsedSet.setGuidelineBegin(R.id.view_width, width); expandedSet.setGuidelineBegin(R.id.view_width, width); mMediaNotifView.updateState(R.id.collapsed, collapsedSet); mMediaNotifView.updateState(R.id.expanded, expandedSet); view.updateState(R.id.collapsed, collapsedSet); view.updateState(R.id.expanded, expandedSet); } public void animatePendingSizeChange(long duration, long startDelay) { Loading
packages/SystemUI/src/com/android/systemui/media/MediaViewManager.kt +53 −41 Original line number Diff line number Diff line Loading @@ -56,8 +56,13 @@ class MediaViewManager @Inject constructor( } } private val scrollChangedListener = object : View.OnScrollChangeListener { override fun onScrollChange(v: View?, scrollX: Int, scrollY: Int, oldScrollX: Int, oldScrollY: Int) { override fun onScrollChange( v: View?, scrollX: Int, scrollY: Int, oldScrollX: Int, oldScrollY: Int ) { if (playerWidthPlusPadding == 0) { return } Loading @@ -79,16 +84,17 @@ class MediaViewManager @Inject constructor( override fun onMediaDataRemoved(key: String) { val removed = mediaPlayers.remove(key) removed?.apply { val beforeActive = mediaContent.indexOfChild(removed.view) <= activeMediaIndex mediaContent.removeView(removed.view) val beforeActive = mediaContent.indexOfChild(removed.view?.player) <= activeMediaIndex mediaContent.removeView(removed.view?.player) removed.onDestroy() updateMediaPaddings() if (beforeActive) { // also update the index here since the scroll below might not always lead // to a scrolling changed activeMediaIndex = Math.max(0, activeMediaIndex - 1) mediaCarousel.scrollX = Math.max(mediaCarousel.scrollX - playerWidthPlusPadding, 0) mediaCarousel.scrollX = Math.max(mediaCarousel.scrollX - playerWidthPlusPadding, 0) } updatePlayerVisibilities() } Loading @@ -103,7 +109,7 @@ class MediaViewManager @Inject constructor( private fun reorderAllPlayers() { for (mediaPlayer in mediaPlayers.values) { val view = mediaPlayer.view val view = mediaPlayer.view?.player if (mediaPlayer.isPlaying && mediaContent.indexOfChild(view) != 0) { mediaContent.removeView(view) mediaContent.addView(view, 0) Loading Loading @@ -142,24 +148,26 @@ class MediaViewManager @Inject constructor( val routeManager = LocalMediaManager(context, localBluetoothManager, imm, data.packageName) existingPlayer = MediaControlPanel(context, mediaContent, routeManager, foregroundExecutor, backgroundExecutor, activityStarter) existingPlayer = MediaControlPanel(context, routeManager, foregroundExecutor, backgroundExecutor, activityStarter) existingPlayer.attach(PlayerViewHolder.create(LayoutInflater.from(context), mediaContent)) mediaPlayers[key] = existingPlayer val lp = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) existingPlayer.view.setLayoutParams(lp) existingPlayer.view?.player?.setLayoutParams(lp) existingPlayer.setListening(currentlyExpanded) if (existingPlayer.isPlaying) { mediaContent.addView(existingPlayer.view, 0) mediaContent.addView(existingPlayer.view?.player, 0) } else { mediaContent.addView(existingPlayer.view) mediaContent.addView(existingPlayer.view?.player) } updatePlayerToCurrentState(existingPlayer) } else if (existingPlayer.isPlaying && mediaContent.indexOfChild(existingPlayer.view) != 0) { mediaContent.indexOfChild(existingPlayer.view?.player) != 0) { if (visualStabilityManager.isReorderingAllowed) { mediaContent.removeView(existingPlayer.view) mediaContent.addView(existingPlayer.view, 0) mediaContent.removeView(existingPlayer.view?.player) mediaContent.addView(existingPlayer.view?.player, 0) } else { visualStabilityManager.addReorderingAllowedCallback(visualStabilityCallback) } Loading @@ -167,7 +175,7 @@ class MediaViewManager @Inject constructor( existingPlayer.bind(data) // Resetting the progress to make sure it's taken into account for the latest // motion model existingPlayer.view.progress = currentState?.expansion ?: 0.0f existingPlayer.view?.player?.progress = currentState?.expansion ?: 0.0f updateMediaPaddings() } Loading @@ -190,7 +198,6 @@ class MediaViewManager @Inject constructor( mediaView.layoutParams = layoutParams } } } /** Loading @@ -201,8 +208,8 @@ class MediaViewManager @Inject constructor( currentState = state currentlyExpanded = state.expansion > 0 for (mediaPlayer in mediaPlayers.values) { val view = mediaPlayer.view view.progress = state.expansion val view = mediaPlayer.view?.player view?.progress = state.expansion } } Loading @@ -215,8 +222,12 @@ class MediaViewManager @Inject constructor( * @param desiredState the target state we're transitioning to * @param animate should this be animated */ fun onDesiredLocationChanged(desiredState: MediaState?, animate: Boolean, duration: Long, startDelay: Long) { fun onDesiredLocationChanged( desiredState: MediaState?, animate: Boolean, duration: Long, startDelay: Long ) { if (desiredState is MediaHost.MediaHostState) { // This is a hosting view, let's remeasure our players this.desiredState = desiredState Loading @@ -224,7 +235,7 @@ class MediaViewManager @Inject constructor( if (playerWidth != width) { setPlayerWidth(width) for (mediaPlayer in mediaPlayers.values) { if (animate && mediaPlayer.view.visibility == View.VISIBLE) { if (animate && mediaPlayer.view?.player?.visibility == View.VISIBLE) { mediaPlayer.animatePendingSizeChange(duration, startDelay) } } Loading Loading @@ -268,22 +279,23 @@ class MediaViewManager @Inject constructor( */ fun obtainMeasurement(input: MediaMeasurementInput): MeasurementOutput? { val firstPlayer = mediaPlayers.values.firstOrNull() ?: return null var result: MeasurementOutput? = null firstPlayer.view?.player?.let { // Let's measure the size of the first player and return its height val previousProgress = firstPlayer.view.progress val previousRight = firstPlayer.view.right val previousBottom = firstPlayer.view.bottom firstPlayer.view.progress = input.expansion val previousProgress = it.progress val previousRight = it.right val previousBottom = it.bottom it.progress = input.expansion firstPlayer.measure(input) // Relayouting is necessary in motionlayout to obtain its size properly .... firstPlayer.view.layout(0, 0, firstPlayer.view.measuredWidth, firstPlayer.view.measuredHeight) val result = MeasurementOutput(firstPlayer.view.measuredWidth, firstPlayer.view.measuredHeight) firstPlayer.view.progress = previousProgress it.layout(0, 0, it.measuredWidth, it.measuredHeight) val result = MeasurementOutput(it.measuredWidth, it.measuredHeight) it.progress = previousProgress if (desiredState != null) { // remeasure it to the old size again! firstPlayer.measure(desiredState!!.measurementInput) firstPlayer.view.layout(0, 0, previousRight, previousBottom) it.layout(0, 0, previousRight, previousBottom) } } return result } Loading @@ -295,7 +307,7 @@ class MediaViewManager @Inject constructor( val widthSpec = desiredState!!.measurementInput?.widthMeasureSpec ?: 0 val heightSpec = desiredState!!.measurementInput?.heightMeasureSpec ?: 0 for (mediaPlayer in mediaPlayers.values) { mediaPlayer.view.measure(widthSpec, heightSpec) mediaPlayer.view?.player?.measure(widthSpec, heightSpec) } } } Loading
packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt 0 → 100644 +91 −0 File added.Preview size limit exceeded, changes collapsed. Show changes