Java tutorial
/* * Copyright (C) 2007 The Android Open Source 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.android.music; import android.app.SearchManager; import android.content.AsyncQueryHandler; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.ServiceConnection; import android.content.res.Resources; import android.database.Cursor; import android.database.CursorWrapper; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.media.AudioManager; import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Message; import android.provider.MediaStore; import android.support.v4.app.Fragment; import android.text.TextUtils; import android.view.ContextMenu; import android.view.ContextMenu.ContextMenuInfo; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.View.OnCreateContextMenuListener; import android.view.ViewGroup; import android.widget.ExpandableListView; import android.widget.ExpandableListView.OnChildClickListener; import android.widget.ImageView; import android.widget.SectionIndexer; import android.widget.SimpleCursorTreeAdapter; import android.widget.TextView; import com.android.music.MusicUtils.ServiceToken; public class ArtistAlbumBrowserFragment extends Fragment implements OnCreateContextMenuListener, MusicUtils.Defs, ServiceConnection, OnChildClickListener { private String mCurrentArtistId; private String mCurrentArtistName; private String mCurrentAlbumId; private String mCurrentAlbumName; private String mCurrentArtistNameForAlbum; boolean mIsUnknownArtist; boolean mIsUnknownAlbum; private ArtistAlbumListAdapter mAdapter; private boolean mAdapterSent; private final static int SEARCH = CHILD_MENU_BASE; private static int mLastListPosCourse = -1; private static int mLastListPosFine = -1; private ServiceToken mToken; private Cursor mArtistCursor; private ExpandableListView lv; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // TODO Auto-generated method stub View view = inflater.inflate(R.layout.media_picker_activity_expanding, null); getActivity().setVolumeControlStream(AudioManager.STREAM_MUSIC); mToken = MusicUtils.bindToService(getActivity(), this); if (savedInstanceState != null) { mCurrentAlbumId = savedInstanceState.getString("selectedalbum"); mCurrentAlbumName = savedInstanceState.getString("selectedalbumname"); mCurrentArtistId = savedInstanceState.getString("selectedartist"); mCurrentArtistName = savedInstanceState.getString("selectedartistname"); } IntentFilter f = new IntentFilter(); f.addAction(Intent.ACTION_MEDIA_SCANNER_STARTED); f.addAction(Intent.ACTION_MEDIA_SCANNER_FINISHED); f.addAction(Intent.ACTION_MEDIA_UNMOUNTED); f.addDataScheme("file"); getActivity().registerReceiver(mScanListener, f); lv = (ExpandableListView) view.findViewById(android.R.id.list); mAdapter = (ArtistAlbumListAdapter) getActivity().getLastNonConfigurationInstance(); if (mAdapter == null) { //Log.i("@@@", "starting query"); mAdapter = new ArtistAlbumListAdapter(getActivity().getApplication(), this, null, // cursor R.layout.track_list_item_group, new String[] {}, new int[] {}, R.layout.track_list_item_child, new String[] {}, new int[] {}); lv.setAdapter(mAdapter); getArtistCursor(mAdapter.getQueryHandler(), null); } else { mAdapter.setActivity(this); lv.setAdapter(mAdapter); mArtistCursor = mAdapter.getCursor(); if (mArtistCursor != null) { init(mArtistCursor); } else { getArtistCursor(mAdapter.getQueryHandler(), null); } } lv.setOnCreateContextMenuListener(this); lv.setOnChildClickListener(this); lv.setTextFilterEnabled(true); return view; } /* @Override public Object onRetainNonConfigurationInstance() { mAdapterSent = true; return mAdapter; }*/ @Override public void onSaveInstanceState(Bundle outState) { // TODO Auto-generated method stub outState.putString("selectedalbum", mCurrentAlbumId); outState.putString("selectedalbumname", mCurrentAlbumName); outState.putString("selectedartist", mCurrentArtistId); outState.putString("selectedartistname", mCurrentArtistName); super.onSaveInstanceState(outState); } @Override public void onDestroy() { // TODO Auto-generated method stub //ExpandableListView lv = getExpandableListView(); if (lv != null) { mLastListPosCourse = lv.getFirstVisiblePosition(); View cv = lv.getChildAt(0); if (cv != null) { mLastListPosFine = cv.getTop(); } } MusicUtils.unbindFromService(mToken); // If we have an adapter and didn't send it off to another activity yet, we should // close its cursor, which we do by assigning a null cursor to it. Doing this // instead of closing the cursor directly keeps the framework from accessing // the closed cursor later. if (!mAdapterSent && mAdapter != null) { mAdapter.changeCursor(null); } // Because we pass the adapter to the next activity, we need to make // sure it doesn't keep a reference to this activity. We can do this // by clearing its DatasetObservers, which setListAdapter(null) does. //lv.setAdapter(null); mAdapter = null; getActivity().unregisterReceiver(mScanListener); lv.setAdapter(mAdapter); super.onDestroy(); } @Override public void onPause() { // TODO Auto-generated method stub getActivity().unregisterReceiver(mTrackListListener); mReScanHandler.removeCallbacksAndMessages(null); super.onPause(); } @Override public void onResume() { // TODO Auto-generated method stub super.onResume(); IntentFilter f = new IntentFilter(); f.addAction(MediaPlaybackService.META_CHANGED); f.addAction(MediaPlaybackService.QUEUE_CHANGED); getActivity().registerReceiver(mTrackListListener, f); mTrackListListener.onReceive(null, null); //need fix MusicUtils.setSpinnerState(getActivity()); } private BroadcastReceiver mTrackListListener = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { lv.invalidateViews(); MusicUtils.updateNowPlaying(getActivity()); } }; private BroadcastReceiver mScanListener = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { MusicUtils.setSpinnerState(getActivity()); mReScanHandler.sendEmptyMessage(0); if (intent.getAction().equals(Intent.ACTION_MEDIA_UNMOUNTED)) { MusicUtils.clearAlbumArtCache(); } } }; private Handler mReScanHandler = new Handler() { @Override public void handleMessage(Message msg) { if (mAdapter != null) { getArtistCursor(mAdapter.getQueryHandler(), null); } } }; public void init(Cursor c) { if (mAdapter == null) { return; } mAdapter.changeCursor(c); // also sets mArtistCursor if (mArtistCursor == null) { MusicUtils.displayDatabaseError(getActivity()); getActivity().closeContextMenu(); mReScanHandler.sendEmptyMessageDelayed(0, 1000); return; } // restore previous position if (mLastListPosCourse >= 0) { //NF ExpandableListView elv = getExpandableListView(); lv.setSelectionFromTop(mLastListPosCourse, mLastListPosFine); mLastListPosCourse = -1; } MusicUtils.hideDatabaseError(getActivity()); //MusicUtils.updateButtonBar(this, R.id.artisttab); } @Override public boolean onChildClick(ExpandableListView parent, View v, int groupPosition, int childPosition, long id) { mCurrentAlbumId = Long.valueOf(id).toString(); Intent intent = new Intent(Intent.ACTION_PICK); intent.setDataAndType(Uri.EMPTY, "vnd.android.cursor.dir/track"); intent.setClass(getActivity(), TrackBrowserActivity.class); intent.putExtra("album", mCurrentAlbumId); Cursor c = (Cursor) mAdapter.getChild(groupPosition, childPosition); String album = c.getString(c.getColumnIndex(MediaStore.Audio.Albums.ALBUM)); if (album == null || album.equals(MediaStore.UNKNOWN_STRING)) { // unknown album, so we should include the artist ID to limit the songs to songs only by that artist mArtistCursor.moveToPosition(groupPosition); mCurrentArtistId = mArtistCursor.getString(mArtistCursor.getColumnIndex(MediaStore.Audio.Artists._ID)); intent.putExtra("artist", mCurrentArtistId); } startActivity(intent); return true; } @Override public boolean onContextItemSelected(MenuItem item) { // TODO Auto-generated method stub return super.onContextItemSelected(item); } @Override public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { // TODO Auto-generated method stub super.onCreateContextMenu(menu, v, menuInfo); } @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { // TODO Auto-generated method stub super.onCreateOptionsMenu(menu, inflater); } @Override public boolean onOptionsItemSelected(MenuItem item) { // TODO Auto-generated method stub return super.onOptionsItemSelected(item); } @Override public void onPrepareOptionsMenu(Menu menu) { // TODO Auto-generated method stub super.onPrepareOptionsMenu(menu); } void doSearch() { CharSequence title = null; String query = null; Intent i = new Intent(); i.setAction(MediaStore.INTENT_ACTION_MEDIA_SEARCH); i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); if (mCurrentArtistId != null) { title = mCurrentArtistName; query = mCurrentArtistName; i.putExtra(MediaStore.EXTRA_MEDIA_ARTIST, mCurrentArtistName); i.putExtra(MediaStore.EXTRA_MEDIA_FOCUS, MediaStore.Audio.Artists.ENTRY_CONTENT_TYPE); } else { if (mIsUnknownAlbum) { title = query = mCurrentArtistNameForAlbum; } else { title = query = mCurrentAlbumName; if (!mIsUnknownArtist) { query = query + " " + mCurrentArtistNameForAlbum; } } i.putExtra(MediaStore.EXTRA_MEDIA_ARTIST, mCurrentArtistNameForAlbum); i.putExtra(MediaStore.EXTRA_MEDIA_ALBUM, mCurrentAlbumName); i.putExtra(MediaStore.EXTRA_MEDIA_FOCUS, MediaStore.Audio.Albums.ENTRY_CONTENT_TYPE); } title = getString(R.string.mediasearch, title); i.putExtra(SearchManager.QUERY, query); startActivity(Intent.createChooser(i, title)); } @Override public void onActivityResult(int requestCode, int resultCode, Intent intent) { switch (requestCode) { case SCAN_DONE: if (resultCode == getActivity().RESULT_CANCELED) { getActivity().finish(); } else { getArtistCursor(mAdapter.getQueryHandler(), null); } break; case NEW_PLAYLIST: if (resultCode == getActivity().RESULT_OK) { Uri uri = intent.getData(); if (uri != null) { long[] list = null; if (mCurrentArtistId != null) { list = MusicUtils.getSongListForArtist(getActivity(), Long.parseLong(mCurrentArtistId)); } else if (mCurrentAlbumId != null) { list = MusicUtils.getSongListForAlbum(getActivity(), Long.parseLong(mCurrentAlbumId)); } MusicUtils.addToPlaylist(getActivity(), list, Long.parseLong(uri.getLastPathSegment())); } } break; } } private Cursor getArtistCursor(AsyncQueryHandler async, String filter) { String[] cols = new String[] { MediaStore.Audio.Artists._ID, MediaStore.Audio.Artists.ARTIST, MediaStore.Audio.Artists.NUMBER_OF_ALBUMS, MediaStore.Audio.Artists.NUMBER_OF_TRACKS }; Uri uri = MediaStore.Audio.Artists.EXTERNAL_CONTENT_URI; if (!TextUtils.isEmpty(filter)) { uri = uri.buildUpon().appendQueryParameter("filter", Uri.encode(filter)).build(); } Cursor ret = null; if (async != null) { async.startQuery(0, null, uri, cols, null, null, MediaStore.Audio.Artists.ARTIST_KEY); } else { ret = MusicUtils.query(getActivity(), uri, cols, null, null, MediaStore.Audio.Artists.ARTIST_KEY); } return ret; } static class ArtistAlbumListAdapter extends SimpleCursorTreeAdapter implements SectionIndexer { private final Drawable mNowPlayingOverlay; private final BitmapDrawable mDefaultAlbumIcon; private int mGroupArtistIdIdx; private int mGroupArtistIdx; private int mGroupAlbumIdx; private int mGroupSongIdx; private final Context mContext; private final Resources mResources; private final String mAlbumSongSeparator; private final String mUnknownAlbum; private final String mUnknownArtist; private final StringBuilder mBuffer = new StringBuilder(); private final Object[] mFormatArgs = new Object[1]; private final Object[] mFormatArgs3 = new Object[3]; private MusicAlphabetIndexer mIndexer; private ArtistAlbumBrowserFragment mActivity; private AsyncQueryHandler mQueryHandler; private String mConstraint = null; private boolean mConstraintIsValid = false; static class ViewHolder { TextView line1; TextView line2; ImageView play_indicator; ImageView icon; } class QueryHandler extends AsyncQueryHandler { QueryHandler(ContentResolver res) { super(res); } @Override protected void onQueryComplete(int token, Object cookie, Cursor cursor) { //Log.i("@@@", "query complete"); mActivity.init(cursor); } } ArtistAlbumListAdapter(Context context, ArtistAlbumBrowserFragment currentactivity, Cursor cursor, int glayout, String[] gfrom, int[] gto, int clayout, String[] cfrom, int[] cto) { super(context, cursor, glayout, gfrom, gto, clayout, cfrom, cto); mActivity = currentactivity; mQueryHandler = new QueryHandler(context.getContentResolver()); Resources r = context.getResources(); mNowPlayingOverlay = r.getDrawable(R.drawable.indicator_ic_mp_playing_list); mDefaultAlbumIcon = (BitmapDrawable) r.getDrawable(R.drawable.albumart_mp_unknown_list); // no filter or dither, it's a lot faster and we can't tell the difference mDefaultAlbumIcon.setFilterBitmap(false); mDefaultAlbumIcon.setDither(false); mContext = context; getColumnIndices(cursor); mResources = context.getResources(); mAlbumSongSeparator = context.getString(R.string.albumsongseparator); mUnknownAlbum = context.getString(R.string.unknown_album_name); mUnknownArtist = context.getString(R.string.unknown_artist_name); } private void getColumnIndices(Cursor cursor) { if (cursor != null) { mGroupArtistIdIdx = cursor.getColumnIndexOrThrow(MediaStore.Audio.Artists._ID); mGroupArtistIdx = cursor.getColumnIndexOrThrow(MediaStore.Audio.Artists.ARTIST); mGroupAlbumIdx = cursor.getColumnIndexOrThrow(MediaStore.Audio.Artists.NUMBER_OF_ALBUMS); mGroupSongIdx = cursor.getColumnIndexOrThrow(MediaStore.Audio.Artists.NUMBER_OF_TRACKS); if (mIndexer != null) { mIndexer.setCursor(cursor); } else { mIndexer = new MusicAlphabetIndexer(cursor, mGroupArtistIdx, mResources.getString(R.string.fast_scroll_alphabet)); } } } public void setActivity(ArtistAlbumBrowserFragment newactivity) { mActivity = newactivity; } public AsyncQueryHandler getQueryHandler() { return mQueryHandler; } @Override public View newGroupView(Context context, Cursor cursor, boolean isExpanded, ViewGroup parent) { View v = super.newGroupView(context, cursor, isExpanded, parent); ImageView iv = (ImageView) v.findViewById(R.id.icon); ViewGroup.LayoutParams p = iv.getLayoutParams(); p.width = ViewGroup.LayoutParams.WRAP_CONTENT; p.height = ViewGroup.LayoutParams.WRAP_CONTENT; ViewHolder vh = new ViewHolder(); vh.line1 = (TextView) v.findViewById(R.id.line1); vh.line2 = (TextView) v.findViewById(R.id.line2); vh.play_indicator = (ImageView) v.findViewById(R.id.play_indicator); vh.icon = (ImageView) v.findViewById(R.id.icon); vh.icon.setPadding(0, 0, 1, 0); v.setTag(vh); return v; } @Override public View newChildView(Context context, Cursor cursor, boolean isLastChild, ViewGroup parent) { View v = super.newChildView(context, cursor, isLastChild, parent); ViewHolder vh = new ViewHolder(); vh.line1 = (TextView) v.findViewById(R.id.line1); vh.line2 = (TextView) v.findViewById(R.id.line2); vh.play_indicator = (ImageView) v.findViewById(R.id.play_indicator); vh.icon = (ImageView) v.findViewById(R.id.icon); vh.icon.setBackgroundDrawable(mDefaultAlbumIcon); vh.icon.setPadding(0, 0, 1, 0); v.setTag(vh); return v; } @Override public void bindGroupView(View view, Context context, Cursor cursor, boolean isexpanded) { ViewHolder vh = (ViewHolder) view.getTag(); String artist = cursor.getString(mGroupArtistIdx); String displayartist = artist; boolean unknown = artist == null || artist.equals(MediaStore.UNKNOWN_STRING); if (unknown) { displayartist = mUnknownArtist; } vh.line1.setText(displayartist); int numalbums = cursor.getInt(mGroupAlbumIdx); int numsongs = cursor.getInt(mGroupSongIdx); String songs_albums = MusicUtils.makeAlbumsLabel(context, numalbums, numsongs, unknown); vh.line2.setText(songs_albums); long currentartistid = MusicUtils.getCurrentArtistId(); long artistid = cursor.getLong(mGroupArtistIdIdx); if (currentartistid == artistid && !isexpanded) { vh.play_indicator.setImageDrawable(mNowPlayingOverlay); } else { vh.play_indicator.setImageDrawable(null); } } @Override public void bindChildView(View view, Context context, Cursor cursor, boolean islast) { ViewHolder vh = (ViewHolder) view.getTag(); String name = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Audio.Albums.ALBUM)); String displayname = name; boolean unknown = name == null || name.equals(MediaStore.UNKNOWN_STRING); if (unknown) { displayname = mUnknownAlbum; } vh.line1.setText(displayname); int numsongs = cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Audio.Albums.NUMBER_OF_SONGS)); int numartistsongs = cursor .getInt(cursor.getColumnIndexOrThrow(MediaStore.Audio.Albums.NUMBER_OF_SONGS_FOR_ARTIST)); final StringBuilder builder = mBuffer; builder.delete(0, builder.length()); if (unknown) { numsongs = numartistsongs; } if (numsongs == 1) { builder.append(context.getString(R.string.onesong)); } else { if (numsongs == numartistsongs) { final Object[] args = mFormatArgs; args[0] = numsongs; builder.append(mResources.getQuantityString(R.plurals.Nsongs, numsongs, args)); } else { final Object[] args = mFormatArgs3; args[0] = numsongs; args[1] = numartistsongs; args[2] = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Audio.Artists.ARTIST)); builder.append(mResources.getQuantityString(R.plurals.Nsongscomp, numsongs, args)); } } vh.line2.setText(builder.toString()); ImageView iv = vh.icon; // We don't actually need the path to the thumbnail file, // we just use it to see if there is album art or not String art = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Audio.Albums.ALBUM_ART)); if (unknown || art == null || art.length() == 0) { iv.setBackgroundDrawable(mDefaultAlbumIcon); iv.setImageDrawable(null); } else { long artIndex = cursor.getLong(0); Drawable d = MusicUtils.getCachedArtwork(context, artIndex, mDefaultAlbumIcon); iv.setImageDrawable(d); } long currentalbumid = MusicUtils.getCurrentAlbumId(); long aid = cursor.getLong(0); iv = vh.play_indicator; if (currentalbumid == aid) { iv.setImageDrawable(mNowPlayingOverlay); } else { iv.setImageDrawable(null); } } @Override protected Cursor getChildrenCursor(Cursor groupCursor) { long id = groupCursor.getLong(groupCursor.getColumnIndexOrThrow(MediaStore.Audio.Artists._ID)); String[] cols = new String[] { MediaStore.Audio.Albums._ID, MediaStore.Audio.Albums.ALBUM, MediaStore.Audio.Albums.NUMBER_OF_SONGS, MediaStore.Audio.Albums.NUMBER_OF_SONGS_FOR_ARTIST, MediaStore.Audio.Albums.ALBUM_ART }; Cursor c = MusicUtils.query(mActivity.getActivity(), MediaStore.Audio.Artists.Albums.getContentUri("external", id), cols, null, null, MediaStore.Audio.Albums.DEFAULT_SORT_ORDER); class MyCursorWrapper extends CursorWrapper { String mArtistName; int mMagicColumnIdx; MyCursorWrapper(Cursor c, String artist) { super(c); mArtistName = artist; if (mArtistName == null || mArtistName.equals(MediaStore.UNKNOWN_STRING)) { mArtistName = mUnknownArtist; } mMagicColumnIdx = c.getColumnCount(); } @Override public String getString(int columnIndex) { if (columnIndex != mMagicColumnIdx) { return super.getString(columnIndex); } return mArtistName; } @Override public int getColumnIndexOrThrow(String name) { if (MediaStore.Audio.Albums.ARTIST.equals(name)) { return mMagicColumnIdx; } return super.getColumnIndexOrThrow(name); } @Override public String getColumnName(int idx) { if (idx != mMagicColumnIdx) { return super.getColumnName(idx); } return MediaStore.Audio.Albums.ARTIST; } @Override public int getColumnCount() { return super.getColumnCount() + 1; } } return new MyCursorWrapper(c, groupCursor.getString(mGroupArtistIdx)); } @Override public void changeCursor(Cursor cursor) { if (mActivity.getActivity().isFinishing() && cursor != null) { cursor.close(); cursor = null; } if (cursor != mActivity.mArtistCursor) { mActivity.mArtistCursor = cursor; getColumnIndices(cursor); super.changeCursor(cursor); } } @Override public Cursor runQueryOnBackgroundThread(CharSequence constraint) { String s = constraint.toString(); if (mConstraintIsValid && ((s == null && mConstraint == null) || (s != null && s.equals(mConstraint)))) { return getCursor(); } Cursor c = mActivity.getArtistCursor(null, s); mConstraint = s; mConstraintIsValid = true; return c; } public Object[] getSections() { return mIndexer.getSections(); } public int getPositionForSection(int sectionIndex) { return mIndexer.getPositionForSection(sectionIndex); } public int getSectionForPosition(int position) { return 0; } } public void onServiceConnected(ComponentName name, IBinder service) { MusicUtils.updateNowPlaying(getActivity()); } public void onServiceDisconnected(ComponentName name) { mArtistCursor.close(); } }