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}