blob: e636707a9722168b6763216f338b5edbff1cc8a7 [file] [log] [blame]
Beth Thibodeau07d20c32019-10-16 13:45:56 -04001/*
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
17package com.android.systemui.qs;
18
Robert Snoeberger7dffd372020-04-01 17:32:44 -040019import static com.android.systemui.util.SysuiLifecycle.viewAttachLifecycle;
20
Beth Thibodeau07d20c32019-10-16 13:45:56 -040021import android.app.Notification;
Beth Thibodeau07d20c32019-10-16 13:45:56 -040022import android.content.Context;
Beth Thibodeau8ebf6ad2020-03-18 20:01:27 -040023import android.content.res.ColorStateList;
Beth Thibodeau07d20c32019-10-16 13:45:56 -040024import android.graphics.drawable.Drawable;
Beth Thibodeau07d20c32019-10-16 13:45:56 -040025import android.graphics.drawable.Icon;
Robert Snoeberger7dffd372020-04-01 17:32:44 -040026import android.media.session.MediaController;
Beth Thibodeau07d20c32019-10-16 13:45:56 -040027import android.media.session.MediaSession;
Beth Thibodeau07d20c32019-10-16 13:45:56 -040028import android.util.Log;
Beth Thibodeau07d20c32019-10-16 13:45:56 -040029import android.view.View;
30import android.view.ViewGroup;
31import android.widget.ImageButton;
Beth Thibodeau8ebf6ad2020-03-18 20:01:27 -040032import android.widget.ImageView;
Beth Thibodeau07d20c32019-10-16 13:45:56 -040033import android.widget.LinearLayout;
Robert Snoeberger7dffd372020-04-01 17:32:44 -040034import android.widget.SeekBar;
Beth Thibodeau8ebf6ad2020-03-18 20:01:27 -040035import android.widget.TextView;
Beth Thibodeau07d20c32019-10-16 13:45:56 -040036
Robert Snoeberger9a7409b2020-04-09 18:12:27 -040037import com.android.settingslib.media.LocalMediaManager;
Beth Thibodeau07d20c32019-10-16 13:45:56 -040038import com.android.systemui.R;
Beth Thibodeau7b6c1782020-03-05 11:43:51 -050039import com.android.systemui.media.MediaControlPanel;
Robert Snoeberger7dffd372020-04-01 17:32:44 -040040import com.android.systemui.media.SeekBarObserver;
41import com.android.systemui.media.SeekBarViewModel;
Beth Thibodeau7b6c1782020-03-05 11:43:51 -050042import com.android.systemui.statusbar.NotificationMediaManager;
Robert Snoeberger7dffd372020-04-01 17:32:44 -040043import com.android.systemui.util.concurrency.DelayableExecutor;
Beth Thibodeau07d20c32019-10-16 13:45:56 -040044
Beth Thibodeau7b6c1782020-03-05 11:43:51 -050045import java.util.concurrent.Executor;
Beth Thibodeaufd51bb22019-11-25 13:57:06 -050046
Beth Thibodeau07d20c32019-10-16 13:45:56 -040047/**
48 * Single media player for carousel in QSPanel
49 */
Beth Thibodeau7b6c1782020-03-05 11:43:51 -050050public class QSMediaPlayer extends MediaControlPanel {
Beth Thibodeau07d20c32019-10-16 13:45:56 -040051
52 private static final String TAG = "QSMediaPlayer";
53
Beth Thibodeau7b6c1782020-03-05 11:43:51 -050054 // 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 Thibodeaufd51bb22019-11-25 13:57:06 -050061 };
Beth Thibodeau07d20c32019-10-16 13:45:56 -040062
Beth Thibodeau8ebf6ad2020-03-18 20:01:27 -040063 private final QSPanel mParent;
Robert Snoeberger7dffd372020-04-01 17:32:44 -040064 private final DelayableExecutor mBackgroundExecutor;
65 private final SeekBarViewModel mSeekBarViewModel;
66 private final SeekBarObserver mSeekBarObserver;
Beth Thibodeau8ebf6ad2020-03-18 20:01:27 -040067
Beth Thibodeau07d20c32019-10-16 13:45:56 -040068 /**
Beth Thibodeau7b6c1782020-03-05 11:43:51 -050069 * Initialize quick shade version of player
Beth Thibodeau07d20c32019-10-16 13:45:56 -040070 * @param context
71 * @param parent
Beth Thibodeau7b6c1782020-03-05 11:43:51 -050072 * @param manager
Beth Thibodeaua51c3142020-03-17 17:27:04 -040073 * @param foregroundExecutor
Beth Thibodeau7b6c1782020-03-05 11:43:51 -050074 * @param backgroundExecutor
Beth Thibodeau07d20c32019-10-16 13:45:56 -040075 */
Beth Thibodeau7b6c1782020-03-05 11:43:51 -050076 public QSMediaPlayer(Context context, ViewGroup parent, NotificationMediaManager manager,
Robert Snoeberger9a7409b2020-04-09 18:12:27 -040077 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 Thibodeau8ebf6ad2020-03-18 20:01:27 -040081 mParent = (QSPanel) parent;
Robert Snoeberger7dffd372020-04-01 17:32:44 -040082 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 Thibodeau07d20c32019-10-16 13:45:56 -040092 }
93
94 /**
Beth Thibodeau7b6c1782020-03-05 11:43:51 -050095 * Update media panel view for the given media session
Beth Thibodeau07d20c32019-10-16 13:45:56 -040096 * @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 Thibodeau69b1dfd2019-11-08 15:39:25 -0500101 * @param notif reference to original notification
102 * @param device current playback device
Beth Thibodeau07d20c32019-10-16 13:45:56 -0400103 */
Beth Thibodeau7b6c1782020-03-05 11:43:51 -0500104 public void setMediaSession(MediaSession.Token token, Icon icon, int iconColor,
Robert Snoeberger9a7409b2020-04-09 18:12:27 -0400105 int bgColor, View actionsContainer, Notification notif) {
Beth Thibodeaufd51bb22019-11-25 13:57:06 -0500106
Beth Thibodeau7b6c1782020-03-05 11:43:51 -0500107 String appName = Notification.Builder.recoverBuilder(getContext(), notif)
108 .loadHeaderAppName();
109 super.setMediaSession(token, icon, iconColor, bgColor, notif.contentIntent,
Robert Snoeberger9a7409b2020-04-09 18:12:27 -0400110 appName);
Beth Thibodeau07d20c32019-10-16 13:45:56 -0400111
112 // Media controls
113 LinearLayout parentActionsLayout = (LinearLayout) actionsContainer;
Beth Thibodeau69b1dfd2019-11-08 15:39:25 -0500114 int i = 0;
Beth Thibodeau7b6c1782020-03-05 11:43:51 -0500115 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 Thibodeau69b1dfd2019-11-08 15:39:25 -0500118 if (thatBtn == null || thatBtn.getDrawable() == null
119 || thatBtn.getVisibility() != View.VISIBLE) {
Beth Thibodeau07d20c32019-10-16 13:45:56 -0400120 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 Thibodeau69b1dfd2019-11-08 15:39:25 -0500132
133 // Hide any unused buttons
Beth Thibodeau7b6c1782020-03-05 11:43:51 -0500134 for (; i < QS_ACTION_IDS.length; i++) {
135 ImageButton thisBtn = mMediaNotifView.findViewById(QS_ACTION_IDS[i]);
Beth Thibodeau69b1dfd2019-11-08 15:39:25 -0500136 thisBtn.setVisibility(View.GONE);
Beth Thibodeau69b1dfd2019-11-08 15:39:25 -0500137 }
Beth Thibodeau8ebf6ad2020-03-18 20:01:27 -0400138
Robert Snoeberger7dffd372020-04-01 17:32:44 -0400139 // Seek Bar
140 final MediaController controller = new MediaController(getContext(), token);
141 mBackgroundExecutor.execute(
142 () -> mSeekBarViewModel.updateController(controller, iconColor));
143
Beth Thibodeau8ebf6ad2020-03-18 20:01:27 -0400144 // 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 Thibodeau07d20c32019-10-16 13:45:56 -0400183 }
Robert Snoeberger7dffd372020-04-01 17:32:44 -0400184
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 Thibodeau07d20c32019-10-16 13:45:56 -0400196}