OSDN Git Service

Eleven: request record audio permission for 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.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;
38
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;
56
57 import java.util.ArrayList;
58
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";
68
69     private static final String STATE_KEY_BASE_FRAGMENT = "BaseFragment";
70
71     private static final int NEW_PHOTO = 1;
72     public static final int EQUALIZER = 2;
73
74     private static final int PERMISSION_REQUEST_STORAGE = 1;
75     private Bundle mSavedInstanceState;
76
77     private String mKey;
78     private boolean mLoadedBaseFragment = false;
79     private boolean mHasPendingPlaybackRequest = false;
80     private Handler mHandler = new Handler();
81     private boolean mBrowsePanelActive = true;
82
83     /**
84      * Used by the up action to determine how to handle this
85      */
86     protected boolean mTopLevelActivity = false;
87
88     @Override
89     protected void onCreate(Bundle savedInstanceState) {
90         super.onCreate(savedInstanceState);
91
92         mSavedInstanceState = savedInstanceState;
93
94         if (!needRequestStoragePermission()) {
95             init();
96         }
97     }
98
99     private void init() {
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);
105         }
106
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));
113             }
114             getSupportFragmentManager().beginTransaction()
115                     .replace(R.id.activity_base_content, fragment)
116                     .commit();
117
118             mLoadedBaseFragment = true;
119             mTopLevelActivity = true;
120         }
121
122         getSupportFragmentManager().addOnBackStackChangedListener(this);
123
124
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;
130
131             // update the action bar based on the top most fragment
132             onBackStackChanged();
133
134             // figure which panel we are on and update the status bar
135             mBrowsePanelActive = (getCurrentPanel() == Panel.Browse);
136             updateStatusBarColor();
137         }
138
139         // if intent wasn't UI related, process it as a audio playback request
140         if (!intentHandled) {
141             handlePlaybackIntent(launchIntent);
142         }
143
144         mSavedInstanceState = null;
145     }
146
147     @Override
148     protected void onSaveInstanceState(Bundle outState) {
149         super.onSaveInstanceState(outState);
150         outState.putBoolean(STATE_KEY_BASE_FRAGMENT, mTopLevelActivity);
151     }
152
153     public Fragment getTopFragment() {
154         return getSupportFragmentManager().findFragmentById(R.id.activity_base_content);
155     }
156
157     public void postRemoveFragment(final Fragment frag) {
158         mHandler.post(new Runnable() {
159             @Override
160             public void run() {
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();
166                 } else {
167                     getSupportFragmentManager().beginTransaction().remove(frag).commit();
168                 }
169             }
170         });
171     }
172
173     @Override
174     protected void onNewIntent(Intent intent) {
175         super.onNewIntent(intent);
176
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);
183         }
184     }
185
186     @Override
187     public void onMetaChanged() {
188         super.onMetaChanged();
189         updateStatusBarColor();
190     }
191
192     @Override
193     protected void onSlide(float slideOffset) {
194         boolean isInBrowser = getCurrentPanel() == Panel.Browse && slideOffset < 0.7f;
195         if (isInBrowser != mBrowsePanelActive) {
196             mBrowsePanelActive = isInBrowser;
197             updateStatusBarColor();
198         }
199     }
200
201     @Override
202     public void onWindowFocusChanged(boolean hasFocus) {
203         super.onWindowFocusChanged(hasFocus);
204
205         if (getCurrentPanel() == Panel.MusicPlayer) {
206             getAudioPlayerFragment().setVisualizerVisible(hasFocus);
207         }
208     }
209
210     private void updateStatusBarColor() {
211         if (mBrowsePanelActive || MusicUtils.getCurrentAlbumId() < 0) {
212             updateStatusBarColor(Color.TRANSPARENT);
213         } else {
214             new AsyncTask<Void, Void, BitmapWithColors>() {
215                 @Override
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);
221                 }
222                 @Override
223                 protected void onPostExecute(BitmapWithColors bmc) {
224                     updateVisualizerColor(bmc != null
225                             ? bmc.getContrastingColor() : Color.TRANSPARENT);
226                     updateStatusBarColor(bmc != null
227                             ? bmc.getVibrantDarkColor() : Color.TRANSPARENT);
228                 }
229             }.execute();
230         }
231     }
232
233     private void updateVisualizerColor(int color) {
234         if (color == Color.TRANSPARENT) {
235             color = getResources().getColor(R.color.visualizer_fill_color);
236         }
237
238         // check for null since updatestatusBarColor is a async task
239         AudioPlayerFragment fragment = getAudioPlayerFragment();
240         if (fragment != null) {
241             fragment.setVisualizerColor(color);
242         }
243     }
244
245     private void updateStatusBarColor(int color) {
246         if (color == Color.TRANSPARENT) {
247             color = getResources().getColor(R.color.primary_dark);
248         }
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);
254         animator.start();
255     }
256
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();
263
264             if (action.equals(ACTION_VIEW_SMART_PLAYLIST)) {
265                 long playlistId = intent.getExtras().getLong(Config.SMART_PLAYLIST_TYPE);
266                 switch (Config.SmartPlaylistType.getTypeById(playlistId)) {
267                     case LastAdded:
268                         targetFragment = new LastAddedFragment();
269                         break;
270                     case RecentlyPlayed:
271                         targetFragment = new RecentFragment();
272                         break;
273                     case TopTracks:
274                         targetFragment = new TopTracksFragment();
275                         break;
276                 }
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();
283             }
284
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);
298                 } else {
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);
304                 }
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);
310                 }
311
312                 transaction.commit();
313                 handled = true;
314             }
315         }
316         return handled;
317     }
318
319     @Override
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();
326
327                 new Thread(new Runnable() {
328                     @Override
329                     public void run() {
330                         Bitmap bitmap = ImageFetcher.decodeSampledBitmapFromUri(getContentResolver(),
331                                 selectedImage);
332
333                         ImageFetcher imageFetcher = ApolloUtils.getImageFetcher(HomeActivity.this);
334                         imageFetcher.addBitmapToCache(mKey, bitmap);
335
336                         MusicUtils.refresh();
337                     }
338                 }).start();
339             }
340         }
341     }
342
343     /**
344      * Starts an activity for result that returns an image from the Gallery.
345      */
346     public void selectNewPhoto(String key) {
347         mKey = 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);
352     }
353
354     @Override
355     public boolean onOptionsItemSelected(final MenuItem item) {
356         switch (item.getItemId()) {
357             case android.R.id.home:
358                 navigateToTop();
359                 return true;
360         }
361
362         return super.onOptionsItemSelected(item);
363     }
364
365     /**
366      * Navigates to the top Activity and places the view to the correct page
367      */
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();
373         }
374
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) {
379             clearBackStack();
380             MusicBrowserPhoneFragment musicFragment = (MusicBrowserPhoneFragment) getTopFragment();
381             musicFragment.setDefaultPageIdx(targetFragmentIndex);
382             showPanel(Panel.Browse);
383         } else {
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);
388         }
389     }
390
391     /**
392      * Immediately clears the backstack
393      */
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);
399         }
400     }
401
402     @Override
403     public void handlePendingPlaybackRequests() {
404         if (mHasPendingPlaybackRequest) {
405             Intent unhandledIntent = getIntent();
406             handlePlaybackIntent(unhandledIntent);
407         }
408     }
409
410     /**
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
414      */
415     private boolean handlePlaybackIntent(Intent intent) {
416
417         if (intent == null) {
418             return false;
419         } else if ( !MusicUtils.isPlaybackServiceConnected() ) {
420             mHasPendingPlaybackRequest = true;
421             return false;
422         }
423
424         String mimeType = intent.getType();
425         boolean handled = false;
426
427         if (MediaStore.Audio.Playlists.CONTENT_TYPE.equals(mimeType)) {
428             long id = parseIdFromIntent(intent, "playlistId", "playlist", -1);
429             if (id >= 0) {
430                 MusicUtils.playPlaylist(this, id, false);
431                 handled = true;
432             }
433         } else if (MediaStore.Audio.Albums.CONTENT_TYPE.equals(mimeType)) {
434             long id = parseIdFromIntent(intent, "albumId", "album", -1);
435             if (id >= 0) {
436                 int position = intent.getIntExtra("position", 0);
437                 MusicUtils.playAlbum(this, id, position, false);
438                 handled = true;
439             }
440         } else if (MediaStore.Audio.Artists.CONTENT_TYPE.equals(mimeType)) {
441             long id = parseIdFromIntent(intent, "artistId", "artist", -1);
442             if (id >= 0) {
443                 int position = intent.getIntExtra("position", 0);
444                 MusicUtils.playArtist(this, id, position, false);
445                 handled = true;
446             }
447         }
448
449         // reset intent as it was handled as a playback request
450         if (handled) {
451             setIntent(new Intent());
452         }
453
454         return handled;
455
456     }
457
458     private long parseIdFromIntent(Intent intent, String longKey,
459                                    String stringKey, long defaultId) {
460         long id = intent.getLongExtra(longKey, -1);
461         if (id < 0) {
462             String idString = intent.getStringExtra(stringKey);
463             if (idString != null) {
464                 try {
465                     id = Long.parseLong(idString);
466                 } catch (NumberFormatException e) {
467                     Log.e(TAG, e.getMessage());
468                 }
469             }
470         }
471         return id;
472     }
473
474     @Override
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();
483
484             getActionBar().setDisplayHomeAsUpEnabled(
485                     !(topFragment instanceof MusicBrowserPhoneFragment));
486         }
487     }
488
489     @Override
490     public void onRequestPermissionsResult(int requestCode, String permissions[],
491             int[] grantResults) {
492         switch (requestCode) {
493             case PERMISSION_REQUEST_STORAGE: {
494                 if (checkPermissionGrantResults(grantResults)) {
495                     init();
496                 } else {
497                     finish();
498                 }
499             }
500         }
501     }
502
503     private boolean needRequestStoragePermission() {
504         if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) return false;
505
506         boolean needRequest = false;
507         String[] permissions = {
508                 Manifest.permission.WRITE_EXTERNAL_STORAGE,
509                 Manifest.permission.READ_EXTERNAL_STORAGE
510         };
511         ArrayList<String> permissionList = new ArrayList<String>();
512         for (String permission : permissions) {
513             if (checkSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) {
514                 permissionList.add(permission);
515                 needRequest = true;
516             }
517         }
518
519         if (needRequest) {
520             int count = permissionList.size();
521             if (count > 0) {
522                 String[] permissionArray = new String[count];
523                 for (int i = 0; i < count; i++) {
524                     permissionArray[i] = permissionList.get(i);
525                 }
526
527                 requestPermissions(permissionArray, PERMISSION_REQUEST_STORAGE);
528             }
529         }
530
531         return needRequest;
532     }
533
534     private boolean checkPermissionGrantResults(int[] grantResults) {
535         for (int result : grantResults) {
536             if (result != PackageManager.PERMISSION_GRANTED) {
537                 return false;
538             }
539         }
540         return true;
541     }
542
543 }