MediaActivity.java revision 349880a1749d601dea9f20106b3dd09ee47f8eb9
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.os.Bundle;
21import android.provider.MediaStore;
22import android.util.Log;
23import com.android.car.app.CarDrawerActivity;
24import com.android.car.app.CarDrawerAdapter;
25import 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 */
31public 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.showQueueInDrawer();
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