Beth Thibodeau | 7b6c178 | 2020-03-05 11:43:51 -0500 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2020 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.media; |
| 18 | |
Beth Thibodeau | 7b6c178 | 2020-03-05 11:43:51 -0500 | [diff] [blame] | 19 | import android.app.PendingIntent; |
Beth Thibodeau | 7b6c178 | 2020-03-05 11:43:51 -0500 | [diff] [blame] | 20 | import android.content.Context; |
| 21 | import android.content.Intent; |
Beth Thibodeau | 7b6c178 | 2020-03-05 11:43:51 -0500 | [diff] [blame] | 22 | import android.content.res.ColorStateList; |
Lucas Dupin | 81e9beb | 2020-05-14 19:28:36 -0700 | [diff] [blame] | 23 | import android.graphics.Color; |
Fabian Kozynski | 47714a9 | 2020-06-17 10:22:35 -0400 | [diff] [blame] | 24 | import android.graphics.Outline; |
Selim Cinek | c543671 | 2020-04-27 15:15:44 -0700 | [diff] [blame] | 25 | import android.graphics.Rect; |
Beth Thibodeau | 7b6c178 | 2020-03-05 11:43:51 -0500 | [diff] [blame] | 26 | import android.graphics.drawable.Drawable; |
| 27 | import android.graphics.drawable.GradientDrawable; |
Selim Cinek | c543671 | 2020-04-27 15:15:44 -0700 | [diff] [blame] | 28 | import android.graphics.drawable.Icon; |
Beth Thibodeau | 7b6c178 | 2020-03-05 11:43:51 -0500 | [diff] [blame] | 29 | import android.graphics.drawable.RippleDrawable; |
Beth Thibodeau | 7b6c178 | 2020-03-05 11:43:51 -0500 | [diff] [blame] | 30 | import android.media.session.MediaController; |
| 31 | import android.media.session.MediaSession; |
| 32 | import android.media.session.PlaybackState; |
Beth Thibodeau | 7b6c178 | 2020-03-05 11:43:51 -0500 | [diff] [blame] | 33 | import android.util.Log; |
Beth Thibodeau | 7b6c178 | 2020-03-05 11:43:51 -0500 | [diff] [blame] | 34 | import android.view.View; |
Fabian Kozynski | 47714a9 | 2020-06-17 10:22:35 -0400 | [diff] [blame] | 35 | import android.view.ViewOutlineProvider; |
Beth Thibodeau | 7b6c178 | 2020-03-05 11:43:51 -0500 | [diff] [blame] | 36 | import android.widget.ImageButton; |
| 37 | import android.widget.ImageView; |
Beth Thibodeau | 7b6c178 | 2020-03-05 11:43:51 -0500 | [diff] [blame] | 38 | import android.widget.TextView; |
| 39 | |
Selim Cinek | 2de5ebb | 2020-05-20 15:39:03 -0700 | [diff] [blame] | 40 | import androidx.annotation.NonNull; |
Robert Snoeberger | 9a7409b | 2020-04-09 18:12:27 -0400 | [diff] [blame] | 41 | import androidx.annotation.Nullable; |
Selim Cinek | f418bb0 | 2020-05-04 17:16:58 -0700 | [diff] [blame] | 42 | import androidx.annotation.UiThread; |
Selim Cinek | 5dbef2d | 2020-05-07 17:44:38 -0700 | [diff] [blame] | 43 | import androidx.constraintlayout.widget.ConstraintSet; |
Beth Thibodeau | 7b6c178 | 2020-03-05 11:43:51 -0500 | [diff] [blame] | 44 | |
Selim Cinek | c543671 | 2020-04-27 15:15:44 -0700 | [diff] [blame] | 45 | import com.android.settingslib.Utils; |
Beth Thibodeau | 7b6c178 | 2020-03-05 11:43:51 -0500 | [diff] [blame] | 46 | import com.android.settingslib.media.MediaOutputSliceConstants; |
| 47 | import com.android.settingslib.widget.AdaptiveIcon; |
Beth Thibodeau | 7b6c178 | 2020-03-05 11:43:51 -0500 | [diff] [blame] | 48 | import com.android.systemui.R; |
Robert Snoeberger | 7cec542 | 2020-05-29 17:09:14 -0400 | [diff] [blame] | 49 | import com.android.systemui.dagger.qualifiers.Background; |
Beth Thibodeau | 7b6c178 | 2020-03-05 11:43:51 -0500 | [diff] [blame] | 50 | import com.android.systemui.plugins.ActivityStarter; |
Selim Cinek | 2de5ebb | 2020-05-20 15:39:03 -0700 | [diff] [blame] | 51 | import com.android.systemui.util.animation.TransitionLayout; |
Selim Cinek | 5dbef2d | 2020-05-07 17:44:38 -0700 | [diff] [blame] | 52 | |
Beth Thibodeau | 7b6c178 | 2020-03-05 11:43:51 -0500 | [diff] [blame] | 53 | import java.util.List; |
| 54 | import java.util.concurrent.Executor; |
| 55 | |
Robert Snoeberger | 7cec542 | 2020-05-29 17:09:14 -0400 | [diff] [blame] | 56 | import javax.inject.Inject; |
| 57 | |
Beth Thibodeau | 7b6c178 | 2020-03-05 11:43:51 -0500 | [diff] [blame] | 58 | /** |
Selim Cinek | d835792 | 2020-04-10 15:06:53 -0700 | [diff] [blame] | 59 | * A view controller used for Media Playback. |
Beth Thibodeau | 7b6c178 | 2020-03-05 11:43:51 -0500 | [diff] [blame] | 60 | */ |
Robert Snoeberger | 3cc2222 | 2020-03-25 15:36:31 -0400 | [diff] [blame] | 61 | public class MediaControlPanel { |
Beth Thibodeau | 7b6c178 | 2020-03-05 11:43:51 -0500 | [diff] [blame] | 62 | private static final String TAG = "MediaControlPanel"; |
Selim Cinek | 5dbef2d | 2020-05-07 17:44:38 -0700 | [diff] [blame] | 63 | |
| 64 | // Button IDs for QS controls |
| 65 | static final int[] ACTION_IDS = { |
| 66 | R.id.action0, |
| 67 | R.id.action1, |
| 68 | R.id.action2, |
| 69 | R.id.action3, |
| 70 | R.id.action4 |
| 71 | }; |
| 72 | |
| 73 | private final SeekBarViewModel mSeekBarViewModel; |
Robert Snoeberger | eb49e94 | 2020-05-12 16:31:09 -0400 | [diff] [blame] | 74 | private SeekBarObserver mSeekBarObserver; |
Beth Thibodeau | 23a33ab | 2020-04-07 20:51:57 -0400 | [diff] [blame] | 75 | protected final Executor mBackgroundExecutor; |
Beth Thibodeau | e561c00 | 2020-04-23 17:33:00 -0400 | [diff] [blame] | 76 | private final ActivityStarter mActivityStarter; |
Beth Thibodeau | 7b6c178 | 2020-03-05 11:43:51 -0500 | [diff] [blame] | 77 | |
| 78 | private Context mContext; |
Robert Snoeberger | eb49e94 | 2020-05-12 16:31:09 -0400 | [diff] [blame] | 79 | private PlayerViewHolder mViewHolder; |
Selim Cinek | 2de5ebb | 2020-05-20 15:39:03 -0700 | [diff] [blame] | 80 | private MediaViewController mMediaViewController; |
Beth Thibodeau | 7b6c178 | 2020-03-05 11:43:51 -0500 | [diff] [blame] | 81 | private MediaSession.Token mToken; |
| 82 | private MediaController mController; |
Beth Thibodeau | 7b6c178 | 2020-03-05 11:43:51 -0500 | [diff] [blame] | 83 | private int mBackgroundColor; |
Selim Cinek | c543671 | 2020-04-27 15:15:44 -0700 | [diff] [blame] | 84 | private int mAlbumArtSize; |
| 85 | private int mAlbumArtRadius; |
Fabian Kozynski | 47714a9 | 2020-06-17 10:22:35 -0400 | [diff] [blame] | 86 | // This will provide the corners for the album art. |
| 87 | private final ViewOutlineProvider mViewOutlineProvider; |
Robert Snoeberger | 9a7409b | 2020-04-09 18:12:27 -0400 | [diff] [blame] | 88 | |
Beth Thibodeau | 7b6c178 | 2020-03-05 11:43:51 -0500 | [diff] [blame] | 89 | /** |
| 90 | * Initialize a new control panel |
| 91 | * @param context |
Beth Thibodeau | 7b6c178 | 2020-03-05 11:43:51 -0500 | [diff] [blame] | 92 | * @param backgroundExecutor background executor, used for processing artwork |
Beth Thibodeau | e561c00 | 2020-04-23 17:33:00 -0400 | [diff] [blame] | 93 | * @param activityStarter activity starter |
Beth Thibodeau | 7b6c178 | 2020-03-05 11:43:51 -0500 | [diff] [blame] | 94 | */ |
Robert Snoeberger | 7cec542 | 2020-05-29 17:09:14 -0400 | [diff] [blame] | 95 | @Inject |
| 96 | public MediaControlPanel(Context context, @Background Executor backgroundExecutor, |
Lucas Dupin | 84f5a0e | 2020-06-08 19:55:33 -0700 | [diff] [blame] | 97 | ActivityStarter activityStarter, MediaViewController mediaViewController, |
Robert Snoeberger | 7cec542 | 2020-05-29 17:09:14 -0400 | [diff] [blame] | 98 | SeekBarViewModel seekBarViewModel) { |
Beth Thibodeau | 7b6c178 | 2020-03-05 11:43:51 -0500 | [diff] [blame] | 99 | mContext = context; |
Beth Thibodeau | 7b6c178 | 2020-03-05 11:43:51 -0500 | [diff] [blame] | 100 | mBackgroundExecutor = backgroundExecutor; |
Beth Thibodeau | e561c00 | 2020-04-23 17:33:00 -0400 | [diff] [blame] | 101 | mActivityStarter = activityStarter; |
Robert Snoeberger | 7cec542 | 2020-05-29 17:09:14 -0400 | [diff] [blame] | 102 | mSeekBarViewModel = seekBarViewModel; |
Lucas Dupin | 84f5a0e | 2020-06-08 19:55:33 -0700 | [diff] [blame] | 103 | mMediaViewController = mediaViewController; |
Selim Cinek | c543671 | 2020-04-27 15:15:44 -0700 | [diff] [blame] | 104 | loadDimens(); |
Fabian Kozynski | 47714a9 | 2020-06-17 10:22:35 -0400 | [diff] [blame] | 105 | |
| 106 | mViewOutlineProvider = new ViewOutlineProvider() { |
| 107 | @Override |
| 108 | public void getOutline(View view, Outline outline) { |
| 109 | outline.setRoundRect(0, 0, mAlbumArtSize, mAlbumArtSize, mAlbumArtRadius); |
| 110 | } |
| 111 | }; |
Selim Cinek | c543671 | 2020-04-27 15:15:44 -0700 | [diff] [blame] | 112 | } |
| 113 | |
Selim Cinek | 098baf4 | 2020-04-27 19:02:06 -0700 | [diff] [blame] | 114 | public void onDestroy() { |
Robert Snoeberger | eb49e94 | 2020-05-12 16:31:09 -0400 | [diff] [blame] | 115 | if (mSeekBarObserver != null) { |
| 116 | mSeekBarViewModel.getProgress().removeObserver(mSeekBarObserver); |
| 117 | } |
Robert Snoeberger | 734463e | 2020-05-20 02:10:56 -0400 | [diff] [blame] | 118 | mSeekBarViewModel.onDestroy(); |
Selim Cinek | 2de5ebb | 2020-05-20 15:39:03 -0700 | [diff] [blame] | 119 | mMediaViewController.onDestroy(); |
Selim Cinek | 098baf4 | 2020-04-27 19:02:06 -0700 | [diff] [blame] | 120 | } |
| 121 | |
Selim Cinek | c543671 | 2020-04-27 15:15:44 -0700 | [diff] [blame] | 122 | private void loadDimens() { |
| 123 | mAlbumArtRadius = mContext.getResources().getDimensionPixelSize( |
| 124 | Utils.getThemeAttr(mContext, android.R.attr.dialogCornerRadius)); |
| 125 | mAlbumArtSize = mContext.getResources().getDimensionPixelSize(R.dimen.qs_media_album_size); |
Beth Thibodeau | 7b6c178 | 2020-03-05 11:43:51 -0500 | [diff] [blame] | 126 | } |
| 127 | |
| 128 | /** |
Robert Snoeberger | eb49e94 | 2020-05-12 16:31:09 -0400 | [diff] [blame] | 129 | * Get the view holder used to display media controls |
| 130 | * @return the view holder |
Beth Thibodeau | 7b6c178 | 2020-03-05 11:43:51 -0500 | [diff] [blame] | 131 | */ |
Robert Snoeberger | eb49e94 | 2020-05-12 16:31:09 -0400 | [diff] [blame] | 132 | @Nullable |
| 133 | public PlayerViewHolder getView() { |
| 134 | return mViewHolder; |
Beth Thibodeau | 7b6c178 | 2020-03-05 11:43:51 -0500 | [diff] [blame] | 135 | } |
| 136 | |
| 137 | /** |
Selim Cinek | 2de5ebb | 2020-05-20 15:39:03 -0700 | [diff] [blame] | 138 | * Get the view controller used to display media controls |
| 139 | * @return the media view controller |
| 140 | */ |
| 141 | @NonNull |
| 142 | public MediaViewController getMediaViewController() { |
| 143 | return mMediaViewController; |
| 144 | } |
| 145 | |
| 146 | /** |
Selim Cinek | 5dbef2d | 2020-05-07 17:44:38 -0700 | [diff] [blame] | 147 | * Sets the listening state of the player. |
| 148 | * |
| 149 | * Should be set to true when the QS panel is open. Otherwise, false. This is a signal to avoid |
| 150 | * unnecessary work when the QS panel is closed. |
| 151 | * |
| 152 | * @param listening True when player should be active. Otherwise, false. |
| 153 | */ |
| 154 | public void setListening(boolean listening) { |
| 155 | mSeekBarViewModel.setListening(listening); |
| 156 | } |
| 157 | |
| 158 | /** |
Beth Thibodeau | 7b6c178 | 2020-03-05 11:43:51 -0500 | [diff] [blame] | 159 | * Get the context |
| 160 | * @return context |
| 161 | */ |
| 162 | public Context getContext() { |
| 163 | return mContext; |
| 164 | } |
| 165 | |
Robert Snoeberger | eb49e94 | 2020-05-12 16:31:09 -0400 | [diff] [blame] | 166 | /** Attaches the player to the view holder. */ |
| 167 | public void attach(PlayerViewHolder vh) { |
| 168 | mViewHolder = vh; |
Selim Cinek | 2de5ebb | 2020-05-20 15:39:03 -0700 | [diff] [blame] | 169 | TransitionLayout player = vh.getPlayer(); |
Fabian Kozynski | 47714a9 | 2020-06-17 10:22:35 -0400 | [diff] [blame] | 170 | |
| 171 | ImageView albumView = vh.getAlbumView(); |
| 172 | albumView.setOutlineProvider(mViewOutlineProvider); |
| 173 | albumView.setClipToOutline(true); |
| 174 | |
Robert Snoeberger | 45be3fb | 2020-05-19 02:00:43 -0400 | [diff] [blame] | 175 | mSeekBarObserver = new SeekBarObserver(vh); |
Robert Snoeberger | eb49e94 | 2020-05-12 16:31:09 -0400 | [diff] [blame] | 176 | mSeekBarViewModel.getProgress().observeForever(mSeekBarObserver); |
Robert Snoeberger | 77d3dca | 2020-06-02 17:36:49 -0400 | [diff] [blame] | 177 | mSeekBarViewModel.attachTouchHandlers(vh.getSeekBar()); |
Selim Cinek | 2de5ebb | 2020-05-20 15:39:03 -0700 | [diff] [blame] | 178 | mMediaViewController.attach(player); |
Robert Snoeberger | eb49e94 | 2020-05-12 16:31:09 -0400 | [diff] [blame] | 179 | } |
| 180 | |
Beth Thibodeau | 7b6c178 | 2020-03-05 11:43:51 -0500 | [diff] [blame] | 181 | /** |
Selim Cinek | 5dbef2d | 2020-05-07 17:44:38 -0700 | [diff] [blame] | 182 | * Bind this view based on the data given |
Beth Thibodeau | 7b6c178 | 2020-03-05 11:43:51 -0500 | [diff] [blame] | 183 | */ |
Fabian Kozynski | 47714a9 | 2020-06-17 10:22:35 -0400 | [diff] [blame] | 184 | public void bind(@NonNull MediaData data) { |
Robert Snoeberger | eb49e94 | 2020-05-12 16:31:09 -0400 | [diff] [blame] | 185 | if (mViewHolder == null) { |
| 186 | return; |
| 187 | } |
Selim Cinek | f0f7495 | 2020-04-21 11:45:16 -0700 | [diff] [blame] | 188 | MediaSession.Token token = data.getToken(); |
Selim Cinek | 5dbef2d | 2020-05-07 17:44:38 -0700 | [diff] [blame] | 189 | mBackgroundColor = data.getBackgroundColor(); |
Beth Thibodeau | 23a33ab | 2020-04-07 20:51:57 -0400 | [diff] [blame] | 190 | if (mToken == null || !mToken.equals(token)) { |
| 191 | mToken = token; |
Beth Thibodeau | 23a33ab | 2020-04-07 20:51:57 -0400 | [diff] [blame] | 192 | } |
| 193 | |
Beth Thibodeau | f55bc6a | 2020-05-20 02:01:31 -0400 | [diff] [blame] | 194 | if (mToken != null) { |
| 195 | mController = new MediaController(mContext, mToken); |
| 196 | } else { |
| 197 | mController = null; |
| 198 | } |
Beth Thibodeau | 7b6c178 | 2020-03-05 11:43:51 -0500 | [diff] [blame] | 199 | |
Selim Cinek | 2de5ebb | 2020-05-20 15:39:03 -0700 | [diff] [blame] | 200 | ConstraintSet expandedSet = mMediaViewController.getExpandedLayout(); |
| 201 | ConstraintSet collapsedSet = mMediaViewController.getCollapsedLayout(); |
Selim Cinek | 8081f09 | 2020-05-01 21:11:13 -0700 | [diff] [blame] | 202 | |
Selim Cinek | 2de5ebb | 2020-05-20 15:39:03 -0700 | [diff] [blame] | 203 | mViewHolder.getPlayer().setBackgroundTintList( |
Selim Cinek | f0f7495 | 2020-04-21 11:45:16 -0700 | [diff] [blame] | 204 | ColorStateList.valueOf(mBackgroundColor)); |
Beth Thibodeau | 7b6c178 | 2020-03-05 11:43:51 -0500 | [diff] [blame] | 205 | |
| 206 | // Click action |
Selim Cinek | 5dbef2d | 2020-05-07 17:44:38 -0700 | [diff] [blame] | 207 | PendingIntent clickIntent = data.getClickIntent(); |
| 208 | if (clickIntent != null) { |
Robert Snoeberger | eb49e94 | 2020-05-12 16:31:09 -0400 | [diff] [blame] | 209 | mViewHolder.getPlayer().setOnClickListener(v -> { |
Selim Cinek | 5dbef2d | 2020-05-07 17:44:38 -0700 | [diff] [blame] | 210 | mActivityStarter.postStartActivityDismissingKeyguard(clickIntent); |
Beth Thibodeau | a51c314 | 2020-03-17 17:27:04 -0400 | [diff] [blame] | 211 | }); |
| 212 | } |
Beth Thibodeau | 7b6c178 | 2020-03-05 11:43:51 -0500 | [diff] [blame] | 213 | |
Robert Snoeberger | eb49e94 | 2020-05-12 16:31:09 -0400 | [diff] [blame] | 214 | ImageView albumView = mViewHolder.getAlbumView(); |
Beth Thibodeau | f55bc6a | 2020-05-20 02:01:31 -0400 | [diff] [blame] | 215 | boolean hasArtwork = data.getArtwork() != null; |
| 216 | if (hasArtwork) { |
Fabian Kozynski | 47714a9 | 2020-06-17 10:22:35 -0400 | [diff] [blame] | 217 | Drawable artwork = scaleDrawable(data.getArtwork()); |
Beth Thibodeau | f55bc6a | 2020-05-20 02:01:31 -0400 | [diff] [blame] | 218 | albumView.setImageDrawable(artwork); |
| 219 | } |
| 220 | setVisibleAndAlpha(collapsedSet, R.id.album_art, hasArtwork); |
| 221 | setVisibleAndAlpha(expandedSet, R.id.album_art, hasArtwork); |
Selim Cinek | f0f7495 | 2020-04-21 11:45:16 -0700 | [diff] [blame] | 222 | |
Beth Thibodeau | 7b6c178 | 2020-03-05 11:43:51 -0500 | [diff] [blame] | 223 | // App icon |
Robert Snoeberger | eb49e94 | 2020-05-12 16:31:09 -0400 | [diff] [blame] | 224 | ImageView appIcon = mViewHolder.getAppIcon(); |
Beth Thibodeau | f55bc6a | 2020-05-20 02:01:31 -0400 | [diff] [blame] | 225 | if (data.getAppIcon() != null) { |
| 226 | appIcon.setImageDrawable(data.getAppIcon()); |
| 227 | } else { |
| 228 | Drawable iconDrawable = mContext.getDrawable(R.drawable.ic_music_note); |
| 229 | appIcon.setImageDrawable(iconDrawable); |
| 230 | } |
Beth Thibodeau | 7b6c178 | 2020-03-05 11:43:51 -0500 | [diff] [blame] | 231 | |
Selim Cinek | f0f7495 | 2020-04-21 11:45:16 -0700 | [diff] [blame] | 232 | // Song name |
Robert Snoeberger | eb49e94 | 2020-05-12 16:31:09 -0400 | [diff] [blame] | 233 | TextView titleText = mViewHolder.getTitleText(); |
Selim Cinek | 5dbef2d | 2020-05-07 17:44:38 -0700 | [diff] [blame] | 234 | titleText.setText(data.getSong()); |
Selim Cinek | f0f7495 | 2020-04-21 11:45:16 -0700 | [diff] [blame] | 235 | |
| 236 | // App title |
Robert Snoeberger | eb49e94 | 2020-05-12 16:31:09 -0400 | [diff] [blame] | 237 | TextView appName = mViewHolder.getAppName(); |
Selim Cinek | 5dbef2d | 2020-05-07 17:44:38 -0700 | [diff] [blame] | 238 | appName.setText(data.getApp()); |
Selim Cinek | f0f7495 | 2020-04-21 11:45:16 -0700 | [diff] [blame] | 239 | |
| 240 | // Artist name |
Robert Snoeberger | eb49e94 | 2020-05-12 16:31:09 -0400 | [diff] [blame] | 241 | TextView artistText = mViewHolder.getArtistText(); |
Selim Cinek | 5dbef2d | 2020-05-07 17:44:38 -0700 | [diff] [blame] | 242 | artistText.setText(data.getArtist()); |
Selim Cinek | f0f7495 | 2020-04-21 11:45:16 -0700 | [diff] [blame] | 243 | |
Beth Thibodeau | 7b6c178 | 2020-03-05 11:43:51 -0500 | [diff] [blame] | 244 | // Transfer chip |
Robert Snoeberger | 70d0d6b | 2020-05-14 16:47:02 -0400 | [diff] [blame] | 245 | mViewHolder.getSeamless().setVisibility(View.VISIBLE); |
| 246 | setVisibleAndAlpha(collapsedSet, R.id.media_seamless, true /*visible */); |
| 247 | setVisibleAndAlpha(expandedSet, R.id.media_seamless, true /*visible */); |
| 248 | mViewHolder.getSeamless().setOnClickListener(v -> { |
| 249 | final Intent intent = new Intent() |
| 250 | .setAction(MediaOutputSliceConstants.ACTION_MEDIA_OUTPUT) |
| 251 | .putExtra(MediaOutputSliceConstants.EXTRA_PACKAGE_NAME, |
Beth Thibodeau | f55bc6a | 2020-05-20 02:01:31 -0400 | [diff] [blame] | 252 | data.getPackageName()) |
Robert Snoeberger | 70d0d6b | 2020-05-14 16:47:02 -0400 | [diff] [blame] | 253 | .putExtra(MediaOutputSliceConstants.KEY_MEDIA_SESSION_TOKEN, mToken); |
| 254 | mActivityStarter.startActivity(intent, false, true /* dismissShade */, |
| 255 | Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); |
| 256 | }); |
Robert Snoeberger | 70d0d6b | 2020-05-14 16:47:02 -0400 | [diff] [blame] | 257 | |
| 258 | ImageView iconView = mViewHolder.getSeamlessIcon(); |
| 259 | TextView deviceName = mViewHolder.getSeamlessText(); |
| 260 | |
| 261 | // Update the outline color |
Lucas Dupin | 5feeaca | 2020-05-28 11:01:54 -0700 | [diff] [blame] | 262 | RippleDrawable bkgDrawable = (RippleDrawable) mViewHolder.getSeamless().getForeground(); |
Robert Snoeberger | 70d0d6b | 2020-05-14 16:47:02 -0400 | [diff] [blame] | 263 | GradientDrawable rect = (GradientDrawable) bkgDrawable.getDrawable(0); |
| 264 | rect.setStroke(2, deviceName.getCurrentTextColor()); |
| 265 | rect.setColor(Color.TRANSPARENT); |
| 266 | |
Robert Snoeberger | 23e3689 | 2020-05-21 00:33:24 -0400 | [diff] [blame] | 267 | final MediaDeviceData device = data.getDevice(); |
Selim Cinek | eb70e13 | 2020-06-12 19:04:42 -0700 | [diff] [blame] | 268 | int seamlessId = mViewHolder.getSeamless().getId(); |
Robert Snoeberger | 23e3689 | 2020-05-21 00:33:24 -0400 | [diff] [blame] | 269 | if (device != null && !device.getEnabled()) { |
Robert Snoeberger | 70d0d6b | 2020-05-14 16:47:02 -0400 | [diff] [blame] | 270 | mViewHolder.getSeamless().setEnabled(false); |
Selim Cinek | eb70e13 | 2020-06-12 19:04:42 -0700 | [diff] [blame] | 271 | expandedSet.setAlpha(seamlessId, 0.38f); |
| 272 | collapsedSet.setAlpha(seamlessId, 0.38f); |
Robert Snoeberger | 70d0d6b | 2020-05-14 16:47:02 -0400 | [diff] [blame] | 273 | iconView.setImageResource(R.drawable.ic_hardware_speaker); |
| 274 | iconView.setVisibility(View.VISIBLE); |
| 275 | deviceName.setText(R.string.media_seamless_remote_device); |
Robert Snoeberger | 23e3689 | 2020-05-21 00:33:24 -0400 | [diff] [blame] | 276 | } else if (device != null) { |
Robert Snoeberger | 70d0d6b | 2020-05-14 16:47:02 -0400 | [diff] [blame] | 277 | mViewHolder.getSeamless().setEnabled(true); |
Selim Cinek | eb70e13 | 2020-06-12 19:04:42 -0700 | [diff] [blame] | 278 | expandedSet.setAlpha(seamlessId, 1.0f); |
| 279 | collapsedSet.setAlpha(seamlessId, 1.0f); |
Robert Snoeberger | 23e3689 | 2020-05-21 00:33:24 -0400 | [diff] [blame] | 280 | Drawable icon = device.getIcon(); |
Robert Snoeberger | 70d0d6b | 2020-05-14 16:47:02 -0400 | [diff] [blame] | 281 | iconView.setVisibility(View.VISIBLE); |
| 282 | |
| 283 | if (icon instanceof AdaptiveIcon) { |
| 284 | AdaptiveIcon aIcon = (AdaptiveIcon) icon; |
| 285 | aIcon.setBackgroundColor(mBackgroundColor); |
| 286 | iconView.setImageDrawable(aIcon); |
| 287 | } else { |
| 288 | iconView.setImageDrawable(icon); |
| 289 | } |
Robert Snoeberger | 23e3689 | 2020-05-21 00:33:24 -0400 | [diff] [blame] | 290 | deviceName.setText(device.getName()); |
Robert Snoeberger | 70d0d6b | 2020-05-14 16:47:02 -0400 | [diff] [blame] | 291 | } else { |
| 292 | // Reset to default |
| 293 | Log.w(TAG, "device is null. Not binding output chip."); |
| 294 | mViewHolder.getSeamless().setEnabled(true); |
Selim Cinek | eb70e13 | 2020-06-12 19:04:42 -0700 | [diff] [blame] | 295 | expandedSet.setAlpha(seamlessId, 1.0f); |
| 296 | collapsedSet.setAlpha(seamlessId, 1.0f); |
Robert Snoeberger | 70d0d6b | 2020-05-14 16:47:02 -0400 | [diff] [blame] | 297 | iconView.setVisibility(View.GONE); |
| 298 | deviceName.setText(com.android.internal.R.string.ext_media_seamless_action); |
| 299 | } |
| 300 | |
Selim Cinek | 5dbef2d | 2020-05-07 17:44:38 -0700 | [diff] [blame] | 301 | List<Integer> actionsWhenCollapsed = data.getActionsToShowInCompact(); |
| 302 | // Media controls |
| 303 | int i = 0; |
| 304 | List<MediaAction> actionIcons = data.getActions(); |
| 305 | for (; i < actionIcons.size() && i < ACTION_IDS.length; i++) { |
Selim Cinek | f0f7495 | 2020-04-21 11:45:16 -0700 | [diff] [blame] | 306 | int actionId = ACTION_IDS[i]; |
Robert Snoeberger | eb49e94 | 2020-05-12 16:31:09 -0400 | [diff] [blame] | 307 | final ImageButton button = mViewHolder.getAction(actionId); |
Selim Cinek | 5dbef2d | 2020-05-07 17:44:38 -0700 | [diff] [blame] | 308 | MediaAction mediaAction = actionIcons.get(i); |
| 309 | button.setImageDrawable(mediaAction.getDrawable()); |
| 310 | button.setContentDescription(mediaAction.getContentDescription()); |
Beth Thibodeau | f55bc6a | 2020-05-20 02:01:31 -0400 | [diff] [blame] | 311 | Runnable action = mediaAction.getAction(); |
Selim Cinek | 5dbef2d | 2020-05-07 17:44:38 -0700 | [diff] [blame] | 312 | |
Beth Thibodeau | f96f4fb | 2020-06-11 19:26:54 -0400 | [diff] [blame] | 313 | if (action == null) { |
| 314 | button.setEnabled(false); |
| 315 | } else { |
| 316 | button.setEnabled(true); |
| 317 | button.setOnClickListener(v -> { |
Beth Thibodeau | f55bc6a | 2020-05-20 02:01:31 -0400 | [diff] [blame] | 318 | action.run(); |
Beth Thibodeau | f96f4fb | 2020-06-11 19:26:54 -0400 | [diff] [blame] | 319 | }); |
| 320 | } |
Selim Cinek | 5dbef2d | 2020-05-07 17:44:38 -0700 | [diff] [blame] | 321 | boolean visibleInCompat = actionsWhenCollapsed.contains(i); |
Selim Cinek | 8081f09 | 2020-05-01 21:11:13 -0700 | [diff] [blame] | 322 | setVisibleAndAlpha(collapsedSet, actionId, visibleInCompat); |
| 323 | setVisibleAndAlpha(expandedSet, actionId, true /*visible */); |
Selim Cinek | 5dbef2d | 2020-05-07 17:44:38 -0700 | [diff] [blame] | 324 | } |
| 325 | |
| 326 | // Hide any unused buttons |
| 327 | for (; i < ACTION_IDS.length; i++) { |
Selim Cinek | 8081f09 | 2020-05-01 21:11:13 -0700 | [diff] [blame] | 328 | setVisibleAndAlpha(expandedSet, ACTION_IDS[i], false /*visible */); |
| 329 | setVisibleAndAlpha(collapsedSet, ACTION_IDS[i], false /*visible */); |
Selim Cinek | 5dbef2d | 2020-05-07 17:44:38 -0700 | [diff] [blame] | 330 | } |
| 331 | |
| 332 | // Seek Bar |
Selim Cinek | f418bb0 | 2020-05-04 17:16:58 -0700 | [diff] [blame] | 333 | final MediaController controller = getController(); |
Lucas Dupin | 81e9beb | 2020-05-14 19:28:36 -0700 | [diff] [blame] | 334 | mBackgroundExecutor.execute(() -> mSeekBarViewModel.updateController(controller)); |
Selim Cinek | 5dbef2d | 2020-05-07 17:44:38 -0700 | [diff] [blame] | 335 | |
| 336 | // Set up long press menu |
| 337 | // TODO: b/156036025 bring back media guts |
| 338 | |
Selim Cinek | 2de5ebb | 2020-05-20 15:39:03 -0700 | [diff] [blame] | 339 | // TODO: We don't need to refresh this state constantly, only if the state actually changed |
| 340 | // to something which might impact the measurement |
| 341 | mMediaViewController.refreshState(); |
Selim Cinek | f0f7495 | 2020-04-21 11:45:16 -0700 | [diff] [blame] | 342 | } |
Beth Thibodeau | 23a33ab | 2020-04-07 20:51:57 -0400 | [diff] [blame] | 343 | |
Selim Cinek | f418bb0 | 2020-05-04 17:16:58 -0700 | [diff] [blame] | 344 | @UiThread |
Fabian Kozynski | 47714a9 | 2020-06-17 10:22:35 -0400 | [diff] [blame] | 345 | private Drawable scaleDrawable(Icon icon) { |
Selim Cinek | c543671 | 2020-04-27 15:15:44 -0700 | [diff] [blame] | 346 | if (icon == null) { |
| 347 | return null; |
| 348 | } |
| 349 | // Let's scale down the View, such that the content always nicely fills the view. |
| 350 | // ThumbnailUtils actually scales it down such that it may not be filled for odd aspect |
| 351 | // ratios |
| 352 | Drawable drawable = icon.loadDrawable(mContext); |
| 353 | float aspectRatio = drawable.getIntrinsicHeight() / (float) drawable.getIntrinsicWidth(); |
| 354 | Rect bounds; |
| 355 | if (aspectRatio > 1.0f) { |
| 356 | bounds = new Rect(0, 0, mAlbumArtSize, (int) (mAlbumArtSize * aspectRatio)); |
| 357 | } else { |
| 358 | bounds = new Rect(0, 0, (int) (mAlbumArtSize / aspectRatio), mAlbumArtSize); |
| 359 | } |
| 360 | if (bounds.width() > mAlbumArtSize || bounds.height() > mAlbumArtSize) { |
| 361 | float offsetX = (bounds.width() - mAlbumArtSize) / 2.0f; |
| 362 | float offsetY = (bounds.height() - mAlbumArtSize) / 2.0f; |
| 363 | bounds.offset((int) -offsetX,(int) -offsetY); |
| 364 | } |
| 365 | drawable.setBounds(bounds); |
Fabian Kozynski | 47714a9 | 2020-06-17 10:22:35 -0400 | [diff] [blame] | 366 | return drawable; |
Selim Cinek | c543671 | 2020-04-27 15:15:44 -0700 | [diff] [blame] | 367 | } |
| 368 | |
Selim Cinek | f0f7495 | 2020-04-21 11:45:16 -0700 | [diff] [blame] | 369 | /** |
Beth Thibodeau | 7b6c178 | 2020-03-05 11:43:51 -0500 | [diff] [blame] | 370 | * Get the current media controller |
| 371 | * @return the controller |
| 372 | */ |
| 373 | public MediaController getController() { |
| 374 | return mController; |
| 375 | } |
| 376 | |
| 377 | /** |
Beth Thibodeau | 7b6c178 | 2020-03-05 11:43:51 -0500 | [diff] [blame] | 378 | * Check whether the media controlled by this player is currently playing |
| 379 | * @return whether it is playing, or false if no controller information |
| 380 | */ |
| 381 | public boolean isPlaying() { |
| 382 | return isPlaying(mController); |
| 383 | } |
| 384 | |
| 385 | /** |
| 386 | * Check whether the given controller is currently playing |
| 387 | * @param controller media controller to check |
| 388 | * @return whether it is playing, or false if no controller information |
| 389 | */ |
| 390 | protected boolean isPlaying(MediaController controller) { |
| 391 | if (controller == null) { |
| 392 | return false; |
| 393 | } |
| 394 | |
| 395 | PlaybackState state = controller.getPlaybackState(); |
| 396 | if (state == null) { |
| 397 | return false; |
| 398 | } |
| 399 | |
| 400 | return (state.getState() == PlaybackState.STATE_PLAYING); |
| 401 | } |
| 402 | |
Selim Cinek | 8081f09 | 2020-05-01 21:11:13 -0700 | [diff] [blame] | 403 | private void setVisibleAndAlpha(ConstraintSet set, int actionId, boolean visible) { |
| 404 | set.setVisibility(actionId, visible? ConstraintSet.VISIBLE : ConstraintSet.GONE); |
| 405 | set.setAlpha(actionId, visible ? 1.0f : 0.0f); |
| 406 | } |
Beth Thibodeau | 7b6c178 | 2020-03-05 11:43:51 -0500 | [diff] [blame] | 407 | } |