1 /* 2 * Copyright (c) 2016, 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 package com.android.car.overview; 17 18 import android.app.PendingIntent; 19 import android.content.Context; 20 import android.text.TextUtils; 21 import android.util.Log; 22 import android.view.View; 23 import android.widget.ImageButton; 24 import android.widget.ImageView; 25 import android.widget.TextView; 26 import com.android.car.stream.MediaPlaybackExtension; 27 import com.android.car.stream.StreamCard; 28 29 /** 30 * A {@link StreamViewHolder} that binds a {@link MediaPlaybackExtension} to 31 * an interactive in playback UI card. 32 */ 33 public class MediaStreamViewHolder extends StreamViewHolder { 34 private static final String TAG = "MediaStreamViewHolder"; 35 36 private static final String ELLIPSIS = "\u2026"; 37 // limit the subtitle to 20 chars, and the rest space is given to the app name. 38 private static final int MAX_TEXT_LENGTH = 20; 39 private static final String UNICODE_BULLET_SPACER = " \u2022 "; 40 41 private final ImageView mPlayerBackground; 42 private final TextView mTitleTextView; 43 private final TextView mSubtitleTextview; 44 45 private final ImageButton mSkipToPreviousButton; 46 private final ImageButton mSkipToNextButton; 47 private final OverviewFabButton mPlayPauseButton; 48 49 private PendingIntent mPlayPauseAction; 50 private PendingIntent mSkipToNextAction; 51 private PendingIntent mSkipToPreviousAction; 52 53 private PendingIntent mContentPendingIntent; 54 MediaStreamViewHolder(Context context, View itemView)55 public MediaStreamViewHolder(Context context, View itemView) { 56 super(context, itemView); 57 58 mPlayerBackground = (ImageView) itemView.findViewById(R.id.media_player_background); 59 mTitleTextView = (TextView) itemView.findViewById(R.id.title); 60 mSubtitleTextview = (TextView) itemView.findViewById(R.id.subtitle); 61 62 mSkipToPreviousButton = (ImageButton) itemView.findViewById(R.id.skip_previous); 63 mSkipToNextButton = (ImageButton) itemView.findViewById(R.id.skip_next); 64 mPlayPauseButton = (OverviewFabButton) itemView.findViewById(R.id.play_pause); 65 66 mSkipToPreviousButton.setImageResource(R.drawable.ic_skip_previous); 67 mSkipToNextButton.setImageResource(R.drawable.ic_skip_next); 68 69 mPlayPauseButton.setOnClickListener(new View.OnClickListener() { 70 @Override 71 public void onClick(View v) { 72 try { 73 if (mPlayPauseAction != null) { 74 mPlayPauseAction.send(); 75 } 76 } catch (PendingIntent.CanceledException e) { 77 Log.e(TAG, "Failed to send play/pause action pending intent", e); 78 } 79 } 80 }); 81 82 mSkipToPreviousButton.setOnClickListener(new View.OnClickListener() { 83 @Override 84 public void onClick(View v) { 85 try { 86 if (mSkipToPreviousAction != null) { 87 mSkipToPreviousAction.send(); 88 } 89 } catch (PendingIntent.CanceledException e) { 90 Log.e(TAG, "Failed to send play/pause action pending intent", e); 91 } 92 } 93 }); 94 95 mSkipToNextButton.setOnClickListener(new View.OnClickListener() { 96 @Override 97 public void onClick(View v) { 98 try { 99 if (mSkipToNextAction != null) { 100 mSkipToNextAction.send(); 101 } 102 } catch (PendingIntent.CanceledException e) { 103 Log.e(TAG, "Failed to send play/pause action pending intent", e); 104 } 105 } 106 }); 107 108 mActionContainer.setOnClickListener(new View.OnClickListener() { 109 @Override 110 public void onClick(View v) { 111 try { 112 if (mContentPendingIntent != null) { 113 mContentPendingIntent.send(); 114 } 115 } catch (PendingIntent.CanceledException e) { 116 Log.e(TAG, "Failed to send content pending intent in media card", e); 117 } 118 } 119 }); 120 } 121 122 @Override bindStreamCard(StreamCard card)123 public void bindStreamCard(StreamCard card) { 124 super.bindStreamCard(card); 125 MediaPlaybackExtension extension = (MediaPlaybackExtension) card.getCardExtension(); 126 127 if (extension.getAlbumArt() != null) { 128 mPlayerBackground.setImageBitmap(extension.getAlbumArt()); 129 mPlayerBackground.setVisibility(View.VISIBLE); 130 } 131 132 String title = extension.getTitle(); 133 if (!TextUtils.isEmpty(title)) { 134 mTitleTextView.setVisibility(View.VISIBLE); 135 mTitleTextView.setText(title); 136 } 137 138 String subtitle = getSubtitle(extension); 139 if (!TextUtils.isEmpty(title)) { 140 mSubtitleTextview.setVisibility(View.VISIBLE); 141 mSubtitleTextview.setText(subtitle); 142 } 143 144 int playPauseIcon; 145 if (extension.isPlaying()) { 146 if (extension.hasPause()) { 147 playPauseIcon = R.drawable.ic_pause; 148 mPlayPauseAction = extension.getPauseAction(); 149 } else { 150 playPauseIcon = R.drawable.ic_stop; 151 mPlayPauseAction = extension.getStopAction(); 152 } 153 } else { 154 playPauseIcon = R.drawable.ic_play_arrow; 155 mPlayPauseAction = extension.getPlayAction(); 156 } 157 158 mPlayPauseButton.setImageResource(playPauseIcon); 159 mPlayPauseButton.setAccentColor(extension.getAppAccentColor()); 160 161 mSkipToPreviousAction = extension.getSkipToPreviousAction(); 162 mSkipToNextAction = extension.getSkipToNextAction(); 163 164 if (!extension.canSkipToNext()) { 165 mSkipToNextButton.setColorFilter(mContext.getColor(R.color.car_grey_700)); 166 mSkipToNextButton.setEnabled(false); 167 } 168 169 if (!extension.canSkipToPrevious()) { 170 mSkipToPreviousButton.setColorFilter(mContext.getColor(R.color.car_grey_700)); 171 mSkipToPreviousButton.setEnabled(false); 172 } 173 174 mContentPendingIntent = card.getContentPendingIntent(); 175 } 176 177 @Override resetViews()178 protected void resetViews() { 179 mSubtitleTextview.setText(null); 180 mTitleTextView.setText(null); 181 182 mSubtitleTextview.setVisibility(View.GONE); 183 mTitleTextView.setVisibility(View.GONE); 184 mPlayerBackground.setVisibility(View.GONE); 185 186 mSkipToNextButton.setEnabled(true); 187 mSkipToPreviousButton.setEnabled(true); 188 mSkipToNextButton.setColorFilter(0); 189 mSkipToPreviousButton.setColorFilter(0); 190 } 191 192 /** 193 * Ellipsize the subtitle and append the app name if available. 194 */ getSubtitle(MediaPlaybackExtension extension)195 private String getSubtitle(MediaPlaybackExtension extension) { 196 String appName = extension.getAppName(); 197 198 StringBuilder sb = new StringBuilder(); 199 if (!TextUtils.isEmpty(extension.getSubtitle())) { 200 sb.append(ellipsizeText(extension.getSubtitle(), MAX_TEXT_LENGTH)); 201 if (!TextUtils.isEmpty(appName)) { 202 sb.append(UNICODE_BULLET_SPACER); 203 sb.append(appName); 204 } 205 } 206 return sb.toString(); 207 } 208 209 /** 210 * Ellipsize text and append "..." to the end if its length exceeds the {@code maxLen}. 211 * <p/> 212 * We are not using the ellipsize in the TextView because we are ellipsizing 213 * 2 strings separately. 214 */ ellipsizeText(CharSequence text, int maxLen)215 private static CharSequence ellipsizeText(CharSequence text, int maxLen) { 216 if (text == null || text.length() <= maxLen) { 217 return text; 218 } 219 220 if (maxLen <= ELLIPSIS.length()) { 221 Log.e(TAG, "Unable to truncate string to " + maxLen); 222 return text; 223 } 224 StringBuilder sb = new StringBuilder(); 225 // now, maxLen > ellip.length() 226 sb.append(text.subSequence(0, maxLen - ELLIPSIS.length())); 227 sb.append(ELLIPSIS); 228 return sb.toString(); 229 } 230 } 231