MediaActivity.java revision 64b173f33aa2d98ea00b9113b52e6f8cb35589ad
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 */ 16package com.android.car.media; 17 18import android.content.ComponentName; 19import android.content.Context; 20import android.content.Intent; 21import android.graphics.Bitmap; 22import android.os.Bundle; 23import android.provider.MediaStore; 24import android.support.car.Car; 25import android.support.car.app.menu.CarDrawerActivity; 26import android.util.Log; 27import android.util.Pair; 28import android.util.TypedValue; 29import android.view.View; 30 31/** 32 * This activity controls the UI of media. It also updates the connection status for the media app 33 * by broadcast. Drawer menu is controlled by {@link MediaCarMenuCallbacks}. 34 */ 35public class MediaActivity extends CarDrawerActivity { 36 private static final String ACTION_MEDIA_APP_STATE_CHANGE 37 = "android.intent.action.MEDIA_APP_STATE_CHANGE"; 38 private static final String EXTRA_MEDIA_APP_FOREGROUND 39 = "android.intent.action.MEDIA_APP_STATE"; 40 41 private static final String TAG = "MediaActivity"; 42 43 /** 44 * Whether or not {@link #onResume()} has been called. 45 */ 46 private static boolean sIsRunning = false; 47 48 /** 49 * Whether or not {@link #onStart()} has been called. 50 */ 51 private boolean mIsStarted; 52 53 /** 54 * {@code true} if there was a request to change the content fragment of this Activity when 55 * it is not started. Then, when onStart() is called, the content fragment will be added. 56 * 57 * <p>This prevents a bug where the content fragment is added when the app is not running, 58 * causing a StateLossException. 59 */ 60 private boolean mContentFragmentChangeQueued; 61 62 private View mScrimView; 63 private CrossfadeImageView mAlbumArtView; 64 private MediaPlaybackFragment mMediaPlaybackFragment; 65 private MediaCarMenuCallbacks mMediaCarMenuCallbacks; 66 67 public MediaActivity(Proxy proxy, Context context, Car car) { 68 super(proxy, context, car); 69 } 70 71 @Override 72 protected void onStart() { 73 super.onStart(); 74 Intent i = new Intent(ACTION_MEDIA_APP_STATE_CHANGE); 75 i.putExtra(EXTRA_MEDIA_APP_FOREGROUND, true); 76 getContext().sendBroadcast(i); 77 78 mIsStarted = true; 79 80 if (mContentFragmentChangeQueued) { 81 if (Log.isLoggable(TAG, Log.DEBUG)) { 82 Log.d(TAG, "Content fragment queued. Attaching now."); 83 } 84 attachContentFragment(); 85 mContentFragmentChangeQueued = false; 86 } 87 } 88 89 @Override 90 protected void onStop() { 91 super.onStop(); 92 Intent i = new Intent(ACTION_MEDIA_APP_STATE_CHANGE); 93 i.putExtra(EXTRA_MEDIA_APP_FOREGROUND, false); 94 getContext().sendBroadcast(i); 95 96 mIsStarted = false; 97 } 98 99 @Override 100 protected void onCreate(Bundle savedInstanceState) { 101 super.onCreate(savedInstanceState); 102 setLightMode(); 103 mMediaCarMenuCallbacks = new MediaCarMenuCallbacks(this); 104 setCarMenuCallbacks(mMediaCarMenuCallbacks); 105 setContentView(R.layout.media_activity); 106 mScrimView = findViewById(R.id.scrim); 107 mAlbumArtView = (CrossfadeImageView) findViewById(R.id.album_art); 108 setBackgroundColor(getContext().getColor(R.color.music_default_artwork)); 109 MediaManager.getInstance(getContext()).addListener(mListener); 110 } 111 112 @Override 113 public void onDestroy() { 114 super.onDestroy(); 115 // Send the broadcast to let the current connected media app know it is disconnected now. 116 sendMediaConnectionStatusBroadcast( 117 MediaManager.getInstance(getContext()).getCurrentComponent(), 118 MediaConstants.MEDIA_DISCONNECTED); 119 mMediaCarMenuCallbacks.cleanup(); 120 MediaManager.getInstance(getContext()).removeListener(mListener); 121 } 122 123 @Override 124 public void onResumeFragments() { 125 if (Log.isLoggable(TAG, Log.DEBUG)) { 126 Log.d(TAG, "onResumeFragments"); 127 } 128 129 super.onResumeFragments(); 130 handleIntent(getIntent()); 131 sIsRunning = true; 132 } 133 134 @Override 135 public void onPause() { 136 if (Log.isLoggable(TAG, Log.DEBUG)) { 137 Log.d(TAG, "onPause"); 138 } 139 140 super.onPause(); 141 sIsRunning = false; 142 } 143 144 @Override 145 protected void onNewIntent(Intent intent) { 146 super.onNewIntent(intent); 147 if (Log.isLoggable(TAG, Log.VERBOSE)) { 148 Log.v(TAG, "onNewIntent(); intent: " + (intent == null ? "<< NULL >>" : intent)); 149 } 150 151 setIntent(intent); 152 if (isDrawerShowing()) { 153 closeDrawer(); 154 } 155 } 156 157 @Override 158 public void onBackPressed() { 159 if (mMediaPlaybackFragment.isOverflowMenuVisible()) { 160 mMediaPlaybackFragment.closeOverflowMenu(); 161 } 162 super.onBackPressed(); 163 } 164 165 /** 166 * Darken scrim when new messages are displayed in {@link MediaPlaybackFragment}. 167 */ 168 public void darkenScrim(boolean dark) { 169 if (!sIsRunning) { 170 return; 171 } 172 173 if (dark) { 174 mScrimView.animate().alpha(0.9f) 175 .setDuration(getResources().getInteger(R.integer.media_scrim_duration_ms)); 176 } else { 177 mScrimView.animate().alpha(0.6f) 178 .setDuration(getResources().getInteger(R.integer.media_scrim_duration_ms)); 179 } 180 } 181 182 /** 183 * Don't show scrim when there is no content, else show it. 184 */ 185 public void setScrimVisibility(boolean visible) { 186 if (visible) { 187 TypedValue scrimAlpha = new TypedValue(); 188 getResources().getValue(R.dimen.media_scrim_alpha, scrimAlpha, true); 189 mScrimView.animate().alpha(scrimAlpha.getFloat()) 190 .setDuration(getResources().getInteger(R.integer.media_scrim_duration_ms)); 191 } else { 192 mScrimView.animate().alpha(0.0f) 193 .setDuration(getResources().getInteger(R.integer.media_scrim_duration_ms)); 194 195 } 196 } 197 198 /** 199 * Return the dimension of the background album art in the form of <width, height>. 200 */ 201 public Pair<Integer, Integer> getAlbumArtSize() { 202 if (mAlbumArtView != null) { 203 return Pair.create(mAlbumArtView.getWidth(), mAlbumArtView.getHeight()); 204 } 205 return null; 206 } 207 208 void setBackgroundBitmap(Bitmap bitmap, boolean showAnimation) { 209 mAlbumArtView.setImageBitmap(bitmap, showAnimation); 210 } 211 212 void setBackgroundColor(int color) { 213 mAlbumArtView.setBackgroundColor(color); 214 } 215 216 private void handleIntent(Intent intent) { 217 Bundle extras = null; 218 if (intent != null) { 219 extras = intent.getExtras(); 220 } 221 222 // If the intent has a media component name set, connect to it directly 223 if (extras != null && extras.containsKey(MediaManager.KEY_MEDIA_PACKAGE) && 224 extras.containsKey(MediaManager.KEY_MEDIA_CLASS)) { 225 if (Log.isLoggable(TAG, Log.DEBUG)) { 226 Log.d(TAG, "Media component in intent."); 227 } 228 229 ComponentName component = new ComponentName( 230 intent.getStringExtra(MediaManager.KEY_MEDIA_PACKAGE), 231 intent.getStringExtra(MediaManager.KEY_MEDIA_CLASS) 232 ); 233 MediaManager.getInstance(getContext()).setMediaClientComponent(component); 234 } else { 235 if (Log.isLoggable(TAG, Log.DEBUG)) { 236 Log.d(TAG, "Launching most recent / default component."); 237 } 238 239 // Set it to the default GPM component. 240 MediaManager.getInstance(getContext()).connectToMostRecentMediaComponent( 241 new CarClientServiceAdapter(getContext().getPackageManager())); 242 } 243 244 if (isSearchIntent(intent)) { 245 MediaManager.getInstance(getContext()).processSearchIntent(intent); 246 setIntent(null); 247 } 248 } 249 250 private boolean isSearchIntent(Intent intent) { 251 return (intent != null && intent.getAction() != null && 252 intent.getAction().equals(MediaStore.INTENT_ACTION_MEDIA_PLAY_FROM_SEARCH)); 253 } 254 255 private void sendMediaConnectionStatusBroadcast( 256 ComponentName componentName, @Car.ConnectionType String connectionStatus) { 257 // It will be no current component if no media app is chosen before. 258 if (componentName == null) { 259 return; 260 } 261 262 Intent intent = new Intent(MediaConstants.ACTION_MEDIA_STATUS); 263 intent.setPackage(componentName.getPackageName()); 264 intent.putExtra(MediaConstants.MEDIA_CONNECTION_STATUS, connectionStatus); 265 getContext().sendBroadcast(intent); 266 } 267 268 public void attachContentFragment() { 269 if (mMediaPlaybackFragment == null) { 270 mMediaPlaybackFragment = new MediaPlaybackFragment(); 271 } 272 273 setContentFragment(mMediaPlaybackFragment); 274 } 275 276 private final MediaManager.Listener mListener = new MediaManager.Listener() { 277 278 @Override 279 public void onMediaAppChanged(ComponentName componentName) { 280 sendMediaConnectionStatusBroadcast(componentName, MediaConstants.MEDIA_CONNECTED); 281 282 // Since this callback happens asynchronously, ensure that the Activity has been 283 // started before changing fragments. Otherwise, the attach fragment will throw 284 // an IllegalStateException due to Fragment's checkStateLoss. 285 if (mIsStarted) { 286 if (Log.isLoggable(TAG, Log.DEBUG)) { 287 Log.d(TAG, "onMediaAppChanged: attaching content fragment"); 288 } 289 attachContentFragment(); 290 } else { 291 if (Log.isLoggable(TAG, Log.DEBUG)) { 292 Log.d(TAG, "onMediaAppChanged: queuing content fragment change"); 293 } 294 mContentFragmentChangeQueued = true; 295 } 296 } 297 298 @Override 299 public void onStatusMessageChanged(String msg) {} 300 }; 301} 302