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.Manifest;
19 import android.animation.ArgbEvaluator;
20 import android.animation.ObjectAnimator;
21 import android.content.Intent;
22 import android.content.pm.PackageManager;
23 import android.graphics.Bitmap;
24 import android.graphics.Color;
25 import android.net.Uri;
26 import android.os.AsyncTask;
27 import android.os.Build;
28 import android.os.Bundle;
29 import android.os.Handler;
30 import android.provider.MediaStore;
31 import android.support.v4.app.Fragment;
32 import android.support.v4.app.FragmentManager;
33 import android.support.v4.app.FragmentTransaction;
34 import android.text.TextUtils;
35 import android.util.Log;
36 import android.view.MenuItem;
37 import android.view.Window;
39 import com.cyanogenmod.eleven.Config;
40 import com.cyanogenmod.eleven.R;
41 import com.cyanogenmod.eleven.cache.ImageFetcher;
42 import com.cyanogenmod.eleven.ui.fragments.AlbumDetailFragment;
43 import com.cyanogenmod.eleven.ui.fragments.ArtistDetailFragment;
44 import com.cyanogenmod.eleven.ui.fragments.AudioPlayerFragment;
45 import com.cyanogenmod.eleven.ui.fragments.IChildFragment;
46 import com.cyanogenmod.eleven.ui.fragments.ISetupActionBar;
47 import com.cyanogenmod.eleven.ui.fragments.PlaylistDetailFragment;
48 import com.cyanogenmod.eleven.ui.fragments.RecentFragment;
49 import com.cyanogenmod.eleven.ui.fragments.phone.MusicBrowserPhoneFragment;
50 import com.cyanogenmod.eleven.ui.fragments.profile.LastAddedFragment;
51 import com.cyanogenmod.eleven.ui.fragments.profile.TopTracksFragment;
52 import com.cyanogenmod.eleven.utils.ApolloUtils;
53 import com.cyanogenmod.eleven.utils.BitmapWithColors;
54 import com.cyanogenmod.eleven.utils.MusicUtils;
55 import com.cyanogenmod.eleven.utils.NavUtils;
57 import java.util.ArrayList;
59 public class HomeActivity extends SlidingPanelActivity implements
60 FragmentManager.OnBackStackChangedListener {
61 private static final String TAG = "HomeActivity";
62 private static final String ACTION_PREFIX = HomeActivity.class.getName();
63 public static final String ACTION_VIEW_ARTIST_DETAILS = ACTION_PREFIX + ".view.ArtistDetails";
64 public static final String ACTION_VIEW_ALBUM_DETAILS = ACTION_PREFIX + ".view.AlbumDetails";
65 public static final String ACTION_VIEW_PLAYLIST_DETAILS = ACTION_PREFIX + ".view.PlaylistDetails";
66 public static final String ACTION_VIEW_SMART_PLAYLIST = ACTION_PREFIX + ".view.SmartPlaylist";
67 public static final String EXTRA_BROWSE_PAGE_IDX = "BrowsePageIndex";
69 private static final String STATE_KEY_BASE_FRAGMENT = "BaseFragment";
71 private static final int NEW_PHOTO = 1;
72 public static final int EQUALIZER = 2;
74 private static final int PERMISSION_REQUEST_STORAGE = 1;
75 private Bundle mSavedInstanceState;
78 private boolean mLoadedBaseFragment = false;
79 private boolean mHasPendingPlaybackRequest = false;
80 private Handler mHandler = new Handler();
81 private boolean mBrowsePanelActive = true;
84 * Used by the up action to determine how to handle this
86 protected boolean mTopLevelActivity = false;
89 protected void onCreate(Bundle savedInstanceState) {
90 super.onCreate(savedInstanceState);
92 mSavedInstanceState = savedInstanceState;
94 if (!needRequestStoragePermission()) {
100 // if we've been launched by an intent, parse it
101 Intent launchIntent = getIntent();
102 boolean intentHandled = false;
103 if (launchIntent != null) {
104 intentHandled = parseIntentForFragment(launchIntent);
107 // if the intent didn't cause us to load a fragment, load the music browse one
108 if (mSavedInstanceState == null && !mLoadedBaseFragment) {
109 final MusicBrowserPhoneFragment fragment = new MusicBrowserPhoneFragment();
110 if (launchIntent != null) {
111 fragment.setDefaultPageIdx(launchIntent.getIntExtra(EXTRA_BROWSE_PAGE_IDX,
112 MusicBrowserPhoneFragment.INVALID_PAGE_INDEX));
114 getSupportFragmentManager().beginTransaction()
115 .replace(R.id.activity_base_content, fragment)
118 mLoadedBaseFragment = true;
119 mTopLevelActivity = true;
122 getSupportFragmentManager().addOnBackStackChangedListener(this);
125 // if we are resuming from a saved instance state
126 if (mSavedInstanceState != null) {
127 // track which fragments are loaded and if this is the top level activity
128 mTopLevelActivity = mSavedInstanceState.getBoolean(STATE_KEY_BASE_FRAGMENT);
129 mLoadedBaseFragment = mTopLevelActivity;
131 // update the action bar based on the top most fragment
132 onBackStackChanged();
134 // figure which panel we are on and update the status bar
135 mBrowsePanelActive = (getCurrentPanel() == Panel.Browse);
136 updateStatusBarColor();
139 // if intent wasn't UI related, process it as a audio playback request
140 if (!intentHandled) {
141 handlePlaybackIntent(launchIntent);
144 mSavedInstanceState = null;
148 protected void onSaveInstanceState(Bundle outState) {
149 super.onSaveInstanceState(outState);
150 outState.putBoolean(STATE_KEY_BASE_FRAGMENT, mTopLevelActivity);
153 public Fragment getTopFragment() {
154 return getSupportFragmentManager().findFragmentById(R.id.activity_base_content);
157 public void postRemoveFragment(final Fragment frag) {
158 mHandler.post(new Runnable() {
161 // removing the fragment doesn't cause the backstack event to be triggered even if
162 // it is the top fragment, so if it is the top fragment, we will just manually
163 // call pop back stack
164 if (frag == getTopFragment()) {
165 getSupportFragmentManager().popBackStack();
167 getSupportFragmentManager().beginTransaction().remove(frag).commit();
174 protected void onNewIntent(Intent intent) {
175 super.onNewIntent(intent);
177 // parse intent to ascertain whether the intent is inter UI communication
178 boolean intentHandled = parseIntentForFragment(intent);
179 // since this activity is marked 'singleTop' (launch mode), an existing activity instance
180 // could be sent media play requests
181 if ( !intentHandled) {
182 handlePlaybackIntent(intent);
187 public void onMetaChanged() {
188 super.onMetaChanged();
189 updateStatusBarColor();
193 protected void onSlide(float slideOffset) {
194 boolean isInBrowser = getCurrentPanel() == Panel.Browse && slideOffset < 0.7f;
195 if (isInBrowser != mBrowsePanelActive) {
196 mBrowsePanelActive = isInBrowser;
197 updateStatusBarColor();
202 public void onWindowFocusChanged(boolean hasFocus) {
203 super.onWindowFocusChanged(hasFocus);
205 if (getCurrentPanel() == Panel.MusicPlayer) {
206 getAudioPlayerFragment().setVisualizerVisible(hasFocus);
210 private void updateStatusBarColor() {
211 if (mBrowsePanelActive || MusicUtils.getCurrentAlbumId() < 0) {
212 updateStatusBarColor(Color.TRANSPARENT);
214 new AsyncTask<Void, Void, BitmapWithColors>() {
216 protected BitmapWithColors doInBackground(Void... params) {
217 ImageFetcher imageFetcher = ImageFetcher.getInstance(HomeActivity.this);
218 return imageFetcher.getArtwork(
219 MusicUtils.getAlbumName(), MusicUtils.getCurrentAlbumId(),
220 MusicUtils.getArtistName(), true);
223 protected void onPostExecute(BitmapWithColors bmc) {
224 updateVisualizerColor(bmc != null
225 ? bmc.getContrastingColor() : Color.TRANSPARENT);
226 updateStatusBarColor(bmc != null
227 ? bmc.getVibrantDarkColor() : Color.TRANSPARENT);
233 private void updateVisualizerColor(int color) {
234 if (color == Color.TRANSPARENT) {
235 color = getResources().getColor(R.color.visualizer_fill_color);
238 // check for null since updatestatusBarColor is a async task
239 AudioPlayerFragment fragment = getAudioPlayerFragment();
240 if (fragment != null) {
241 fragment.setVisualizerColor(color);
245 private void updateStatusBarColor(int color) {
246 if (color == Color.TRANSPARENT) {
247 color = getResources().getColor(R.color.primary_dark);
249 final Window window = getWindow();
250 ObjectAnimator animator = ObjectAnimator.ofInt(window,
251 "statusBarColor", window.getStatusBarColor(), color);
252 animator.setEvaluator(new ArgbEvaluator());
253 animator.setDuration(300);
257 private boolean parseIntentForFragment(Intent intent) {
258 boolean handled = false;
259 if (intent.getAction() != null) {
260 final String action = intent.getAction();
261 Fragment targetFragment = null;
262 FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
264 if (action.equals(ACTION_VIEW_SMART_PLAYLIST)) {
265 long playlistId = intent.getExtras().getLong(Config.SMART_PLAYLIST_TYPE);
266 switch (Config.SmartPlaylistType.getTypeById(playlistId)) {
268 targetFragment = new LastAddedFragment();
271 targetFragment = new RecentFragment();
274 targetFragment = new TopTracksFragment();
277 } else if (action.equals(ACTION_VIEW_PLAYLIST_DETAILS)) {
278 targetFragment = new PlaylistDetailFragment();
279 } else if (action.equals(ACTION_VIEW_ALBUM_DETAILS)) {
280 targetFragment = new AlbumDetailFragment();
281 } else if (action.equals(ACTION_VIEW_ARTIST_DETAILS)) {
282 targetFragment = new ArtistDetailFragment();
285 if (targetFragment != null) {
286 targetFragment.setArguments(intent.getExtras());
287 transaction.setCustomAnimations(0, 0, 0, R.anim.fade_out);
288 // If we ever come back to this because of memory concerns because
289 // none of the fragments are being removed from memory, we can fix this
290 // by using "replace" instead of "add". The caveat is that the performance of
291 // returning to previous fragments is a bit more sluggish because the fragment
292 // view needs to be recreated. If we do remove that, we can remove the back stack
293 // change listener code above
294 transaction.add(R.id.activity_base_content, targetFragment);
295 if (mLoadedBaseFragment) {
296 transaction.addToBackStack(null);
297 showPanel(Panel.Browse);
299 // else mark the fragment as loaded so we don't load the music browse fragment.
300 // this happens when they launch search which is its own activity and then
301 // browse through that back to home activity
302 mLoadedBaseFragment = true;
303 getActionBar().setDisplayHomeAsUpEnabled(true);
305 // the current top fragment is about to be hidden by what we are replacing
306 // it with -- so tell that fragment not to make its action bar menu items visible
307 Fragment oldTop = getTopFragment();
308 if (oldTop != null) {
309 oldTop.setMenuVisibility(false);
312 transaction.commit();
320 protected void onActivityResult(int requestCode, int resultCode, Intent data) {
321 super.onActivityResult(requestCode, resultCode, data);
322 if (requestCode == NEW_PHOTO && !TextUtils.isEmpty(mKey)) {
323 if (resultCode == RESULT_OK) {
324 MusicUtils.removeFromCache(this, mKey);
325 final Uri selectedImage = data.getData();
327 new Thread(new Runnable() {
330 Bitmap bitmap = ImageFetcher.decodeSampledBitmapFromUri(getContentResolver(),
333 ImageFetcher imageFetcher = ApolloUtils.getImageFetcher(HomeActivity.this);
334 imageFetcher.addBitmapToCache(mKey, bitmap);
336 MusicUtils.refresh();
344 * Starts an activity for result that returns an image from the Gallery.
346 public void selectNewPhoto(String key) {
348 // Now open the gallery
349 final Intent intent = new Intent(Intent.ACTION_GET_CONTENT, null);
350 intent.setType("image/*");
351 startActivityForResult(intent, NEW_PHOTO);
355 public boolean onOptionsItemSelected(final MenuItem item) {
356 switch (item.getItemId()) {
357 case android.R.id.home:
362 return super.onOptionsItemSelected(item);
366 * Navigates to the top Activity and places the view to the correct page
368 protected void navigateToTop() {
369 final Fragment topFragment = getTopFragment();
370 int targetFragmentIndex = MusicBrowserPhoneFragment.INVALID_PAGE_INDEX;
371 if (topFragment instanceof IChildFragment) {
372 targetFragmentIndex = ((IChildFragment)topFragment).getMusicFragmentParent().ordinal();
375 // If we are the top activity in the stack (as determined by the activity that has loaded
376 // the MusicBrowserPhoneFragment) then clear the back stack and move the browse fragment
377 // to the appropriate page as per Android up standards
378 if (mTopLevelActivity) {
380 MusicBrowserPhoneFragment musicFragment = (MusicBrowserPhoneFragment) getTopFragment();
381 musicFragment.setDefaultPageIdx(targetFragmentIndex);
382 showPanel(Panel.Browse);
384 // I've tried all other combinations with parent activities, support.NavUtils and
385 // there is no easy way to achieve what we want that I'm aware of, so clear everything
386 // and jump to the right page
387 NavUtils.goHome(this, targetFragmentIndex);
392 * Immediately clears the backstack
394 protected void clearBackStack() {
395 final FragmentManager fragmentManager = getSupportFragmentManager();
396 if (fragmentManager.getBackStackEntryCount() > 0) {
397 final int id = fragmentManager.getBackStackEntryAt(0).getId();
398 fragmentManager.popBackStackImmediate(id, FragmentManager.POP_BACK_STACK_INCLUSIVE);
403 public void handlePendingPlaybackRequests() {
404 if (mHasPendingPlaybackRequest) {
405 Intent unhandledIntent = getIntent();
406 handlePlaybackIntent(unhandledIntent);
411 * Checks whether the passed intent contains a playback request,
412 * and starts playback if that's the case
413 * @return true if the intent was consumed
415 private boolean handlePlaybackIntent(Intent intent) {
417 if (intent == null) {
419 } else if ( !MusicUtils.isPlaybackServiceConnected() ) {
420 mHasPendingPlaybackRequest = true;
424 String mimeType = intent.getType();
425 boolean handled = false;
427 if (MediaStore.Audio.Playlists.CONTENT_TYPE.equals(mimeType)) {
428 long id = parseIdFromIntent(intent, "playlistId", "playlist", -1);
430 MusicUtils.playPlaylist(this, id, false);
433 } else if (MediaStore.Audio.Albums.CONTENT_TYPE.equals(mimeType)) {
434 long id = parseIdFromIntent(intent, "albumId", "album", -1);
436 int position = intent.getIntExtra("position", 0);
437 MusicUtils.playAlbum(this, id, position, false);
440 } else if (MediaStore.Audio.Artists.CONTENT_TYPE.equals(mimeType)) {
441 long id = parseIdFromIntent(intent, "artistId", "artist", -1);
443 int position = intent.getIntExtra("position", 0);
444 MusicUtils.playArtist(this, id, position, false);
449 // reset intent as it was handled as a playback request
451 setIntent(new Intent());
458 private long parseIdFromIntent(Intent intent, String longKey,
459 String stringKey, long defaultId) {
460 long id = intent.getLongExtra(longKey, -1);
462 String idString = intent.getStringExtra(stringKey);
463 if (idString != null) {
465 id = Long.parseLong(idString);
466 } catch (NumberFormatException e) {
467 Log.e(TAG, e.getMessage());
475 public void onBackStackChanged() {
476 Fragment topFragment = getTopFragment();
477 if (topFragment != null) {
478 // the fragment that has come back to the top should now have its menu items
479 // added to the action bar -- so tell it to make it menu items visible
480 topFragment.setMenuVisibility(true);
481 ISetupActionBar setupActionBar = (ISetupActionBar) topFragment;
482 setupActionBar.setupActionBar();
484 getActionBar().setDisplayHomeAsUpEnabled(
485 !(topFragment instanceof MusicBrowserPhoneFragment));
490 public void onRequestPermissionsResult(int requestCode, String permissions[],
491 int[] grantResults) {
492 switch (requestCode) {
493 case PERMISSION_REQUEST_STORAGE: {
494 if (checkPermissionGrantResults(grantResults)) {
503 private boolean needRequestStoragePermission() {
504 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) return false;
506 boolean needRequest = false;
507 String[] permissions = {
508 Manifest.permission.WRITE_EXTERNAL_STORAGE,
509 Manifest.permission.READ_EXTERNAL_STORAGE
511 ArrayList<String> permissionList = new ArrayList<String>();
512 for (String permission : permissions) {
513 if (checkSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) {
514 permissionList.add(permission);
520 int count = permissionList.size();
522 String[] permissionArray = new String[count];
523 for (int i = 0; i < count; i++) {
524 permissionArray[i] = permissionList.get(i);
527 requestPermissions(permissionArray, PERMISSION_REQUEST_STORAGE);
534 private boolean checkPermissionGrantResults(int[] grantResults) {
535 for (int result : grantResults) {
536 if (result != PackageManager.PERMISSION_GRANTED) {