blob: ab282dc48a9c7b06ae4a16015e70ca9cf24ad1fb [file] [log] [blame]
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.car.media;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.os.Bundle;
import android.provider.MediaStore;
import android.support.car.Car;
import android.support.car.app.menu.CarDrawerActivity;
import android.util.Log;
import android.util.Pair;
import android.util.TypedValue;
import android.view.View;
/**
* This activity controls the UI of media. It also updates the connection status for the media app
* by broadcast. Drawer menu is controlled by {@link MediaCarMenuCallbacks}.
*/
public class MediaActivity extends CarDrawerActivity {
private static final String ACTION_MEDIA_APP_STATE_CHANGE
= "android.intent.action.MEDIA_APP_STATE_CHANGE";
private static final String EXTRA_MEDIA_APP_FOREGROUND
= "android.intent.action.MEDIA_APP_STATE";
private static final String TAG = "MediaActivity";
/**
* Whether or not {@link #onResume()} has been called.
*/
private static boolean sIsRunning = false;
/**
* Whether or not {@link #onStart()} has been called.
*/
private boolean mIsStarted;
/**
* {@code true} if there was a request to change the content fragment of this Activity when
* it is not started. Then, when onStart() is called, the content fragment will be added.
*
* <p>This prevents a bug where the content fragment is added when the app is not running,
* causing a StateLossException.
*/
private boolean mContentFragmentChangeQueued;
private View mScrimView;
private CrossfadeImageView mAlbumArtView;
private MediaPlaybackFragment mMediaPlaybackFragment;
private MediaCarMenuCallbacks mMediaCarMenuCallbacks;
public MediaActivity(Proxy proxy, Context context, Car car) {
super(proxy, context, car);
}
@Override
protected void onStart() {
super.onStart();
Intent i = new Intent(ACTION_MEDIA_APP_STATE_CHANGE);
i.putExtra(EXTRA_MEDIA_APP_FOREGROUND, true);
getContext().sendBroadcast(i);
mIsStarted = true;
if (mContentFragmentChangeQueued) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Content fragment queued. Attaching now.");
}
attachContentFragment();
mContentFragmentChangeQueued = false;
}
}
@Override
protected void onStop() {
super.onStop();
Intent i = new Intent(ACTION_MEDIA_APP_STATE_CHANGE);
i.putExtra(EXTRA_MEDIA_APP_FOREGROUND, false);
getContext().sendBroadcast(i);
mIsStarted = false;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setLightMode();
mMediaCarMenuCallbacks = new MediaCarMenuCallbacks(this);
setCarMenuCallbacks(mMediaCarMenuCallbacks);
setContentView(R.layout.media_activity);
mScrimView = findViewById(R.id.scrim);
mAlbumArtView = (CrossfadeImageView) findViewById(R.id.album_art);
setBackgroundColor(getContext().getColor(R.color.music_default_artwork));
MediaManager.getInstance(getContext()).addListener(mListener);
}
@Override
public void onDestroy() {
super.onDestroy();
// Send the broadcast to let the current connected media app know it is disconnected now.
sendMediaConnectionStatusBroadcast(
MediaManager.getInstance(getContext()).getCurrentComponent(),
MediaConstants.MEDIA_DISCONNECTED);
mMediaCarMenuCallbacks.cleanup();
MediaManager.getInstance(getContext()).removeListener(mListener);
}
@Override
public void onResumeFragments() {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "onResumeFragments");
}
super.onResumeFragments();
handleIntent(getIntent());
sIsRunning = true;
}
@Override
public void onPause() {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "onPause");
}
super.onPause();
sIsRunning = false;
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "onNewIntent(); intent: " + (intent == null ? "<< NULL >>" : intent));
}
setIntent(intent);
if (isDrawerShowing()) {
closeDrawer();
}
}
@Override
public void onBackPressed() {
if (mMediaPlaybackFragment.isOverflowMenuVisible()) {
mMediaPlaybackFragment.closeOverflowMenu();
}
super.onBackPressed();
}
/**
* Darken scrim when new messages are displayed in {@link MediaPlaybackFragment}.
*/
public void darkenScrim(boolean dark) {
if (!sIsRunning) {
return;
}
if (dark) {
mScrimView.animate().alpha(0.9f)
.setDuration(getResources().getInteger(R.integer.media_scrim_duration_ms));
} else {
mScrimView.animate().alpha(0.6f)
.setDuration(getResources().getInteger(R.integer.media_scrim_duration_ms));
}
}
/**
* Don't show scrim when there is no content, else show it.
*/
public void setScrimVisibility(boolean visible) {
if (visible) {
TypedValue scrimAlpha = new TypedValue();
getResources().getValue(R.dimen.media_scrim_alpha, scrimAlpha, true);
mScrimView.animate().alpha(scrimAlpha.getFloat())
.setDuration(getResources().getInteger(R.integer.media_scrim_duration_ms));
} else {
mScrimView.animate().alpha(0.0f)
.setDuration(getResources().getInteger(R.integer.media_scrim_duration_ms));
}
}
/**
* Return the dimension of the background album art in the form of <width, height>.
*/
public Pair<Integer, Integer> getAlbumArtSize() {
if (mAlbumArtView != null) {
return Pair.create(mAlbumArtView.getWidth(), mAlbumArtView.getHeight());
}
return null;
}
void setBackgroundBitmap(Bitmap bitmap, boolean showAnimation) {
mAlbumArtView.setImageBitmap(bitmap, showAnimation);
}
void setBackgroundColor(int color) {
mAlbumArtView.setBackgroundColor(color);
}
private void handleIntent(Intent intent) {
Bundle extras = null;
if (intent != null) {
extras = intent.getExtras();
}
// If the intent has a media component name set, connect to it directly
if (extras != null && extras.containsKey(MediaManager.KEY_MEDIA_PACKAGE) &&
extras.containsKey(MediaManager.KEY_MEDIA_CLASS)) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Media component in intent.");
}
ComponentName component = new ComponentName(
intent.getStringExtra(MediaManager.KEY_MEDIA_PACKAGE),
intent.getStringExtra(MediaManager.KEY_MEDIA_CLASS)
);
MediaManager.getInstance(getContext()).setMediaClientComponent(component);
} else {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Launching most recent / default component.");
}
// Set it to the default GPM component.
MediaManager.getInstance(getContext()).connectToMostRecentMediaComponent(
new CarClientServiceAdapter(getContext().getPackageManager()));
}
if (isSearchIntent(intent)) {
MediaManager.getInstance(getContext()).processSearchIntent(intent);
setIntent(null);
}
}
private boolean isSearchIntent(Intent intent) {
return (intent != null && intent.getAction() != null &&
intent.getAction().equals(MediaStore.INTENT_ACTION_MEDIA_PLAY_FROM_SEARCH));
}
private void sendMediaConnectionStatusBroadcast(
ComponentName componentName, @Car.ConnectionType String connectionStatus) {
// It will be no current component if no media app is chosen before.
if (componentName == null) {
return;
}
Intent intent = new Intent(MediaConstants.ACTION_MEDIA_STATUS);
intent.setPackage(componentName.getPackageName());
intent.putExtra(MediaConstants.MEDIA_CONNECTION_STATUS, connectionStatus);
getContext().sendBroadcast(intent);
}
public void attachContentFragment() {
if (mMediaPlaybackFragment == null) {
mMediaPlaybackFragment = new MediaPlaybackFragment();
}
setContentFragment(mMediaPlaybackFragment);
}
private final MediaManager.Listener mListener = new MediaManager.Listener() {
@Override
public void onMediaAppChanged(ComponentName componentName) {
sendMediaConnectionStatusBroadcast(componentName, MediaConstants.MEDIA_CONNECTED);
// Since this callback happens asynchronously, ensure that the Activity has been
// started before changing fragments. Otherwise, the attach fragment will throw
// an IllegalStateException due to Fragment's checkStateLoss.
if (mIsStarted) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "onMediaAppChanged: attaching content fragment");
}
attachContentFragment();
} else {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "onMediaAppChanged: queuing content fragment change");
}
mContentFragmentChangeQueued = true;
}
}
@Override
public void onStatusMessageChanged(String msg) {}
};
}