diff options
Diffstat (limited to 'src/org/lineageos/eleven/ui/activities/HomeActivity.java')
-rw-r--r-- | src/org/lineageos/eleven/ui/activities/HomeActivity.java | 543 |
1 files changed, 543 insertions, 0 deletions
diff --git a/src/org/lineageos/eleven/ui/activities/HomeActivity.java b/src/org/lineageos/eleven/ui/activities/HomeActivity.java new file mode 100644 index 0000000..6499a20 --- /dev/null +++ b/src/org/lineageos/eleven/ui/activities/HomeActivity.java @@ -0,0 +1,543 @@ +/* + * 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<Void, Void, BitmapWithColors>() { + @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<String> permissionList = new ArrayList<String>(); + 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; + } + +} |