MediaActivity.java revision 5cb58af61618d6c457685a5feba630540f25567c
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.Intent; 20import android.graphics.Bitmap; 21import android.os.Bundle; 22import android.provider.MediaStore; 23import android.support.v4.app.Fragment; 24import android.util.Log; 25import android.util.Pair; 26import android.util.TypedValue; 27import android.view.View; 28 29import com.android.car.app.CarDrawerActivity; 30import com.android.car.app.CarDrawerAdapter; 31import com.android.car.media.drawer.MediaDrawerController; 32 33/** 34 * This activity controls the UI of media. It also updates the connection status for the media app 35 * by broadcast. Drawer menu is controlled by {@link MediaDrawerController}. 36 */ 37public class MediaActivity extends CarDrawerActivity { 38 private static final String ACTION_MEDIA_APP_STATE_CHANGE 39 = "android.intent.action.MEDIA_APP_STATE_CHANGE"; 40 private static final String EXTRA_MEDIA_APP_FOREGROUND 41 = "android.intent.action.MEDIA_APP_STATE"; 42 43 private static final String TAG = "MediaActivity"; 44 45 /** 46 * Whether or not {@link #onResume()} has been called. 47 */ 48 private static boolean sIsRunning = false; 49 50 /** 51 * Whether or not {@link #onStart()} has been called. 52 */ 53 private boolean mIsStarted; 54 55 /** 56 * {@code true} if there was a request to change the content fragment of this Activity when 57 * it is not started. Then, when onStart() is called, the content fragment will be added. 58 * 59 * <p>This prevents a bug where the content fragment is added when the app is not running, 60 * causing a StateLossException. 61 */ 62 private boolean mContentFragmentChangeQueued; 63 64 private MediaDrawerController mDrawerController; 65 private View mScrimView; 66 private CrossfadeImageView mAlbumArtView; 67 private MediaPlaybackFragment mMediaPlaybackFragment; 68 69 @Override 70 protected void onStart() { 71 super.onStart(); 72 Intent i = new Intent(ACTION_MEDIA_APP_STATE_CHANGE); 73 i.putExtra(EXTRA_MEDIA_APP_FOREGROUND, true); 74 sendBroadcast(i); 75 76 mIsStarted = true; 77 78 if (mContentFragmentChangeQueued) { 79 if (Log.isLoggable(TAG, Log.DEBUG)) { 80 Log.d(TAG, "Content fragment queued. Attaching now."); 81 } 82 attachContentFragment(); 83 mContentFragmentChangeQueued = false; 84 } 85 } 86 87 @Override 88 protected void onStop() { 89 super.onStop(); 90 Intent i = new Intent(ACTION_MEDIA_APP_STATE_CHANGE); 91 i.putExtra(EXTRA_MEDIA_APP_FOREGROUND, false); 92 sendBroadcast(i); 93 94 mIsStarted = false; 95 } 96 97 @Override 98 protected void onCreate(Bundle savedInstanceState) { 99 mDrawerController = new MediaDrawerController(this); 100 super.onCreate(savedInstanceState); 101 102 setMainContent(R.layout.media_activity); 103 mScrimView = findViewById(R.id.scrim); 104 mAlbumArtView = (CrossfadeImageView) findViewById(R.id.album_art); 105 setBackgroundColor(getColor(R.color.music_default_artwork)); 106 MediaManager.getInstance(this).addListener(mListener); 107 } 108 109 @Override 110 public void onDestroy() { 111 super.onDestroy(); 112 // Send the broadcast to let the current connected media app know it is disconnected now. 113 sendMediaConnectionStatusBroadcast( 114 MediaManager.getInstance(this).getCurrentComponent(), 115 MediaConstants.MEDIA_DISCONNECTED); 116 mDrawerController.cleanup(); 117 MediaManager.getInstance(this).removeListener(mListener); 118 } 119 120 @Override 121 protected CarDrawerAdapter getRootAdapter() { 122 return mDrawerController.getRootAdapter(); 123 } 124 125 @Override 126 public void onResumeFragments() { 127 if (Log.isLoggable(TAG, Log.DEBUG)) { 128 Log.d(TAG, "onResumeFragments"); 129 } 130 131 super.onResumeFragments(); 132 handleIntent(getIntent()); 133 sIsRunning = true; 134 } 135 136 @Override 137 public void onPause() { 138 if (Log.isLoggable(TAG, Log.DEBUG)) { 139 Log.d(TAG, "onPause"); 140 } 141 142 super.onPause(); 143 sIsRunning = false; 144 } 145 146 @Override 147 protected void onNewIntent(Intent intent) { 148 super.onNewIntent(intent); 149 if (Log.isLoggable(TAG, Log.VERBOSE)) { 150 Log.v(TAG, "onNewIntent(); intent: " + (intent == null ? "<< NULL >>" : intent)); 151 } 152 153 setIntent(intent); 154 closeDrawer(); 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(this).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(this).connectToMostRecentMediaComponent( 241 new CarClientServiceAdapter(getPackageManager())); 242 } 243 244 if (isSearchIntent(intent)) { 245 MediaManager.getInstance(this).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, 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 sendBroadcast(intent); 266 } 267 268 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 private void setContentFragment(Fragment fragment) { 303 getSupportFragmentManager().beginTransaction() 304 .replace(getContentContainerId(), fragment) 305 .commit(); 306 } 307 308 309 void showQueueInDrawer() { 310 mDrawerController.showQueueInDrawer(); 311 } 312}