Loading packages/SystemUI/res/layout/media_session_view.xml +35 −3 Original line number Diff line number Diff line Loading @@ -157,7 +157,7 @@ app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="parent" app:barrierDirection="start" app:constraint_referenced_ids="actionPrev,media_progress_bar,actionNext,action0,action1,action2,action3,action4" app:constraint_referenced_ids="actionPrev,media_scrubbing_elapsed_time,media_progress_bar,actionNext,media_scrubbing_total_time,action0,action1,action2,action3,action4" /> <androidx.constraintlayout.widget.Barrier android:id="@+id/media_action_barrier_end" Loading @@ -167,7 +167,7 @@ app:layout_constraintTop_toBottomOf="@id/media_seamless" app:layout_constraintBottom_toBottomOf="parent" app:barrierDirection="end" app:constraint_referenced_ids="actionPrev,media_progress_bar,actionNext,action0,action1,action2,action3,action4" app:constraint_referenced_ids="actionPrev,media_scrubbing_elapsed_time,media_progress_bar,actionNext,media_scrubbing_total_time,action0,action1,action2,action3,action4" app:layout_constraintStart_toStartOf="parent" /> Loading @@ -177,7 +177,7 @@ android:layout_height="0dp" app:layout_constraintBottom_toBottomOf="parent" app:barrierDirection="top" app:constraint_referenced_ids="actionPrev,media_progress_bar,actionNext,action0,action1,action2,action3,action4" app:constraint_referenced_ids="actionPrev,media_scrubbing_elapsed_time,media_progress_bar,actionNext,media_scrubbing_total_time,action0,action1,action2,action3,action4" /> <!-- Button visibility will be controlled in code --> Loading @@ -192,6 +192,22 @@ android:layout_marginTop="0dp" /> <!-- Elapsed time, shown only when scrubbing --> <!-- The space to the left of the progress bar will either be actionPrev or media_scrubbing_elapsed_time, so they use the same layout constraints. Visibilities of elements are controlled in code. --> <TextView android:id="@+id/media_scrubbing_elapsed_time" style="@style/MediaPlayer.ScrubbingTime" android:layout_width="48dp" android:layout_height="48dp" android:layout_marginStart="4dp" android:layout_marginEnd="0dp" android:layout_marginBottom="@dimen/qs_media_padding" android:layout_marginTop="0dp" android:visibility="gone" /> <!-- Seek Bar --> <!-- As per Material Design on Bidirectionality, this is forced to LTR in code --> <SeekBar Loading @@ -218,6 +234,22 @@ android:layout_marginBottom="@dimen/qs_media_padding" android:layout_marginTop="0dp" /> <!-- Total time, shown only when scrubbing --> <!-- The space to the right of the progress bar will either be actionNext or media_scrubbing_total_time, so they use the same layout constraints. Visibilities of elements are controlled in code. --> <TextView android:id="@+id/media_scrubbing_total_time" style="@style/MediaPlayer.ScrubbingTime" android:layout_width="48dp" android:layout_height="48dp" android:layout_marginStart="0dp" android:layout_marginEnd="@dimen/qs_media_action_spacing" android:layout_marginBottom="@dimen/qs_media_padding" android:layout_marginTop="0dp" android:visibility="gone" /> <ImageButton android:id="@+id/action0" style="@style/MediaPlayer.SessionAction.Secondary" Loading packages/SystemUI/res/values/styles.xml +6 −0 Original line number Diff line number Diff line Loading @@ -601,6 +601,12 @@ <item name="android:textColor">?android:attr/textColorSecondary</item> </style> <style name="MediaPlayer.ScrubbingTime"> <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item> <item name="android:textSize">12sp</item> <item name="android:gravity">center</item> </style> <style name="MediaPlayer.Action" parent="@android:style/Widget.Material.Button.Borderless.Small"> <item name="android:background">@drawable/qs_media_light_source</item> <item name="android:tint">?android:attr/textColorPrimary</item> Loading packages/SystemUI/res/xml/media_session_collapsed.xml +10 −0 Original line number Diff line number Diff line Loading @@ -93,6 +93,11 @@ app:layout_constraintTop_toBottomOf="@id/media_seamless" app:layout_constraintLeft_toRightOf="@id/media_action_barrier" /> <!-- Showing time while scrubbing isn't available in collapsed mode. --> <Constraint android:id="@+id/media_scrubbing_elapsed_time" android:visibility="gone" /> <Constraint android:id="@+id/media_progress_bar" android:layout_width="0dp" Loading @@ -116,6 +121,11 @@ app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintTop_toBottomOf="@id/media_seamless" /> <!-- Showing time while scrubbing isn't available in collapsed mode. --> <Constraint android:id="@+id/media_scrubbing_total_time" android:visibility="gone" /> <Constraint android:id="@+id/action0" android:layout_width="48dp" Loading packages/SystemUI/res/xml/media_session_expanded.xml +21 −4 Original line number Diff line number Diff line Loading @@ -68,10 +68,19 @@ The chain is set to "spread" so that the progress bar can be weighted to fill any empty space. --> <Constraint android:id="@+id/actionPrev" android:id="@+id/media_scrubbing_elapsed_time" android:layout_width="48dp" android:layout_height="48dp" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toLeftOf="@id/actionPrev" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintHorizontal_chainStyle="spread" /> <Constraint android:id="@+id/actionPrev" android:layout_width="48dp" android:layout_height="48dp" app:layout_constraintLeft_toRightOf="@id/media_scrubbing_elapsed_time" app:layout_constraintRight_toLeftOf="@id/media_progress_bar" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintHorizontal_chainStyle="spread" /> Loading @@ -90,6 +99,14 @@ android:layout_width="48dp" android:layout_height="48dp" app:layout_constraintLeft_toRightOf="@id/media_progress_bar" app:layout_constraintRight_toLeftOf="@id/media_scrubbing_total_time" app:layout_constraintBottom_toBottomOf="parent" /> <Constraint android:id="@+id/media_scrubbing_total_time" android:layout_width="48dp" android:layout_height="48dp" app:layout_constraintLeft_toRightOf="@id/actionNext" app:layout_constraintRight_toLeftOf="@id/action0" app:layout_constraintBottom_toBottomOf="parent" /> Loading @@ -97,7 +114,7 @@ android:id="@+id/action0" android:layout_width="48dp" android:layout_height="48dp" app:layout_constraintLeft_toRightOf="@id/actionNext" app:layout_constraintLeft_toRightOf="@id/media_scrubbing_total_time" app:layout_constraintRight_toLeftOf="@id/action1" app:layout_constraintBottom_toBottomOf="parent" /> Loading packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java +153 −49 Original line number Diff line number Diff line Loading @@ -60,6 +60,7 @@ import com.android.systemui.animation.ActivityLaunchAnimator; import com.android.systemui.animation.GhostedViewLaunchAnimatorController; import com.android.systemui.broadcast.BroadcastSender; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.media.dialog.MediaOutputDialogFactory; import com.android.systemui.monet.ColorScheme; import com.android.systemui.plugins.ActivityStarter; Loading Loading @@ -108,6 +109,13 @@ public class MediaControlPanel { R.id.actionNext ); // Buttons that should get hidden when we're scrubbing (they will be replaced with the views // showing scrubbing time) private static final List<Integer> SEMANTIC_ACTIONS_HIDE_WHEN_SCRUBBING = List.of( R.id.actionPrev, R.id.actionNext ); // Buttons to show in small player when using semantic actions private static final List<Integer> SEMANTIC_ACTIONS_ALL = List.of( R.id.actionPlayPause, Loading @@ -120,6 +128,7 @@ public class MediaControlPanel { private final SeekBarViewModel mSeekBarViewModel; private SeekBarObserver mSeekBarObserver; protected final Executor mBackgroundExecutor; private final Executor mMainExecutor; private final ActivityStarter mActivityStarter; private final BroadcastSender mBroadcastSender; Loading @@ -127,6 +136,7 @@ public class MediaControlPanel { private MediaViewHolder mMediaViewHolder; private RecommendationViewHolder mRecommendationViewHolder; private String mKey; private MediaData mMediaData; private MediaViewController mMediaViewController; private MediaSession.Token mToken; private MediaController mController; Loading @@ -147,14 +157,23 @@ public class MediaControlPanel { protected int mSmartspaceId = -1; private String mPackageName; private boolean mIsScrubbing = false; private final SeekBarViewModel.ScrubbingChangeListener mScrubbingChangeListener = this::setIsScrubbing; /** * Initialize a new control panel * * @param backgroundExecutor background executor, used for processing artwork * @param mainExecutor main thread executor, used if we receive callbacks on the background * thread that then trigger UI changes. * @param activityStarter activity starter */ @Inject public MediaControlPanel(Context context, @Background Executor backgroundExecutor, public MediaControlPanel(Context context, @Background Executor backgroundExecutor, @Main Executor mainExecutor, ActivityStarter activityStarter, BroadcastSender broadcastSender, MediaViewController mediaViewController, SeekBarViewModel seekBarViewModel, Lazy<MediaDataManager> lazyMediaDataManager, Loading @@ -163,6 +182,7 @@ public class MediaControlPanel { FalsingManager falsingManager, SystemClock systemClock, MediaUiEventLogger logger) { mContext = context; mBackgroundExecutor = backgroundExecutor; mMainExecutor = mainExecutor; mActivityStarter = activityStarter; mBroadcastSender = broadcastSender; mSeekBarViewModel = seekBarViewModel; Loading @@ -186,6 +206,7 @@ public class MediaControlPanel { public void onDestroy() { if (mSeekBarObserver != null) { mSeekBarViewModel.getProgress().removeObserver(mSeekBarObserver); mSeekBarViewModel.removeScrubbingChangeListener(mScrubbingChangeListener); } mSeekBarViewModel.onDestroy(); mMediaViewController.onDestroy(); Loading Loading @@ -232,6 +253,19 @@ public class MediaControlPanel { mSeekBarViewModel.setListening(listening); } /** Sets whether the user is touching the seek bar to change the track position. */ public void setIsScrubbing(boolean isScrubbing) { if (mMediaData == null || mMediaData.getSemanticActions() == null) { return; } if (isScrubbing == this.mIsScrubbing) { return; } this.mIsScrubbing = isScrubbing; mMainExecutor.execute(() -> updateDisplayForScrubbingChange(mMediaData.getSemanticActions())); } /** * Get the context * Loading @@ -249,6 +283,7 @@ public class MediaControlPanel { mSeekBarObserver = new SeekBarObserver(vh); mSeekBarViewModel.getProgress().observeForever(mSeekBarObserver); mSeekBarViewModel.attachTouchHandlers(vh.getSeekBar()); mSeekBarViewModel.setScrubbingChangeListener(mScrubbingChangeListener); mMediaViewController.attach(player, MediaViewController.TYPE.PLAYER); vh.getPlayer().setOnLongClickListener(v -> { Loading Loading @@ -307,6 +342,7 @@ public class MediaControlPanel { return; } mKey = key; mMediaData = data; MediaSession.Token token = data.getToken(); mPackageName = data.getPackageName(); mUid = data.getAppUid(); Loading Loading @@ -361,6 +397,7 @@ public class MediaControlPanel { bindOutputSwitcherChip(data); bindLongPressMenu(data); bindActionButtons(data); bindScrubbingTime(data); bindArtworkAndColors(data); // TODO: We don't need to refresh this state constantly, only if the state actually changed Loading Loading @@ -544,6 +581,8 @@ public class MediaControlPanel { seekbar.getThumb().setTintList(textColorList); seekbar.setProgressTintList(textColorList); seekbar.setProgressBackgroundTintList(ColorStateList.valueOf(textTertiary)); mMediaViewHolder.getScrubbingElapsedTimeView().setTextColor(textColorList); mMediaViewHolder.getScrubbingTotalTimeView().setTextColor(textColorList); // Action buttons mMediaViewHolder.getActionPlayPause().setBackgroundTintList(accentColorList); Loading Loading @@ -589,10 +628,9 @@ public class MediaControlPanel { } for (int id : SEMANTIC_ACTIONS_ALL) { boolean showInCompact = SEMANTIC_ACTIONS_COMPACT.contains(id); ImageButton button = mMediaViewHolder.getAction(id); MediaAction action = semanticActions.getActionById(id); setSemanticButton(button, action, collapsedSet, expandedSet, showInCompact); setSemanticButton(button, action); } } else { // Hide buttons that only appear for semantic actions Loading @@ -607,12 +645,21 @@ public class MediaControlPanel { int i = 0; for (; i < actions.size(); i++) { boolean showInCompact = actionsWhenCollapsed.contains(i); setSemanticButton(genericButtons[i], actions.get(i), collapsedSet, expandedSet, showInCompact); setGenericButton( genericButtons[i], actions.get(i), collapsedSet, expandedSet, showInCompact); } for (; i < 5; i++) { // Hide any unused buttons setSemanticButton(genericButtons[i], null, collapsedSet, expandedSet, false); setGenericButton( genericButtons[i], /* mediaAction= */ null, collapsedSet, expandedSet, /* showInCompact= */ false); } } expandedSet.setVisibility(R.id.media_progress_bar, getSeekBarVisibility()); Loading Loading @@ -640,8 +687,19 @@ public class MediaControlPanel { return false; } private void setSemanticButton(final ImageButton button, MediaAction mediaAction, ConstraintSet collapsedSet, ConstraintSet expandedSet, boolean showInCompact) { private void setGenericButton( final ImageButton button, @Nullable MediaAction mediaAction, ConstraintSet collapsedSet, ConstraintSet expandedSet, boolean showInCompact) { bindButtonCommon(button, mediaAction); boolean visible = mediaAction != null; setVisibleAndAlpha(expandedSet, button.getId(), visible); setVisibleAndAlpha(collapsedSet, button.getId(), visible && showInCompact); } private void setSemanticButton(final ImageButton button, @Nullable MediaAction mediaAction) { AnimationBindHandler animHandler; if (button.getTag() == null) { animHandler = new AnimationBindHandler(); Loading @@ -651,28 +709,36 @@ public class MediaControlPanel { } animHandler.tryExecute(() -> { bindSemanticButton(animHandler, button, mediaAction, collapsedSet, expandedSet, showInCompact); bindButtonWithAnimations(button, mediaAction, animHandler); setSemanticButtonVisibleAndAlpha(button.getId(), mediaAction); }); } private void bindSemanticButton(final AnimationBindHandler animHandler, final ImageButton button, MediaAction mediaAction, ConstraintSet collapsedSet, ConstraintSet expandedSet, boolean showInCompact) { private void bindButtonWithAnimations( final ImageButton button, @Nullable MediaAction mediaAction, @NonNull AnimationBindHandler animHandler) { if (mediaAction != null) { if (animHandler.updateRebindId(mediaAction.getRebindId())) { animHandler.unregisterAll(); animHandler.tryRegister(mediaAction.getIcon()); animHandler.tryRegister(mediaAction.getBackground()); bindButtonCommon(button, mediaAction); } } else { animHandler.unregisterAll(); clearButton(button); } } private void bindButtonCommon(final ImageButton button, @Nullable MediaAction mediaAction) { if (mediaAction != null) { final Drawable icon = mediaAction.getIcon(); button.setImageDrawable(icon); button.setContentDescription(mediaAction.getContentDescription()); final Drawable bgDrawable = mediaAction.getBackground(); button.setBackground(bgDrawable); animHandler.tryRegister(icon); animHandler.tryRegister(bgDrawable); Runnable action = mediaAction.getAction(); if (action == null) { button.setEnabled(false); Loading @@ -693,17 +759,55 @@ public class MediaControlPanel { } }); } } } else { animHandler.unregisterAll(); clearButton(button); } } private void clearButton(final ImageButton button) { button.setImageDrawable(null); button.setContentDescription(null); button.setEnabled(false); button.setBackground(null); } setVisibleAndAlpha(collapsedSet, button.getId(), mediaAction != null && showInCompact); setVisibleAndAlpha(expandedSet, button.getId(), mediaAction != null); private void setSemanticButtonVisibleAndAlpha( int buttonId, MediaAction mediaAction) { ConstraintSet collapsedSet = mMediaViewController.getCollapsedLayout(); ConstraintSet expandedSet = mMediaViewController.getExpandedLayout(); boolean showInCompact = SEMANTIC_ACTIONS_COMPACT.contains(buttonId); boolean hideWhenScrubbing = SEMANTIC_ACTIONS_HIDE_WHEN_SCRUBBING.contains(buttonId); boolean shouldBeHiddenDueToScrubbing = hideWhenScrubbing && mIsScrubbing; boolean visible = mediaAction != null && !shouldBeHiddenDueToScrubbing; setVisibleAndAlpha(expandedSet, buttonId, visible); setVisibleAndAlpha(collapsedSet, buttonId, visible && showInCompact); } /** Updates all the views that might change due to a scrubbing state change. */ // TODO(b/209656742): Handle scenarios where actionPrev and/or actionNext aren't active. private void updateDisplayForScrubbingChange(@NonNull MediaButton semanticActions) { // Update visibilities of the scrubbing time views and the scrubbing-dependent buttons. bindScrubbingTime(mMediaData); SEMANTIC_ACTIONS_HIDE_WHEN_SCRUBBING.forEach((id) -> setSemanticButtonVisibleAndAlpha(id, semanticActions.getActionById(id))); // Trigger a state refresh so that we immediately update visibilities. mMediaViewController.refreshState(); } private void bindScrubbingTime(MediaData data) { ConstraintSet expandedSet = mMediaViewController.getExpandedLayout(); ConstraintSet collapsedSet = mMediaViewController.getCollapsedLayout(); int elapsedTimeId = mMediaViewHolder.getScrubbingElapsedTimeView().getId(); int totalTimeId = mMediaViewHolder.getScrubbingTotalTimeView().getId(); boolean visible = data.getSemanticActions() != null && mIsScrubbing; setVisibleAndAlpha(expandedSet, elapsedTimeId, visible); setVisibleAndAlpha(expandedSet, totalTimeId, visible); // Never show in collapsed setVisibleAndAlpha(collapsedSet, elapsedTimeId, false); setVisibleAndAlpha(collapsedSet, totalTimeId, false); } // AnimationBindHandler is responsible for tracking the bound animation state and preventing Loading Loading
packages/SystemUI/res/layout/media_session_view.xml +35 −3 Original line number Diff line number Diff line Loading @@ -157,7 +157,7 @@ app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="parent" app:barrierDirection="start" app:constraint_referenced_ids="actionPrev,media_progress_bar,actionNext,action0,action1,action2,action3,action4" app:constraint_referenced_ids="actionPrev,media_scrubbing_elapsed_time,media_progress_bar,actionNext,media_scrubbing_total_time,action0,action1,action2,action3,action4" /> <androidx.constraintlayout.widget.Barrier android:id="@+id/media_action_barrier_end" Loading @@ -167,7 +167,7 @@ app:layout_constraintTop_toBottomOf="@id/media_seamless" app:layout_constraintBottom_toBottomOf="parent" app:barrierDirection="end" app:constraint_referenced_ids="actionPrev,media_progress_bar,actionNext,action0,action1,action2,action3,action4" app:constraint_referenced_ids="actionPrev,media_scrubbing_elapsed_time,media_progress_bar,actionNext,media_scrubbing_total_time,action0,action1,action2,action3,action4" app:layout_constraintStart_toStartOf="parent" /> Loading @@ -177,7 +177,7 @@ android:layout_height="0dp" app:layout_constraintBottom_toBottomOf="parent" app:barrierDirection="top" app:constraint_referenced_ids="actionPrev,media_progress_bar,actionNext,action0,action1,action2,action3,action4" app:constraint_referenced_ids="actionPrev,media_scrubbing_elapsed_time,media_progress_bar,actionNext,media_scrubbing_total_time,action0,action1,action2,action3,action4" /> <!-- Button visibility will be controlled in code --> Loading @@ -192,6 +192,22 @@ android:layout_marginTop="0dp" /> <!-- Elapsed time, shown only when scrubbing --> <!-- The space to the left of the progress bar will either be actionPrev or media_scrubbing_elapsed_time, so they use the same layout constraints. Visibilities of elements are controlled in code. --> <TextView android:id="@+id/media_scrubbing_elapsed_time" style="@style/MediaPlayer.ScrubbingTime" android:layout_width="48dp" android:layout_height="48dp" android:layout_marginStart="4dp" android:layout_marginEnd="0dp" android:layout_marginBottom="@dimen/qs_media_padding" android:layout_marginTop="0dp" android:visibility="gone" /> <!-- Seek Bar --> <!-- As per Material Design on Bidirectionality, this is forced to LTR in code --> <SeekBar Loading @@ -218,6 +234,22 @@ android:layout_marginBottom="@dimen/qs_media_padding" android:layout_marginTop="0dp" /> <!-- Total time, shown only when scrubbing --> <!-- The space to the right of the progress bar will either be actionNext or media_scrubbing_total_time, so they use the same layout constraints. Visibilities of elements are controlled in code. --> <TextView android:id="@+id/media_scrubbing_total_time" style="@style/MediaPlayer.ScrubbingTime" android:layout_width="48dp" android:layout_height="48dp" android:layout_marginStart="0dp" android:layout_marginEnd="@dimen/qs_media_action_spacing" android:layout_marginBottom="@dimen/qs_media_padding" android:layout_marginTop="0dp" android:visibility="gone" /> <ImageButton android:id="@+id/action0" style="@style/MediaPlayer.SessionAction.Secondary" Loading
packages/SystemUI/res/values/styles.xml +6 −0 Original line number Diff line number Diff line Loading @@ -601,6 +601,12 @@ <item name="android:textColor">?android:attr/textColorSecondary</item> </style> <style name="MediaPlayer.ScrubbingTime"> <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item> <item name="android:textSize">12sp</item> <item name="android:gravity">center</item> </style> <style name="MediaPlayer.Action" parent="@android:style/Widget.Material.Button.Borderless.Small"> <item name="android:background">@drawable/qs_media_light_source</item> <item name="android:tint">?android:attr/textColorPrimary</item> Loading
packages/SystemUI/res/xml/media_session_collapsed.xml +10 −0 Original line number Diff line number Diff line Loading @@ -93,6 +93,11 @@ app:layout_constraintTop_toBottomOf="@id/media_seamless" app:layout_constraintLeft_toRightOf="@id/media_action_barrier" /> <!-- Showing time while scrubbing isn't available in collapsed mode. --> <Constraint android:id="@+id/media_scrubbing_elapsed_time" android:visibility="gone" /> <Constraint android:id="@+id/media_progress_bar" android:layout_width="0dp" Loading @@ -116,6 +121,11 @@ app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintTop_toBottomOf="@id/media_seamless" /> <!-- Showing time while scrubbing isn't available in collapsed mode. --> <Constraint android:id="@+id/media_scrubbing_total_time" android:visibility="gone" /> <Constraint android:id="@+id/action0" android:layout_width="48dp" Loading
packages/SystemUI/res/xml/media_session_expanded.xml +21 −4 Original line number Diff line number Diff line Loading @@ -68,10 +68,19 @@ The chain is set to "spread" so that the progress bar can be weighted to fill any empty space. --> <Constraint android:id="@+id/actionPrev" android:id="@+id/media_scrubbing_elapsed_time" android:layout_width="48dp" android:layout_height="48dp" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toLeftOf="@id/actionPrev" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintHorizontal_chainStyle="spread" /> <Constraint android:id="@+id/actionPrev" android:layout_width="48dp" android:layout_height="48dp" app:layout_constraintLeft_toRightOf="@id/media_scrubbing_elapsed_time" app:layout_constraintRight_toLeftOf="@id/media_progress_bar" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintHorizontal_chainStyle="spread" /> Loading @@ -90,6 +99,14 @@ android:layout_width="48dp" android:layout_height="48dp" app:layout_constraintLeft_toRightOf="@id/media_progress_bar" app:layout_constraintRight_toLeftOf="@id/media_scrubbing_total_time" app:layout_constraintBottom_toBottomOf="parent" /> <Constraint android:id="@+id/media_scrubbing_total_time" android:layout_width="48dp" android:layout_height="48dp" app:layout_constraintLeft_toRightOf="@id/actionNext" app:layout_constraintRight_toLeftOf="@id/action0" app:layout_constraintBottom_toBottomOf="parent" /> Loading @@ -97,7 +114,7 @@ android:id="@+id/action0" android:layout_width="48dp" android:layout_height="48dp" app:layout_constraintLeft_toRightOf="@id/actionNext" app:layout_constraintLeft_toRightOf="@id/media_scrubbing_total_time" app:layout_constraintRight_toLeftOf="@id/action1" app:layout_constraintBottom_toBottomOf="parent" /> Loading
packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java +153 −49 Original line number Diff line number Diff line Loading @@ -60,6 +60,7 @@ import com.android.systemui.animation.ActivityLaunchAnimator; import com.android.systemui.animation.GhostedViewLaunchAnimatorController; import com.android.systemui.broadcast.BroadcastSender; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.media.dialog.MediaOutputDialogFactory; import com.android.systemui.monet.ColorScheme; import com.android.systemui.plugins.ActivityStarter; Loading Loading @@ -108,6 +109,13 @@ public class MediaControlPanel { R.id.actionNext ); // Buttons that should get hidden when we're scrubbing (they will be replaced with the views // showing scrubbing time) private static final List<Integer> SEMANTIC_ACTIONS_HIDE_WHEN_SCRUBBING = List.of( R.id.actionPrev, R.id.actionNext ); // Buttons to show in small player when using semantic actions private static final List<Integer> SEMANTIC_ACTIONS_ALL = List.of( R.id.actionPlayPause, Loading @@ -120,6 +128,7 @@ public class MediaControlPanel { private final SeekBarViewModel mSeekBarViewModel; private SeekBarObserver mSeekBarObserver; protected final Executor mBackgroundExecutor; private final Executor mMainExecutor; private final ActivityStarter mActivityStarter; private final BroadcastSender mBroadcastSender; Loading @@ -127,6 +136,7 @@ public class MediaControlPanel { private MediaViewHolder mMediaViewHolder; private RecommendationViewHolder mRecommendationViewHolder; private String mKey; private MediaData mMediaData; private MediaViewController mMediaViewController; private MediaSession.Token mToken; private MediaController mController; Loading @@ -147,14 +157,23 @@ public class MediaControlPanel { protected int mSmartspaceId = -1; private String mPackageName; private boolean mIsScrubbing = false; private final SeekBarViewModel.ScrubbingChangeListener mScrubbingChangeListener = this::setIsScrubbing; /** * Initialize a new control panel * * @param backgroundExecutor background executor, used for processing artwork * @param mainExecutor main thread executor, used if we receive callbacks on the background * thread that then trigger UI changes. * @param activityStarter activity starter */ @Inject public MediaControlPanel(Context context, @Background Executor backgroundExecutor, public MediaControlPanel(Context context, @Background Executor backgroundExecutor, @Main Executor mainExecutor, ActivityStarter activityStarter, BroadcastSender broadcastSender, MediaViewController mediaViewController, SeekBarViewModel seekBarViewModel, Lazy<MediaDataManager> lazyMediaDataManager, Loading @@ -163,6 +182,7 @@ public class MediaControlPanel { FalsingManager falsingManager, SystemClock systemClock, MediaUiEventLogger logger) { mContext = context; mBackgroundExecutor = backgroundExecutor; mMainExecutor = mainExecutor; mActivityStarter = activityStarter; mBroadcastSender = broadcastSender; mSeekBarViewModel = seekBarViewModel; Loading @@ -186,6 +206,7 @@ public class MediaControlPanel { public void onDestroy() { if (mSeekBarObserver != null) { mSeekBarViewModel.getProgress().removeObserver(mSeekBarObserver); mSeekBarViewModel.removeScrubbingChangeListener(mScrubbingChangeListener); } mSeekBarViewModel.onDestroy(); mMediaViewController.onDestroy(); Loading Loading @@ -232,6 +253,19 @@ public class MediaControlPanel { mSeekBarViewModel.setListening(listening); } /** Sets whether the user is touching the seek bar to change the track position. */ public void setIsScrubbing(boolean isScrubbing) { if (mMediaData == null || mMediaData.getSemanticActions() == null) { return; } if (isScrubbing == this.mIsScrubbing) { return; } this.mIsScrubbing = isScrubbing; mMainExecutor.execute(() -> updateDisplayForScrubbingChange(mMediaData.getSemanticActions())); } /** * Get the context * Loading @@ -249,6 +283,7 @@ public class MediaControlPanel { mSeekBarObserver = new SeekBarObserver(vh); mSeekBarViewModel.getProgress().observeForever(mSeekBarObserver); mSeekBarViewModel.attachTouchHandlers(vh.getSeekBar()); mSeekBarViewModel.setScrubbingChangeListener(mScrubbingChangeListener); mMediaViewController.attach(player, MediaViewController.TYPE.PLAYER); vh.getPlayer().setOnLongClickListener(v -> { Loading Loading @@ -307,6 +342,7 @@ public class MediaControlPanel { return; } mKey = key; mMediaData = data; MediaSession.Token token = data.getToken(); mPackageName = data.getPackageName(); mUid = data.getAppUid(); Loading Loading @@ -361,6 +397,7 @@ public class MediaControlPanel { bindOutputSwitcherChip(data); bindLongPressMenu(data); bindActionButtons(data); bindScrubbingTime(data); bindArtworkAndColors(data); // TODO: We don't need to refresh this state constantly, only if the state actually changed Loading Loading @@ -544,6 +581,8 @@ public class MediaControlPanel { seekbar.getThumb().setTintList(textColorList); seekbar.setProgressTintList(textColorList); seekbar.setProgressBackgroundTintList(ColorStateList.valueOf(textTertiary)); mMediaViewHolder.getScrubbingElapsedTimeView().setTextColor(textColorList); mMediaViewHolder.getScrubbingTotalTimeView().setTextColor(textColorList); // Action buttons mMediaViewHolder.getActionPlayPause().setBackgroundTintList(accentColorList); Loading Loading @@ -589,10 +628,9 @@ public class MediaControlPanel { } for (int id : SEMANTIC_ACTIONS_ALL) { boolean showInCompact = SEMANTIC_ACTIONS_COMPACT.contains(id); ImageButton button = mMediaViewHolder.getAction(id); MediaAction action = semanticActions.getActionById(id); setSemanticButton(button, action, collapsedSet, expandedSet, showInCompact); setSemanticButton(button, action); } } else { // Hide buttons that only appear for semantic actions Loading @@ -607,12 +645,21 @@ public class MediaControlPanel { int i = 0; for (; i < actions.size(); i++) { boolean showInCompact = actionsWhenCollapsed.contains(i); setSemanticButton(genericButtons[i], actions.get(i), collapsedSet, expandedSet, showInCompact); setGenericButton( genericButtons[i], actions.get(i), collapsedSet, expandedSet, showInCompact); } for (; i < 5; i++) { // Hide any unused buttons setSemanticButton(genericButtons[i], null, collapsedSet, expandedSet, false); setGenericButton( genericButtons[i], /* mediaAction= */ null, collapsedSet, expandedSet, /* showInCompact= */ false); } } expandedSet.setVisibility(R.id.media_progress_bar, getSeekBarVisibility()); Loading Loading @@ -640,8 +687,19 @@ public class MediaControlPanel { return false; } private void setSemanticButton(final ImageButton button, MediaAction mediaAction, ConstraintSet collapsedSet, ConstraintSet expandedSet, boolean showInCompact) { private void setGenericButton( final ImageButton button, @Nullable MediaAction mediaAction, ConstraintSet collapsedSet, ConstraintSet expandedSet, boolean showInCompact) { bindButtonCommon(button, mediaAction); boolean visible = mediaAction != null; setVisibleAndAlpha(expandedSet, button.getId(), visible); setVisibleAndAlpha(collapsedSet, button.getId(), visible && showInCompact); } private void setSemanticButton(final ImageButton button, @Nullable MediaAction mediaAction) { AnimationBindHandler animHandler; if (button.getTag() == null) { animHandler = new AnimationBindHandler(); Loading @@ -651,28 +709,36 @@ public class MediaControlPanel { } animHandler.tryExecute(() -> { bindSemanticButton(animHandler, button, mediaAction, collapsedSet, expandedSet, showInCompact); bindButtonWithAnimations(button, mediaAction, animHandler); setSemanticButtonVisibleAndAlpha(button.getId(), mediaAction); }); } private void bindSemanticButton(final AnimationBindHandler animHandler, final ImageButton button, MediaAction mediaAction, ConstraintSet collapsedSet, ConstraintSet expandedSet, boolean showInCompact) { private void bindButtonWithAnimations( final ImageButton button, @Nullable MediaAction mediaAction, @NonNull AnimationBindHandler animHandler) { if (mediaAction != null) { if (animHandler.updateRebindId(mediaAction.getRebindId())) { animHandler.unregisterAll(); animHandler.tryRegister(mediaAction.getIcon()); animHandler.tryRegister(mediaAction.getBackground()); bindButtonCommon(button, mediaAction); } } else { animHandler.unregisterAll(); clearButton(button); } } private void bindButtonCommon(final ImageButton button, @Nullable MediaAction mediaAction) { if (mediaAction != null) { final Drawable icon = mediaAction.getIcon(); button.setImageDrawable(icon); button.setContentDescription(mediaAction.getContentDescription()); final Drawable bgDrawable = mediaAction.getBackground(); button.setBackground(bgDrawable); animHandler.tryRegister(icon); animHandler.tryRegister(bgDrawable); Runnable action = mediaAction.getAction(); if (action == null) { button.setEnabled(false); Loading @@ -693,17 +759,55 @@ public class MediaControlPanel { } }); } } } else { animHandler.unregisterAll(); clearButton(button); } } private void clearButton(final ImageButton button) { button.setImageDrawable(null); button.setContentDescription(null); button.setEnabled(false); button.setBackground(null); } setVisibleAndAlpha(collapsedSet, button.getId(), mediaAction != null && showInCompact); setVisibleAndAlpha(expandedSet, button.getId(), mediaAction != null); private void setSemanticButtonVisibleAndAlpha( int buttonId, MediaAction mediaAction) { ConstraintSet collapsedSet = mMediaViewController.getCollapsedLayout(); ConstraintSet expandedSet = mMediaViewController.getExpandedLayout(); boolean showInCompact = SEMANTIC_ACTIONS_COMPACT.contains(buttonId); boolean hideWhenScrubbing = SEMANTIC_ACTIONS_HIDE_WHEN_SCRUBBING.contains(buttonId); boolean shouldBeHiddenDueToScrubbing = hideWhenScrubbing && mIsScrubbing; boolean visible = mediaAction != null && !shouldBeHiddenDueToScrubbing; setVisibleAndAlpha(expandedSet, buttonId, visible); setVisibleAndAlpha(collapsedSet, buttonId, visible && showInCompact); } /** Updates all the views that might change due to a scrubbing state change. */ // TODO(b/209656742): Handle scenarios where actionPrev and/or actionNext aren't active. private void updateDisplayForScrubbingChange(@NonNull MediaButton semanticActions) { // Update visibilities of the scrubbing time views and the scrubbing-dependent buttons. bindScrubbingTime(mMediaData); SEMANTIC_ACTIONS_HIDE_WHEN_SCRUBBING.forEach((id) -> setSemanticButtonVisibleAndAlpha(id, semanticActions.getActionById(id))); // Trigger a state refresh so that we immediately update visibilities. mMediaViewController.refreshState(); } private void bindScrubbingTime(MediaData data) { ConstraintSet expandedSet = mMediaViewController.getExpandedLayout(); ConstraintSet collapsedSet = mMediaViewController.getCollapsedLayout(); int elapsedTimeId = mMediaViewHolder.getScrubbingElapsedTimeView().getId(); int totalTimeId = mMediaViewHolder.getScrubbingTotalTimeView().getId(); boolean visible = data.getSemanticActions() != null && mIsScrubbing; setVisibleAndAlpha(expandedSet, elapsedTimeId, visible); setVisibleAndAlpha(expandedSet, totalTimeId, visible); // Never show in collapsed setVisibleAndAlpha(collapsedSet, elapsedTimeId, false); setVisibleAndAlpha(collapsedSet, totalTimeId, false); } // AnimationBindHandler is responsible for tracking the bound animation state and preventing Loading