/* * Copyright (C) 2014 The CyanogenMod Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.cyanogenmod.eleven.ui.activities; import android.Manifest; import android.animation.ArgbEvaluator; import android.animation.ObjectAnimator; import android.content.Intent; import android.content.pm.PackageManager; import android.graphics.Bitmap; import android.graphics.Color; import android.net.Uri; import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.provider.MediaStore; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentTransaction; import android.text.TextUtils; import android.util.Log; import android.view.MenuItem; import android.view.Window; import com.cyanogenmod.eleven.Config; import com.cyanogenmod.eleven.R; import com.cyanogenmod.eleven.cache.ImageFetcher; import com.cyanogenmod.eleven.ui.fragments.AlbumDetailFragment; import com.cyanogenmod.eleven.ui.fragments.ArtistDetailFragment; import com.cyanogenmod.eleven.ui.fragments.AudioPlayerFragment; import com.cyanogenmod.eleven.ui.fragments.IChildFragment; import com.cyanogenmod.eleven.ui.fragments.ISetupActionBar; import com.cyanogenmod.eleven.ui.fragments.PlaylistDetailFragment; import com.cyanogenmod.eleven.ui.fragments.RecentFragment; import com.cyanogenmod.eleven.ui.fragments.phone.MusicBrowserPhoneFragment; import com.cyanogenmod.eleven.ui.fragments.profile.LastAddedFragment; import com.cyanogenmod.eleven.ui.fragments.profile.TopTracksFragment; import com.cyanogenmod.eleven.utils.ApolloUtils; import com.cyanogenmod.eleven.utils.BitmapWithColors; import com.cyanogenmod.eleven.utils.MusicUtils; import com.cyanogenmod.eleven.utils.NavUtils; import java.util.ArrayList; public class HomeActivity extends SlidingPanelActivity implements FragmentManager.OnBackStackChangedListener { private static final String TAG = "HomeActivity"; private static final String ACTION_PREFIX = HomeActivity.class.getName(); public static final String ACTION_VIEW_ARTIST_DETAILS = ACTION_PREFIX + ".view.ArtistDetails"; public static final String ACTION_VIEW_ALBUM_DETAILS = ACTION_PREFIX + ".view.AlbumDetails"; public static final String ACTION_VIEW_PLAYLIST_DETAILS = ACTION_PREFIX + ".view.PlaylistDetails"; public static final String ACTION_VIEW_SMART_PLAYLIST = ACTION_PREFIX + ".view.SmartPlaylist"; public static final String EXTRA_BROWSE_PAGE_IDX = "BrowsePageIndex"; private static final String STATE_KEY_BASE_FRAGMENT = "BaseFragment"; private static final int NEW_PHOTO = 1; public static final int EQUALIZER = 2; private static final int PERMISSION_REQUEST_STORAGE = 1; private Bundle mSavedInstanceState; private String mKey; private boolean mLoadedBaseFragment = false; private boolean mHasPendingPlaybackRequest = false; private Handler mHandler = new Handler(); private boolean mBrowsePanelActive = true; /** * Used by the up action to determine how to handle this */ protected boolean mTopLevelActivity = false; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mSavedInstanceState = savedInstanceState; if (!needRequestStoragePermission()) { init(); } } private void init() { // if we've been launched by an intent, parse it Intent launchIntent = getIntent(); boolean intentHandled = false; if (launchIntent != null) { intentHandled = parseIntentForFragment(launchIntent); } // if the intent didn't cause us to load a fragment, load the music browse one if (mSavedInstanceState == null && !mLoadedBaseFragment) { final MusicBrowserPhoneFragment fragment = new MusicBrowserPhoneFragment(); if (launchIntent != null) { fragment.setDefaultPageIdx(launchIntent.getIntExtra(EXTRA_BROWSE_PAGE_IDX, MusicBrowserPhoneFragment.INVALID_PAGE_INDEX)); } getSupportFragmentManager().beginTransaction() .replace(R.id.activity_base_content, fragment) .commit(); mLoadedBaseFragment = true; mTopLevelActivity = true; } getSupportFragmentManager().addOnBackStackChangedListener(this); // if we are resuming from a saved instance state if (mSavedInstanceState != null) { // track which fragments are loaded and if this is the top level activity mTopLevelActivity = mSavedInstanceState.getBoolean(STATE_KEY_BASE_FRAGMENT); mLoadedBaseFragment = mTopLevelActivity; // update the action bar based on the top most fragment onBackStackChanged(); // figure which panel we are on and update the status bar mBrowsePanelActive = (getCurrentPanel() == Panel.Browse); updateStatusBarColor(); } // if intent wasn't UI related, process it as a audio playback request if (!intentHandled) { handlePlaybackIntent(launchIntent); } mSavedInstanceState = null; } @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putBoolean(STATE_KEY_BASE_FRAGMENT, mTopLevelActivity); } public Fragment getTopFragment() { return getSupportFragmentManager().findFragmentById(R.id.activity_base_content); } public void postRemoveFragment(final Fragment frag) { mHandler.post(new Runnable() { @Override public void run() { // removing the fragment doesn't cause the backstack event to be triggered even if // it is the top fragment, so if it is the top fragment, we will just manually // call pop back stack if (frag == getTopFragment()) { getSupportFragmentManager().popBackStack(); } else { getSupportFragmentManager().beginTransaction().remove(frag).commit(); } } }); } @Override protected void onNewIntent(Intent intent) { super.onNewIntent(intent); // parse intent to ascertain whether the intent is inter UI communication boolean intentHandled = parseIntentForFragment(intent); // since this activity is marked 'singleTop' (launch mode), an existing activity instance // could be sent media play requests if ( !intentHandled) { handlePlaybackIntent(intent); } } @Override public void onMetaChanged() { super.onMetaChanged(); updateStatusBarColor(); } @Override protected void onSlide(float slideOffset) { boolean isInBrowser = getCurrentPanel() == Panel.Browse && slideOffset < 0.7f; if (isInBrowser != mBrowsePanelActive) { mBrowsePanelActive = isInBrowser; updateStatusBarColor(); } } @Override public void onWindowFocusChanged(boolean hasFocus) { super.onWindowFocusChanged(hasFocus); if (getCurrentPanel() == Panel.MusicPlayer) { getAudioPlayerFragment().setVisualizerVisible(hasFocus); } } private void updateStatusBarColor() { if (mBrowsePanelActive || MusicUtils.getCurrentAlbumId() < 0) { updateStatusBarColor(Color.TRANSPARENT); } else { new AsyncTask() { @Override protected BitmapWithColors doInBackground(Void... params) { ImageFetcher imageFetcher = ImageFetcher.getInstance(HomeActivity.this); return imageFetcher.getArtwork( MusicUtils.getAlbumName(), MusicUtils.getCurrentAlbumId(), MusicUtils.getArtistName(), true); } @Override protected void onPostExecute(BitmapWithColors bmc) { updateVisualizerColor(bmc != null ? bmc.getContrastingColor() : Color.TRANSPARENT); updateStatusBarColor(bmc != null ? bmc.getVibrantDarkColor() : Color.TRANSPARENT); } }.execute(); } } private void updateVisualizerColor(int color) { if (color == Color.TRANSPARENT) { color = getResources().getColor(R.color.visualizer_fill_color); } // check for null since updatestatusBarColor is a async task AudioPlayerFragment fragment = getAudioPlayerFragment(); if (fragment != null) { fragment.setVisualizerColor(color); } } private void updateStatusBarColor(int color) { if (color == Color.TRANSPARENT) { color = getResources().getColor(R.color.primary_dark); } final Window window = getWindow(); ObjectAnimator animator = ObjectAnimator.ofInt(window, "statusBarColor", window.getStatusBarColor(), color); animator.setEvaluator(new ArgbEvaluator()); animator.setDuration(300); animator.start(); } private boolean parseIntentForFragment(Intent intent) { boolean handled = false; if (intent.getAction() != null) { final String action = intent.getAction(); Fragment targetFragment = null; FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); if (action.equals(ACTION_VIEW_SMART_PLAYLIST)) { long playlistId = intent.getExtras().getLong(Config.SMART_PLAYLIST_TYPE); switch (Config.SmartPlaylistType.getTypeById(playlistId)) { case LastAdded: targetFragment = new LastAddedFragment(); break; case RecentlyPlayed: targetFragment = new RecentFragment(); break; case TopTracks: targetFragment = new TopTracksFragment(); break; } } else if (action.equals(ACTION_VIEW_PLAYLIST_DETAILS)) { targetFragment = new PlaylistDetailFragment(); } else if (action.equals(ACTION_VIEW_ALBUM_DETAILS)) { targetFragment = new AlbumDetailFragment(); } else if (action.equals(ACTION_VIEW_ARTIST_DETAILS)) { targetFragment = new ArtistDetailFragment(); } if (targetFragment != null) { targetFragment.setArguments(intent.getExtras()); transaction.setCustomAnimations(0, 0, 0, R.anim.fade_out); // If we ever come back to this because of memory concerns because // none of the fragments are being removed from memory, we can fix this // by using "replace" instead of "add". The caveat is that the performance of // returning to previous fragments is a bit more sluggish because the fragment // view needs to be recreated. If we do remove that, we can remove the back stack // change listener code above transaction.add(R.id.activity_base_content, targetFragment); if (mLoadedBaseFragment) { transaction.addToBackStack(null); showPanel(Panel.Browse); } else { // else mark the fragment as loaded so we don't load the music browse fragment. // this happens when they launch search which is its own activity and then // browse through that back to home activity mLoadedBaseFragment = true; getActionBar().setDisplayHomeAsUpEnabled(true); } // the current top fragment is about to be hidden by what we are replacing // it with -- so tell that fragment not to make its action bar menu items visible Fragment oldTop = getTopFragment(); if (oldTop != null) { oldTop.setMenuVisibility(false); } transaction.commit(); handled = true; } } return handled; } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == NEW_PHOTO && !TextUtils.isEmpty(mKey)) { if (resultCode == RESULT_OK) { MusicUtils.removeFromCache(this, mKey); final Uri selectedImage = data.getData(); new Thread(new Runnable() { @Override public void run() { Bitmap bitmap = ImageFetcher.decodeSampledBitmapFromUri(getContentResolver(), selectedImage); ImageFetcher imageFetcher = ApolloUtils.getImageFetcher(HomeActivity.this); imageFetcher.addBitmapToCache(mKey, bitmap); MusicUtils.refresh(); } }).start(); } } } /** * Starts an activity for result that returns an image from the Gallery. */ public void selectNewPhoto(String key) { mKey = key; // Now open the gallery final Intent intent = new Intent(Intent.ACTION_GET_CONTENT, null); intent.setType("image/*"); startActivityForResult(intent, NEW_PHOTO); } @Override public boolean onOptionsItemSelected(final MenuItem item) { switch (item.getItemId()) { case android.R.id.home: navigateToTop(); return true; } return super.onOptionsItemSelected(item); } /** * Navigates to the top Activity and places the view to the correct page */ protected void navigateToTop() { final Fragment topFragment = getTopFragment(); int targetFragmentIndex = MusicBrowserPhoneFragment.INVALID_PAGE_INDEX; if (topFragment instanceof IChildFragment) { targetFragmentIndex = ((IChildFragment)topFragment).getMusicFragmentParent().ordinal(); } // If we are the top activity in the stack (as determined by the activity that has loaded // the MusicBrowserPhoneFragment) then clear the back stack and move the browse fragment // to the appropriate page as per Android up standards if (mTopLevelActivity) { clearBackStack(); MusicBrowserPhoneFragment musicFragment = (MusicBrowserPhoneFragment) getTopFragment(); musicFragment.setDefaultPageIdx(targetFragmentIndex); showPanel(Panel.Browse); } else { // I've tried all other combinations with parent activities, support.NavUtils and // there is no easy way to achieve what we want that I'm aware of, so clear everything // and jump to the right page NavUtils.goHome(this, targetFragmentIndex); } } /** * Immediately clears the backstack */ protected void clearBackStack() { final FragmentManager fragmentManager = getSupportFragmentManager(); if (fragmentManager.getBackStackEntryCount() > 0) { final int id = fragmentManager.getBackStackEntryAt(0).getId(); fragmentManager.popBackStackImmediate(id, FragmentManager.POP_BACK_STACK_INCLUSIVE); } } @Override public void handlePendingPlaybackRequests() { if (mHasPendingPlaybackRequest) { Intent unhandledIntent = getIntent(); handlePlaybackIntent(unhandledIntent); } } /** * Checks whether the passed intent contains a playback request, * and starts playback if that's the case * @return true if the intent was consumed */ private boolean handlePlaybackIntent(Intent intent) { if (intent == null) { return false; } else if ( !MusicUtils.isPlaybackServiceConnected() ) { mHasPendingPlaybackRequest = true; return false; } String mimeType = intent.getType(); boolean handled = false; if (MediaStore.Audio.Playlists.CONTENT_TYPE.equals(mimeType)) { long id = parseIdFromIntent(intent, "playlistId", "playlist", -1); if (id >= 0) { MusicUtils.playPlaylist(this, id, false); handled = true; } } else if (MediaStore.Audio.Albums.CONTENT_TYPE.equals(mimeType)) { long id = parseIdFromIntent(intent, "albumId", "album", -1); if (id >= 0) { int position = intent.getIntExtra("position", 0); MusicUtils.playAlbum(this, id, position, false); handled = true; } } else if (MediaStore.Audio.Artists.CONTENT_TYPE.equals(mimeType)) { long id = parseIdFromIntent(intent, "artistId", "artist", -1); if (id >= 0) { int position = intent.getIntExtra("position", 0); MusicUtils.playArtist(this, id, position, false); handled = true; } } // reset intent as it was handled as a playback request if (handled) { setIntent(new Intent()); } return handled; } private long parseIdFromIntent(Intent intent, String longKey, String stringKey, long defaultId) { long id = intent.getLongExtra(longKey, -1); if (id < 0) { String idString = intent.getStringExtra(stringKey); if (idString != null) { try { id = Long.parseLong(idString); } catch (NumberFormatException e) { Log.e(TAG, e.getMessage()); } } } return id; } @Override public void onBackStackChanged() { Fragment topFragment = getTopFragment(); if (topFragment != null) { // the fragment that has come back to the top should now have its menu items // added to the action bar -- so tell it to make it menu items visible topFragment.setMenuVisibility(true); ISetupActionBar setupActionBar = (ISetupActionBar) topFragment; setupActionBar.setupActionBar(); getActionBar().setDisplayHomeAsUpEnabled( !(topFragment instanceof MusicBrowserPhoneFragment)); } } @Override public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) { switch (requestCode) { case PERMISSION_REQUEST_STORAGE: { if (checkPermissionGrantResults(grantResults)) { init(); } else { finish(); } } } } private boolean needRequestStoragePermission() { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) return false; boolean needRequest = false; String[] permissions = { Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE }; ArrayList permissionList = new ArrayList(); for (String permission : permissions) { if (checkSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) { permissionList.add(permission); needRequest = true; } } if (needRequest) { int count = permissionList.size(); if (count > 0) { String[] permissionArray = new String[count]; for (int i = 0; i < count; i++) { permissionArray[i] = permissionList.get(i); } requestPermissions(permissionArray, PERMISSION_REQUEST_STORAGE); } } return needRequest; } private boolean checkPermissionGrantResults(int[] grantResults) { for (int result : grantResults) { if (result != PackageManager.PERMISSION_GRANTED) { return false; } } return true; } }