Beth Thibodeau | 07d20c3 | 2019-10-16 13:45:56 -0400 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2019 The Android Open Source Project |
| 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. |
| 6 | * You may obtain a copy of the License at |
| 7 | * |
| 8 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | * |
| 10 | * Unless required by applicable law or agreed to in writing, software |
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | * See the License for the specific language governing permissions and |
| 14 | * limitations under the License. |
| 15 | */ |
| 16 | |
| 17 | package com.android.systemui.qs; |
| 18 | |
Robert Snoeberger | 7dffd37 | 2020-04-01 17:32:44 -0400 | [diff] [blame] | 19 | import static com.android.systemui.util.SysuiLifecycle.viewAttachLifecycle; |
| 20 | |
Beth Thibodeau | 07d20c3 | 2019-10-16 13:45:56 -0400 | [diff] [blame] | 21 | import android.app.Notification; |
Beth Thibodeau | 07d20c3 | 2019-10-16 13:45:56 -0400 | [diff] [blame] | 22 | import android.content.Context; |
Beth Thibodeau | 8ebf6ad | 2020-03-18 20:01:27 -0400 | [diff] [blame] | 23 | import android.content.res.ColorStateList; |
Beth Thibodeau | 07d20c3 | 2019-10-16 13:45:56 -0400 | [diff] [blame] | 24 | import android.graphics.drawable.Drawable; |
Beth Thibodeau | 07d20c3 | 2019-10-16 13:45:56 -0400 | [diff] [blame] | 25 | import android.graphics.drawable.Icon; |
Robert Snoeberger | 7dffd37 | 2020-04-01 17:32:44 -0400 | [diff] [blame] | 26 | import android.media.session.MediaController; |
Beth Thibodeau | 07d20c3 | 2019-10-16 13:45:56 -0400 | [diff] [blame] | 27 | import android.media.session.MediaSession; |
Beth Thibodeau | 07d20c3 | 2019-10-16 13:45:56 -0400 | [diff] [blame] | 28 | import android.util.Log; |
Beth Thibodeau | 07d20c3 | 2019-10-16 13:45:56 -0400 | [diff] [blame] | 29 | import android.view.View; |
| 30 | import android.view.ViewGroup; |
| 31 | import android.widget.ImageButton; |
Beth Thibodeau | 8ebf6ad | 2020-03-18 20:01:27 -0400 | [diff] [blame] | 32 | import android.widget.ImageView; |
Beth Thibodeau | 07d20c3 | 2019-10-16 13:45:56 -0400 | [diff] [blame] | 33 | import android.widget.LinearLayout; |
Robert Snoeberger | 7dffd37 | 2020-04-01 17:32:44 -0400 | [diff] [blame] | 34 | import android.widget.SeekBar; |
Beth Thibodeau | 8ebf6ad | 2020-03-18 20:01:27 -0400 | [diff] [blame] | 35 | import android.widget.TextView; |
Beth Thibodeau | 07d20c3 | 2019-10-16 13:45:56 -0400 | [diff] [blame] | 36 | |
Robert Snoeberger | 9a7409b | 2020-04-09 18:12:27 -0400 | [diff] [blame] | 37 | import com.android.settingslib.media.LocalMediaManager; |
Beth Thibodeau | 07d20c3 | 2019-10-16 13:45:56 -0400 | [diff] [blame] | 38 | import com.android.systemui.R; |
Beth Thibodeau | 7b6c178 | 2020-03-05 11:43:51 -0500 | [diff] [blame] | 39 | import com.android.systemui.media.MediaControlPanel; |
Robert Snoeberger | 7dffd37 | 2020-04-01 17:32:44 -0400 | [diff] [blame] | 40 | import com.android.systemui.media.SeekBarObserver; |
| 41 | import com.android.systemui.media.SeekBarViewModel; |
Beth Thibodeau | 7b6c178 | 2020-03-05 11:43:51 -0500 | [diff] [blame] | 42 | import com.android.systemui.statusbar.NotificationMediaManager; |
Robert Snoeberger | 7dffd37 | 2020-04-01 17:32:44 -0400 | [diff] [blame] | 43 | import com.android.systemui.util.concurrency.DelayableExecutor; |
Beth Thibodeau | 07d20c3 | 2019-10-16 13:45:56 -0400 | [diff] [blame] | 44 | |
Beth Thibodeau | 7b6c178 | 2020-03-05 11:43:51 -0500 | [diff] [blame] | 45 | import java.util.concurrent.Executor; |
Beth Thibodeau | fd51bb2 | 2019-11-25 13:57:06 -0500 | [diff] [blame] | 46 | |
Beth Thibodeau | 07d20c3 | 2019-10-16 13:45:56 -0400 | [diff] [blame] | 47 | /** |
| 48 | * Single media player for carousel in QSPanel |
| 49 | */ |
Beth Thibodeau | 7b6c178 | 2020-03-05 11:43:51 -0500 | [diff] [blame] | 50 | public class QSMediaPlayer extends MediaControlPanel { |
Beth Thibodeau | 07d20c3 | 2019-10-16 13:45:56 -0400 | [diff] [blame] | 51 | |
| 52 | private static final String TAG = "QSMediaPlayer"; |
| 53 | |
Beth Thibodeau | 7b6c178 | 2020-03-05 11:43:51 -0500 | [diff] [blame] | 54 | // Button IDs for QS controls |
| 55 | static final int[] QS_ACTION_IDS = { |
| 56 | R.id.action0, |
| 57 | R.id.action1, |
| 58 | R.id.action2, |
| 59 | R.id.action3, |
| 60 | R.id.action4 |
Beth Thibodeau | fd51bb2 | 2019-11-25 13:57:06 -0500 | [diff] [blame] | 61 | }; |
Beth Thibodeau | 07d20c3 | 2019-10-16 13:45:56 -0400 | [diff] [blame] | 62 | |
Beth Thibodeau | 8ebf6ad | 2020-03-18 20:01:27 -0400 | [diff] [blame] | 63 | private final QSPanel mParent; |
Robert Snoeberger | 7dffd37 | 2020-04-01 17:32:44 -0400 | [diff] [blame] | 64 | private final DelayableExecutor mBackgroundExecutor; |
| 65 | private final SeekBarViewModel mSeekBarViewModel; |
| 66 | private final SeekBarObserver mSeekBarObserver; |
Beth Thibodeau | 8ebf6ad | 2020-03-18 20:01:27 -0400 | [diff] [blame] | 67 | |
Beth Thibodeau | 07d20c3 | 2019-10-16 13:45:56 -0400 | [diff] [blame] | 68 | /** |
Beth Thibodeau | 7b6c178 | 2020-03-05 11:43:51 -0500 | [diff] [blame] | 69 | * Initialize quick shade version of player |
Beth Thibodeau | 07d20c3 | 2019-10-16 13:45:56 -0400 | [diff] [blame] | 70 | * @param context |
| 71 | * @param parent |
Beth Thibodeau | 7b6c178 | 2020-03-05 11:43:51 -0500 | [diff] [blame] | 72 | * @param manager |
Beth Thibodeau | a51c314 | 2020-03-17 17:27:04 -0400 | [diff] [blame] | 73 | * @param foregroundExecutor |
Beth Thibodeau | 7b6c178 | 2020-03-05 11:43:51 -0500 | [diff] [blame] | 74 | * @param backgroundExecutor |
Beth Thibodeau | 07d20c3 | 2019-10-16 13:45:56 -0400 | [diff] [blame] | 75 | */ |
Beth Thibodeau | 7b6c178 | 2020-03-05 11:43:51 -0500 | [diff] [blame] | 76 | public QSMediaPlayer(Context context, ViewGroup parent, NotificationMediaManager manager, |
Robert Snoeberger | 9a7409b | 2020-04-09 18:12:27 -0400 | [diff] [blame] | 77 | LocalMediaManager routeManager, Executor foregroundExecutor, |
| 78 | DelayableExecutor backgroundExecutor) { |
| 79 | super(context, parent, manager, routeManager, R.layout.qs_media_panel, QS_ACTION_IDS, |
| 80 | foregroundExecutor, backgroundExecutor); |
Beth Thibodeau | 8ebf6ad | 2020-03-18 20:01:27 -0400 | [diff] [blame] | 81 | mParent = (QSPanel) parent; |
Robert Snoeberger | 7dffd37 | 2020-04-01 17:32:44 -0400 | [diff] [blame] | 82 | mBackgroundExecutor = backgroundExecutor; |
| 83 | mSeekBarViewModel = new SeekBarViewModel(backgroundExecutor); |
| 84 | mSeekBarObserver = new SeekBarObserver(getView()); |
| 85 | // Can't use the viewAttachLifecycle of media player because remove/add is used to adjust |
| 86 | // priority of players. As soon as it is removed, the lifecycle will end and the seek bar |
| 87 | // will stop updating. So, use the lifecycle of the parent instead. |
| 88 | mSeekBarViewModel.getProgress().observe(viewAttachLifecycle(parent), mSeekBarObserver); |
| 89 | SeekBar bar = getView().findViewById(R.id.media_progress_bar); |
| 90 | bar.setOnSeekBarChangeListener(mSeekBarViewModel.getSeekBarListener()); |
| 91 | bar.setOnTouchListener(mSeekBarViewModel.getSeekBarTouchListener()); |
Beth Thibodeau | 07d20c3 | 2019-10-16 13:45:56 -0400 | [diff] [blame] | 92 | } |
| 93 | |
| 94 | /** |
Beth Thibodeau | 7b6c178 | 2020-03-05 11:43:51 -0500 | [diff] [blame] | 95 | * Update media panel view for the given media session |
Beth Thibodeau | 07d20c3 | 2019-10-16 13:45:56 -0400 | [diff] [blame] | 96 | * @param token token for this media session |
| 97 | * @param icon app notification icon |
| 98 | * @param iconColor foreground color (for text, icons) |
| 99 | * @param bgColor background color |
| 100 | * @param actionsContainer a LinearLayout containing the media action buttons |
Beth Thibodeau | 69b1dfd | 2019-11-08 15:39:25 -0500 | [diff] [blame] | 101 | * @param notif reference to original notification |
| 102 | * @param device current playback device |
Beth Thibodeau | 07d20c3 | 2019-10-16 13:45:56 -0400 | [diff] [blame] | 103 | */ |
Beth Thibodeau | 7b6c178 | 2020-03-05 11:43:51 -0500 | [diff] [blame] | 104 | public void setMediaSession(MediaSession.Token token, Icon icon, int iconColor, |
Robert Snoeberger | 9a7409b | 2020-04-09 18:12:27 -0400 | [diff] [blame] | 105 | int bgColor, View actionsContainer, Notification notif) { |
Beth Thibodeau | fd51bb2 | 2019-11-25 13:57:06 -0500 | [diff] [blame] | 106 | |
Beth Thibodeau | 7b6c178 | 2020-03-05 11:43:51 -0500 | [diff] [blame] | 107 | String appName = Notification.Builder.recoverBuilder(getContext(), notif) |
| 108 | .loadHeaderAppName(); |
| 109 | super.setMediaSession(token, icon, iconColor, bgColor, notif.contentIntent, |
Robert Snoeberger | 9a7409b | 2020-04-09 18:12:27 -0400 | [diff] [blame] | 110 | appName); |
Beth Thibodeau | 07d20c3 | 2019-10-16 13:45:56 -0400 | [diff] [blame] | 111 | |
| 112 | // Media controls |
| 113 | LinearLayout parentActionsLayout = (LinearLayout) actionsContainer; |
Beth Thibodeau | 69b1dfd | 2019-11-08 15:39:25 -0500 | [diff] [blame] | 114 | int i = 0; |
Beth Thibodeau | 7b6c178 | 2020-03-05 11:43:51 -0500 | [diff] [blame] | 115 | for (; i < parentActionsLayout.getChildCount() && i < QS_ACTION_IDS.length; i++) { |
| 116 | ImageButton thisBtn = mMediaNotifView.findViewById(QS_ACTION_IDS[i]); |
| 117 | ImageButton thatBtn = parentActionsLayout.findViewById(NOTIF_ACTION_IDS[i]); |
Beth Thibodeau | 69b1dfd | 2019-11-08 15:39:25 -0500 | [diff] [blame] | 118 | if (thatBtn == null || thatBtn.getDrawable() == null |
| 119 | || thatBtn.getVisibility() != View.VISIBLE) { |
Beth Thibodeau | 07d20c3 | 2019-10-16 13:45:56 -0400 | [diff] [blame] | 120 | thisBtn.setVisibility(View.GONE); |
| 121 | continue; |
| 122 | } |
| 123 | |
| 124 | Drawable thatIcon = thatBtn.getDrawable(); |
| 125 | thisBtn.setImageDrawable(thatIcon.mutate()); |
| 126 | thisBtn.setVisibility(View.VISIBLE); |
| 127 | thisBtn.setOnClickListener(v -> { |
| 128 | Log.d(TAG, "clicking on other button"); |
| 129 | thatBtn.performClick(); |
| 130 | }); |
| 131 | } |
Beth Thibodeau | 69b1dfd | 2019-11-08 15:39:25 -0500 | [diff] [blame] | 132 | |
| 133 | // Hide any unused buttons |
Beth Thibodeau | 7b6c178 | 2020-03-05 11:43:51 -0500 | [diff] [blame] | 134 | for (; i < QS_ACTION_IDS.length; i++) { |
| 135 | ImageButton thisBtn = mMediaNotifView.findViewById(QS_ACTION_IDS[i]); |
Beth Thibodeau | 69b1dfd | 2019-11-08 15:39:25 -0500 | [diff] [blame] | 136 | thisBtn.setVisibility(View.GONE); |
Beth Thibodeau | 69b1dfd | 2019-11-08 15:39:25 -0500 | [diff] [blame] | 137 | } |
Beth Thibodeau | 8ebf6ad | 2020-03-18 20:01:27 -0400 | [diff] [blame] | 138 | |
Robert Snoeberger | 7dffd37 | 2020-04-01 17:32:44 -0400 | [diff] [blame] | 139 | // Seek Bar |
| 140 | final MediaController controller = new MediaController(getContext(), token); |
| 141 | mBackgroundExecutor.execute( |
| 142 | () -> mSeekBarViewModel.updateController(controller, iconColor)); |
| 143 | |
Beth Thibodeau | 8ebf6ad | 2020-03-18 20:01:27 -0400 | [diff] [blame] | 144 | // Set up long press menu |
| 145 | View guts = mMediaNotifView.findViewById(R.id.media_guts); |
| 146 | View options = mMediaNotifView.findViewById(R.id.qs_media_controls_options); |
| 147 | options.setMinimumHeight(guts.getHeight()); |
| 148 | |
| 149 | View clearView = options.findViewById(R.id.remove); |
| 150 | clearView.setOnClickListener(b -> { |
| 151 | mParent.removeMediaPlayer(QSMediaPlayer.this); |
| 152 | }); |
| 153 | ImageView removeIcon = options.findViewById(R.id.remove_icon); |
| 154 | removeIcon.setImageTintList(ColorStateList.valueOf(iconColor)); |
| 155 | TextView removeText = options.findViewById(R.id.remove_text); |
| 156 | removeText.setTextColor(iconColor); |
| 157 | |
| 158 | TextView cancelView = options.findViewById(R.id.cancel); |
| 159 | cancelView.setTextColor(iconColor); |
| 160 | cancelView.setOnClickListener(b -> { |
| 161 | options.setVisibility(View.GONE); |
| 162 | guts.setVisibility(View.VISIBLE); |
| 163 | }); |
| 164 | // ... but don't enable it yet, and make sure is reset when the session is updated |
| 165 | mMediaNotifView.setOnLongClickListener(null); |
| 166 | options.setVisibility(View.GONE); |
| 167 | guts.setVisibility(View.VISIBLE); |
| 168 | } |
| 169 | |
| 170 | @Override |
| 171 | public void clearControls() { |
| 172 | super.clearControls(); |
| 173 | |
| 174 | View guts = mMediaNotifView.findViewById(R.id.media_guts); |
| 175 | View options = mMediaNotifView.findViewById(R.id.qs_media_controls_options); |
| 176 | |
| 177 | mMediaNotifView.setOnLongClickListener(v -> { |
| 178 | // Replace player view with close/cancel view |
| 179 | guts.setVisibility(View.GONE); |
| 180 | options.setVisibility(View.VISIBLE); |
| 181 | return true; // consumed click |
| 182 | }); |
Beth Thibodeau | 07d20c3 | 2019-10-16 13:45:56 -0400 | [diff] [blame] | 183 | } |
Robert Snoeberger | 7dffd37 | 2020-04-01 17:32:44 -0400 | [diff] [blame] | 184 | |
| 185 | /** |
| 186 | * Sets the listening state of the player. |
| 187 | * |
| 188 | * Should be set to true when the QS panel is open. Otherwise, false. This is a signal to avoid |
| 189 | * unnecessary work when the QS panel is closed. |
| 190 | * |
| 191 | * @param listening True when player should be active. Otherwise, false. |
| 192 | */ |
| 193 | public void setListening(boolean listening) { |
| 194 | mSeekBarViewModel.setListening(listening); |
| 195 | } |
Beth Thibodeau | 07d20c3 | 2019-10-16 13:45:56 -0400 | [diff] [blame] | 196 | } |