Home | History | Annotate | Download | only in media
      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.media;
     17 
     18 import android.content.ComponentName;
     19 import android.content.Intent;
     20 import android.os.Bundle;
     21 import android.provider.MediaStore;
     22 import android.util.Log;
     23 import com.android.car.app.CarDrawerActivity;
     24 import com.android.car.app.CarDrawerAdapter;
     25 import com.android.car.media.drawer.MediaDrawerController;
     26 
     27 /**
     28  * This activity controls the UI of media. It also updates the connection status for the media app
     29  * by broadcast. Drawer menu is controlled by {@link MediaDrawerController}.
     30  */
     31 public class MediaActivity extends CarDrawerActivity
     32         implements MediaPlaybackFragment.PlayQueueRevealer {
     33     private static final String ACTION_MEDIA_APP_STATE_CHANGE
     34             = "android.intent.action.MEDIA_APP_STATE_CHANGE";
     35     private static final String EXTRA_MEDIA_APP_FOREGROUND
     36             = "android.intent.action.MEDIA_APP_STATE";
     37 
     38     private static final String TAG = "MediaActivity";
     39 
     40     /**
     41      * Whether or not {@link #onStart()} has been called.
     42      */
     43     private boolean mIsStarted;
     44 
     45     /**
     46      * {@code true} if there was a request to change the content fragment of this Activity when
     47      * it is not started. Then, when onStart() is called, the content fragment will be added.
     48      *
     49      * <p>This prevents a bug where the content fragment is added when the app is not running,
     50      * causing a StateLossException.
     51      */
     52     private boolean mContentFragmentChangeQueued;
     53 
     54     private MediaDrawerController mDrawerController;
     55     private MediaPlaybackFragment mMediaPlaybackFragment;
     56 
     57     @Override
     58     protected void onStart() {
     59         super.onStart();
     60         Intent i = new Intent(ACTION_MEDIA_APP_STATE_CHANGE);
     61         i.putExtra(EXTRA_MEDIA_APP_FOREGROUND, true);
     62         sendBroadcast(i);
     63 
     64         mIsStarted = true;
     65 
     66         if (mContentFragmentChangeQueued) {
     67             if (Log.isLoggable(TAG, Log.DEBUG)) {
     68                 Log.d(TAG, "Content fragment queued. Attaching now.");
     69             }
     70             showMediaPlaybackFragment();
     71             mContentFragmentChangeQueued = false;
     72         }
     73     }
     74 
     75     @Override
     76     protected void onStop() {
     77         super.onStop();
     78         Intent i = new Intent(ACTION_MEDIA_APP_STATE_CHANGE);
     79         i.putExtra(EXTRA_MEDIA_APP_FOREGROUND, false);
     80         sendBroadcast(i);
     81 
     82         mIsStarted = false;
     83     }
     84 
     85     @Override
     86     protected void onCreate(Bundle savedInstanceState) {
     87         // The drawer must be initialized before the super call because CarDrawerActivity.onCreate
     88         // looks up the rootAdapter from its subclasses. The MediaDrawerController provides the
     89         // root adapter.
     90         mDrawerController = new MediaDrawerController(this);
     91 
     92         super.onCreate(savedInstanceState);
     93 
     94         setMainContent(R.layout.media_activity);
     95         MediaManager.getInstance(this).addListener(mListener);
     96     }
     97 
     98     @Override
     99     public void onDestroy() {
    100         super.onDestroy();
    101         // Send the broadcast to let the current connected media app know it is disconnected now.
    102         sendMediaConnectionStatusBroadcast(
    103                 MediaManager.getInstance(this).getCurrentComponent(),
    104                 MediaConstants.MEDIA_DISCONNECTED);
    105         mDrawerController.cleanup();
    106         MediaManager.getInstance(this).removeListener(mListener);
    107         mMediaPlaybackFragment = null;
    108     }
    109 
    110     @Override
    111     protected CarDrawerAdapter getRootAdapter() {
    112         return mDrawerController.getRootAdapter();
    113     }
    114 
    115     @Override
    116     public void onResumeFragments() {
    117         if (Log.isLoggable(TAG, Log.DEBUG)) {
    118             Log.d(TAG, "onResumeFragments");
    119         }
    120 
    121         super.onResumeFragments();
    122         handleIntent(getIntent());
    123     }
    124 
    125     @Override
    126     protected void onNewIntent(Intent intent) {
    127         super.onNewIntent(intent);
    128         if (Log.isLoggable(TAG, Log.VERBOSE)) {
    129             Log.v(TAG, "onNewIntent(); intent: " + (intent == null ? "<< NULL >>" : intent));
    130         }
    131 
    132         setIntent(intent);
    133         closeDrawer();
    134     }
    135 
    136     @Override
    137     public void onBackPressed() {
    138         if (mMediaPlaybackFragment != null) {
    139             mMediaPlaybackFragment.closeOverflowMenu();
    140         }
    141         super.onBackPressed();
    142     }
    143 
    144     private void handleIntent(Intent intent) {
    145         Bundle extras = null;
    146         if (intent != null) {
    147             extras = intent.getExtras();
    148         }
    149 
    150         // If the intent has a media component name set, connect to it directly
    151         if (extras != null && extras.containsKey(MediaManager.KEY_MEDIA_PACKAGE) &&
    152                 extras.containsKey(MediaManager.KEY_MEDIA_CLASS)) {
    153             if (Log.isLoggable(TAG, Log.DEBUG)) {
    154                 Log.d(TAG, "Media component in intent.");
    155             }
    156 
    157             ComponentName component = new ComponentName(
    158                     intent.getStringExtra(MediaManager.KEY_MEDIA_PACKAGE),
    159                     intent.getStringExtra(MediaManager.KEY_MEDIA_CLASS)
    160             );
    161             MediaManager.getInstance(this).setMediaClientComponent(component);
    162         } else {
    163             if (Log.isLoggable(TAG, Log.DEBUG)) {
    164                 Log.d(TAG, "Launching most recent / default component.");
    165             }
    166 
    167             // Set it to the default GPM component.
    168             MediaManager.getInstance(this).connectToMostRecentMediaComponent(
    169                     new CarClientServiceAdapter(getPackageManager()));
    170         }
    171 
    172         if (isSearchIntent(intent)) {
    173             MediaManager.getInstance(this).processSearchIntent(intent);
    174             setIntent(null);
    175         }
    176     }
    177 
    178     /**
    179      * Returns {@code true} if the given intent is one that contains a search query for the
    180      * attached media application.
    181      */
    182     private boolean isSearchIntent(Intent intent) {
    183         return (intent != null && intent.getAction() != null &&
    184                 intent.getAction().equals(MediaStore.INTENT_ACTION_MEDIA_PLAY_FROM_SEARCH));
    185     }
    186 
    187     private void sendMediaConnectionStatusBroadcast(ComponentName componentName,
    188             String connectionStatus) {
    189         // There will be no current component if no media app has been chosen before.
    190         if (componentName == null) {
    191             return;
    192         }
    193 
    194         Intent intent = new Intent(MediaConstants.ACTION_MEDIA_STATUS);
    195         intent.setPackage(componentName.getPackageName());
    196         intent.putExtra(MediaConstants.MEDIA_CONNECTION_STATUS, connectionStatus);
    197         sendBroadcast(intent);
    198     }
    199 
    200     private void showMediaPlaybackFragment() {
    201         // If the fragment has already been created, then it has been attached already.
    202         if (mMediaPlaybackFragment != null) {
    203             return;
    204         }
    205 
    206         mMediaPlaybackFragment = new MediaPlaybackFragment();
    207         mMediaPlaybackFragment.setPlayQueueRevealer(this);
    208 
    209        getSupportFragmentManager().beginTransaction()
    210                 .replace(R.id.fragment_container, mMediaPlaybackFragment)
    211                 .commit();
    212     }
    213 
    214     @Override
    215     public void showPlayQueue() {
    216         mDrawerController.showPlayQueue();
    217     }
    218 
    219     private final MediaManager.Listener mListener = new MediaManager.Listener() {
    220         @Override
    221         public void onMediaAppChanged(ComponentName componentName) {
    222             sendMediaConnectionStatusBroadcast(componentName, MediaConstants.MEDIA_CONNECTED);
    223 
    224             // Since this callback happens asynchronously, ensure that the Activity has been
    225             // started before changing fragments. Otherwise, the attach fragment will throw
    226             // an IllegalStateException due to Fragment's checkStateLoss.
    227             if (mIsStarted) {
    228                 if (Log.isLoggable(TAG, Log.DEBUG)) {
    229                     Log.d(TAG, "onMediaAppChanged: attaching content fragment");
    230                 }
    231                 showMediaPlaybackFragment();
    232             } else {
    233                 if (Log.isLoggable(TAG, Log.DEBUG)) {
    234                     Log.d(TAG, "onMediaAppChanged: queuing content fragment change");
    235                 }
    236                 mContentFragmentChangeQueued = true;
    237             }
    238         }
    239 
    240         @Override
    241         public void onStatusMessageChanged(String msg) {}
    242     };
    243 }
    244