Java tutorial
/* * Copyright (C) 2014 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.chinaftw.music.model; import android.media.MediaMetadata; import android.os.AsyncTask; import android.util.Log; import com.chinaftw.music.api.MusicAPI; import com.chinaftw.music.utils.LogHelper; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import java.io.BufferedInputStream; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.net.URL; import java.net.URLConnection; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Locale; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; public class MusicProvider { private static final String TAG = LogHelper.makeLogTag(MusicProvider.class); public static final String CUSTOM_METADATA_TRACK_SOURCE = "__SOURCE__"; /** JSON OUTPUT { "result": { "songCount": 1, "songs": [ { "album": { "name": "\u795e\u7684\u6e38\u620f", "id": 32311, }, "name": "\u73ab\u7470\u8272\u7684\u4f60", "artists": [ { "picUrl": null, "id": 10557, "name": "\u5f20\u60ac" } ], "duration": 297927, "id": 326695 } ] } }**/ private static final String JSON_RESULT = "result"; //Object | Ground level (0) private static final String JSON_SONG_COUNT = "songCount"; //Int | RESULT (1) private static final String JSON_TRACKS = "tracks"; //Array | RESULT (1) private static final String JSON_SONGS = "songs"; //Array | RESULT (1) private static final String JSON_ALBUM = "album"; //Object | SONGS (2) private static final String JSON_ALBUM_ID = "id"; //Int | ALBUM (3) private static final String JSON_ALBUM_NAME = "name"; //String | ALBUM (3) private static final String JSON_ALBUM_ARTIST = "artist"; //String | ALBUM (3) private static final String JSON_ALBUM_IMAGE_ID = "picId"; //? | ARTIST (4) private static final String JSON_TITLE = "name"; //String | SONGS (2) private static final String JSON_ARTIST = "artists"; //Array | SONGS (2) private static final String JSON_ARTIST_ID = "id"; //Int | ARTIST (3) private static final String JSON_ARTIST_NAME = "name"; //String | ARTIST (3) private static final String JSON_SONG_ID = "id"; //Int | SONGS (2) private static final String JSON_DURATION = "duration"; //Int | SONGS (2) // Categorized caches for music track data: private ConcurrentMap<String, List<MediaMetadata>> mMusicListByGenre; private final ConcurrentMap<String, MutableMediaMetadata> mMusicListById; private final Set<String> mFavoriteTracks; enum State { NON_INITIALIZED, INITIALIZING, INITIALIZED } private volatile State mCurrentState = State.NON_INITIALIZED; public interface Callback { void onMusicCatalogReady(boolean success); } public MusicProvider() { mMusicListByGenre = new ConcurrentHashMap<>(); mMusicListById = new ConcurrentHashMap<>(); mFavoriteTracks = Collections.newSetFromMap(new ConcurrentHashMap<String, Boolean>()); } /** * Get an iterator over the list of genres * * @return genres */ public Iterable<String> getGenres() { if (mCurrentState != State.INITIALIZED) { return Collections.emptyList(); } return mMusicListByGenre.keySet(); } public Iterable<MediaMetadata> getMusicsByGenre(String genre) { if (mCurrentState != State.INITIALIZED || !mMusicListByGenre.containsKey(genre)) { return Collections.emptyList(); } return mMusicListByGenre.get(genre); } /** * Very basic implementation of a search that filter music tracks with title containing * the given query. * */ public Iterable<MediaMetadata> searchMusicBySongTitle(String query) { return searchMusic(MediaMetadata.METADATA_KEY_TITLE, query); } /** * Very basic implementation of a search that filter music tracks with album containing * the given query. * */ public Iterable<MediaMetadata> searchMusicByAlbum(String query) { return searchMusic(MediaMetadata.METADATA_KEY_ALBUM, query); } /** * Very basic implementation of a search that filter music tracks with artist containing * the given query. * */ public Iterable<MediaMetadata> searchMusicByArtist(String query) { return searchMusic(MediaMetadata.METADATA_KEY_ARTIST, query); } Iterable<MediaMetadata> searchMusic(String metadataField, String query) { if (mCurrentState != State.INITIALIZED) { return Collections.emptyList(); } ArrayList<MediaMetadata> result = new ArrayList<>(); query = query.toLowerCase(Locale.US); for (MutableMediaMetadata track : mMusicListById.values()) { if (track.metadata.getString(metadataField).toLowerCase(Locale.US).contains(query)) { result.add(track.metadata); } } return result; } /** * Return the MediaMetadata for the given musicID. * * @param musicId The unique, non-hierarchical music ID. */ public MediaMetadata getMusic(String musicId) { return mMusicListById.containsKey(musicId) ? mMusicListById.get(musicId).metadata : null; } public synchronized void updateMusic(String musicId, MediaMetadata metadata) { MutableMediaMetadata track = mMusicListById.get(musicId); if (track == null) { return; } String oldGenre = track.metadata.getString(MediaMetadata.METADATA_KEY_GENRE); String newGenre = metadata.getString(MediaMetadata.METADATA_KEY_GENRE); track.metadata = metadata; // if genre has changed, we need to rebuild the list by genre if (!oldGenre.equals(newGenre)) { buildListsByGenre(); } } public void setFavorite(String musicId, boolean favorite) { if (favorite) { mFavoriteTracks.add(musicId); } else { mFavoriteTracks.remove(musicId); } } public boolean isFavorite(String musicId) { return mFavoriteTracks.contains(musicId); } public boolean isInitialized() { return mCurrentState == State.INITIALIZED; } /** * Get the list of music tracks from a server and caches the track information * for future reference, keying tracks by musicId and grouping by genre. */ public void retrieveMediaAsync(final Callback callback) { LogHelper.d(TAG, "retrieveMediaAsync called"); if (mCurrentState == State.INITIALIZED) { // Nothing to do, execute callback immediately callback.onMusicCatalogReady(true); return; } // Asynchronously load the music catalog in a separate thread new AsyncTask<Void, Void, State>() { @Override protected State doInBackground(Void... params) { retrieveMedia(); return mCurrentState; } @Override protected void onPostExecute(State current) { if (callback != null) { callback.onMusicCatalogReady(current == State.INITIALIZED); } } }.execute(); } private synchronized void buildListsByGenre() { ConcurrentMap<String, List<MediaMetadata>> newMusicListByGenre = new ConcurrentHashMap<>(); for (MutableMediaMetadata m : mMusicListById.values()) { String genre = m.metadata.getString(MediaMetadata.METADATA_KEY_GENRE); List<MediaMetadata> list = newMusicListByGenre.get(genre); if (list == null) { list = new ArrayList<>(); newMusicListByGenre.put(genre, list); } list.add(m.metadata); } mMusicListByGenre = newMusicListByGenre; } private synchronized void retrieveMedia() { try { if (mCurrentState == State.NON_INITIALIZED) { mCurrentState = State.INITIALIZING; JSONObject jsonObj = MusicAPI.getPlaylistDetails("60198"); if (jsonObj == null) { return; } JSONArray tracks = jsonObj.getJSONArray(JSON_TRACKS); if (tracks != null) { for (int j = 0; j < 20; j++) { MediaMetadata item = buildFromJSON(tracks.getJSONObject(j)); String musicId = item.getString(MediaMetadata.METADATA_KEY_MEDIA_ID); mMusicListById.put(musicId, new MutableMediaMetadata(musicId, item)); } buildListsByGenre(); } mCurrentState = State.INITIALIZED; } } catch (JSONException e) { e.printStackTrace(); } finally { if (mCurrentState != State.INITIALIZED) { // Something bad happened, so we reset state to NON_INITIALIZED to allow // retries (eg if the network connection is temporary unavailable) mCurrentState = State.NON_INITIALIZED; } } } private MediaMetadata buildFromJSON(JSONObject json) throws JSONException { String id = String.valueOf(json.getInt(JSON_SONG_ID)); String title = json.getString(JSON_TITLE); String album = json.getJSONObject(JSON_ALBUM).getString(JSON_ALBUM_NAME); String albumId = json.getJSONObject(JSON_ALBUM).getString(JSON_ALBUM_ID); String artist = json.getJSONArray(JSON_ARTIST).getJSONObject(0).getString(JSON_ARTIST_NAME); String artistId = json.getJSONObject(JSON_ALBUM).getString(JSON_ARTIST_ID); String imageId = json.getJSONObject(JSON_ALBUM).getString(JSON_ALBUM_IMAGE_ID); int duration = json.getInt(JSON_DURATION); // ms LogHelper.d(TAG, "Found music track: ", json); String source = MusicAPI.getSongUrl(id); String imageUri = MusicAPI.getPictureUrl(imageId); // Since we don't have a unique ID in the server, we fake one using the hashcode of // the music source. In a real world app, this could come from the server. // Adding the music source to the MediaMetadata (and consequently using it in the // mediaSession.setMetadata) is not a good idea for a real world music app, because // the session metadata can be accessed by notification listeners. This is done in this // sample for convenience only. return new MediaMetadata.Builder().putString(MediaMetadata.METADATA_KEY_MEDIA_ID, id) .putString(CUSTOM_METADATA_TRACK_SOURCE, source).putString(MediaMetadata.METADATA_KEY_ALBUM, album) .putString(MediaMetadata.METADATA_KEY_ARTIST, artist) .putLong(MediaMetadata.METADATA_KEY_DURATION, duration) .putString(MediaMetadata.METADATA_KEY_GENRE, "BILLBOARD") //TODO .putString(MediaMetadata.METADATA_KEY_TITLE, title) .putString(MediaMetadata.METADATA_KEY_ART_URI, imageUri) .putString(MediaMetadata.METADATA_KEY_ALBUM_ART_URI, imageUri) .putString(MediaMetadata.METADATA_KEY_DISPLAY_ICON_URI, imageUri).build(); } }