blob: 95532396a6ab3b46435b8e933d86dadfce607d24 [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.Intent;
import android.os.Bundle;
import android.provider.MediaStore;
import android.util.Log;
import com.android.car.app.CarDrawerActivity;
import com.android.car.app.CarDrawerAdapter;
import com.android.car.media.drawer.MediaDrawerController;
/**
* 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 MediaDrawerController}.
*/
public class MediaActivity extends CarDrawerActivity
implements MediaPlaybackFragment.PlayQueueRevealer {
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 #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 MediaDrawerController mDrawerController;
private MediaPlaybackFragment mMediaPlaybackFragment;
@Override
protected void onStart() {
super.onStart();
Intent i = new Intent(ACTION_MEDIA_APP_STATE_CHANGE);
i.putExtra(EXTRA_MEDIA_APP_FOREGROUND, true);
sendBroadcast(i);
mIsStarted = true;
if (mContentFragmentChangeQueued) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Content fragment queued. Attaching now.");
}
showMediaPlaybackFragment();
mContentFragmentChangeQueued = false;
}
}
@Override
protected void onStop() {
super.onStop();
Intent i = new Intent(ACTION_MEDIA_APP_STATE_CHANGE);
i.putExtra(EXTRA_MEDIA_APP_FOREGROUND, false);
sendBroadcast(i);
mIsStarted = false;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
// The drawer must be initialized before the super call because CarDrawerActivity.onCreate
// looks up the rootAdapter from its subclasses. The MediaDrawerController provides the
// root adapter.
mDrawerController = new MediaDrawerController(this);
super.onCreate(savedInstanceState);
setMainContent(R.layout.media_activity);
MediaManager.getInstance(this).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(this).getCurrentComponent(),
MediaConstants.MEDIA_DISCONNECTED);
mDrawerController.cleanup();
MediaManager.getInstance(this).removeListener(mListener);
mMediaPlaybackFragment = null;
}
@Override
protected CarDrawerAdapter getRootAdapter() {
return mDrawerController.getRootAdapter();
}
@Override
public void onResumeFragments() {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "onResumeFragments");
}
super.onResumeFragments();
handleIntent(getIntent());
}
@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);
closeDrawer();
}
@Override
public void onBackPressed() {
if (mMediaPlaybackFragment != null) {
mMediaPlaybackFragment.closeOverflowMenu();
}
super.onBackPressed();
}
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(this).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(this).connectToMostRecentMediaComponent(
new CarClientServiceAdapter(getPackageManager()));
}
if (isSearchIntent(intent)) {
MediaManager.getInstance(this).processSearchIntent(intent);
setIntent(null);
}
}
/**
* Returns {@code true} if the given intent is one that contains a search query for the
* attached media application.
*/
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,
String connectionStatus) {
// There will be no current component if no media app has been 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);
sendBroadcast(intent);
}
private void showMediaPlaybackFragment() {
// If the fragment has already been created, then it has been attached already.
if (mMediaPlaybackFragment != null) {
return;
}
mMediaPlaybackFragment = new MediaPlaybackFragment();
mMediaPlaybackFragment.setPlayQueueRevealer(this);
getSupportFragmentManager().beginTransaction()
.replace(R.id.fragment_container, mMediaPlaybackFragment)
.commit();
}
@Override
public void showPlayQueue() {
mDrawerController.showPlayQueue();
}
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");
}
showMediaPlaybackFragment();
} else {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "onMediaAppChanged: queuing content fragment change");
}
mContentFragmentChangeQueued = true;
}
}
@Override
public void onStatusMessageChanged(String msg) {}
};
}