MediaActivity.java revision 64b173f33aa2d98ea00b9113b52e6f8cb35589ad
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.Context;
20import android.content.Intent;
21import android.graphics.Bitmap;
22import android.os.Bundle;
23import android.provider.MediaStore;
24import android.support.car.Car;
25import android.support.car.app.menu.CarDrawerActivity;
26import android.util.Log;
27import android.util.Pair;
28import android.util.TypedValue;
29import android.view.View;
30
31/**
32 * This activity controls the UI of media. It also updates the connection status for the media app
33 * by broadcast. Drawer menu is controlled by {@link MediaCarMenuCallbacks}.
34 */
35public class MediaActivity extends CarDrawerActivity {
36    private static final String ACTION_MEDIA_APP_STATE_CHANGE
37            = "android.intent.action.MEDIA_APP_STATE_CHANGE";
38    private static final String EXTRA_MEDIA_APP_FOREGROUND
39            = "android.intent.action.MEDIA_APP_STATE";
40
41    private static final String TAG = "MediaActivity";
42
43    /**
44     * Whether or not {@link #onResume()} has been called.
45     */
46    private static boolean sIsRunning = false;
47
48    /**
49     * Whether or not {@link #onStart()} has been called.
50     */
51    private boolean mIsStarted;
52
53    /**
54     * {@code true} if there was a request to change the content fragment of this Activity when
55     * it is not started. Then, when onStart() is called, the content fragment will be added.
56     *
57     * <p>This prevents a bug where the content fragment is added when the app is not running,
58     * causing a StateLossException.
59     */
60    private boolean mContentFragmentChangeQueued;
61
62    private View mScrimView;
63    private CrossfadeImageView mAlbumArtView;
64    private MediaPlaybackFragment mMediaPlaybackFragment;
65    private MediaCarMenuCallbacks mMediaCarMenuCallbacks;
66
67    public MediaActivity(Proxy proxy, Context context, Car car) {
68        super(proxy, context, car);
69    }
70
71    @Override
72    protected void onStart() {
73        super.onStart();
74        Intent i = new Intent(ACTION_MEDIA_APP_STATE_CHANGE);
75        i.putExtra(EXTRA_MEDIA_APP_FOREGROUND, true);
76        getContext().sendBroadcast(i);
77
78        mIsStarted = true;
79
80        if (mContentFragmentChangeQueued) {
81            if (Log.isLoggable(TAG, Log.DEBUG)) {
82                Log.d(TAG, "Content fragment queued. Attaching now.");
83            }
84            attachContentFragment();
85            mContentFragmentChangeQueued = false;
86        }
87    }
88
89    @Override
90    protected void onStop() {
91        super.onStop();
92        Intent i = new Intent(ACTION_MEDIA_APP_STATE_CHANGE);
93        i.putExtra(EXTRA_MEDIA_APP_FOREGROUND, false);
94        getContext().sendBroadcast(i);
95
96        mIsStarted = false;
97    }
98
99    @Override
100    protected void onCreate(Bundle savedInstanceState) {
101        super.onCreate(savedInstanceState);
102        setLightMode();
103        mMediaCarMenuCallbacks = new MediaCarMenuCallbacks(this);
104        setCarMenuCallbacks(mMediaCarMenuCallbacks);
105        setContentView(R.layout.media_activity);
106        mScrimView = findViewById(R.id.scrim);
107        mAlbumArtView = (CrossfadeImageView) findViewById(R.id.album_art);
108        setBackgroundColor(getContext().getColor(R.color.music_default_artwork));
109        MediaManager.getInstance(getContext()).addListener(mListener);
110    }
111
112    @Override
113    public void onDestroy() {
114        super.onDestroy();
115        // Send the broadcast to let the current connected media app know it is disconnected now.
116        sendMediaConnectionStatusBroadcast(
117                MediaManager.getInstance(getContext()).getCurrentComponent(),
118                MediaConstants.MEDIA_DISCONNECTED);
119        mMediaCarMenuCallbacks.cleanup();
120        MediaManager.getInstance(getContext()).removeListener(mListener);
121    }
122
123    @Override
124    public void onResumeFragments() {
125        if (Log.isLoggable(TAG, Log.DEBUG)) {
126            Log.d(TAG, "onResumeFragments");
127        }
128
129        super.onResumeFragments();
130        handleIntent(getIntent());
131        sIsRunning = true;
132    }
133
134    @Override
135    public void onPause() {
136        if (Log.isLoggable(TAG, Log.DEBUG)) {
137            Log.d(TAG, "onPause");
138        }
139
140        super.onPause();
141        sIsRunning = false;
142    }
143
144    @Override
145    protected void onNewIntent(Intent intent) {
146        super.onNewIntent(intent);
147        if (Log.isLoggable(TAG, Log.VERBOSE)) {
148            Log.v(TAG, "onNewIntent(); intent: " + (intent == null ? "<< NULL >>" : intent));
149        }
150
151        setIntent(intent);
152        if (isDrawerShowing()) {
153            closeDrawer();
154        }
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(getContext()).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(getContext()).connectToMostRecentMediaComponent(
241                    new CarClientServiceAdapter(getContext().getPackageManager()));
242        }
243
244        if (isSearchIntent(intent)) {
245            MediaManager.getInstance(getContext()).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, @Car.ConnectionType 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        getContext().sendBroadcast(intent);
266    }
267
268    public 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