2 * Copyright (C) 2014 The CyanogenMod Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
16 package com.cyanogenmod.eleven.ui.activities;
18 import android.animation.ArgbEvaluator;
19 import android.animation.ObjectAnimator;
20 import android.content.Intent;
21 import android.graphics.Bitmap;
22 import android.graphics.Color;
23 import android.net.Uri;
24 import android.os.AsyncTask;
25 import android.os.Bundle;
26 import android.os.Handler;
27 import android.provider.MediaStore;
28 import android.support.v4.app.Fragment;
29 import android.support.v4.app.FragmentManager;
30 import android.support.v4.app.FragmentTransaction;
31 import android.text.TextUtils;
32 import android.util.Log;
33 import android.view.MenuItem;
34 import android.view.Window;
36 import com.cyanogenmod.eleven.Config;
37 import com.cyanogenmod.eleven.R;
38 import com.cyanogenmod.eleven.cache.ImageFetcher;
39 import com.cyanogenmod.eleven.ui.fragments.AlbumDetailFragment;
40 import com.cyanogenmod.eleven.ui.fragments.ArtistDetailFragment;
41 import com.cyanogenmod.eleven.ui.fragments.IChildFragment;
42 import com.cyanogenmod.eleven.ui.fragments.ISetupActionBar;
43 import com.cyanogenmod.eleven.ui.fragments.PlaylistDetailFragment;
44 import com.cyanogenmod.eleven.ui.fragments.RecentFragment;
45 import com.cyanogenmod.eleven.ui.fragments.phone.MusicBrowserPhoneFragment;
46 import com.cyanogenmod.eleven.ui.fragments.profile.LastAddedFragment;
47 import com.cyanogenmod.eleven.ui.fragments.profile.TopTracksFragment;
48 import com.cyanogenmod.eleven.utils.ApolloUtils;
49 import com.cyanogenmod.eleven.utils.BitmapWithColors;
50 import com.cyanogenmod.eleven.utils.MusicUtils;
51 import com.cyanogenmod.eleven.utils.NavUtils;
53 public class HomeActivity extends SlidingPanelActivity implements
54 FragmentManager.OnBackStackChangedListener {
55 private static final String TAG = "HomeActivity";
56 private static final String ACTION_PREFIX = HomeActivity.class.getName();
57 public static final String ACTION_VIEW_ARTIST_DETAILS = ACTION_PREFIX + ".view.ArtistDetails";
58 public static final String ACTION_VIEW_ALBUM_DETAILS = ACTION_PREFIX + ".view.AlbumDetails";
59 public static final String ACTION_VIEW_PLAYLIST_DETAILS = ACTION_PREFIX + ".view.PlaylistDetails";
60 public static final String ACTION_VIEW_SMART_PLAYLIST = ACTION_PREFIX + ".view.SmartPlaylist";
61 public static final String EXTRA_BROWSE_PAGE_IDX = "BrowsePageIndex";
63 private static final String STATE_KEY_BASE_FRAGMENT = "BaseFragment";
65 private static final int NEW_PHOTO = 1;
66 public static final int EQUALIZER = 2;
69 private boolean mLoadedBaseFragment = false;
70 private boolean mHasPendingPlaybackRequest = false;
71 private Handler mHandler = new Handler();
72 private boolean mBrowsePanelActive = true;
75 * Used by the up action to determine how to handle this
77 protected boolean mTopLevelActivity = false;
80 protected void onCreate(Bundle savedInstanceState) {
81 super.onCreate(savedInstanceState);
83 // if we've been launched by an intent, parse it
84 Intent launchIntent = getIntent();
85 boolean intentHandled = false;
86 if (launchIntent != null) {
87 intentHandled = parseIntentForFragment(launchIntent);
90 // if the intent didn't cause us to load a fragment, load the music browse one
91 if (savedInstanceState == null && !mLoadedBaseFragment) {
92 final MusicBrowserPhoneFragment fragment = new MusicBrowserPhoneFragment();
93 if (launchIntent != null) {
94 fragment.setDefaultPageIdx(launchIntent.getIntExtra(EXTRA_BROWSE_PAGE_IDX,
95 MusicBrowserPhoneFragment.INVALID_PAGE_INDEX));
97 getSupportFragmentManager().beginTransaction()
98 .replace(R.id.activity_base_content, fragment)
101 mLoadedBaseFragment = true;
102 mTopLevelActivity = true;
105 getSupportFragmentManager().addOnBackStackChangedListener(this);
107 // if we are resuming from a saved instance state
108 if (savedInstanceState != null) {
109 // track which fragments are loaded and if this is the top level activity
110 mTopLevelActivity = savedInstanceState.getBoolean(STATE_KEY_BASE_FRAGMENT);
111 mLoadedBaseFragment = mTopLevelActivity;
113 // update the action bar based on the top most fragment
114 onBackStackChanged();
116 // figure which panel we are on and update the status bar
117 mBrowsePanelActive = (getCurrentPanel() == Panel.Browse);
118 updateStatusBarColor();
121 // if intent wasn't UI related, process it as a audio playback request
122 if (!intentHandled) {
123 handlePlaybackIntent(launchIntent);
128 protected void onSaveInstanceState(Bundle outState) {
129 super.onSaveInstanceState(outState);
130 outState.putBoolean(STATE_KEY_BASE_FRAGMENT, mTopLevelActivity);
133 public Fragment getTopFragment() {
134 return getSupportFragmentManager().findFragmentById(R.id.activity_base_content);
137 public void postRemoveFragment(final Fragment frag) {
138 mHandler.post(new Runnable() {
141 // removing the fragment doesn't cause the backstack event to be triggered even if
142 // it is the top fragment, so if it is the top fragment, we will just manually
143 // call pop back stack
144 if (frag == getTopFragment()) {
145 getSupportFragmentManager().popBackStack();
147 getSupportFragmentManager().beginTransaction().remove(frag).commit();
154 protected void onNewIntent(Intent intent) {
155 super.onNewIntent(intent);
157 // parse intent to ascertain whether the intent is inter UI communication
158 boolean intentHandled = parseIntentForFragment(intent);
159 // since this activity is marked 'singleTop' (launch mode), an existing activity instance
160 // could be sent media play requests
161 if ( !intentHandled) {
162 handlePlaybackIntent(intent);
167 public void onMetaChanged() {
168 super.onMetaChanged();
169 updateStatusBarColor();
173 protected void onSlide(float slideOffset) {
174 super.onSlide(slideOffset);
176 boolean isInBrowser = getCurrentPanel() == Panel.Browse && slideOffset < 0.7f;
177 if (isInBrowser != mBrowsePanelActive) {
178 mBrowsePanelActive = isInBrowser;
179 updateStatusBarColor();
184 public void onWindowFocusChanged(boolean hasFocus) {
185 super.onWindowFocusChanged(hasFocus);
187 getAudioPlayerFragment().setVisualizerVisible(hasFocus
188 && getCurrentPanel() == Panel.MusicPlayer);
191 private void updateStatusBarColor() {
192 if (mBrowsePanelActive || MusicUtils.getCurrentAlbumId() < 0) {
193 updateStatusBarColor(Color.TRANSPARENT);
195 new AsyncTask<Void, Void, BitmapWithColors>() {
197 protected BitmapWithColors doInBackground(Void... params) {
198 ImageFetcher imageFetcher = ImageFetcher.getInstance(HomeActivity.this);
199 final BitmapWithColors bitmap = imageFetcher.getArtwork(
200 MusicUtils.getAlbumName(), MusicUtils.getCurrentAlbumId(),
201 MusicUtils.getArtistName(), true);
205 protected void onPostExecute(BitmapWithColors bmc) {
206 updateVisualizerColor(bmc != null
207 ? bmc.getVibrantColor() : Color.TRANSPARENT);
208 updateStatusBarColor(bmc != null
209 ? bmc.getVibrantDarkColor() : Color.TRANSPARENT);
215 private void updateVisualizerColor(int color) {
216 if (color == Color.TRANSPARENT) {
217 color = getResources().getColor(R.color.visualizer_fill_color);
220 getAudioPlayerFragment().setVisualizerColor(color);
223 private void updateStatusBarColor(int color) {
224 if (color == Color.TRANSPARENT) {
225 color = getResources().getColor(R.color.primary_dark);
227 final Window window = getWindow();
228 ObjectAnimator animator = ObjectAnimator.ofInt(window,
229 "statusBarColor", window.getStatusBarColor(), color);
230 animator.setEvaluator(new ArgbEvaluator());
231 animator.setDuration(300);
235 private boolean parseIntentForFragment(Intent intent) {
236 boolean handled = false;
237 if (intent.getAction() != null) {
238 final String action = intent.getAction();
239 Fragment targetFragment = null;
240 FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
242 if (action.equals(ACTION_VIEW_SMART_PLAYLIST)) {
243 long playlistId = intent.getExtras().getLong(Config.SMART_PLAYLIST_TYPE);
244 switch (Config.SmartPlaylistType.getTypeById(playlistId)) {
246 targetFragment = new LastAddedFragment();
249 targetFragment = new RecentFragment();
252 targetFragment = new TopTracksFragment();
255 } else if (action.equals(ACTION_VIEW_PLAYLIST_DETAILS)) {
256 targetFragment = new PlaylistDetailFragment();
257 } else if (action.equals(ACTION_VIEW_ALBUM_DETAILS)) {
258 targetFragment = new AlbumDetailFragment();
259 } else if (action.equals(ACTION_VIEW_ARTIST_DETAILS)) {
260 targetFragment = new ArtistDetailFragment();
263 if (targetFragment != null) {
264 targetFragment.setArguments(intent.getExtras());
265 transaction.setCustomAnimations(0, 0, 0, R.anim.fade_out);
266 // If we ever come back to this because of memory concerns because
267 // none of the fragments are being removed from memory, we can fix this
268 // by using "replace" instead of "add". The caveat is that the performance of
269 // returning to previous fragments is a bit more sluggish because the fragment
270 // view needs to be recreated. If we do remove that, we can remove the back stack
271 // change listener code above
272 transaction.add(R.id.activity_base_content, targetFragment);
273 if (mLoadedBaseFragment) {
274 transaction.addToBackStack(null);
275 showPanel(Panel.Browse);
277 // else mark the fragment as loaded so we don't load the music browse fragment.
278 // this happens when they launch search which is its own activity and then
279 // browse through that back to home activity
280 mLoadedBaseFragment = true;
281 getActionBar().setDisplayHomeAsUpEnabled(true);
283 // the current top fragment is about to be hidden by what we are replacing
284 // it with -- so tell that fragment not to make its action bar menu items visible
285 Fragment oldTop = getTopFragment();
286 if (oldTop != null) {
287 oldTop.setMenuVisibility(false);
290 transaction.commit();
298 protected void onActivityResult(int requestCode, int resultCode, Intent data) {
299 super.onActivityResult(requestCode, resultCode, data);
300 if (requestCode == NEW_PHOTO && !TextUtils.isEmpty(mKey)) {
301 if (resultCode == RESULT_OK) {
302 MusicUtils.removeFromCache(this, mKey);
303 final Uri selectedImage = data.getData();
305 new Thread(new Runnable() {
308 Bitmap bitmap = ImageFetcher.decodeSampledBitmapFromUri(getContentResolver(),
311 ImageFetcher imageFetcher = ApolloUtils.getImageFetcher(HomeActivity.this);
312 imageFetcher.addBitmapToCache(mKey, bitmap);
314 MusicUtils.refresh();
322 * Starts an activity for result that returns an image from the Gallery.
324 public void selectNewPhoto(String key) {
326 // Now open the gallery
327 final Intent intent = new Intent(Intent.ACTION_GET_CONTENT, null);
328 intent.setType("image/*");
329 startActivityForResult(intent, NEW_PHOTO);
333 public boolean onOptionsItemSelected(final MenuItem item) {
334 switch (item.getItemId()) {
335 case android.R.id.home:
340 return super.onOptionsItemSelected(item);
344 * Navigates to the top Activity and places the view to the correct page
346 protected void navigateToTop() {
347 final Fragment topFragment = getTopFragment();
348 int targetFragmentIndex = MusicBrowserPhoneFragment.INVALID_PAGE_INDEX;
349 if (topFragment instanceof IChildFragment) {
350 targetFragmentIndex = ((IChildFragment)topFragment).getMusicFragmentParent().ordinal();
353 // If we are the top activity in the stack (as determined by the activity that has loaded
354 // the MusicBrowserPhoneFragment) then clear the back stack and move the browse fragment
355 // to the appropriate page as per Android up standards
356 if (mTopLevelActivity) {
358 MusicBrowserPhoneFragment musicFragment = (MusicBrowserPhoneFragment) getTopFragment();
359 musicFragment.setDefaultPageIdx(targetFragmentIndex);
360 showPanel(Panel.Browse);
362 // I've tried all other combinations with parent activities, support.NavUtils and
363 // there is no easy way to achieve what we want that I'm aware of, so clear everything
364 // and jump to the right page
365 NavUtils.goHome(this, targetFragmentIndex);
370 * Immediately clears the backstack
372 protected void clearBackStack() {
373 final FragmentManager fragmentManager = getSupportFragmentManager();
374 if (fragmentManager.getBackStackEntryCount() > 0) {
375 final int id = fragmentManager.getBackStackEntryAt(0).getId();
376 fragmentManager.popBackStackImmediate(id, FragmentManager.POP_BACK_STACK_INCLUSIVE);
381 public void handlePendingPlaybackRequests() {
382 if (mHasPendingPlaybackRequest) {
383 Intent unhandledIntent = getIntent();
384 handlePlaybackIntent(unhandledIntent);
389 * Checks whether the passed intent contains a playback request,
390 * and starts playback if that's the case
391 * @return true if the intent was consumed
393 private boolean handlePlaybackIntent(Intent intent) {
395 if (intent == null) {
397 } else if ( !MusicUtils.isPlaybackServiceConnected() ) {
398 mHasPendingPlaybackRequest = true;
402 String mimeType = intent.getType();
403 boolean handled = false;
405 if (MediaStore.Audio.Playlists.CONTENT_TYPE.equals(mimeType)) {
406 long id = parseIdFromIntent(intent, "playlistId", "playlist", -1);
408 MusicUtils.playPlaylist(this, id, false);
411 } else if (MediaStore.Audio.Albums.CONTENT_TYPE.equals(mimeType)) {
412 long id = parseIdFromIntent(intent, "albumId", "album", -1);
414 int position = intent.getIntExtra("position", 0);
415 MusicUtils.playAlbum(this, id, position, false);
418 } else if (MediaStore.Audio.Artists.CONTENT_TYPE.equals(mimeType)) {
419 long id = parseIdFromIntent(intent, "artistId", "artist", -1);
421 int position = intent.getIntExtra("position", 0);
422 MusicUtils.playArtist(this, id, position, false);
427 // reset intent as it was handled as a playback request
429 setIntent(new Intent());
436 private long parseIdFromIntent(Intent intent, String longKey,
437 String stringKey, long defaultId) {
438 long id = intent.getLongExtra(longKey, -1);
440 String idString = intent.getStringExtra(stringKey);
441 if (idString != null) {
443 id = Long.parseLong(idString);
444 } catch (NumberFormatException e) {
445 Log.e(TAG, e.getMessage());
453 public void onBackStackChanged() {
454 Fragment topFragment = getTopFragment();
455 if (topFragment != null) {
456 // the fragment that has come back to the top should now have its menu items
457 // added to the action bar -- so tell it to make it menu items visible
458 topFragment.setMenuVisibility(true);
459 ISetupActionBar setupActionBar = (ISetupActionBar) topFragment;
460 setupActionBar.setupActionBar();
462 getActionBar().setDisplayHomeAsUpEnabled(
463 !(topFragment instanceof MusicBrowserPhoneFragment));