OSDN Git Service

Upgrade visualizer
[android-x86/packages-apps-Eleven.git] / src / com / cyanogenmod / eleven / ui / activities / HomeActivity.java
1 /*
2  * Copyright (C) 2014 The CyanogenMod 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.cyanogenmod.eleven.ui.activities;
17
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;
35
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;
52
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";
62
63     private static final String STATE_KEY_BASE_FRAGMENT = "BaseFragment";
64
65     private static final int NEW_PHOTO = 1;
66     public static final int EQUALIZER = 2;
67
68     private String mKey;
69     private boolean mLoadedBaseFragment = false;
70     private boolean mHasPendingPlaybackRequest = false;
71     private Handler mHandler = new Handler();
72     private boolean mBrowsePanelActive = true;
73
74     /**
75      * Used by the up action to determine how to handle this
76      */
77     protected boolean mTopLevelActivity = false;
78
79     @Override
80     protected void onCreate(Bundle savedInstanceState) {
81         super.onCreate(savedInstanceState);
82
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);
88         }
89
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));
96             }
97             getSupportFragmentManager().beginTransaction()
98                     .replace(R.id.activity_base_content, fragment)
99                     .commit();
100
101             mLoadedBaseFragment = true;
102             mTopLevelActivity = true;
103         }
104
105         getSupportFragmentManager().addOnBackStackChangedListener(this);
106
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;
112
113             // update the action bar based on the top most fragment
114             onBackStackChanged();
115
116             // figure which panel we are on and update the status bar
117             mBrowsePanelActive = (getCurrentPanel() == Panel.Browse);
118             updateStatusBarColor();
119         }
120
121         // if intent wasn't UI related, process it as a audio playback request
122         if (!intentHandled) {
123             handlePlaybackIntent(launchIntent);
124         }
125     }
126
127     @Override
128     protected void onSaveInstanceState(Bundle outState) {
129         super.onSaveInstanceState(outState);
130         outState.putBoolean(STATE_KEY_BASE_FRAGMENT, mTopLevelActivity);
131     }
132
133     public Fragment getTopFragment() {
134         return getSupportFragmentManager().findFragmentById(R.id.activity_base_content);
135     }
136
137     public void postRemoveFragment(final Fragment frag) {
138         mHandler.post(new Runnable() {
139             @Override
140             public void run() {
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();
146                 } else {
147                     getSupportFragmentManager().beginTransaction().remove(frag).commit();
148                 }
149             }
150         });
151     }
152
153     @Override
154     protected void onNewIntent(Intent intent) {
155         super.onNewIntent(intent);
156
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);
163         }
164     }
165
166     @Override
167     public void onMetaChanged() {
168         super.onMetaChanged();
169         updateStatusBarColor();
170     }
171
172     @Override
173     protected void onSlide(float slideOffset) {
174         super.onSlide(slideOffset);
175
176         boolean isInBrowser = getCurrentPanel() == Panel.Browse && slideOffset < 0.7f;
177         if (isInBrowser != mBrowsePanelActive) {
178             mBrowsePanelActive = isInBrowser;
179             updateStatusBarColor();
180         }
181     }
182
183     @Override
184     public void onWindowFocusChanged(boolean hasFocus) {
185         super.onWindowFocusChanged(hasFocus);
186
187         getAudioPlayerFragment().setVisualizerVisible(hasFocus
188                 && getCurrentPanel() == Panel.MusicPlayer);
189     }
190
191     private void updateStatusBarColor() {
192         if (mBrowsePanelActive || MusicUtils.getCurrentAlbumId() < 0) {
193             updateStatusBarColor(Color.TRANSPARENT);
194         } else {
195             new AsyncTask<Void, Void, BitmapWithColors>() {
196                 @Override
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);
202                     return bitmap;
203                 }
204                 @Override
205                 protected void onPostExecute(BitmapWithColors bmc) {
206                     updateVisualizerColor(bmc != null
207                             ? bmc.getVibrantColor() : Color.TRANSPARENT);
208                     updateStatusBarColor(bmc != null
209                             ? bmc.getVibrantDarkColor() : Color.TRANSPARENT);
210                 }
211             }.execute();
212         }
213     }
214
215     private void updateVisualizerColor(int color) {
216         if (color == Color.TRANSPARENT) {
217             color = getResources().getColor(R.color.visualizer_fill_color);
218         }
219
220         getAudioPlayerFragment().setVisualizerColor(color);
221     }
222
223     private void updateStatusBarColor(int color) {
224         if (color == Color.TRANSPARENT) {
225             color = getResources().getColor(R.color.primary_dark);
226         }
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);
232         animator.start();
233     }
234
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();
241
242             if (action.equals(ACTION_VIEW_SMART_PLAYLIST)) {
243                 long playlistId = intent.getExtras().getLong(Config.SMART_PLAYLIST_TYPE);
244                 switch (Config.SmartPlaylistType.getTypeById(playlistId)) {
245                     case LastAdded:
246                         targetFragment = new LastAddedFragment();
247                         break;
248                     case RecentlyPlayed:
249                         targetFragment = new RecentFragment();
250                         break;
251                     case TopTracks:
252                         targetFragment = new TopTracksFragment();
253                         break;
254                 }
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();
261             }
262
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);
276                 } else {
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);
282                 }
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);
288                 }
289
290                 transaction.commit();
291                 handled = true;
292             }
293         }
294         return handled;
295     }
296
297     @Override
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();
304
305                 new Thread(new Runnable() {
306                     @Override
307                     public void run() {
308                         Bitmap bitmap = ImageFetcher.decodeSampledBitmapFromUri(getContentResolver(),
309                                 selectedImage);
310
311                         ImageFetcher imageFetcher = ApolloUtils.getImageFetcher(HomeActivity.this);
312                         imageFetcher.addBitmapToCache(mKey, bitmap);
313
314                         MusicUtils.refresh();
315                     }
316                 }).start();
317             }
318         }
319     }
320
321     /**
322      * Starts an activity for result that returns an image from the Gallery.
323      */
324     public void selectNewPhoto(String key) {
325         mKey = 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);
330     }
331
332     @Override
333     public boolean onOptionsItemSelected(final MenuItem item) {
334         switch (item.getItemId()) {
335             case android.R.id.home:
336                 navigateToTop();
337                 return true;
338         }
339
340         return super.onOptionsItemSelected(item);
341     }
342
343     /**
344      * Navigates to the top Activity and places the view to the correct page
345      */
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();
351         }
352
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) {
357             clearBackStack();
358             MusicBrowserPhoneFragment musicFragment = (MusicBrowserPhoneFragment) getTopFragment();
359             musicFragment.setDefaultPageIdx(targetFragmentIndex);
360             showPanel(Panel.Browse);
361         } else {
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);
366         }
367     }
368
369     /**
370      * Immediately clears the backstack
371      */
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);
377         }
378     }
379
380     @Override
381     public void handlePendingPlaybackRequests() {
382         if (mHasPendingPlaybackRequest) {
383             Intent unhandledIntent = getIntent();
384             handlePlaybackIntent(unhandledIntent);
385         }
386     }
387
388     /**
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
392      */
393     private boolean handlePlaybackIntent(Intent intent) {
394
395         if (intent == null) {
396             return false;
397         } else if ( !MusicUtils.isPlaybackServiceConnected() ) {
398             mHasPendingPlaybackRequest = true;
399             return false;
400         }
401
402         String mimeType = intent.getType();
403         boolean handled = false;
404
405         if (MediaStore.Audio.Playlists.CONTENT_TYPE.equals(mimeType)) {
406             long id = parseIdFromIntent(intent, "playlistId", "playlist", -1);
407             if (id >= 0) {
408                 MusicUtils.playPlaylist(this, id, false);
409                 handled = true;
410             }
411         } else if (MediaStore.Audio.Albums.CONTENT_TYPE.equals(mimeType)) {
412             long id = parseIdFromIntent(intent, "albumId", "album", -1);
413             if (id >= 0) {
414                 int position = intent.getIntExtra("position", 0);
415                 MusicUtils.playAlbum(this, id, position, false);
416                 handled = true;
417             }
418         } else if (MediaStore.Audio.Artists.CONTENT_TYPE.equals(mimeType)) {
419             long id = parseIdFromIntent(intent, "artistId", "artist", -1);
420             if (id >= 0) {
421                 int position = intent.getIntExtra("position", 0);
422                 MusicUtils.playArtist(this, id, position, false);
423                 handled = true;
424             }
425         }
426
427         // reset intent as it was handled as a playback request
428         if (handled) {
429             setIntent(new Intent());
430         }
431
432         return handled;
433
434     }
435
436     private long parseIdFromIntent(Intent intent, String longKey,
437                                    String stringKey, long defaultId) {
438         long id = intent.getLongExtra(longKey, -1);
439         if (id < 0) {
440             String idString = intent.getStringExtra(stringKey);
441             if (idString != null) {
442                 try {
443                     id = Long.parseLong(idString);
444                 } catch (NumberFormatException e) {
445                     Log.e(TAG, e.getMessage());
446                 }
447             }
448         }
449         return id;
450     }
451
452     @Override
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();
461
462             getActionBar().setDisplayHomeAsUpEnabled(
463                     !(topFragment instanceof MusicBrowserPhoneFragment));
464         }
465     }
466 }