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