Loading packages/SystemUI/res/layout/qs_media_panel.xml +41 −0 Original line number Diff line number Diff line Loading @@ -136,6 +136,47 @@ </LinearLayout> </LinearLayout> <!-- Seek Bar --> <SeekBar android:id="@+id/media_progress_bar" android:clickable="true" android:layout_width="match_parent" android:layout_height="wrap_content" android:maxHeight="3dp" android:paddingTop="24dp" android:paddingBottom="24dp" android:layout_marginBottom="-24dp" android:layout_marginTop="-24dp" android:splitTrack="false" /> <FrameLayout android:id="@+id/notification_media_progress_time" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="center" > <!-- width is set to "match_parent" to avoid extra layout calls --> <TextView android:id="@+id/media_elapsed_time" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:fontFamily="@*android:string/config_bodyFontFamily" android:textSize="14sp" android:gravity="left" /> <TextView android:id="@+id/media_total_time" android:layout_width="match_parent" android:layout_height="wrap_content" android:fontFamily="@*android:string/config_bodyFontFamily" android:layout_alignParentRight="true" android:textSize="14sp" android:gravity="right" /> </FrameLayout> <!-- Controls --> <LinearLayout android:id="@+id/media_actions" Loading packages/SystemUI/src/com/android/systemui/media/SeekBarObserver.kt 0 → 100644 +86 −0 Original line number Diff line number Diff line /* * 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.content.res.ColorStateList import android.text.format.DateUtils import android.view.View import android.widget.SeekBar import android.widget.TextView import androidx.annotation.UiThread import androidx.lifecycle.Observer import com.android.systemui.R /** * Observer for changes from SeekBarViewModel. * * <p>Updates the seek bar views in response to changes to the model. */ class SeekBarObserver(view: View) : Observer<SeekBarViewModel.Progress> { private val seekBarView: SeekBar private val elapsedTimeView: TextView private val totalTimeView: TextView init { seekBarView = view.findViewById(R.id.media_progress_bar) elapsedTimeView = view.findViewById(R.id.media_elapsed_time) totalTimeView = view.findViewById(R.id.media_total_time) } /** Updates seek bar views when the data model changes. */ @UiThread override fun onChanged(data: SeekBarViewModel.Progress) { if (data.enabled && seekBarView.visibility == View.GONE) { seekBarView.visibility = View.VISIBLE elapsedTimeView.visibility = View.VISIBLE totalTimeView.visibility = View.VISIBLE } else if (!data.enabled && seekBarView.visibility == View.VISIBLE) { seekBarView.visibility = View.GONE elapsedTimeView.visibility = View.GONE totalTimeView.visibility = View.GONE return } // TODO: update the style of the disabled progress bar seekBarView.setEnabled(data.seekAvailable) data.color?.let { var tintList = ColorStateList.valueOf(it) seekBarView.setThumbTintList(tintList) tintList = tintList.withAlpha(192) // 75% seekBarView.setProgressTintList(tintList) tintList = tintList.withAlpha(128) // 50% seekBarView.setProgressBackgroundTintList(tintList) elapsedTimeView.setTextColor(it) totalTimeView.setTextColor(it) } data.elapsedTime?.let { seekBarView.setProgress(it) elapsedTimeView.setText(DateUtils.formatElapsedTime( it / DateUtils.SECOND_IN_MILLIS)) } data.duration?.let { seekBarView.setMax(it) totalTimeView.setText(DateUtils.formatElapsedTime( it / DateUtils.SECOND_IN_MILLIS)) } } } packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt 0 → 100644 +152 −0 Original line number Diff line number Diff line /* * 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.media.MediaMetadata import android.media.session.MediaController import android.media.session.PlaybackState import android.view.MotionEvent import android.view.View import android.widget.SeekBar import androidx.annotation.AnyThread import androidx.annotation.WorkerThread import androidx.lifecycle.MutableLiveData import androidx.lifecycle.LiveData import com.android.systemui.util.concurrency.DelayableExecutor private const val POSITION_UPDATE_INTERVAL_MILLIS = 100L /** ViewModel for seek bar in QS media player. */ class SeekBarViewModel(val bgExecutor: DelayableExecutor) { private val _progress = MutableLiveData<Progress>().apply { postValue(Progress(false, false, null, null, null)) } val progress: LiveData<Progress> get() = _progress private var controller: MediaController? = null private var playbackState: PlaybackState? = null /** Listening state (QS open or closed) is used to control polling of progress. */ var listening = true set(value) { if (value) { checkPlaybackPosition() } } /** * Handle request to change the current position in the media track. * @param position Place to seek to in the track. */ @WorkerThread fun onSeek(position: Long) { controller?.transportControls?.seekTo(position) } /** * Updates media information. * @param mediaController controller for media session * @param color foreground color for UI elements */ @WorkerThread fun updateController(mediaController: MediaController?, color: Int) { controller = mediaController playbackState = controller?.playbackState val mediaMetadata = controller?.metadata val seekAvailable = ((playbackState?.actions ?: 0L) and PlaybackState.ACTION_SEEK_TO) != 0L val position = playbackState?.position?.toInt() val duration = mediaMetadata?.getLong(MediaMetadata.METADATA_KEY_DURATION)?.toInt() val enabled = if (duration != null && duration <= 0) false else true _progress.postValue(Progress(enabled, seekAvailable, position, duration, color)) if (shouldPollPlaybackPosition()) { checkPlaybackPosition() } } @AnyThread private fun checkPlaybackPosition(): Runnable = bgExecutor.executeDelayed({ val currentPosition = controller?.playbackState?.position?.toInt() if (currentPosition != null && _progress.value!!.elapsedTime != currentPosition) { _progress.postValue(_progress.value!!.copy(elapsedTime = currentPosition)) } if (shouldPollPlaybackPosition()) { checkPlaybackPosition() } }, POSITION_UPDATE_INTERVAL_MILLIS) @WorkerThread private fun shouldPollPlaybackPosition(): Boolean { val state = playbackState?.state val moving = if (state == null) false else state == PlaybackState.STATE_PLAYING || state == PlaybackState.STATE_BUFFERING || state == PlaybackState.STATE_FAST_FORWARDING || state == PlaybackState.STATE_REWINDING return moving && listening } /** Gets a listener to attach to the seek bar to handle seeking. */ val seekBarListener: SeekBar.OnSeekBarChangeListener get() { return SeekBarChangeListener(this, bgExecutor) } /** Gets a listener to attach to the seek bar to disable touch intercepting. */ val seekBarTouchListener: View.OnTouchListener get() { return SeekBarTouchListener() } private class SeekBarChangeListener( val viewModel: SeekBarViewModel, val bgExecutor: DelayableExecutor ) : SeekBar.OnSeekBarChangeListener { override fun onProgressChanged(bar: SeekBar, progress: Int, fromUser: Boolean) { if (fromUser) { bgExecutor.execute { viewModel.onSeek(progress.toLong()) } } } override fun onStartTrackingTouch(bar: SeekBar) { } override fun onStopTrackingTouch(bar: SeekBar) { val pos = bar.progress.toLong() bgExecutor.execute { viewModel.onSeek(pos) } } } private class SeekBarTouchListener : View.OnTouchListener { override fun onTouch(view: View, event: MotionEvent): Boolean { view.parent.requestDisallowInterceptTouchEvent(true) return view.onTouchEvent(event) } } /** State seen by seek bar UI. */ data class Progress( val enabled: Boolean, val seekAvailable: Boolean, val elapsedTime: Int?, val duration: Int?, val color: Int? ) } packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java +38 −1 Original line number Diff line number Diff line Loading @@ -16,11 +16,14 @@ package com.android.systemui.qs; import static com.android.systemui.util.SysuiLifecycle.viewAttachLifecycle; import android.app.Notification; import android.content.Context; import android.content.res.ColorStateList; import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; import android.media.session.MediaController; import android.media.session.MediaSession; import android.util.Log; import android.view.View; Loading @@ -28,12 +31,16 @@ 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 com.android.settingslib.media.MediaDevice; import com.android.systemui.R; import com.android.systemui.media.MediaControlPanel; import com.android.systemui.media.SeekBarObserver; import com.android.systemui.media.SeekBarViewModel; import com.android.systemui.statusbar.NotificationMediaManager; import com.android.systemui.util.concurrency.DelayableExecutor; import java.util.concurrent.Executor; Loading @@ -54,6 +61,9 @@ public class QSMediaPlayer extends MediaControlPanel { }; private final QSPanel mParent; private final DelayableExecutor mBackgroundExecutor; private final SeekBarViewModel mSeekBarViewModel; private final SeekBarObserver mSeekBarObserver; /** * Initialize quick shade version of player Loading @@ -64,10 +74,20 @@ public class QSMediaPlayer extends MediaControlPanel { * @param backgroundExecutor */ public QSMediaPlayer(Context context, ViewGroup parent, NotificationMediaManager manager, Executor foregroundExecutor, Executor backgroundExecutor) { Executor foregroundExecutor, DelayableExecutor backgroundExecutor) { super(context, parent, manager, R.layout.qs_media_panel, QS_ACTION_IDS, foregroundExecutor, backgroundExecutor); mParent = (QSPanel) parent; 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()); } /** Loading Loading @@ -115,6 +135,11 @@ public class QSMediaPlayer extends MediaControlPanel { thisBtn.setVisibility(View.GONE); } // Seek Bar final MediaController controller = new MediaController(getContext(), token); mBackgroundExecutor.execute( () -> mSeekBarViewModel.updateController(controller, iconColor)); // Set up long press menu View guts = mMediaNotifView.findViewById(R.id.media_guts); View options = mMediaNotifView.findViewById(R.id.qs_media_controls_options); Loading Loading @@ -155,4 +180,16 @@ public class QSMediaPlayer extends MediaControlPanel { 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); } } packages/SystemUI/src/com/android/systemui/qs/QSPanel.java +7 −3 Original line number Diff line number Diff line Loading @@ -69,6 +69,7 @@ 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; Loading Loading @@ -103,7 +104,7 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne private final NotificationMediaManager mNotificationMediaManager; private final LocalBluetoothManager mLocalBluetoothManager; private final Executor mForegroundExecutor; private final Executor mBackgroundExecutor; private final DelayableExecutor mBackgroundExecutor; private LocalMediaManager mLocalMediaManager; private MediaDevice mDevice; private boolean mUpdateCarousel = false; Loading Loading @@ -166,7 +167,7 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne QSLogger qsLogger, NotificationMediaManager notificationMediaManager, @Main Executor foregroundExecutor, @Background Executor backgroundExecutor, @Background DelayableExecutor backgroundExecutor, @Nullable LocalBluetoothManager localBluetoothManager ) { super(context, attrs); Loading Loading @@ -278,7 +279,7 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne Log.d(TAG, "creating new player"); player = new QSMediaPlayer(mContext, this, mNotificationMediaManager, mForegroundExecutor, mBackgroundExecutor); player.setListening(mListening); if (player.isPlaying()) { mMediaCarousel.addView(player.getView(), 0, lp); // add in front } else { Loading Loading @@ -584,6 +585,9 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne if (mListening) { refreshAllTiles(); } for (QSMediaPlayer player : mMediaPlayers) { player.setListening(mListening); } } private String getTilesSpecs() { Loading Loading
packages/SystemUI/res/layout/qs_media_panel.xml +41 −0 Original line number Diff line number Diff line Loading @@ -136,6 +136,47 @@ </LinearLayout> </LinearLayout> <!-- Seek Bar --> <SeekBar android:id="@+id/media_progress_bar" android:clickable="true" android:layout_width="match_parent" android:layout_height="wrap_content" android:maxHeight="3dp" android:paddingTop="24dp" android:paddingBottom="24dp" android:layout_marginBottom="-24dp" android:layout_marginTop="-24dp" android:splitTrack="false" /> <FrameLayout android:id="@+id/notification_media_progress_time" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="center" > <!-- width is set to "match_parent" to avoid extra layout calls --> <TextView android:id="@+id/media_elapsed_time" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:fontFamily="@*android:string/config_bodyFontFamily" android:textSize="14sp" android:gravity="left" /> <TextView android:id="@+id/media_total_time" android:layout_width="match_parent" android:layout_height="wrap_content" android:fontFamily="@*android:string/config_bodyFontFamily" android:layout_alignParentRight="true" android:textSize="14sp" android:gravity="right" /> </FrameLayout> <!-- Controls --> <LinearLayout android:id="@+id/media_actions" Loading
packages/SystemUI/src/com/android/systemui/media/SeekBarObserver.kt 0 → 100644 +86 −0 Original line number Diff line number Diff line /* * 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.content.res.ColorStateList import android.text.format.DateUtils import android.view.View import android.widget.SeekBar import android.widget.TextView import androidx.annotation.UiThread import androidx.lifecycle.Observer import com.android.systemui.R /** * Observer for changes from SeekBarViewModel. * * <p>Updates the seek bar views in response to changes to the model. */ class SeekBarObserver(view: View) : Observer<SeekBarViewModel.Progress> { private val seekBarView: SeekBar private val elapsedTimeView: TextView private val totalTimeView: TextView init { seekBarView = view.findViewById(R.id.media_progress_bar) elapsedTimeView = view.findViewById(R.id.media_elapsed_time) totalTimeView = view.findViewById(R.id.media_total_time) } /** Updates seek bar views when the data model changes. */ @UiThread override fun onChanged(data: SeekBarViewModel.Progress) { if (data.enabled && seekBarView.visibility == View.GONE) { seekBarView.visibility = View.VISIBLE elapsedTimeView.visibility = View.VISIBLE totalTimeView.visibility = View.VISIBLE } else if (!data.enabled && seekBarView.visibility == View.VISIBLE) { seekBarView.visibility = View.GONE elapsedTimeView.visibility = View.GONE totalTimeView.visibility = View.GONE return } // TODO: update the style of the disabled progress bar seekBarView.setEnabled(data.seekAvailable) data.color?.let { var tintList = ColorStateList.valueOf(it) seekBarView.setThumbTintList(tintList) tintList = tintList.withAlpha(192) // 75% seekBarView.setProgressTintList(tintList) tintList = tintList.withAlpha(128) // 50% seekBarView.setProgressBackgroundTintList(tintList) elapsedTimeView.setTextColor(it) totalTimeView.setTextColor(it) } data.elapsedTime?.let { seekBarView.setProgress(it) elapsedTimeView.setText(DateUtils.formatElapsedTime( it / DateUtils.SECOND_IN_MILLIS)) } data.duration?.let { seekBarView.setMax(it) totalTimeView.setText(DateUtils.formatElapsedTime( it / DateUtils.SECOND_IN_MILLIS)) } } }
packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt 0 → 100644 +152 −0 Original line number Diff line number Diff line /* * 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.media.MediaMetadata import android.media.session.MediaController import android.media.session.PlaybackState import android.view.MotionEvent import android.view.View import android.widget.SeekBar import androidx.annotation.AnyThread import androidx.annotation.WorkerThread import androidx.lifecycle.MutableLiveData import androidx.lifecycle.LiveData import com.android.systemui.util.concurrency.DelayableExecutor private const val POSITION_UPDATE_INTERVAL_MILLIS = 100L /** ViewModel for seek bar in QS media player. */ class SeekBarViewModel(val bgExecutor: DelayableExecutor) { private val _progress = MutableLiveData<Progress>().apply { postValue(Progress(false, false, null, null, null)) } val progress: LiveData<Progress> get() = _progress private var controller: MediaController? = null private var playbackState: PlaybackState? = null /** Listening state (QS open or closed) is used to control polling of progress. */ var listening = true set(value) { if (value) { checkPlaybackPosition() } } /** * Handle request to change the current position in the media track. * @param position Place to seek to in the track. */ @WorkerThread fun onSeek(position: Long) { controller?.transportControls?.seekTo(position) } /** * Updates media information. * @param mediaController controller for media session * @param color foreground color for UI elements */ @WorkerThread fun updateController(mediaController: MediaController?, color: Int) { controller = mediaController playbackState = controller?.playbackState val mediaMetadata = controller?.metadata val seekAvailable = ((playbackState?.actions ?: 0L) and PlaybackState.ACTION_SEEK_TO) != 0L val position = playbackState?.position?.toInt() val duration = mediaMetadata?.getLong(MediaMetadata.METADATA_KEY_DURATION)?.toInt() val enabled = if (duration != null && duration <= 0) false else true _progress.postValue(Progress(enabled, seekAvailable, position, duration, color)) if (shouldPollPlaybackPosition()) { checkPlaybackPosition() } } @AnyThread private fun checkPlaybackPosition(): Runnable = bgExecutor.executeDelayed({ val currentPosition = controller?.playbackState?.position?.toInt() if (currentPosition != null && _progress.value!!.elapsedTime != currentPosition) { _progress.postValue(_progress.value!!.copy(elapsedTime = currentPosition)) } if (shouldPollPlaybackPosition()) { checkPlaybackPosition() } }, POSITION_UPDATE_INTERVAL_MILLIS) @WorkerThread private fun shouldPollPlaybackPosition(): Boolean { val state = playbackState?.state val moving = if (state == null) false else state == PlaybackState.STATE_PLAYING || state == PlaybackState.STATE_BUFFERING || state == PlaybackState.STATE_FAST_FORWARDING || state == PlaybackState.STATE_REWINDING return moving && listening } /** Gets a listener to attach to the seek bar to handle seeking. */ val seekBarListener: SeekBar.OnSeekBarChangeListener get() { return SeekBarChangeListener(this, bgExecutor) } /** Gets a listener to attach to the seek bar to disable touch intercepting. */ val seekBarTouchListener: View.OnTouchListener get() { return SeekBarTouchListener() } private class SeekBarChangeListener( val viewModel: SeekBarViewModel, val bgExecutor: DelayableExecutor ) : SeekBar.OnSeekBarChangeListener { override fun onProgressChanged(bar: SeekBar, progress: Int, fromUser: Boolean) { if (fromUser) { bgExecutor.execute { viewModel.onSeek(progress.toLong()) } } } override fun onStartTrackingTouch(bar: SeekBar) { } override fun onStopTrackingTouch(bar: SeekBar) { val pos = bar.progress.toLong() bgExecutor.execute { viewModel.onSeek(pos) } } } private class SeekBarTouchListener : View.OnTouchListener { override fun onTouch(view: View, event: MotionEvent): Boolean { view.parent.requestDisallowInterceptTouchEvent(true) return view.onTouchEvent(event) } } /** State seen by seek bar UI. */ data class Progress( val enabled: Boolean, val seekAvailable: Boolean, val elapsedTime: Int?, val duration: Int?, val color: Int? ) }
packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java +38 −1 Original line number Diff line number Diff line Loading @@ -16,11 +16,14 @@ package com.android.systemui.qs; import static com.android.systemui.util.SysuiLifecycle.viewAttachLifecycle; import android.app.Notification; import android.content.Context; import android.content.res.ColorStateList; import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; import android.media.session.MediaController; import android.media.session.MediaSession; import android.util.Log; import android.view.View; Loading @@ -28,12 +31,16 @@ 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 com.android.settingslib.media.MediaDevice; import com.android.systemui.R; import com.android.systemui.media.MediaControlPanel; import com.android.systemui.media.SeekBarObserver; import com.android.systemui.media.SeekBarViewModel; import com.android.systemui.statusbar.NotificationMediaManager; import com.android.systemui.util.concurrency.DelayableExecutor; import java.util.concurrent.Executor; Loading @@ -54,6 +61,9 @@ public class QSMediaPlayer extends MediaControlPanel { }; private final QSPanel mParent; private final DelayableExecutor mBackgroundExecutor; private final SeekBarViewModel mSeekBarViewModel; private final SeekBarObserver mSeekBarObserver; /** * Initialize quick shade version of player Loading @@ -64,10 +74,20 @@ public class QSMediaPlayer extends MediaControlPanel { * @param backgroundExecutor */ public QSMediaPlayer(Context context, ViewGroup parent, NotificationMediaManager manager, Executor foregroundExecutor, Executor backgroundExecutor) { Executor foregroundExecutor, DelayableExecutor backgroundExecutor) { super(context, parent, manager, R.layout.qs_media_panel, QS_ACTION_IDS, foregroundExecutor, backgroundExecutor); mParent = (QSPanel) parent; 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()); } /** Loading Loading @@ -115,6 +135,11 @@ public class QSMediaPlayer extends MediaControlPanel { thisBtn.setVisibility(View.GONE); } // Seek Bar final MediaController controller = new MediaController(getContext(), token); mBackgroundExecutor.execute( () -> mSeekBarViewModel.updateController(controller, iconColor)); // Set up long press menu View guts = mMediaNotifView.findViewById(R.id.media_guts); View options = mMediaNotifView.findViewById(R.id.qs_media_controls_options); Loading Loading @@ -155,4 +180,16 @@ public class QSMediaPlayer extends MediaControlPanel { 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); } }
packages/SystemUI/src/com/android/systemui/qs/QSPanel.java +7 −3 Original line number Diff line number Diff line Loading @@ -69,6 +69,7 @@ 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; Loading Loading @@ -103,7 +104,7 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne private final NotificationMediaManager mNotificationMediaManager; private final LocalBluetoothManager mLocalBluetoothManager; private final Executor mForegroundExecutor; private final Executor mBackgroundExecutor; private final DelayableExecutor mBackgroundExecutor; private LocalMediaManager mLocalMediaManager; private MediaDevice mDevice; private boolean mUpdateCarousel = false; Loading Loading @@ -166,7 +167,7 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne QSLogger qsLogger, NotificationMediaManager notificationMediaManager, @Main Executor foregroundExecutor, @Background Executor backgroundExecutor, @Background DelayableExecutor backgroundExecutor, @Nullable LocalBluetoothManager localBluetoothManager ) { super(context, attrs); Loading Loading @@ -278,7 +279,7 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne Log.d(TAG, "creating new player"); player = new QSMediaPlayer(mContext, this, mNotificationMediaManager, mForegroundExecutor, mBackgroundExecutor); player.setListening(mListening); if (player.isPlaying()) { mMediaCarousel.addView(player.getView(), 0, lp); // add in front } else { Loading Loading @@ -584,6 +585,9 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne if (mListening) { refreshAllTiles(); } for (QSMediaPlayer player : mMediaPlayers) { player.setListening(mListening); } } private String getTilesSpecs() { Loading