From 2b3aea51faeacda81b56007dead357480753722a Mon Sep 17 00:00:00 2001 From: Tom Date: Tue, 4 Jun 2013 19:56:51 +0100 Subject: updated project files and keystore --- .gitignore | 3 ++- subsonic-android/ant.properties | 20 -------------------- subsonic-android/subsonic.keystore | Bin 1194 -> 2262 bytes 3 files changed, 2 insertions(+), 21 deletions(-) delete mode 100644 subsonic-android/ant.properties diff --git a/.gitignore b/.gitignore index 686306c4..553414a3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ +subsonic-android/ant.properties subsonic-android/.classpath subsonic-android/.project subsonic-android/bin/* subsonic-android/gen/* subsonic-android/private/* -subsonic-android/nbandroid/* \ No newline at end of file +subsonic-android/nbandroid/* diff --git a/subsonic-android/ant.properties b/subsonic-android/ant.properties deleted file mode 100644 index de5f19ef..00000000 --- a/subsonic-android/ant.properties +++ /dev/null @@ -1,20 +0,0 @@ -# This file is used to override default values used by the Ant build system. -# -# This file must be checked in Version Control Systems, as it is -# integral to the build system of your project. - -# This file is only used by the Ant script. - -# You can use this to override default values such as -# 'source.dir' for the location of your java source folder and -# 'out.dir' for the location of your output folder. - -# You can also use it define how the release builds are signed by declaring -# the following properties: -# 'key.store' for the location of your keystore and -# 'key.alias' for the name of the key to use. -# The password will be asked during the build when you use the 'release' target. - -key.store=C:/Users/Scott/Documents/Subsonic/subsonic-android/subsonic.keystore -key.alias=subsonic - diff --git a/subsonic-android/subsonic.keystore b/subsonic-android/subsonic.keystore index 46996d4c..60d57868 100644 Binary files a/subsonic-android/subsonic.keystore and b/subsonic-android/subsonic.keystore differ -- cgit v1.2.3 From 96d6c306e75533423137a31b3cff378f7f916d8d Mon Sep 17 00:00:00 2001 From: Tom Date: Tue, 4 Jun 2013 20:02:56 +0100 Subject: Added Offline Scrobbling functionality: FileUtil: new function to return name of offline scrobbles file CachedMusicService: added code to scrobble() that appends to offline scrobbles file a search query made from song path and the millis time of that song play RESTMusicService: override scrobble function that also takes time in millis of scrobble RESTMusicService: new function to look for offline scrobbleis file and process it by using searchNew to get the song id using the cached query (needs putting on a thread) RESTMusicServer: Added call to processOfflineScrobbles in checkLicence --- .../dsub/service/OfflineMusicService.java | 23 +++++++- .../daneren2005/dsub/service/RESTMusicService.java | 67 +++++++++++++++++++++- .../github/daneren2005/dsub/util/Constants.java | 2 + .../src/github/daneren2005/dsub/util/FileUtil.java | 5 ++ .../src/github/daneren2005/dsub/util/Util.java | 4 +- 5 files changed, 96 insertions(+), 5 deletions(-) diff --git a/subsonic-android/src/github/daneren2005/dsub/service/OfflineMusicService.java b/subsonic-android/src/github/daneren2005/dsub/service/OfflineMusicService.java index 9af58b16..83c7f960 100644 --- a/subsonic-android/src/github/daneren2005/dsub/service/OfflineMusicService.java +++ b/subsonic-android/src/github/daneren2005/dsub/service/OfflineMusicService.java @@ -36,6 +36,7 @@ import android.content.SharedPreferences; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.media.MediaMetadataRetriever; +import android.os.Environment; import android.util.Log; import github.daneren2005.dsub.domain.Artist; import github.daneren2005.dsub.domain.Genre; @@ -429,7 +430,27 @@ public class OfflineMusicService extends RESTMusicService { @Override public void scrobble(String id, boolean submission, Context context, ProgressListener progressListener) throws Exception { - throw new OfflineException("Scrobbling not available in offline mode"); + + if(!submission) + return; + + SharedPreferences prefs = Util.getPreferences(context); + String cacheLocn = prefs.getString(Constants.PREFERENCES_KEY_CACHE_LOCATION, null); + + File offlineScrobblesFile = FileUtil.getOfflineScrobblesFile(); + + String scrobbleSearchCriteria = id.replace(cacheLocn, ""); + if(scrobbleSearchCriteria.startsWith("/")) + scrobbleSearchCriteria = scrobbleSearchCriteria.substring(1); + + scrobbleSearchCriteria = scrobbleSearchCriteria.replace(".complete", "").replace(".partial", ""); + scrobbleSearchCriteria = scrobbleSearchCriteria.replace("/", " ").replace("-", "\\-").replace(".", "\\."); + + BufferedWriter bw = new BufferedWriter(new FileWriter(offlineScrobblesFile)); + bw.write(scrobbleSearchCriteria + "," + System.currentTimeMillis()); + bw.newLine(); + bw.flush(); + bw.close(); } @Override diff --git a/subsonic-android/src/github/daneren2005/dsub/service/RESTMusicService.java b/subsonic-android/src/github/daneren2005/dsub/service/RESTMusicService.java index f4f64046..be0763e7 100644 --- a/subsonic-android/src/github/daneren2005/dsub/service/RESTMusicService.java +++ b/subsonic-android/src/github/daneren2005/dsub/service/RESTMusicService.java @@ -163,6 +163,54 @@ public class RESTMusicService implements MusicService { return org.apache.http.conn.ssl.SSLSocketFactory.getSocketFactory(); } } + + public void processOfflineScrobbles(final Context context, final ProgressListener progressListener){ + File offlineScrobblesFile = FileUtil.getOfflineScrobblesFile(); + try{ + + BufferedReader br = new BufferedReader(new FileReader(offlineScrobblesFile)); + String line; + + ArrayList lines = new ArrayList(); + while ((line = br.readLine()) != null) { + lines.add(line); + } + br.close(); + offlineScrobblesFile.delete(); + + //TODO make a prompt: "Found " + lines.size() + " offline scrobbles. Scrobble? Ignore? Clear File?" + for(int i = 0; i < lines.size(); i++){ + line = lines.get(i); + String filename = line.substring(0, line.lastIndexOf(',')); + + Log.i(TAG, "Searching for id of file" + filename); + try{ + long time = Long.parseLong(line.substring(line.lastIndexOf(',')+1)); + SearchCritera critera = new SearchCritera(filename, 0, 0, 1); + SearchResult result = searchNew(critera, context, progressListener); + if(result.getSongs().size() == 1){ + Log.i(TAG, "Query '" + filename + "' returned song " + result.getSongs().get(0).getTitle() + " by " + result.getSongs().get(0).getArtist() + " with id " + result.getSongs().get(0).getId()); + Log.i(TAG, "Scrobbling " + result.getSongs().get(0).getId() + " with time " + time); + scrobble(result.getSongs().get(0).getId(), true, time, context, progressListener); + } + } + catch(Exception e){ + Log.e(TAG, e.toString()); + BufferedWriter bw = new BufferedWriter(new FileWriter(offlineScrobblesFile)); + bw.write(line); + bw.newLine(); + bw.flush(); + bw.close(); + } + } + } + catch(FileNotFoundException fnfe){ + //ignore, we dont care + } + catch(Exception e){ + Log.e(TAG, e.toString()); + } + } @Override public void ping(Context context, ProgressListener progressListener) throws Exception { @@ -176,7 +224,11 @@ public class RESTMusicService implements MusicService { @Override public boolean isLicenseValid(Context context, ProgressListener progressListener) throws Exception { - Reader reader = getReader(context, progressListener, "getLicense", null); + + //TODO run on a thread + processOfflineScrobbles(context, progressListener); + + Reader reader = getReader(context, progressListener, "getLicense", null); try { ServerInfo serverInfo = new LicenseParser(context).parse(reader); return serverInfo.isLicenseValid(); @@ -186,6 +238,7 @@ public class RESTMusicService implements MusicService { } public List getMusicFolders(boolean refresh, Context context, ProgressListener progressListener) throws Exception { + List cachedMusicFolders = readCachedMusicFolders(context); if (cachedMusicFolders != null && !refresh) { return cachedMusicFolders; @@ -464,8 +517,18 @@ public class RESTMusicService implements MusicService { @Override public void scrobble(String id, boolean submission, Context context, ProgressListener progressListener) throws Exception { + scrobble(id, submission, 0, context, progressListener); + } + + public void scrobble(String id, boolean submission, long time, Context context, ProgressListener progressListener) throws Exception { checkServerVersion(context, "1.5", "Scrobbling not supported."); - Reader reader = getReader(context, progressListener, "scrobble", null, Arrays.asList("id", "submission"), Arrays.asList(id, submission)); + Reader reader; + if(time > 0){ + checkServerVersion(context, "1.8", "Scrobbling with a time not supported."); + reader = getReader(context, progressListener, "scrobble", null, Arrays.asList("id", "submission", "time"), Arrays.asList(id, submission, time)); + } + else + reader = getReader(context, progressListener, "scrobble", null, Arrays.asList("id", "submission"), Arrays.asList(id, submission)); try { new ErrorParser(context).parse(reader); } finally { diff --git a/subsonic-android/src/github/daneren2005/dsub/util/Constants.java b/subsonic-android/src/github/daneren2005/dsub/util/Constants.java index 5de13e73..af5f7e08 100644 --- a/subsonic-android/src/github/daneren2005/dsub/util/Constants.java +++ b/subsonic-android/src/github/daneren2005/dsub/util/Constants.java @@ -125,6 +125,8 @@ public final class Constants { public static final String DONATION_URL = "http://subsonic.org/pages/android-donation.jsp"; public static final String ALBUM_ART_FILE = "albumart.jpg"; + + public static final String OFFLINE_SCROBBLES_FILE = "offline_scrobbles.log"; private Constants() { } diff --git a/subsonic-android/src/github/daneren2005/dsub/util/FileUtil.java b/subsonic-android/src/github/daneren2005/dsub/util/FileUtil.java index 7ead874c..ca06b55f 100644 --- a/subsonic-android/src/github/daneren2005/dsub/util/FileUtil.java +++ b/subsonic-android/src/github/daneren2005/dsub/util/FileUtil.java @@ -149,6 +149,11 @@ public class FileUtil { } return dir; } + + public static File getOfflineScrobblesFile(){ + File offlineScrobblesFile = new File(getSubsonicDirectory(), fileSystemSafe(Constants.OFFLINE_SCROBBLES_FILE)); + return offlineScrobblesFile; + } public static void createDirectoryForParent(File file) { File dir = file.getParentFile(); diff --git a/subsonic-android/src/github/daneren2005/dsub/util/Util.java b/subsonic-android/src/github/daneren2005/dsub/util/Util.java index a8918923..9d785212 100644 --- a/subsonic-android/src/github/daneren2005/dsub/util/Util.java +++ b/subsonic-android/src/github/daneren2005/dsub/util/Util.java @@ -147,9 +147,9 @@ public final class Util { } public static boolean isScrobblingEnabled(Context context) { - if (isOffline(context)) { + /*if (isOffline(context)) { return false; - } + }*/ SharedPreferences prefs = getPreferences(context); return prefs.getBoolean(Constants.PREFERENCES_KEY_SCROBBLE, false); } -- cgit v1.2.3 From 240f7521c82f693863b3ba398d2a75a2d3b67399 Mon Sep 17 00:00:00 2001 From: Tom Briden Date: Wed, 5 Jun 2013 08:30:25 +0100 Subject: open the offline scrobbles file in append mode, d'oh --- .../src/github/daneren2005/dsub/service/OfflineMusicService.java | 2 +- .../src/github/daneren2005/dsub/service/RESTMusicService.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/subsonic-android/src/github/daneren2005/dsub/service/OfflineMusicService.java b/subsonic-android/src/github/daneren2005/dsub/service/OfflineMusicService.java index 83c7f960..b8050bb2 100644 --- a/subsonic-android/src/github/daneren2005/dsub/service/OfflineMusicService.java +++ b/subsonic-android/src/github/daneren2005/dsub/service/OfflineMusicService.java @@ -446,7 +446,7 @@ public class OfflineMusicService extends RESTMusicService { scrobbleSearchCriteria = scrobbleSearchCriteria.replace(".complete", "").replace(".partial", ""); scrobbleSearchCriteria = scrobbleSearchCriteria.replace("/", " ").replace("-", "\\-").replace(".", "\\."); - BufferedWriter bw = new BufferedWriter(new FileWriter(offlineScrobblesFile)); + BufferedWriter bw = new BufferedWriter(new FileWriter(offlineScrobblesFile, true)); bw.write(scrobbleSearchCriteria + "," + System.currentTimeMillis()); bw.newLine(); bw.flush(); diff --git a/subsonic-android/src/github/daneren2005/dsub/service/RESTMusicService.java b/subsonic-android/src/github/daneren2005/dsub/service/RESTMusicService.java index be0763e7..ce17754a 100644 --- a/subsonic-android/src/github/daneren2005/dsub/service/RESTMusicService.java +++ b/subsonic-android/src/github/daneren2005/dsub/service/RESTMusicService.java @@ -196,7 +196,7 @@ public class RESTMusicService implements MusicService { } catch(Exception e){ Log.e(TAG, e.toString()); - BufferedWriter bw = new BufferedWriter(new FileWriter(offlineScrobblesFile)); + BufferedWriter bw = new BufferedWriter(new FileWriter(offlineScrobblesFile, true)); bw.write(line); bw.newLine(); bw.flush(); -- cgit v1.2.3 From 24bb93ce26cbe60fb2de373ddc914b8a74fe8ea2 Mon Sep 17 00:00:00 2001 From: Tom Briden Date: Wed, 5 Jun 2013 08:33:40 +0100 Subject: Revert "Moved metadata parsing back to createEntry, added disc number parsing" This reverts commit 9e14459fb8691624febe20e9df3d35518c6ec422. This change made loading playlists while offline reallly slow --- .../dsub/service/OfflineMusicService.java | 21 --------------------- .../src/github/daneren2005/dsub/view/SongView.java | 17 +++++++++++++++++ 2 files changed, 17 insertions(+), 21 deletions(-) diff --git a/subsonic-android/src/github/daneren2005/dsub/service/OfflineMusicService.java b/subsonic-android/src/github/daneren2005/dsub/service/OfflineMusicService.java index b8050bb2..d2f99d0b 100644 --- a/subsonic-android/src/github/daneren2005/dsub/service/OfflineMusicService.java +++ b/subsonic-android/src/github/daneren2005/dsub/service/OfflineMusicService.java @@ -172,27 +172,6 @@ public class OfflineMusicService extends RESTMusicService { // Failed parseInt, just means track filled out } } - - try { - MediaMetadataRetriever metadata = new MediaMetadataRetriever(); - metadata.setDataSource(file.getAbsolutePath()); - String discNumber = metadata.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DISC_NUMBER); - if(discNumber == null) { - discNumber = "1/1"; - } - int slashIndex = discNumber.indexOf("/"); - if(slashIndex > 0) { - discNumber = discNumber.substring(0, slashIndex); - } - entry.setDiscNumber(Integer.parseInt(discNumber)); - String bitrate = metadata.extractMetadata(MediaMetadataRetriever.METADATA_KEY_BITRATE); - entry.setBitRate(Integer.parseInt((bitrate != null) ? bitrate : "0") / 1000); - String length = metadata.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION); - entry.setDuration(Integer.parseInt(length) / 1000); - metadata.release(); - } catch(Exception e) { - Log.i(TAG, "Device doesn't properly support MediaMetadataRetreiver"); - } } entry.setTitle(title); diff --git a/subsonic-android/src/github/daneren2005/dsub/view/SongView.java b/subsonic-android/src/github/daneren2005/dsub/view/SongView.java index 40bedad6..51927304 100644 --- a/subsonic-android/src/github/daneren2005/dsub/view/SongView.java +++ b/subsonic-android/src/github/daneren2005/dsub/view/SongView.java @@ -72,6 +72,23 @@ public class SongView extends UpdateView implements Checkable { public void setSong(MusicDirectory.Entry song, boolean checkable) { this.song = song; + if(Util.isOffline(context)) { + DownloadFile downloadFile = new DownloadFile(context, song, false); + File file = downloadFile.getCompleteFile(); + if(file.exists()) { + try { + MediaMetadataRetriever metadata = new MediaMetadataRetriever(); + metadata.setDataSource(file.getAbsolutePath()); + String bitrate = metadata.extractMetadata(MediaMetadataRetriever.METADATA_KEY_BITRATE); + song.setBitRate(Integer.parseInt((bitrate != null) ? bitrate : "0") / 1000); + String length = metadata.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION); + song.setDuration(Integer.parseInt(length) / 1000); + } catch(Exception e) { + Log.i(TAG, "Device doesn't properly support MediaMetadataRetreiver"); + } + } + } + StringBuilder artist = new StringBuilder(40); String bitRate = null; -- cgit v1.2.3 From 081cd153f539a9b0627ec0ceac7fd8e68608f54d Mon Sep 17 00:00:00 2001 From: Tom Briden Date: Wed, 5 Jun 2013 08:55:17 +0100 Subject: readded discnumber parsing to SongView createEntry --- subsonic-android/src/github/daneren2005/dsub/view/SongView.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/subsonic-android/src/github/daneren2005/dsub/view/SongView.java b/subsonic-android/src/github/daneren2005/dsub/view/SongView.java index 51927304..fc50ff79 100644 --- a/subsonic-android/src/github/daneren2005/dsub/view/SongView.java +++ b/subsonic-android/src/github/daneren2005/dsub/view/SongView.java @@ -79,6 +79,15 @@ public class SongView extends UpdateView implements Checkable { try { MediaMetadataRetriever metadata = new MediaMetadataRetriever(); metadata.setDataSource(file.getAbsolutePath()); + String discNumber = metadata.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DISC_NUMBER); + if(discNumber == null) { + discNumber = "1/1"; + } + int slashIndex = discNumber.indexOf("/"); + if(slashIndex > 0) { + discNumber = discNumber.substring(0, slashIndex); + } + song.setDiscNumber(Integer.parseInt(discNumber)); String bitrate = metadata.extractMetadata(MediaMetadataRetriever.METADATA_KEY_BITRATE); song.setBitRate(Integer.parseInt((bitrate != null) ? bitrate : "0") / 1000); String length = metadata.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION); -- cgit v1.2.3 From f313378bc3ddc4023b50c9e8fa0af4b72f34cc7c Mon Sep 17 00:00:00 2001 From: Tom Briden Date: Wed, 5 Jun 2013 14:21:24 +0100 Subject: changed to use a proper lucene query from using just artist/title parsed from filename --- .../github/daneren2005/dsub/service/OfflineMusicService.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/subsonic-android/src/github/daneren2005/dsub/service/OfflineMusicService.java b/subsonic-android/src/github/daneren2005/dsub/service/OfflineMusicService.java index d2f99d0b..9b4dde92 100644 --- a/subsonic-android/src/github/daneren2005/dsub/service/OfflineMusicService.java +++ b/subsonic-android/src/github/daneren2005/dsub/service/OfflineMusicService.java @@ -422,8 +422,14 @@ public class OfflineMusicService extends RESTMusicService { if(scrobbleSearchCriteria.startsWith("/")) scrobbleSearchCriteria = scrobbleSearchCriteria.substring(1); - scrobbleSearchCriteria = scrobbleSearchCriteria.replace(".complete", "").replace(".partial", ""); - scrobbleSearchCriteria = scrobbleSearchCriteria.replace("/", " ").replace("-", "\\-").replace(".", "\\."); + scrobbleSearchCriteria = scrobbleSearchCriteria.replace(".complete", "").replace(".partial", "").replace(".mp3", ""); + String[] details = scrobbleSearchCriteria.split("/"); + + //last.fm only uses artist and track title so broaden the search by just using those. doesn't matter if it find the track on a different album + String artist = "artist:\""+details[0]+"\""; + String title = "title:\""+details[2].substring(details[2].indexOf('-')+1)+"\""; + + scrobbleSearchCriteria = artist + " AND " + title; BufferedWriter bw = new BufferedWriter(new FileWriter(offlineScrobblesFile, true)); bw.write(scrobbleSearchCriteria + "," + System.currentTimeMillis()); -- cgit v1.2.3 From e6413a84ece8dd70bf09b505da8a339b49a1ea5e Mon Sep 17 00:00:00 2001 From: Tom Briden Date: Wed, 5 Jun 2013 14:25:19 +0100 Subject: throw exception if song not found so it can be put back on the list removed unneeded log line --- .../src/github/daneren2005/dsub/service/RESTMusicService.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/subsonic-android/src/github/daneren2005/dsub/service/RESTMusicService.java b/subsonic-android/src/github/daneren2005/dsub/service/RESTMusicService.java index ce17754a..e0bffc57 100644 --- a/subsonic-android/src/github/daneren2005/dsub/service/RESTMusicService.java +++ b/subsonic-android/src/github/daneren2005/dsub/service/RESTMusicService.java @@ -183,7 +183,6 @@ public class RESTMusicService implements MusicService { line = lines.get(i); String filename = line.substring(0, line.lastIndexOf(',')); - Log.i(TAG, "Searching for id of file" + filename); try{ long time = Long.parseLong(line.substring(line.lastIndexOf(',')+1)); SearchCritera critera = new SearchCritera(filename, 0, 0, 1); @@ -193,6 +192,9 @@ public class RESTMusicService implements MusicService { Log.i(TAG, "Scrobbling " + result.getSongs().get(0).getId() + " with time " + time); scrobble(result.getSongs().get(0).getId(), true, time, context, progressListener); } + else{ + throw new Exception("Song not found on server"); + } } catch(Exception e){ Log.e(TAG, e.toString()); -- cgit v1.2.3 From 43fa167ca8b9ffbcd73711a8853fe67ebf4a78a7 Mon Sep 17 00:00:00 2001 From: Tom Briden Date: Wed, 5 Jun 2013 14:38:27 +0100 Subject: Added hasOfflineScrobbles function to MusicService so the fragment can query and if the file exists can prompt to process ignore or delete --- .../dsub/fragments/SelectDirectoryFragment.java | 7 +- .../dsub/service/CachedMusicService.java | 8 ++ .../daneren2005/dsub/service/MusicService.java | 2 + .../dsub/service/OfflineMusicService.java | 13 ++- .../daneren2005/dsub/service/RESTMusicService.java | 108 +++++++++++---------- 5 files changed, 79 insertions(+), 59 deletions(-) diff --git a/subsonic-android/src/github/daneren2005/dsub/fragments/SelectDirectoryFragment.java b/subsonic-android/src/github/daneren2005/dsub/fragments/SelectDirectoryFragment.java index 13f61625..65b11e21 100644 --- a/subsonic-android/src/github/daneren2005/dsub/fragments/SelectDirectoryFragment.java +++ b/subsonic-android/src/github/daneren2005/dsub/fragments/SelectDirectoryFragment.java @@ -347,6 +347,11 @@ public class SelectDirectoryFragment extends SubsonicFragment implements Adapter protected Pair doInBackground() throws Throwable { MusicService musicService = MusicServiceFactory.getMusicService(context); MusicDirectory dir = load(musicService); + //this may be done better elsewhere but i'm guessing licence is checked infrequently enough for it to be ok here + if(musicService.hasOfflineScrobbles()){ + + } + boolean valid = musicService.isLicenseValid(context, this); return new Pair(dir, valid); } @@ -692,4 +697,4 @@ public class SelectDirectoryFragment extends SubsonicFragment implements Adapter return null; } } -} \ No newline at end of file +} diff --git a/subsonic-android/src/github/daneren2005/dsub/service/CachedMusicService.java b/subsonic-android/src/github/daneren2005/dsub/service/CachedMusicService.java index 314f065e..24132bea 100644 --- a/subsonic-android/src/github/daneren2005/dsub/service/CachedMusicService.java +++ b/subsonic-android/src/github/daneren2005/dsub/service/CachedMusicService.java @@ -38,6 +38,7 @@ import github.daneren2005.dsub.domain.SearchResult; import github.daneren2005.dsub.domain.Share; import github.daneren2005.dsub.domain.Version; import github.daneren2005.dsub.util.CancellableTask; +import github.daneren2005.dsub.util.FileUtil; import github.daneren2005.dsub.util.LRUCache; import github.daneren2005.dsub.util.ProgressListener; import github.daneren2005.dsub.util.TimeLimitedCache; @@ -295,6 +296,13 @@ public class CachedMusicService implements MusicService { public MusicDirectory getSongsByGenre(String genre, int count, int offset, Context context, ProgressListener progressListener) throws Exception { return musicService.getSongsByGenre(genre, count, offset, context, progressListener); } + + @Override + public boolean hasOfflineScrobbles(){ + return musicService.hasOfflineScrobbles(); + } + + private void checkSettingsChanged(Context context) { String newUrl = Util.getRestUrl(context, null); diff --git a/subsonic-android/src/github/daneren2005/dsub/service/MusicService.java b/subsonic-android/src/github/daneren2005/dsub/service/MusicService.java index f69ebfb1..1b769fa6 100644 --- a/subsonic-android/src/github/daneren2005/dsub/service/MusicService.java +++ b/subsonic-android/src/github/daneren2005/dsub/service/MusicService.java @@ -115,4 +115,6 @@ public interface MusicService { List getGenres(boolean refresh, Context context, ProgressListener progressListener) throws Exception; public MusicDirectory getSongsByGenre(String genre, int count, int offset, Context context, ProgressListener progressListener) throws Exception; + + boolean hasOfflineScrobbles(); } \ No newline at end of file diff --git a/subsonic-android/src/github/daneren2005/dsub/service/OfflineMusicService.java b/subsonic-android/src/github/daneren2005/dsub/service/OfflineMusicService.java index 9b4dde92..4fa026c0 100644 --- a/subsonic-android/src/github/daneren2005/dsub/service/OfflineMusicService.java +++ b/subsonic-android/src/github/daneren2005/dsub/service/OfflineMusicService.java @@ -19,8 +19,6 @@ package github.daneren2005.dsub.service; import java.io.File; -import java.io.FileInputStream; -import java.io.InputStream; import java.io.Reader; import java.io.FileReader; import java.util.ArrayList; @@ -34,9 +32,6 @@ import java.util.Set; import android.content.Context; import android.content.SharedPreferences; import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.media.MediaMetadataRetriever; -import android.os.Environment; import android.util.Log; import github.daneren2005.dsub.domain.Artist; import github.daneren2005.dsub.domain.Genre; @@ -48,7 +43,6 @@ import github.daneren2005.dsub.domain.MusicFolder; import github.daneren2005.dsub.domain.Playlist; import github.daneren2005.dsub.domain.SearchCritera; import github.daneren2005.dsub.domain.SearchResult; -import github.daneren2005.dsub.service.parser.PlaylistParser; import github.daneren2005.dsub.util.Constants; import github.daneren2005.dsub.util.FileUtil; import github.daneren2005.dsub.util.ProgressListener; @@ -516,6 +510,11 @@ public class OfflineMusicService extends RESTMusicService { return result; } + + @Override + public boolean hasOfflineScrobbles(){ + return false; + } private void listFilesRecursively(File parent, List children) { for (File file : FileUtil.listMediaFiles(parent)) { @@ -526,4 +525,4 @@ public class OfflineMusicService extends RESTMusicService { } } } -} +} \ No newline at end of file diff --git a/subsonic-android/src/github/daneren2005/dsub/service/RESTMusicService.java b/subsonic-android/src/github/daneren2005/dsub/service/RESTMusicService.java index e0bffc57..e609e033 100644 --- a/subsonic-android/src/github/daneren2005/dsub/service/RESTMusicService.java +++ b/subsonic-android/src/github/daneren2005/dsub/service/RESTMusicService.java @@ -162,57 +162,7 @@ public class RESTMusicService implements MusicService { Log.e(TAG, "Failed to create custom SSL socket factory, using default.", x); return org.apache.http.conn.ssl.SSLSocketFactory.getSocketFactory(); } - } - - public void processOfflineScrobbles(final Context context, final ProgressListener progressListener){ - File offlineScrobblesFile = FileUtil.getOfflineScrobblesFile(); - try{ - - BufferedReader br = new BufferedReader(new FileReader(offlineScrobblesFile)); - String line; - - ArrayList lines = new ArrayList(); - while ((line = br.readLine()) != null) { - lines.add(line); - } - br.close(); - offlineScrobblesFile.delete(); - - //TODO make a prompt: "Found " + lines.size() + " offline scrobbles. Scrobble? Ignore? Clear File?" - for(int i = 0; i < lines.size(); i++){ - line = lines.get(i); - String filename = line.substring(0, line.lastIndexOf(',')); - - try{ - long time = Long.parseLong(line.substring(line.lastIndexOf(',')+1)); - SearchCritera critera = new SearchCritera(filename, 0, 0, 1); - SearchResult result = searchNew(critera, context, progressListener); - if(result.getSongs().size() == 1){ - Log.i(TAG, "Query '" + filename + "' returned song " + result.getSongs().get(0).getTitle() + " by " + result.getSongs().get(0).getArtist() + " with id " + result.getSongs().get(0).getId()); - Log.i(TAG, "Scrobbling " + result.getSongs().get(0).getId() + " with time " + time); - scrobble(result.getSongs().get(0).getId(), true, time, context, progressListener); - } - else{ - throw new Exception("Song not found on server"); - } - } - catch(Exception e){ - Log.e(TAG, e.toString()); - BufferedWriter bw = new BufferedWriter(new FileWriter(offlineScrobblesFile, true)); - bw.write(line); - bw.newLine(); - bw.flush(); - bw.close(); - } - } - } - catch(FileNotFoundException fnfe){ - //ignore, we dont care - } - catch(Exception e){ - Log.e(TAG, e.toString()); - } - } + } @Override public void ping(Context context, ProgressListener progressListener) throws Exception { @@ -891,6 +841,62 @@ public class RESTMusicService implements MusicService { Util.close(reader); } } + + @Override + public boolean hasOfflineScrobbles(){ + return FileUtil.getOfflineScrobblesFile().exists(); + } + + public void processOfflineScrobbles(final Context context, final ProgressListener progressListener){ + File offlineScrobblesFile = FileUtil.getOfflineScrobblesFile(); + try{ + + BufferedReader br = new BufferedReader(new FileReader(offlineScrobblesFile)); + String line; + + ArrayList lines = new ArrayList(); + while ((line = br.readLine()) != null) { + lines.add(line); + } + br.close(); + offlineScrobblesFile.delete(); + + for(int i = 0; i < lines.size(); i++){ + line = lines.get(i); + String filename = line.substring(0, line.lastIndexOf(',')); + + try{ + long time = Long.parseLong(line.substring(line.lastIndexOf(',')+1)); + SearchCritera critera = new SearchCritera(filename, 0, 0, 1); + SearchResult result = searchNew(critera, context, progressListener); + if(result.getSongs().size() == 1){ + Log.i(TAG, "Query '" + filename + "' returned song " + result.getSongs().get(0).getTitle() + " by " + result.getSongs().get(0).getArtist() + " with id " + result.getSongs().get(0).getId()); + Log.i(TAG, "Scrobbling " + result.getSongs().get(0).getId() + " with time " + time); + scrobble(result.getSongs().get(0).getId(), true, time, context, progressListener); + } + else{ + throw new Exception("Song not found on server"); + } + } + catch(Exception e){ + Log.e(TAG, e.toString()); + BufferedWriter bw = new BufferedWriter(new FileWriter(offlineScrobblesFile, true)); + bw.write(line); + bw.newLine(); + bw.flush(); + bw.close(); + } + } + } + catch(FileNotFoundException fnfe){ + //ignore, we dont care + } + catch(Exception e){ + Log.e(TAG, e.toString()); + } + } + + private Reader getReader(Context context, ProgressListener progressListener, String method, HttpParams requestParams) throws Exception { return getReader(context, progressListener, method, requestParams, Collections.emptyList(), Collections.emptyList()); -- cgit v1.2.3 From ad6c9503cbb56eda5b0ff3a0060b625f827e753c Mon Sep 17 00:00:00 2001 From: Tom Briden Date: Wed, 5 Jun 2013 15:45:37 +0100 Subject: Added dialog when offline scrobble file found to scrobble now, ignore or just delete it Made processOfflineScrobbles a musicservice function so it can be called from fragment --- subsonic-android/res/values/strings.xml | 5 ++ .../dsub/fragments/SelectDirectoryFragment.java | 66 +++++++++++++++++++--- .../dsub/service/CachedMusicService.java | 5 ++ .../daneren2005/dsub/service/MusicService.java | 2 + .../dsub/service/OfflineMusicService.java | 5 ++ .../daneren2005/dsub/service/RESTMusicService.java | 8 +-- 6 files changed, 79 insertions(+), 12 deletions(-) diff --git a/subsonic-android/res/values/strings.xml b/subsonic-android/res/values/strings.xml index b5e8f3e1..5aa09d76 100644 --- a/subsonic-android/res/values/strings.xml +++ b/subsonic-android/res/values/strings.xml @@ -114,6 +114,11 @@ Now Later Trial period is over + Offline scrobbles file found. + Process offline scrobbles file? + Yes + No + Delete file No genres found diff --git a/subsonic-android/src/github/daneren2005/dsub/fragments/SelectDirectoryFragment.java b/subsonic-android/src/github/daneren2005/dsub/fragments/SelectDirectoryFragment.java index 65b11e21..c4da3070 100644 --- a/subsonic-android/src/github/daneren2005/dsub/fragments/SelectDirectoryFragment.java +++ b/subsonic-android/src/github/daneren2005/dsub/fragments/SelectDirectoryFragment.java @@ -27,6 +27,7 @@ import github.daneren2005.dsub.service.MusicServiceFactory; import github.daneren2005.dsub.service.OfflineException; import github.daneren2005.dsub.service.ServerTooOldException; import github.daneren2005.dsub.util.Constants; +import github.daneren2005.dsub.util.FileUtil; import github.daneren2005.dsub.util.LoadingTask; import github.daneren2005.dsub.util.Pair; import github.daneren2005.dsub.util.TabBackgroundTask; @@ -276,6 +277,12 @@ public class SelectDirectoryFragment extends SubsonicFragment implements Adapter } else { getMusicDirectory(id, name, refresh); } + + //this may be done better elsewhere but it works for now :) + MusicService musicService = MusicServiceFactory.getMusicService(context); + if(musicService.hasOfflineScrobbles()){ + showOfflineScrobblesDialog(); + } } private void getMusicDirectory(final String id, final String name, final boolean refresh) { @@ -345,14 +352,9 @@ public class SelectDirectoryFragment extends SubsonicFragment implements Adapter @Override protected Pair doInBackground() throws Throwable { - MusicService musicService = MusicServiceFactory.getMusicService(context); + MusicService musicService = MusicServiceFactory.getMusicService(context); MusicDirectory dir = load(musicService); - //this may be done better elsewhere but i'm guessing licence is checked infrequently enough for it to be ok here - if(musicService.hasOfflineScrobbles()){ - - } - - boolean valid = musicService.isLicenseValid(context, this); + boolean valid = musicService.isLicenseValid(context, this); return new Pair(dir, valid); } @@ -640,6 +642,56 @@ public class SelectDirectoryFragment extends SubsonicFragment implements Adapter builder.create().show(); } + + private void showOfflineScrobblesDialog() { + + + AlertDialog.Builder builder = new AlertDialog.Builder(context); + builder.setIcon(android.R.drawable.ic_dialog_info); + + builder.setTitle(R.string.select_album_offline_scrobbles_dialog_title); + + builder.setMessage(R.string.select_album_offline_scrobbles_dialog_message); + + //want this on the left and delete on the right hence the backwards button types + builder.setNegativeButton(R.string.select_album_offline_scrobbles_dialog_yes, + + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + new Thread("Scrobble offline") { + @Override + public void run() { + try { + MusicService musicService = MusicServiceFactory.getMusicService(context); + musicService.processOfflineScrobbles(context, null); + } catch (Exception x) { + Log.i(TAG, "Failed to process offline sc/robbles"); + } + } + }.start(); + } + }); + + builder.setPositiveButton(R.string.select_album_offline_scrobbles_dialog_delete, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + dialogInterface.dismiss(); + FileUtil.getOfflineScrobblesFile().delete(); + } + }); + + builder.setNeutralButton(R.string.select_album_offline_scrobbles_dialog_no, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + dialogInterface.dismiss(); + } + }); + + builder.create().show(); + } private View createHeader(List entries) { View header = entryList.findViewById(R.id.select_album_header); diff --git a/subsonic-android/src/github/daneren2005/dsub/service/CachedMusicService.java b/subsonic-android/src/github/daneren2005/dsub/service/CachedMusicService.java index 24132bea..49542045 100644 --- a/subsonic-android/src/github/daneren2005/dsub/service/CachedMusicService.java +++ b/subsonic-android/src/github/daneren2005/dsub/service/CachedMusicService.java @@ -301,6 +301,11 @@ public class CachedMusicService implements MusicService { public boolean hasOfflineScrobbles(){ return musicService.hasOfflineScrobbles(); } + + @Override + public void processOfflineScrobbles(final Context context, final ProgressListener progressListener) throws Exception{ + musicService.processOfflineScrobbles(context, progressListener); + } diff --git a/subsonic-android/src/github/daneren2005/dsub/service/MusicService.java b/subsonic-android/src/github/daneren2005/dsub/service/MusicService.java index 1b769fa6..c99551b4 100644 --- a/subsonic-android/src/github/daneren2005/dsub/service/MusicService.java +++ b/subsonic-android/src/github/daneren2005/dsub/service/MusicService.java @@ -117,4 +117,6 @@ public interface MusicService { public MusicDirectory getSongsByGenre(String genre, int count, int offset, Context context, ProgressListener progressListener) throws Exception; boolean hasOfflineScrobbles(); + + void processOfflineScrobbles(final Context context, final ProgressListener progressListener) throws Exception; } \ No newline at end of file diff --git a/subsonic-android/src/github/daneren2005/dsub/service/OfflineMusicService.java b/subsonic-android/src/github/daneren2005/dsub/service/OfflineMusicService.java index 4fa026c0..c2a85488 100644 --- a/subsonic-android/src/github/daneren2005/dsub/service/OfflineMusicService.java +++ b/subsonic-android/src/github/daneren2005/dsub/service/OfflineMusicService.java @@ -515,6 +515,11 @@ public class OfflineMusicService extends RESTMusicService { public boolean hasOfflineScrobbles(){ return false; } + + @Override + public void processOfflineScrobbles(final Context context, final ProgressListener progressListener) throws Exception{ + throw new OfflineException("Offline scrobble cached can not be processes while in offline mode"); + } private void listFilesRecursively(File parent, List children) { for (File file : FileUtil.listMediaFiles(parent)) { diff --git a/subsonic-android/src/github/daneren2005/dsub/service/RESTMusicService.java b/subsonic-android/src/github/daneren2005/dsub/service/RESTMusicService.java index e609e033..27a51145 100644 --- a/subsonic-android/src/github/daneren2005/dsub/service/RESTMusicService.java +++ b/subsonic-android/src/github/daneren2005/dsub/service/RESTMusicService.java @@ -177,10 +177,7 @@ public class RESTMusicService implements MusicService { @Override public boolean isLicenseValid(Context context, ProgressListener progressListener) throws Exception { - //TODO run on a thread - processOfflineScrobbles(context, progressListener); - - Reader reader = getReader(context, progressListener, "getLicense", null); + Reader reader = getReader(context, progressListener, "getLicense", null); try { ServerInfo serverInfo = new LicenseParser(context).parse(reader); return serverInfo.isLicenseValid(); @@ -847,7 +844,8 @@ public class RESTMusicService implements MusicService { return FileUtil.getOfflineScrobblesFile().exists(); } - public void processOfflineScrobbles(final Context context, final ProgressListener progressListener){ + @Override + public void processOfflineScrobbles(final Context context, final ProgressListener progressListener) throws Exception{ File offlineScrobblesFile = FileUtil.getOfflineScrobblesFile(); try{ -- cgit v1.2.3 From 1549d7f25c2ad8e3fe6ca63e6ff66c4e3ee99e1f Mon Sep 17 00:00:00 2001 From: Tom Briden Date: Wed, 5 Jun 2013 16:02:30 +0100 Subject: fix typo in strings.xml --- subsonic-android/res/values/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/subsonic-android/res/values/strings.xml b/subsonic-android/res/values/strings.xml index 5aa09d76..5635a74e 100644 --- a/subsonic-android/res/values/strings.xml +++ b/subsonic-android/res/values/strings.xml @@ -118,7 +118,7 @@ Process offline scrobbles file? Yes No - Delete file + Delete file No genres found -- cgit v1.2.3 From 51cd59e90977f02317cc2b5d4b45dc78250b22b2 Mon Sep 17 00:00:00 2001 From: Tom Date: Fri, 7 Jun 2013 21:30:53 +0100 Subject: set ALBUMARTIST metadata as thats what the lockscreen uses put the album name into the album metadata --- .../github/daneren2005/dsub/util/compat/RemoteControlClientICS.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/subsonic-android/src/github/daneren2005/dsub/util/compat/RemoteControlClientICS.java b/subsonic-android/src/github/daneren2005/dsub/util/compat/RemoteControlClientICS.java index a8fed63d..50989468 100644 --- a/subsonic-android/src/github/daneren2005/dsub/util/compat/RemoteControlClientICS.java +++ b/subsonic-android/src/github/daneren2005/dsub/util/compat/RemoteControlClientICS.java @@ -62,7 +62,8 @@ public class RemoteControlClientICS extends RemoteControlClientHelper { // Update the remote controls mRemoteControl.editMetadata(true) .putString(MediaMetadataRetriever.METADATA_KEY_ARTIST, (currentSong == null) ? null : currentSong.getArtist()) - .putString(MediaMetadataRetriever.METADATA_KEY_ALBUM, (currentSong == null) ? null : currentSong.getArtist()) + .putString(MediaMetadataRetriever.METADATA_KEY_ALBUMARTIST, (currentSong == null) ? null : currentSong.getArtist()) + .putString(MediaMetadataRetriever.METADATA_KEY_ALBUM, (currentSong == null) ? null : currentSong.getAlbum()) .putString(MediaMetadataRetriever.METADATA_KEY_TITLE, (currentSong) == null ? null : currentSong.getTitle()) .putLong(MediaMetadataRetriever.METADATA_KEY_DURATION, (currentSong == null) ? 0 : ((currentSong.getDuration() == null) ? 0 : currentSong.getDuration())) -- cgit v1.2.3 From 1fc1da5a19567eefe888347042edb0cbbf696d27 Mon Sep 17 00:00:00 2001 From: Scott Jackson Date: Mon, 10 Jun 2013 06:20:04 -0700 Subject: Tested and it doesn't appear to be so for all devices (with this change Artist still showed up on the lockscreen for me) --- .../github/daneren2005/dsub/util/compat/RemoteControlClientICS.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/subsonic-android/src/github/daneren2005/dsub/util/compat/RemoteControlClientICS.java b/subsonic-android/src/github/daneren2005/dsub/util/compat/RemoteControlClientICS.java index 50989468..a8fed63d 100644 --- a/subsonic-android/src/github/daneren2005/dsub/util/compat/RemoteControlClientICS.java +++ b/subsonic-android/src/github/daneren2005/dsub/util/compat/RemoteControlClientICS.java @@ -62,8 +62,7 @@ public class RemoteControlClientICS extends RemoteControlClientHelper { // Update the remote controls mRemoteControl.editMetadata(true) .putString(MediaMetadataRetriever.METADATA_KEY_ARTIST, (currentSong == null) ? null : currentSong.getArtist()) - .putString(MediaMetadataRetriever.METADATA_KEY_ALBUMARTIST, (currentSong == null) ? null : currentSong.getArtist()) - .putString(MediaMetadataRetriever.METADATA_KEY_ALBUM, (currentSong == null) ? null : currentSong.getAlbum()) + .putString(MediaMetadataRetriever.METADATA_KEY_ALBUM, (currentSong == null) ? null : currentSong.getArtist()) .putString(MediaMetadataRetriever.METADATA_KEY_TITLE, (currentSong) == null ? null : currentSong.getTitle()) .putLong(MediaMetadataRetriever.METADATA_KEY_DURATION, (currentSong == null) ? 0 : ((currentSong.getDuration() == null) ? 0 : currentSong.getDuration())) -- cgit v1.2.3 From 6a7ce1c3963bc09ac56b90f2129d84e9dc730e1b Mon Sep 17 00:00:00 2001 From: Scott Jackson Date: Mon, 10 Jun 2013 06:22:51 -0700 Subject: Breaks stuff --- subsonic-android/src/github/daneren2005/dsub/view/SongView.java | 9 --------- 1 file changed, 9 deletions(-) diff --git a/subsonic-android/src/github/daneren2005/dsub/view/SongView.java b/subsonic-android/src/github/daneren2005/dsub/view/SongView.java index fc50ff79..51927304 100644 --- a/subsonic-android/src/github/daneren2005/dsub/view/SongView.java +++ b/subsonic-android/src/github/daneren2005/dsub/view/SongView.java @@ -79,15 +79,6 @@ public class SongView extends UpdateView implements Checkable { try { MediaMetadataRetriever metadata = new MediaMetadataRetriever(); metadata.setDataSource(file.getAbsolutePath()); - String discNumber = metadata.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DISC_NUMBER); - if(discNumber == null) { - discNumber = "1/1"; - } - int slashIndex = discNumber.indexOf("/"); - if(slashIndex > 0) { - discNumber = discNumber.substring(0, slashIndex); - } - song.setDiscNumber(Integer.parseInt(discNumber)); String bitrate = metadata.extractMetadata(MediaMetadataRetriever.METADATA_KEY_BITRATE); song.setBitRate(Integer.parseInt((bitrate != null) ? bitrate : "0") / 1000); String length = metadata.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION); -- cgit v1.2.3 From bc2cb9c471472bb250a32a1a1beed89201e18ec7 Mon Sep 17 00:00:00 2001 From: Scott Jackson Date: Mon, 10 Jun 2013 06:23:23 -0700 Subject: Breaks stuff --- .../dsub/service/OfflineMusicService.java | 21 +++++++++++++++++++++ .../src/github/daneren2005/dsub/view/SongView.java | 17 ----------------- 2 files changed, 21 insertions(+), 17 deletions(-) diff --git a/subsonic-android/src/github/daneren2005/dsub/service/OfflineMusicService.java b/subsonic-android/src/github/daneren2005/dsub/service/OfflineMusicService.java index 606eab88..17afbbcc 100644 --- a/subsonic-android/src/github/daneren2005/dsub/service/OfflineMusicService.java +++ b/subsonic-android/src/github/daneren2005/dsub/service/OfflineMusicService.java @@ -166,6 +166,27 @@ public class OfflineMusicService extends RESTMusicService { // Failed parseInt, just means track filled out } } + + try { + MediaMetadataRetriever metadata = new MediaMetadataRetriever(); + metadata.setDataSource(file.getAbsolutePath()); + String discNumber = metadata.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DISC_NUMBER); + if(discNumber == null) { + discNumber = "1/1"; + } + int slashIndex = discNumber.indexOf("/"); + if(slashIndex > 0) { + discNumber = discNumber.substring(0, slashIndex); + } + entry.setDiscNumber(Integer.parseInt(discNumber)); + String bitrate = metadata.extractMetadata(MediaMetadataRetriever.METADATA_KEY_BITRATE); + entry.setBitRate(Integer.parseInt((bitrate != null) ? bitrate : "0") / 1000); + String length = metadata.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION); + entry.setDuration(Integer.parseInt(length) / 1000); + metadata.release(); + } catch(Exception e) { + Log.i(TAG, "Device doesn't properly support MediaMetadataRetreiver"); + } } entry.setTitle(title); diff --git a/subsonic-android/src/github/daneren2005/dsub/view/SongView.java b/subsonic-android/src/github/daneren2005/dsub/view/SongView.java index 51927304..40bedad6 100644 --- a/subsonic-android/src/github/daneren2005/dsub/view/SongView.java +++ b/subsonic-android/src/github/daneren2005/dsub/view/SongView.java @@ -72,23 +72,6 @@ public class SongView extends UpdateView implements Checkable { public void setSong(MusicDirectory.Entry song, boolean checkable) { this.song = song; - if(Util.isOffline(context)) { - DownloadFile downloadFile = new DownloadFile(context, song, false); - File file = downloadFile.getCompleteFile(); - if(file.exists()) { - try { - MediaMetadataRetriever metadata = new MediaMetadataRetriever(); - metadata.setDataSource(file.getAbsolutePath()); - String bitrate = metadata.extractMetadata(MediaMetadataRetriever.METADATA_KEY_BITRATE); - song.setBitRate(Integer.parseInt((bitrate != null) ? bitrate : "0") / 1000); - String length = metadata.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION); - song.setDuration(Integer.parseInt(length) / 1000); - } catch(Exception e) { - Log.i(TAG, "Device doesn't properly support MediaMetadataRetreiver"); - } - } - } - StringBuilder artist = new StringBuilder(40); String bitRate = null; -- cgit v1.2.3 From 55cc8def85de5df7ffb8a5502da38de6e6338e61 Mon Sep 17 00:00:00 2001 From: Scott Jackson Date: Mon, 10 Jun 2013 06:33:37 -0700 Subject: Revert --- subsonic-android/ant.properties | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 subsonic-android/ant.properties diff --git a/subsonic-android/ant.properties b/subsonic-android/ant.properties new file mode 100644 index 00000000..de5f19ef --- /dev/null +++ b/subsonic-android/ant.properties @@ -0,0 +1,20 @@ +# This file is used to override default values used by the Ant build system. +# +# This file must be checked in Version Control Systems, as it is +# integral to the build system of your project. + +# This file is only used by the Ant script. + +# You can use this to override default values such as +# 'source.dir' for the location of your java source folder and +# 'out.dir' for the location of your output folder. + +# You can also use it define how the release builds are signed by declaring +# the following properties: +# 'key.store' for the location of your keystore and +# 'key.alias' for the name of the key to use. +# The password will be asked during the build when you use the 'release' target. + +key.store=C:/Users/Scott/Documents/Subsonic/subsonic-android/subsonic.keystore +key.alias=subsonic + -- cgit v1.2.3 From ed437f8f2666336f1e7b0f3af5a91293247fc95c Mon Sep 17 00:00:00 2001 From: Scott Jackson Date: Mon, 10 Jun 2013 06:34:26 -0700 Subject: Revert --- subsonic-android/subsonic.keystore | Bin 2262 -> 1194 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/subsonic-android/subsonic.keystore b/subsonic-android/subsonic.keystore index 60d57868..46996d4c 100644 Binary files a/subsonic-android/subsonic.keystore and b/subsonic-android/subsonic.keystore differ -- cgit v1.2.3 From d5cfe2f6543558f31e11367540f468d55d9bcfdd Mon Sep 17 00:00:00 2001 From: Scott Jackson Date: Mon, 10 Jun 2013 06:34:32 -0700 Subject: Revert --- .gitignore | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitignore b/.gitignore index 443eb753..709e194a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ -subsonic-android/ant.properties subsonic-android/.classpath subsonic-android/.project subsonic-android/bin/* -- cgit v1.2.3 From 0017012b94a597e4cf24f7425f2e348562f11137 Mon Sep 17 00:00:00 2001 From: Scott Jackson Date: Mon, 10 Jun 2013 06:36:31 -0700 Subject: Fix line spacing --- .../dsub/fragments/SelectDirectoryFragment.java | 8 +- .../daneren2005/dsub/service/MusicService.java | 8 +- .../dsub/service/OfflineMusicService.java | 53 +- .../daneren2005/dsub/service/RESTMusicService.java | 98 +- .../src/github/daneren2005/dsub/util/Util.java | 2145 ++++++++++---------- 5 files changed, 1154 insertions(+), 1158 deletions(-) diff --git a/subsonic-android/src/github/daneren2005/dsub/fragments/SelectDirectoryFragment.java b/subsonic-android/src/github/daneren2005/dsub/fragments/SelectDirectoryFragment.java index a38fa5fd..971bdd62 100644 --- a/subsonic-android/src/github/daneren2005/dsub/fragments/SelectDirectoryFragment.java +++ b/subsonic-android/src/github/daneren2005/dsub/fragments/SelectDirectoryFragment.java @@ -275,10 +275,10 @@ public class SelectDirectoryFragment extends SubsonicFragment implements Adapter } //this may be done better elsewhere but it works for now :) - MusicService musicService = MusicServiceFactory.getMusicService(context); - if(musicService.hasOfflineScrobbles()){ - showOfflineScrobblesDialog(); - } + MusicService musicService = MusicServiceFactory.getMusicService(context); + if(musicService.hasOfflineScrobbles()){ + showOfflineScrobblesDialog(); + } } private void getMusicDirectory(final String id, final String name, final boolean refresh) { diff --git a/subsonic-android/src/github/daneren2005/dsub/service/MusicService.java b/subsonic-android/src/github/daneren2005/dsub/service/MusicService.java index 046610f4..2787b75b 100644 --- a/subsonic-android/src/github/daneren2005/dsub/service/MusicService.java +++ b/subsonic-android/src/github/daneren2005/dsub/service/MusicService.java @@ -117,8 +117,8 @@ public interface MusicService { List getGenres(boolean refresh, Context context, ProgressListener progressListener) throws Exception; public MusicDirectory getSongsByGenre(String genre, int count, int offset, Context context, ProgressListener progressListener) throws Exception; - - boolean hasOfflineScrobbles(); - - void processOfflineScrobbles(final Context context, final ProgressListener progressListener) throws Exception; + + boolean hasOfflineScrobbles(); + + void processOfflineScrobbles(final Context context, final ProgressListener progressListener) throws Exception; } \ No newline at end of file diff --git a/subsonic-android/src/github/daneren2005/dsub/service/OfflineMusicService.java b/subsonic-android/src/github/daneren2005/dsub/service/OfflineMusicService.java index 17afbbcc..3efb22dc 100644 --- a/subsonic-android/src/github/daneren2005/dsub/service/OfflineMusicService.java +++ b/subsonic-android/src/github/daneren2005/dsub/service/OfflineMusicService.java @@ -429,33 +429,34 @@ public class OfflineMusicService extends RESTMusicService { @Override public void scrobble(String id, boolean submission, Context context, ProgressListener progressListener) throws Exception { + if(!submission) { + return; + } + + SharedPreferences prefs = Util.getPreferences(context); + String cacheLocn = prefs.getString(Constants.PREFERENCES_KEY_CACHE_LOCATION, null); + + File offlineScrobblesFile = FileUtil.getOfflineScrobblesFile(); + + String scrobbleSearchCriteria = id.replace(cacheLocn, ""); + if(scrobbleSearchCriteria.startsWith("/")) { + scrobbleSearchCriteria = scrobbleSearchCriteria.substring(1); + } + + scrobbleSearchCriteria = scrobbleSearchCriteria.replace(".complete", "").replace(".partial", "").replace(".mp3", ""); + String[] details = scrobbleSearchCriteria.split("/"); + + //last.fm only uses artist and track title so broaden the search by just using those. doesn't matter if it find the track on a different album + String artist = "artist:\""+details[0]+"\""; + String title = "title:\""+details[2].substring(details[2].indexOf('-')+1)+"\""; + + scrobbleSearchCriteria = artist + " AND " + title; - if(!submission) - return; - - SharedPreferences prefs = Util.getPreferences(context); - String cacheLocn = prefs.getString(Constants.PREFERENCES_KEY_CACHE_LOCATION, null); - - File offlineScrobblesFile = FileUtil.getOfflineScrobblesFile(); - - String scrobbleSearchCriteria = id.replace(cacheLocn, ""); - if(scrobbleSearchCriteria.startsWith("/")) - scrobbleSearchCriteria = scrobbleSearchCriteria.substring(1); - - scrobbleSearchCriteria = scrobbleSearchCriteria.replace(".complete", "").replace(".partial", "").replace(".mp3", ""); - String[] details = scrobbleSearchCriteria.split("/"); - - //last.fm only uses artist and track title so broaden the search by just using those. doesn't matter if it find the track on a different album - String artist = "artist:\""+details[0]+"\""; - String title = "title:\""+details[2].substring(details[2].indexOf('-')+1)+"\""; - - scrobbleSearchCriteria = artist + " AND " + title; - - BufferedWriter bw = new BufferedWriter(new FileWriter(offlineScrobblesFile, true)); - bw.write(scrobbleSearchCriteria + "," + System.currentTimeMillis()); - bw.newLine(); - bw.flush(); - bw.close(); + BufferedWriter bw = new BufferedWriter(new FileWriter(offlineScrobblesFile, true)); + bw.write(scrobbleSearchCriteria + "," + System.currentTimeMillis()); + bw.newLine(); + bw.flush(); + bw.close(); } @Override diff --git a/subsonic-android/src/github/daneren2005/dsub/service/RESTMusicService.java b/subsonic-android/src/github/daneren2005/dsub/service/RESTMusicService.java index 31554ceb..8a84cd21 100644 --- a/subsonic-android/src/github/daneren2005/dsub/service/RESTMusicService.java +++ b/subsonic-android/src/github/daneren2005/dsub/service/RESTMusicService.java @@ -870,60 +870,58 @@ public class RESTMusicService implements MusicService { @Override public boolean hasOfflineScrobbles(){ - return FileUtil.getOfflineScrobblesFile().exists(); + return FileUtil.getOfflineScrobblesFile().exists(); } @Override public void processOfflineScrobbles(final Context context, final ProgressListener progressListener) throws Exception{ - File offlineScrobblesFile = FileUtil.getOfflineScrobblesFile(); - try{ - - BufferedReader br = new BufferedReader(new FileReader(offlineScrobblesFile)); - String line; - - ArrayList lines = new ArrayList(); - while ((line = br.readLine()) != null) { - lines.add(line); - } - br.close(); - offlineScrobblesFile.delete(); - - for(int i = 0; i < lines.size(); i++){ - line = lines.get(i); - String filename = line.substring(0, line.lastIndexOf(',')); - - try{ - long time = Long.parseLong(line.substring(line.lastIndexOf(',')+1)); - SearchCritera critera = new SearchCritera(filename, 0, 0, 1); - SearchResult result = searchNew(critera, context, progressListener); - if(result.getSongs().size() == 1){ - Log.i(TAG, "Query '" + filename + "' returned song " + result.getSongs().get(0).getTitle() + " by " + result.getSongs().get(0).getArtist() + " with id " + result.getSongs().get(0).getId()); - Log.i(TAG, "Scrobbling " + result.getSongs().get(0).getId() + " with time " + time); - scrobble(result.getSongs().get(0).getId(), true, time, context, progressListener); - } - else{ - throw new Exception("Song not found on server"); - } - } - catch(Exception e){ - Log.e(TAG, e.toString()); - BufferedWriter bw = new BufferedWriter(new FileWriter(offlineScrobblesFile, true)); - bw.write(line); - bw.newLine(); - bw.flush(); - bw.close(); - } - } - } - catch(FileNotFoundException fnfe){ - //ignore, we dont care - } - catch(Exception e){ - Log.e(TAG, e.toString()); - } - } - - + File offlineScrobblesFile = FileUtil.getOfflineScrobblesFile(); + try{ + + BufferedReader br = new BufferedReader(new FileReader(offlineScrobblesFile)); + String line; + + ArrayList lines = new ArrayList(); + while ((line = br.readLine()) != null) { + lines.add(line); + } + br.close(); + offlineScrobblesFile.delete(); + + for(int i = 0; i < lines.size(); i++){ + line = lines.get(i); + String filename = line.substring(0, line.lastIndexOf(',')); + + try{ + long time = Long.parseLong(line.substring(line.lastIndexOf(',')+1)); + SearchCritera critera = new SearchCritera(filename, 0, 0, 1); + SearchResult result = searchNew(critera, context, progressListener); + if(result.getSongs().size() == 1){ + Log.i(TAG, "Query '" + filename + "' returned song " + result.getSongs().get(0).getTitle() + " by " + result.getSongs().get(0).getArtist() + " with id " + result.getSongs().get(0).getId()); + Log.i(TAG, "Scrobbling " + result.getSongs().get(0).getId() + " with time " + time); + scrobble(result.getSongs().get(0).getId(), true, time, context, progressListener); + } + else{ + throw new Exception("Song not found on server"); + } + } + catch(Exception e){ + Log.e(TAG, e.toString()); + BufferedWriter bw = new BufferedWriter(new FileWriter(offlineScrobblesFile, true)); + bw.write(line); + bw.newLine(); + bw.flush(); + bw.close(); + } + } + } + catch(FileNotFoundException fnfe){ + //ignore, we dont care + } + catch(Exception e){ + Log.e(TAG, e.toString()); + } + } private Reader getReader(Context context, ProgressListener progressListener, String method, HttpParams requestParams) throws Exception { return getReader(context, progressListener, method, requestParams, Collections.emptyList(), Collections.emptyList()); diff --git a/subsonic-android/src/github/daneren2005/dsub/util/Util.java b/subsonic-android/src/github/daneren2005/dsub/util/Util.java index 18b30d49..269b05d3 100644 --- a/subsonic-android/src/github/daneren2005/dsub/util/Util.java +++ b/subsonic-android/src/github/daneren2005/dsub/util/Util.java @@ -1,1074 +1,1071 @@ -/* - This file is part of Subsonic. - - Subsonic is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Subsonic is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with Subsonic. If not, see . - - Copyright 2009 (C) Sindre Mehus - */ -package github.daneren2005.dsub.util; - -import android.annotation.TargetApi; -import android.app.Activity; -import android.app.AlertDialog; -import android.app.Notification; -import android.app.NotificationManager; -import android.app.PendingIntent; -import android.app.Service; -import android.content.ComponentName; -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; -import android.content.SharedPreferences; -import android.content.res.Resources; -import android.graphics.Bitmap; -import android.graphics.drawable.BitmapDrawable; -import android.graphics.drawable.Drawable; -import android.media.AudioManager; -import android.media.AudioManager.OnAudioFocusChangeListener; -import android.net.ConnectivityManager; -import android.net.NetworkInfo; -import android.net.wifi.WifiManager; -import android.os.Build; -import android.os.Environment; -import android.os.Handler; -import android.support.v4.app.NotificationCompat; -import android.util.Log; -import android.view.Gravity; -import android.view.ViewGroup; -import android.view.KeyEvent; -import android.widget.LinearLayout; -import android.widget.RemoteViews; -import android.widget.TextView; -import android.widget.Toast; -import github.daneren2005.dsub.R; -import github.daneren2005.dsub.activity.MainActivity; -import github.daneren2005.dsub.domain.MusicDirectory; -import github.daneren2005.dsub.domain.PlayerState; -import github.daneren2005.dsub.domain.RepeatMode; -import github.daneren2005.dsub.domain.Version; -import github.daneren2005.dsub.provider.DSubWidgetProvider; -import github.daneren2005.dsub.receiver.MediaButtonIntentReceiver; -import github.daneren2005.dsub.service.DownloadService; -import github.daneren2005.dsub.service.DownloadServiceImpl; -import org.apache.http.HttpEntity; - -import java.io.ByteArrayOutputStream; -import java.io.Closeable; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.UnsupportedEncodingException; -import java.lang.reflect.Constructor; -import java.lang.reflect.Method; -import java.security.MessageDigest; -import java.text.DecimalFormat; -import java.text.NumberFormat; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -/** - * @author Sindre Mehus - * @version $Id$ - */ -public final class Util { - private static final String TAG = Util.class.getSimpleName(); - - private static final DecimalFormat GIGA_BYTE_FORMAT = new DecimalFormat("0.00 GB"); - private static final DecimalFormat MEGA_BYTE_FORMAT = new DecimalFormat("0.00 MB"); - private static final DecimalFormat KILO_BYTE_FORMAT = new DecimalFormat("0 KB"); - - private static DecimalFormat GIGA_BYTE_LOCALIZED_FORMAT = null; - private static DecimalFormat MEGA_BYTE_LOCALIZED_FORMAT = null; - private static DecimalFormat KILO_BYTE_LOCALIZED_FORMAT = null; - private static DecimalFormat BYTE_LOCALIZED_FORMAT = null; - - public static final String EVENT_META_CHANGED = "github.daneren2005.dsub.EVENT_META_CHANGED"; - public static final String EVENT_PLAYSTATE_CHANGED = "github.daneren2005.dsub.EVENT_PLAYSTATE_CHANGED"; - - public static final String AVRCP_PLAYSTATE_CHANGED = "com.android.music.playstatechanged"; - public static final String AVRCP_METADATA_CHANGED = "com.android.music.metachanged"; - - private static boolean hasFocus = false; - private static boolean pauseFocus = false; - private static boolean lowerFocus = false; - - private static final Map SERVER_REST_VERSIONS = new ConcurrentHashMap(); - - // Used by hexEncode() - private static final char[] HEX_DIGITS = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; - - private final static Pair NOTIFICATION_TEXT_COLORS = new Pair(); - private static Toast toast; - - private Util() { - } - - public static boolean isOffline(Context context) { - SharedPreferences prefs = getPreferences(context); - return prefs.getBoolean(Constants.PREFERENCES_KEY_OFFLINE, false); - } - - public static void setOffline(Context context, boolean offline) { - SharedPreferences prefs = getPreferences(context); - SharedPreferences.Editor editor = prefs.edit(); - editor.putBoolean(Constants.PREFERENCES_KEY_OFFLINE, offline); - editor.commit(); - } - - public static boolean isScreenLitOnDownload(Context context) { - SharedPreferences prefs = getPreferences(context); - return prefs.getBoolean(Constants.PREFERENCES_KEY_SCREEN_LIT_ON_DOWNLOAD, false); - } - - public static RepeatMode getRepeatMode(Context context) { - SharedPreferences prefs = getPreferences(context); - return RepeatMode.valueOf(prefs.getString(Constants.PREFERENCES_KEY_REPEAT_MODE, RepeatMode.OFF.name())); - } - - public static void setRepeatMode(Context context, RepeatMode repeatMode) { - SharedPreferences prefs = getPreferences(context); - SharedPreferences.Editor editor = prefs.edit(); - editor.putString(Constants.PREFERENCES_KEY_REPEAT_MODE, repeatMode.name()); - editor.commit(); - } - - public static boolean isScrobblingEnabled(Context context) { - /*if (isOffline(context)) { - return false; - }*/ - SharedPreferences prefs = getPreferences(context); - return prefs.getBoolean(Constants.PREFERENCES_KEY_SCROBBLE, false); - } - - public static void setActiveServer(Context context, int instance) { - SharedPreferences prefs = getPreferences(context); - SharedPreferences.Editor editor = prefs.edit(); - editor.putInt(Constants.PREFERENCES_KEY_SERVER_INSTANCE, instance); - editor.putString(Constants.PREFERENCES_KEY_MUSIC_FOLDER_ID + instance, null); - editor.commit(); - } - - public static int getActiveServer(Context context) { - SharedPreferences prefs = getPreferences(context); - return prefs.getInt(Constants.PREFERENCES_KEY_SERVER_INSTANCE, 1); - } - - public static boolean checkServerVersion(Context context, String requiredVersion) { - Version version = Util.getServerRestVersion(context); - Version required = new Version(requiredVersion); - if(version != null && version.compareTo(required) >= 0) { - return true; - } else { - return false; - } - } - - public static int getServerCount(Context context) { - SharedPreferences prefs = getPreferences(context); - return prefs.getInt(Constants.PREFERENCES_KEY_SERVER_COUNT, 1); - } - - public static void removeInstanceName(Context context, int instance, int activeInstance) { - SharedPreferences prefs = getPreferences(context); - SharedPreferences.Editor editor = prefs.edit(); - - int newInstance = instance + 1; - - String server = prefs.getString(Constants.PREFERENCES_KEY_SERVER_KEY + newInstance, null); - String serverName = prefs.getString(Constants.PREFERENCES_KEY_SERVER_NAME + newInstance, null); - String serverUrl = prefs.getString(Constants.PREFERENCES_KEY_SERVER_URL + newInstance, null); - String userName = prefs.getString(Constants.PREFERENCES_KEY_USERNAME + newInstance, null); - String password = prefs.getString(Constants.PREFERENCES_KEY_PASSWORD + newInstance, null); - - editor.putString(Constants.PREFERENCES_KEY_SERVER_KEY + instance, server); - editor.putString(Constants.PREFERENCES_KEY_SERVER_NAME + instance, serverName); - editor.putString(Constants.PREFERENCES_KEY_SERVER_URL + instance, serverUrl); - editor.putString(Constants.PREFERENCES_KEY_USERNAME + instance, userName); - editor.putString(Constants.PREFERENCES_KEY_PASSWORD + instance, password); - - editor.putString(Constants.PREFERENCES_KEY_SERVER_KEY + newInstance, null); - editor.putString(Constants.PREFERENCES_KEY_SERVER_NAME + newInstance, null); - editor.putString(Constants.PREFERENCES_KEY_SERVER_URL + newInstance, null); - editor.putString(Constants.PREFERENCES_KEY_USERNAME + newInstance, null); - editor.putString(Constants.PREFERENCES_KEY_PASSWORD + newInstance, null); - editor.commit(); - - if (instance == activeInstance) { - if(instance != 1) { - Util.setActiveServer(context, 1); - } else { - Util.setOffline(context, true); - } - } else if (newInstance == activeInstance) { - Util.setActiveServer(context, instance); - } - } - - public static String getServerName(Context context, int instance) { - SharedPreferences prefs = getPreferences(context); - return prefs.getString(Constants.PREFERENCES_KEY_SERVER_NAME + instance, null); - } - - public static String getUserName(Context context, int instance) { - if (instance == 0) { - return context.getResources().getString(R.string.main_offline); - } - SharedPreferences prefs = getPreferences(context); - return prefs.getString(Constants.PREFERENCES_KEY_USERNAME + instance, null); - } - - public static void setServerRestVersion(Context context, Version version) { - SERVER_REST_VERSIONS.put(getActiveServer(context), version); - } - - public static Version getServerRestVersion(Context context) { - return SERVER_REST_VERSIONS.get(getActiveServer(context)); - } - - public static void setSelectedMusicFolderId(Context context, String musicFolderId) { - int instance = getActiveServer(context); - SharedPreferences prefs = getPreferences(context); - SharedPreferences.Editor editor = prefs.edit(); - editor.putString(Constants.PREFERENCES_KEY_MUSIC_FOLDER_ID + instance, musicFolderId); - editor.commit(); - } - - public static String getSelectedMusicFolderId(Context context) { - SharedPreferences prefs = getPreferences(context); - int instance = getActiveServer(context); - return prefs.getString(Constants.PREFERENCES_KEY_MUSIC_FOLDER_ID + instance, null); - } - - public static String getTheme(Context context) { - SharedPreferences prefs = getPreferences(context); - return prefs.getString(Constants.PREFERENCES_KEY_THEME, null); - } - - public static int getMaxBitrate(Context context) { - ConnectivityManager manager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); - NetworkInfo networkInfo = manager.getActiveNetworkInfo(); - if (networkInfo == null) { - return 0; - } - - boolean wifi = networkInfo.getType() == ConnectivityManager.TYPE_WIFI; - SharedPreferences prefs = getPreferences(context); - return Integer.parseInt(prefs.getString(wifi ? Constants.PREFERENCES_KEY_MAX_BITRATE_WIFI : Constants.PREFERENCES_KEY_MAX_BITRATE_MOBILE, "0")); - } - - public static int getMaxVideoBitrate(Context context) { - ConnectivityManager manager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); - NetworkInfo networkInfo = manager.getActiveNetworkInfo(); - if (networkInfo == null) { - return 0; - } - - boolean wifi = networkInfo.getType() == ConnectivityManager.TYPE_WIFI; - SharedPreferences prefs = getPreferences(context); - return Integer.parseInt(prefs.getString(wifi ? Constants.PREFERENCES_KEY_MAX_VIDEO_BITRATE_WIFI : Constants.PREFERENCES_KEY_MAX_VIDEO_BITRATE_MOBILE, "0")); - } - - public static int getPreloadCount(Context context) { - ConnectivityManager manager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); - NetworkInfo networkInfo = manager.getActiveNetworkInfo(); - if (networkInfo == null) { - return 3; - } - - SharedPreferences prefs = getPreferences(context); - boolean wifi = networkInfo.getType() == ConnectivityManager.TYPE_WIFI; - int preloadCount = Integer.parseInt(prefs.getString(wifi ? Constants.PREFERENCES_KEY_PRELOAD_COUNT_WIFI : Constants.PREFERENCES_KEY_PRELOAD_COUNT_MOBILE, "-1")); - return preloadCount == -1 ? Integer.MAX_VALUE : preloadCount; - } - - public static int getCacheSizeMB(Context context) { - SharedPreferences prefs = getPreferences(context); - int cacheSize = Integer.parseInt(prefs.getString(Constants.PREFERENCES_KEY_CACHE_SIZE, "-1")); - return cacheSize == -1 ? Integer.MAX_VALUE : cacheSize; - } - - public static String getRestUrl(Context context, String method) { - StringBuilder builder = new StringBuilder(); - - SharedPreferences prefs = getPreferences(context); - - int instance = prefs.getInt(Constants.PREFERENCES_KEY_SERVER_INSTANCE, 1); - String serverUrl = prefs.getString(Constants.PREFERENCES_KEY_SERVER_URL + instance, null); - String username = prefs.getString(Constants.PREFERENCES_KEY_USERNAME + instance, null); - String password = prefs.getString(Constants.PREFERENCES_KEY_PASSWORD + instance, null); - - // Slightly obfuscate password - password = "enc:" + Util.utf8HexEncode(password); - - builder.append(serverUrl); - if (builder.charAt(builder.length() - 1) != '/') { - builder.append("/"); - } - builder.append("rest/").append(method).append(".view"); - builder.append("?u=").append(username); - builder.append("&p=").append(password); - builder.append("&v=").append(Constants.REST_PROTOCOL_VERSION); - builder.append("&c=").append(Constants.REST_CLIENT_ID); - - return builder.toString(); - } - - public static String getVideoPlayerType(Context context) { - SharedPreferences prefs = getPreferences(context); - return prefs.getString(Constants.PREFERENCES_KEY_VIDEO_PLAYER, "raw"); - } - - public static SharedPreferences getPreferences(Context context) { - return context.getSharedPreferences(Constants.PREFERENCES_FILE_NAME, 0); - } - - public static String getContentType(HttpEntity entity) { - if (entity == null || entity.getContentType() == null) { - return null; - } - return entity.getContentType().getValue(); - } - - public static int getRemainingTrialDays(Context context) { - SharedPreferences prefs = getPreferences(context); - long installTime = prefs.getLong(Constants.PREFERENCES_KEY_INSTALL_TIME, 0L); - - if (installTime == 0L) { - installTime = System.currentTimeMillis(); - SharedPreferences.Editor editor = prefs.edit(); - editor.putLong(Constants.PREFERENCES_KEY_INSTALL_TIME, installTime); - editor.commit(); - } - - long now = System.currentTimeMillis(); - long millisPerDay = 24L * 60L * 60L * 1000L; - int daysSinceInstall = (int) ((now - installTime) / millisPerDay); - return Math.max(0, Constants.FREE_TRIAL_DAYS - daysSinceInstall); - } - - /** - * Get the contents of an InputStream as a byte[]. - *

- * This method buffers the input internally, so there is no need to use a - * BufferedInputStream. - * - * @param input the InputStream to read from - * @return the requested byte array - * @throws NullPointerException if the input is null - * @throws IOException if an I/O error occurs - */ - public static byte[] toByteArray(InputStream input) throws IOException { - ByteArrayOutputStream output = new ByteArrayOutputStream(); - copy(input, output); - return output.toByteArray(); - } - - public static long copy(InputStream input, OutputStream output) - throws IOException { - byte[] buffer = new byte[1024 * 4]; - long count = 0; - int n; - while (-1 != (n = input.read(buffer))) { - output.write(buffer, 0, n); - count += n; - } - return count; - } - - public static void atomicCopy(File from, File to) throws IOException { - FileInputStream in = null; - FileOutputStream out = null; - File tmp = null; - try { - tmp = new File(to.getPath() + ".tmp"); - in = new FileInputStream(from); - out = new FileOutputStream(tmp); - in.getChannel().transferTo(0, from.length(), out.getChannel()); - out.close(); - if (!tmp.renameTo(to)) { - throw new IOException("Failed to rename " + tmp + " to " + to); - } - Log.i(TAG, "Copied " + from + " to " + to); - } catch (IOException x) { - close(out); - delete(to); - throw x; - } finally { - close(in); - close(out); - delete(tmp); - } - } - public static void renameFile(File from, File to) throws IOException { - if(from.renameTo(to)) { - Log.i(TAG, "Renamed " + from + " to " + to); - } else { - atomicCopy(from, to); - } - } - - public static void close(Closeable closeable) { - try { - if (closeable != null) { - closeable.close(); - } - } catch (Throwable x) { - // Ignored - } - } - - public static boolean delete(File file) { - if (file != null && file.exists()) { - if (!file.delete()) { - Log.w(TAG, "Failed to delete file " + file); - return false; - } - Log.i(TAG, "Deleted file " + file); - } - return true; - } - public static boolean recursiveDelete(File dir) { - if (dir != null && dir.exists()) { - for(File file: dir.listFiles()) { - if(file.isDirectory()) { - if(!recursiveDelete(file)) { - return false; - } - } else if(file.exists()) { - if(!file.delete()) { - return false; - } - } - } - return dir.delete(); - } - return false; - } - - public static void toast(Context context, int messageId) { - toast(context, messageId, true); - } - - public static void toast(Context context, int messageId, boolean shortDuration) { - toast(context, context.getString(messageId), shortDuration); - } - - public static void toast(Context context, String message) { - toast(context, message, true); - } - - public static void toast(Context context, String message, boolean shortDuration) { - if (toast == null) { - toast = Toast.makeText(context, message, shortDuration ? Toast.LENGTH_SHORT : Toast.LENGTH_LONG); - toast.setGravity(Gravity.CENTER, 0, 0); - } else { - toast.setText(message); - toast.setDuration(shortDuration ? Toast.LENGTH_SHORT : Toast.LENGTH_LONG); - } - toast.show(); - } - - /** - * Converts a byte-count to a formatted string suitable for display to the user. - * For instance: - *

    - *
  • format(918) returns "918 B".
  • - *
  • format(98765) returns "96 KB".
  • - *
  • format(1238476) returns "1.2 MB".
  • - *
- * This method assumes that 1 KB is 1024 bytes. - * To get a localized string, please use formatLocalizedBytes instead. - * - * @param byteCount The number of bytes. - * @return The formatted string. - */ - public static synchronized String formatBytes(long byteCount) { - - // More than 1 GB? - if (byteCount >= 1024 * 1024 * 1024) { - NumberFormat gigaByteFormat = GIGA_BYTE_FORMAT; - return gigaByteFormat.format((double) byteCount / (1024 * 1024 * 1024)); - } - - // More than 1 MB? - if (byteCount >= 1024 * 1024) { - NumberFormat megaByteFormat = MEGA_BYTE_FORMAT; - return megaByteFormat.format((double) byteCount / (1024 * 1024)); - } - - // More than 1 KB? - if (byteCount >= 1024) { - NumberFormat kiloByteFormat = KILO_BYTE_FORMAT; - return kiloByteFormat.format((double) byteCount / 1024); - } - - return byteCount + " B"; - } - - /** - * Converts a byte-count to a formatted string suitable for display to the user. - * For instance: - *
    - *
  • format(918) returns "918 B".
  • - *
  • format(98765) returns "96 KB".
  • - *
  • format(1238476) returns "1.2 MB".
  • - *
- * This method assumes that 1 KB is 1024 bytes. - * This version of the method returns a localized string. - * - * @param byteCount The number of bytes. - * @return The formatted string. - */ - public static synchronized String formatLocalizedBytes(long byteCount, Context context) { - - // More than 1 GB? - if (byteCount >= 1024 * 1024 * 1024) { - if (GIGA_BYTE_LOCALIZED_FORMAT == null) { - GIGA_BYTE_LOCALIZED_FORMAT = new DecimalFormat(context.getResources().getString(R.string.util_bytes_format_gigabyte)); - } - - return GIGA_BYTE_LOCALIZED_FORMAT.format((double) byteCount / (1024 * 1024 * 1024)); - } - - // More than 1 MB? - if (byteCount >= 1024 * 1024) { - if (MEGA_BYTE_LOCALIZED_FORMAT == null) { - MEGA_BYTE_LOCALIZED_FORMAT = new DecimalFormat(context.getResources().getString(R.string.util_bytes_format_megabyte)); - } - - return MEGA_BYTE_LOCALIZED_FORMAT.format((double) byteCount / (1024 * 1024)); - } - - // More than 1 KB? - if (byteCount >= 1024) { - if (KILO_BYTE_LOCALIZED_FORMAT == null) { - KILO_BYTE_LOCALIZED_FORMAT = new DecimalFormat(context.getResources().getString(R.string.util_bytes_format_kilobyte)); - } - - return KILO_BYTE_LOCALIZED_FORMAT.format((double) byteCount / 1024); - } - - if (BYTE_LOCALIZED_FORMAT == null) { - BYTE_LOCALIZED_FORMAT = new DecimalFormat(context.getResources().getString(R.string.util_bytes_format_byte)); - } - - return BYTE_LOCALIZED_FORMAT.format((double) byteCount); - } - - public static String formatDuration(Integer seconds) { - if (seconds == null) { - return null; - } - - int hours = seconds / 3600; - int minutes = (seconds / 60) % 60; - int secs = seconds % 60; - - StringBuilder builder = new StringBuilder(7); - if(hours > 0) { - builder.append(hours).append(":"); - if(minutes < 10) { - builder.append("0"); - } - } - builder.append(minutes).append(":"); - if (secs < 10) { - builder.append("0"); - } - builder.append(secs); - return builder.toString(); - } - - public static boolean equals(Object object1, Object object2) { - if (object1 == object2) { - return true; - } - if (object1 == null || object2 == null) { - return false; - } - return object1.equals(object2); - - } - - /** - * Encodes the given string by using the hexadecimal representation of its UTF-8 bytes. - * - * @param s The string to encode. - * @return The encoded string. - */ - public static String utf8HexEncode(String s) { - if (s == null) { - return null; - } - byte[] utf8; - try { - utf8 = s.getBytes(Constants.UTF_8); - } catch (UnsupportedEncodingException x) { - throw new RuntimeException(x); - } - return hexEncode(utf8); - } - - /** - * Converts an array of bytes into an array of characters representing the hexadecimal values of each byte in order. - * The returned array will be double the length of the passed array, as it takes two characters to represent any - * given byte. - * - * @param data Bytes to convert to hexadecimal characters. - * @return A string containing hexadecimal characters. - */ - public static String hexEncode(byte[] data) { - int length = data.length; - char[] out = new char[length << 1]; - // two characters form the hex value. - for (int i = 0, j = 0; i < length; i++) { - out[j++] = HEX_DIGITS[(0xF0 & data[i]) >>> 4]; - out[j++] = HEX_DIGITS[0x0F & data[i]]; - } - return new String(out); - } - - /** - * Calculates the MD5 digest and returns the value as a 32 character hex string. - * - * @param s Data to digest. - * @return MD5 digest as a hex string. - */ - public static String md5Hex(String s) { - if (s == null) { - return null; - } - - try { - MessageDigest md5 = MessageDigest.getInstance("MD5"); - return hexEncode(md5.digest(s.getBytes(Constants.UTF_8))); - } catch (Exception x) { - throw new RuntimeException(x.getMessage(), x); - } - } - - public static boolean isNullOrWhiteSpace(String string) { - return string == null || string.isEmpty() || string.trim().isEmpty(); - } - - public static boolean isNetworkConnected(Context context) { - ConnectivityManager manager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); - NetworkInfo networkInfo = manager.getActiveNetworkInfo(); - boolean connected = networkInfo != null && networkInfo.isConnected(); - - boolean wifiConnected = connected && networkInfo.getType() == ConnectivityManager.TYPE_WIFI; - boolean wifiRequired = isWifiRequiredForDownload(context); - - return connected && (!wifiRequired || wifiConnected); - } - - public static boolean isExternalStoragePresent() { - return Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()); - } - - private static boolean isWifiRequiredForDownload(Context context) { - SharedPreferences prefs = getPreferences(context); - return prefs.getBoolean(Constants.PREFERENCES_KEY_WIFI_REQUIRED_FOR_DOWNLOAD, false); - } - - public static void info(Context context, int titleId, int messageId) { - showDialog(context, android.R.drawable.ic_dialog_info, titleId, messageId); - } - public static void info(Context context, int titleId, String message) { - showDialog(context, android.R.drawable.ic_dialog_info, titleId, message); - } - - private static void showDialog(Context context, int icon, int titleId, int messageId) { - new AlertDialog.Builder(context) - .setIcon(icon) - .setTitle(titleId) - .setMessage(messageId) - .setPositiveButton(R.string.common_ok, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int i) { - dialog.dismiss(); - } - }) - .show(); - } - private static void showDialog(Context context, int icon, int titleId, String message) { - new AlertDialog.Builder(context) - .setIcon(icon) - .setTitle(titleId) - .setMessage(message) - .setPositiveButton(R.string.common_ok, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int i) { - dialog.dismiss(); - } - }) - .show(); - } - - public static void showPlayingNotification(final Context context, final DownloadServiceImpl downloadService, Handler handler, MusicDirectory.Entry song) { - // Set the icon, scrolling text and timestamp - final Notification notification = new Notification(R.drawable.stat_notify_playing, song.getTitle(), System.currentTimeMillis()); - notification.flags |= Notification.FLAG_NO_CLEAR | Notification.FLAG_ONGOING_EVENT; - - boolean playing = downloadService.getPlayerState() == PlayerState.STARTED; - if (Build.VERSION.SDK_INT>= Build.VERSION_CODES.JELLY_BEAN){ - RemoteViews expandedContentView = new RemoteViews(context.getPackageName(), R.layout.notification_expanded); - setupViews(expandedContentView,context,song, playing); - notification.bigContentView = expandedContentView; - } - - RemoteViews smallContentView = new RemoteViews(context.getPackageName(), R.layout.notification); - setupViews(smallContentView, context, song, playing); - notification.contentView = smallContentView; - - Intent notificationIntent = new Intent(context, MainActivity.class); - notificationIntent.putExtra(Constants.INTENT_EXTRA_NAME_DOWNLOAD, true); - notificationIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); - notification.contentIntent = PendingIntent.getActivity(context, 0, notificationIntent, 0); - - handler.post(new Runnable() { - @Override - public void run() { - downloadService.startForeground(Constants.NOTIFICATION_ID_PLAYING, notification); - } - }); - - // Update widget - DSubWidgetProvider.notifyInstances(context, downloadService, true); - } - - private static void setupViews(RemoteViews rv, Context context, MusicDirectory.Entry song, boolean playing){ - - // Use the same text for the ticker and the expanded notification - String title = song.getTitle(); - String arist = song.getArtist(); - String album = song.getAlbum(); - - // Set the album art. - try { - int size = context.getResources().getDrawable(R.drawable.unknown_album).getIntrinsicHeight(); - Bitmap bitmap = FileUtil.getAlbumArtBitmap(context, song, size); - if (bitmap == null) { - // set default album art - rv.setImageViewResource(R.id.notification_image, R.drawable.unknown_album); - } else { - rv.setImageViewBitmap(R.id.notification_image, bitmap); - } - } catch (Exception x) { - Log.w(TAG, "Failed to get notification cover art", x); - rv.setImageViewResource(R.id.notification_image, R.drawable.unknown_album); - } - - // set the text for the notifications - rv.setTextViewText(R.id.notification_title, title); - rv.setTextViewText(R.id.notification_artist, arist); - rv.setTextViewText(R.id.notification_album, album); - - Pair colors = getNotificationTextColors(context); - if (colors.getFirst() != null) { - rv.setTextColor(R.id.notification_title, colors.getFirst()); - } - if (colors.getSecond() != null) { - rv.setTextColor(R.id.notification_artist, colors.getSecond()); - } - - if(!playing) { - rv.setImageViewResource(R.id.control_pause, R.drawable.notification_play); - rv.setImageViewResource(R.id.control_previous, R.drawable.notification_stop); - } - - // Create actions for media buttons - PendingIntent pendingIntent; - if(playing) { - Intent prevIntent = new Intent("KEYCODE_MEDIA_PREVIOUS"); - prevIntent.setComponent(new ComponentName(context, DownloadServiceImpl.class)); - prevIntent.putExtra(Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MEDIA_PREVIOUS)); - pendingIntent = PendingIntent.getService(context, 0, prevIntent, 0); - rv.setOnClickPendingIntent(R.id.control_previous, pendingIntent); - } else { - Intent prevIntent = new Intent("KEYCODE_MEDIA_STOP"); - prevIntent.setComponent(new ComponentName(context, DownloadServiceImpl.class)); - prevIntent.putExtra(Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MEDIA_STOP)); - pendingIntent = PendingIntent.getService(context, 0, prevIntent, 0); - rv.setOnClickPendingIntent(R.id.control_previous, pendingIntent); - } - - Intent pauseIntent = new Intent("KEYCODE_MEDIA_PLAY_PAUSE"); - pauseIntent.setComponent(new ComponentName(context, DownloadServiceImpl.class)); - pauseIntent.putExtra(Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE)); - pendingIntent = PendingIntent.getService(context, 0, pauseIntent, 0); - rv.setOnClickPendingIntent(R.id.control_pause, pendingIntent); - - Intent nextIntent = new Intent("KEYCODE_MEDIA_NEXT"); - nextIntent.setComponent(new ComponentName(context, DownloadServiceImpl.class)); - nextIntent.putExtra(Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MEDIA_NEXT)); - pendingIntent = PendingIntent.getService(context, 0, nextIntent, 0); - rv.setOnClickPendingIntent(R.id.control_next, pendingIntent); - } - - public static void hidePlayingNotification(final Context context, final DownloadServiceImpl downloadService, Handler handler) { - // Remove notification and remove the service from the foreground - handler.post(new Runnable() { - @Override - public void run() { - downloadService.stopForeground(true); - } - }); - - // Update widget - DSubWidgetProvider.notifyInstances(context, downloadService, false); - } - - public static void sleepQuietly(long millis) { - try { - Thread.sleep(millis); - } catch (InterruptedException x) { - Log.w(TAG, "Interrupted from sleep.", x); - } - } - - public static void startActivityWithoutTransition(Activity currentActivity, Class newActivitiy) { - startActivityWithoutTransition(currentActivity, new Intent(currentActivity, newActivitiy)); - } - - public static void startActivityWithoutTransition(Activity currentActivity, Intent intent) { - currentActivity.startActivity(intent); - disablePendingTransition(currentActivity); - } - - public static void disablePendingTransition(Activity activity) { - - // Activity.overridePendingTransition() was introduced in Android 2.0. Use reflection to maintain - // compatibility with 1.5. - try { - Method method = Activity.class.getMethod("overridePendingTransition", int.class, int.class); - method.invoke(activity, 0, 0); - } catch (Throwable x) { - // Ignored - } - } - - public static Drawable createDrawableFromBitmap(Context context, Bitmap bitmap) { - // BitmapDrawable(Resources, Bitmap) was introduced in Android 1.6. Use reflection to maintain - // compatibility with 1.5. - try { - Constructor constructor = BitmapDrawable.class.getConstructor(Resources.class, Bitmap.class); - return constructor.newInstance(context.getResources(), bitmap); - } catch (Throwable x) { - return new BitmapDrawable(bitmap); - } - } - - public static void registerMediaButtonEventReceiver(Context context) { - - // Only do it if enabled in the settings. - SharedPreferences prefs = getPreferences(context); - boolean enabled = prefs.getBoolean(Constants.PREFERENCES_KEY_MEDIA_BUTTONS, true); - - if (enabled) { - - // AudioManager.registerMediaButtonEventReceiver() was introduced in Android 2.2. - // Use reflection to maintain compatibility with 1.5. - try { - AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); - ComponentName componentName = new ComponentName(context.getPackageName(), MediaButtonIntentReceiver.class.getName()); - Method method = AudioManager.class.getMethod("registerMediaButtonEventReceiver", ComponentName.class); - method.invoke(audioManager, componentName); - } catch (Throwable x) { - // Ignored. - } - } - } - - public static void unregisterMediaButtonEventReceiver(Context context) { - // AudioManager.unregisterMediaButtonEventReceiver() was introduced in Android 2.2. - // Use reflection to maintain compatibility with 1.5. - try { - AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); - ComponentName componentName = new ComponentName(context.getPackageName(), MediaButtonIntentReceiver.class.getName()); - Method method = AudioManager.class.getMethod("unregisterMediaButtonEventReceiver", ComponentName.class); - method.invoke(audioManager, componentName); - } catch (Throwable x) { - // Ignored. - } - } - - @TargetApi(8) - public static void requestAudioFocus(final Context context) { - if (Build.VERSION.SDK_INT >= 8 && !hasFocus) { - final AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); - hasFocus = true; - audioManager.requestAudioFocus(new OnAudioFocusChangeListener() { - public void onAudioFocusChange(int focusChange) { - DownloadServiceImpl downloadService = (DownloadServiceImpl)context; - if((focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT || focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK) && !downloadService.isJukeboxEnabled()) { - if(downloadService.getPlayerState() == PlayerState.STARTED) { - SharedPreferences prefs = getPreferences(context); - int lossPref = Integer.parseInt(prefs.getString(Constants.PREFERENCES_KEY_TEMP_LOSS, "1")); - if(lossPref == 2 || (lossPref == 1 && focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK)) { - lowerFocus = true; - downloadService.setVolume(0.1f); - } else if(lossPref == 0 || (lossPref == 1 && focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT)) { - pauseFocus = true; - downloadService.pause(); - } - } - } else if (focusChange == AudioManager.AUDIOFOCUS_GAIN) { - if(pauseFocus) { - pauseFocus = false; - downloadService.start(); - } else if(lowerFocus) { - lowerFocus = false; - downloadService.setVolume(1.0f); - } - } else if(focusChange == AudioManager.AUDIOFOCUS_LOSS && !downloadService.isJukeboxEnabled()) { - hasFocus = false; - downloadService.pause(); - audioManager.abandonAudioFocus(this); - } - } - }, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN); - } - } - - /** - *

Broadcasts the given song info as the new song being played.

- */ - public static void broadcastNewTrackInfo(Context context, MusicDirectory.Entry song) { - DownloadService downloadService = (DownloadServiceImpl)context; - Intent intent = new Intent(EVENT_META_CHANGED); - Intent avrcpIntent = new Intent(AVRCP_METADATA_CHANGED); - - if (song != null) { - intent.putExtra("title", song.getTitle()); - intent.putExtra("artist", song.getArtist()); - intent.putExtra("album", song.getAlbum()); - - File albumArtFile = FileUtil.getAlbumArtFile(context, song); - intent.putExtra("coverart", albumArtFile.getAbsolutePath()); - - avrcpIntent.putExtra("playing", true); - avrcpIntent.putExtra("track", song.getTitle()); - avrcpIntent.putExtra("artist", song.getArtist()); - avrcpIntent.putExtra("album", song.getAlbum()); - avrcpIntent.putExtra("ListSize",(long) downloadService.getSongs().size()); - avrcpIntent.putExtra("id", (long) downloadService.getCurrentPlayingIndex()+1); - avrcpIntent.putExtra("duration", (long) downloadService.getPlayerDuration()); - avrcpIntent.putExtra("position", (long) downloadService.getPlayerPosition()); - avrcpIntent.putExtra("coverart", albumArtFile.getAbsolutePath()); - } else { - intent.putExtra("title", ""); - intent.putExtra("artist", ""); - intent.putExtra("album", ""); - intent.putExtra("coverart", ""); - - avrcpIntent.putExtra("playing", false); - avrcpIntent.putExtra("track", ""); - avrcpIntent.putExtra("artist", ""); - avrcpIntent.putExtra("album", ""); - avrcpIntent.putExtra("ListSize",(long)0); - avrcpIntent.putExtra("id", (long) 0); - avrcpIntent.putExtra("duration", (long )0); - avrcpIntent.putExtra("position", (long) 0); - avrcpIntent.putExtra("coverart", ""); - } - - context.sendBroadcast(intent); - context.sendBroadcast(avrcpIntent); - } - - /** - *

Broadcasts the given player state as the one being set.

- */ - public static void broadcastPlaybackStatusChange(Context context, PlayerState state) { - Intent intent = new Intent(EVENT_PLAYSTATE_CHANGED); - Intent avrcpIntent = new Intent(AVRCP_PLAYSTATE_CHANGED); - - switch (state) { - case STARTED: - intent.putExtra("state", "play"); - avrcpIntent.putExtra("playing", true); - break; - case STOPPED: - intent.putExtra("state", "stop"); - avrcpIntent.putExtra("playing", false); - break; - case PAUSED: - intent.putExtra("state", "pause"); - avrcpIntent.putExtra("playing", false); - break; - case COMPLETED: - intent.putExtra("state", "complete"); - avrcpIntent.putExtra("playing", false); - break; - default: - return; // No need to broadcast. - } - - context.sendBroadcast(intent); - context.sendBroadcast(avrcpIntent); - } - - /** - * Resolves the default text color for notifications. - * - * Based on http://stackoverflow.com/questions/4867338/custom-notification-layouts-and-text-colors/7320604#7320604 - */ - private static Pair getNotificationTextColors(Context context) { - if (NOTIFICATION_TEXT_COLORS.getFirst() == null && NOTIFICATION_TEXT_COLORS.getSecond() == null) { - try { - Notification notification = new Notification(); - String title = "title"; - String content = "content"; - notification.setLatestEventInfo(context, title, content, null); - LinearLayout group = new LinearLayout(context); - ViewGroup event = (ViewGroup) notification.contentView.apply(context, group); - findNotificationTextColors(event, title, content); - group.removeAllViews(); - } catch (Exception x) { - Log.w(TAG, "Failed to resolve notification text colors.", x); - } - } - return NOTIFICATION_TEXT_COLORS; - } - - private static void findNotificationTextColors(ViewGroup group, String title, String content) { - for (int i = 0; i < group.getChildCount(); i++) { - if (group.getChildAt(i) instanceof TextView) { - TextView textView = (TextView) group.getChildAt(i); - String text = textView.getText().toString(); - if (title.equals(text)) { - NOTIFICATION_TEXT_COLORS.setFirst(textView.getTextColors().getDefaultColor()); - } - else if (content.equals(text)) { - NOTIFICATION_TEXT_COLORS.setSecond(textView.getTextColors().getDefaultColor()); - } - } - else if (group.getChildAt(i) instanceof ViewGroup) - findNotificationTextColors((ViewGroup) group.getChildAt(i), title, content); - } - } - - public static WifiManager.WifiLock createWifiLock(Context context, String tag) { - WifiManager wm = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); - int lockType = WifiManager.WIFI_MODE_FULL; - if (Build.VERSION.SDK_INT >= 12) { - lockType = 3; - } - return wm.createWifiLock(lockType, tag); - } -} +/* + This file is part of Subsonic. + + Subsonic is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Subsonic is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Subsonic. If not, see . + + Copyright 2009 (C) Sindre Mehus + */ +package github.daneren2005.dsub.util; + +import android.annotation.TargetApi; +import android.app.Activity; +import android.app.AlertDialog; +import android.app.Notification; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.app.Service; +import android.content.ComponentName; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.SharedPreferences; +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.media.AudioManager; +import android.media.AudioManager.OnAudioFocusChangeListener; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; +import android.net.wifi.WifiManager; +import android.os.Build; +import android.os.Environment; +import android.os.Handler; +import android.support.v4.app.NotificationCompat; +import android.util.Log; +import android.view.Gravity; +import android.view.ViewGroup; +import android.view.KeyEvent; +import android.widget.LinearLayout; +import android.widget.RemoteViews; +import android.widget.TextView; +import android.widget.Toast; +import github.daneren2005.dsub.R; +import github.daneren2005.dsub.activity.MainActivity; +import github.daneren2005.dsub.domain.MusicDirectory; +import github.daneren2005.dsub.domain.PlayerState; +import github.daneren2005.dsub.domain.RepeatMode; +import github.daneren2005.dsub.domain.Version; +import github.daneren2005.dsub.provider.DSubWidgetProvider; +import github.daneren2005.dsub.receiver.MediaButtonIntentReceiver; +import github.daneren2005.dsub.service.DownloadService; +import github.daneren2005.dsub.service.DownloadServiceImpl; +import org.apache.http.HttpEntity; + +import java.io.ByteArrayOutputStream; +import java.io.Closeable; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; +import java.security.MessageDigest; +import java.text.DecimalFormat; +import java.text.NumberFormat; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * @author Sindre Mehus + * @version $Id$ + */ +public final class Util { + private static final String TAG = Util.class.getSimpleName(); + + private static final DecimalFormat GIGA_BYTE_FORMAT = new DecimalFormat("0.00 GB"); + private static final DecimalFormat MEGA_BYTE_FORMAT = new DecimalFormat("0.00 MB"); + private static final DecimalFormat KILO_BYTE_FORMAT = new DecimalFormat("0 KB"); + + private static DecimalFormat GIGA_BYTE_LOCALIZED_FORMAT = null; + private static DecimalFormat MEGA_BYTE_LOCALIZED_FORMAT = null; + private static DecimalFormat KILO_BYTE_LOCALIZED_FORMAT = null; + private static DecimalFormat BYTE_LOCALIZED_FORMAT = null; + + public static final String EVENT_META_CHANGED = "github.daneren2005.dsub.EVENT_META_CHANGED"; + public static final String EVENT_PLAYSTATE_CHANGED = "github.daneren2005.dsub.EVENT_PLAYSTATE_CHANGED"; + + public static final String AVRCP_PLAYSTATE_CHANGED = "com.android.music.playstatechanged"; + public static final String AVRCP_METADATA_CHANGED = "com.android.music.metachanged"; + + private static boolean hasFocus = false; + private static boolean pauseFocus = false; + private static boolean lowerFocus = false; + + private static final Map SERVER_REST_VERSIONS = new ConcurrentHashMap(); + + // Used by hexEncode() + private static final char[] HEX_DIGITS = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; + + private final static Pair NOTIFICATION_TEXT_COLORS = new Pair(); + private static Toast toast; + + private Util() { + } + + public static boolean isOffline(Context context) { + SharedPreferences prefs = getPreferences(context); + return prefs.getBoolean(Constants.PREFERENCES_KEY_OFFLINE, false); + } + + public static void setOffline(Context context, boolean offline) { + SharedPreferences prefs = getPreferences(context); + SharedPreferences.Editor editor = prefs.edit(); + editor.putBoolean(Constants.PREFERENCES_KEY_OFFLINE, offline); + editor.commit(); + } + + public static boolean isScreenLitOnDownload(Context context) { + SharedPreferences prefs = getPreferences(context); + return prefs.getBoolean(Constants.PREFERENCES_KEY_SCREEN_LIT_ON_DOWNLOAD, false); + } + + public static RepeatMode getRepeatMode(Context context) { + SharedPreferences prefs = getPreferences(context); + return RepeatMode.valueOf(prefs.getString(Constants.PREFERENCES_KEY_REPEAT_MODE, RepeatMode.OFF.name())); + } + + public static void setRepeatMode(Context context, RepeatMode repeatMode) { + SharedPreferences prefs = getPreferences(context); + SharedPreferences.Editor editor = prefs.edit(); + editor.putString(Constants.PREFERENCES_KEY_REPEAT_MODE, repeatMode.name()); + editor.commit(); + } + + public static boolean isScrobblingEnabled(Context context) { + SharedPreferences prefs = getPreferences(context); + return prefs.getBoolean(Constants.PREFERENCES_KEY_SCROBBLE, false); + } + + public static void setActiveServer(Context context, int instance) { + SharedPreferences prefs = getPreferences(context); + SharedPreferences.Editor editor = prefs.edit(); + editor.putInt(Constants.PREFERENCES_KEY_SERVER_INSTANCE, instance); + editor.putString(Constants.PREFERENCES_KEY_MUSIC_FOLDER_ID + instance, null); + editor.commit(); + } + + public static int getActiveServer(Context context) { + SharedPreferences prefs = getPreferences(context); + return prefs.getInt(Constants.PREFERENCES_KEY_SERVER_INSTANCE, 1); + } + + public static boolean checkServerVersion(Context context, String requiredVersion) { + Version version = Util.getServerRestVersion(context); + Version required = new Version(requiredVersion); + if(version != null && version.compareTo(required) >= 0) { + return true; + } else { + return false; + } + } + + public static int getServerCount(Context context) { + SharedPreferences prefs = getPreferences(context); + return prefs.getInt(Constants.PREFERENCES_KEY_SERVER_COUNT, 1); + } + + public static void removeInstanceName(Context context, int instance, int activeInstance) { + SharedPreferences prefs = getPreferences(context); + SharedPreferences.Editor editor = prefs.edit(); + + int newInstance = instance + 1; + + String server = prefs.getString(Constants.PREFERENCES_KEY_SERVER_KEY + newInstance, null); + String serverName = prefs.getString(Constants.PREFERENCES_KEY_SERVER_NAME + newInstance, null); + String serverUrl = prefs.getString(Constants.PREFERENCES_KEY_SERVER_URL + newInstance, null); + String userName = prefs.getString(Constants.PREFERENCES_KEY_USERNAME + newInstance, null); + String password = prefs.getString(Constants.PREFERENCES_KEY_PASSWORD + newInstance, null); + + editor.putString(Constants.PREFERENCES_KEY_SERVER_KEY + instance, server); + editor.putString(Constants.PREFERENCES_KEY_SERVER_NAME + instance, serverName); + editor.putString(Constants.PREFERENCES_KEY_SERVER_URL + instance, serverUrl); + editor.putString(Constants.PREFERENCES_KEY_USERNAME + instance, userName); + editor.putString(Constants.PREFERENCES_KEY_PASSWORD + instance, password); + + editor.putString(Constants.PREFERENCES_KEY_SERVER_KEY + newInstance, null); + editor.putString(Constants.PREFERENCES_KEY_SERVER_NAME + newInstance, null); + editor.putString(Constants.PREFERENCES_KEY_SERVER_URL + newInstance, null); + editor.putString(Constants.PREFERENCES_KEY_USERNAME + newInstance, null); + editor.putString(Constants.PREFERENCES_KEY_PASSWORD + newInstance, null); + editor.commit(); + + if (instance == activeInstance) { + if(instance != 1) { + Util.setActiveServer(context, 1); + } else { + Util.setOffline(context, true); + } + } else if (newInstance == activeInstance) { + Util.setActiveServer(context, instance); + } + } + + public static String getServerName(Context context, int instance) { + SharedPreferences prefs = getPreferences(context); + return prefs.getString(Constants.PREFERENCES_KEY_SERVER_NAME + instance, null); + } + + public static String getUserName(Context context, int instance) { + if (instance == 0) { + return context.getResources().getString(R.string.main_offline); + } + SharedPreferences prefs = getPreferences(context); + return prefs.getString(Constants.PREFERENCES_KEY_USERNAME + instance, null); + } + + public static void setServerRestVersion(Context context, Version version) { + SERVER_REST_VERSIONS.put(getActiveServer(context), version); + } + + public static Version getServerRestVersion(Context context) { + return SERVER_REST_VERSIONS.get(getActiveServer(context)); + } + + public static void setSelectedMusicFolderId(Context context, String musicFolderId) { + int instance = getActiveServer(context); + SharedPreferences prefs = getPreferences(context); + SharedPreferences.Editor editor = prefs.edit(); + editor.putString(Constants.PREFERENCES_KEY_MUSIC_FOLDER_ID + instance, musicFolderId); + editor.commit(); + } + + public static String getSelectedMusicFolderId(Context context) { + SharedPreferences prefs = getPreferences(context); + int instance = getActiveServer(context); + return prefs.getString(Constants.PREFERENCES_KEY_MUSIC_FOLDER_ID + instance, null); + } + + public static String getTheme(Context context) { + SharedPreferences prefs = getPreferences(context); + return prefs.getString(Constants.PREFERENCES_KEY_THEME, null); + } + + public static int getMaxBitrate(Context context) { + ConnectivityManager manager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + NetworkInfo networkInfo = manager.getActiveNetworkInfo(); + if (networkInfo == null) { + return 0; + } + + boolean wifi = networkInfo.getType() == ConnectivityManager.TYPE_WIFI; + SharedPreferences prefs = getPreferences(context); + return Integer.parseInt(prefs.getString(wifi ? Constants.PREFERENCES_KEY_MAX_BITRATE_WIFI : Constants.PREFERENCES_KEY_MAX_BITRATE_MOBILE, "0")); + } + + public static int getMaxVideoBitrate(Context context) { + ConnectivityManager manager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + NetworkInfo networkInfo = manager.getActiveNetworkInfo(); + if (networkInfo == null) { + return 0; + } + + boolean wifi = networkInfo.getType() == ConnectivityManager.TYPE_WIFI; + SharedPreferences prefs = getPreferences(context); + return Integer.parseInt(prefs.getString(wifi ? Constants.PREFERENCES_KEY_MAX_VIDEO_BITRATE_WIFI : Constants.PREFERENCES_KEY_MAX_VIDEO_BITRATE_MOBILE, "0")); + } + + public static int getPreloadCount(Context context) { + ConnectivityManager manager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + NetworkInfo networkInfo = manager.getActiveNetworkInfo(); + if (networkInfo == null) { + return 3; + } + + SharedPreferences prefs = getPreferences(context); + boolean wifi = networkInfo.getType() == ConnectivityManager.TYPE_WIFI; + int preloadCount = Integer.parseInt(prefs.getString(wifi ? Constants.PREFERENCES_KEY_PRELOAD_COUNT_WIFI : Constants.PREFERENCES_KEY_PRELOAD_COUNT_MOBILE, "-1")); + return preloadCount == -1 ? Integer.MAX_VALUE : preloadCount; + } + + public static int getCacheSizeMB(Context context) { + SharedPreferences prefs = getPreferences(context); + int cacheSize = Integer.parseInt(prefs.getString(Constants.PREFERENCES_KEY_CACHE_SIZE, "-1")); + return cacheSize == -1 ? Integer.MAX_VALUE : cacheSize; + } + + public static String getRestUrl(Context context, String method) { + StringBuilder builder = new StringBuilder(); + + SharedPreferences prefs = getPreferences(context); + + int instance = prefs.getInt(Constants.PREFERENCES_KEY_SERVER_INSTANCE, 1); + String serverUrl = prefs.getString(Constants.PREFERENCES_KEY_SERVER_URL + instance, null); + String username = prefs.getString(Constants.PREFERENCES_KEY_USERNAME + instance, null); + String password = prefs.getString(Constants.PREFERENCES_KEY_PASSWORD + instance, null); + + // Slightly obfuscate password + password = "enc:" + Util.utf8HexEncode(password); + + builder.append(serverUrl); + if (builder.charAt(builder.length() - 1) != '/') { + builder.append("/"); + } + builder.append("rest/").append(method).append(".view"); + builder.append("?u=").append(username); + builder.append("&p=").append(password); + builder.append("&v=").append(Constants.REST_PROTOCOL_VERSION); + builder.append("&c=").append(Constants.REST_CLIENT_ID); + + return builder.toString(); + } + + public static String getVideoPlayerType(Context context) { + SharedPreferences prefs = getPreferences(context); + return prefs.getString(Constants.PREFERENCES_KEY_VIDEO_PLAYER, "raw"); + } + + public static SharedPreferences getPreferences(Context context) { + return context.getSharedPreferences(Constants.PREFERENCES_FILE_NAME, 0); + } + + public static String getContentType(HttpEntity entity) { + if (entity == null || entity.getContentType() == null) { + return null; + } + return entity.getContentType().getValue(); + } + + public static int getRemainingTrialDays(Context context) { + SharedPreferences prefs = getPreferences(context); + long installTime = prefs.getLong(Constants.PREFERENCES_KEY_INSTALL_TIME, 0L); + + if (installTime == 0L) { + installTime = System.currentTimeMillis(); + SharedPreferences.Editor editor = prefs.edit(); + editor.putLong(Constants.PREFERENCES_KEY_INSTALL_TIME, installTime); + editor.commit(); + } + + long now = System.currentTimeMillis(); + long millisPerDay = 24L * 60L * 60L * 1000L; + int daysSinceInstall = (int) ((now - installTime) / millisPerDay); + return Math.max(0, Constants.FREE_TRIAL_DAYS - daysSinceInstall); + } + + /** + * Get the contents of an InputStream as a byte[]. + *

+ * This method buffers the input internally, so there is no need to use a + * BufferedInputStream. + * + * @param input the InputStream to read from + * @return the requested byte array + * @throws NullPointerException if the input is null + * @throws IOException if an I/O error occurs + */ + public static byte[] toByteArray(InputStream input) throws IOException { + ByteArrayOutputStream output = new ByteArrayOutputStream(); + copy(input, output); + return output.toByteArray(); + } + + public static long copy(InputStream input, OutputStream output) + throws IOException { + byte[] buffer = new byte[1024 * 4]; + long count = 0; + int n; + while (-1 != (n = input.read(buffer))) { + output.write(buffer, 0, n); + count += n; + } + return count; + } + + public static void atomicCopy(File from, File to) throws IOException { + FileInputStream in = null; + FileOutputStream out = null; + File tmp = null; + try { + tmp = new File(to.getPath() + ".tmp"); + in = new FileInputStream(from); + out = new FileOutputStream(tmp); + in.getChannel().transferTo(0, from.length(), out.getChannel()); + out.close(); + if (!tmp.renameTo(to)) { + throw new IOException("Failed to rename " + tmp + " to " + to); + } + Log.i(TAG, "Copied " + from + " to " + to); + } catch (IOException x) { + close(out); + delete(to); + throw x; + } finally { + close(in); + close(out); + delete(tmp); + } + } + public static void renameFile(File from, File to) throws IOException { + if(from.renameTo(to)) { + Log.i(TAG, "Renamed " + from + " to " + to); + } else { + atomicCopy(from, to); + } + } + + public static void close(Closeable closeable) { + try { + if (closeable != null) { + closeable.close(); + } + } catch (Throwable x) { + // Ignored + } + } + + public static boolean delete(File file) { + if (file != null && file.exists()) { + if (!file.delete()) { + Log.w(TAG, "Failed to delete file " + file); + return false; + } + Log.i(TAG, "Deleted file " + file); + } + return true; + } + public static boolean recursiveDelete(File dir) { + if (dir != null && dir.exists()) { + for(File file: dir.listFiles()) { + if(file.isDirectory()) { + if(!recursiveDelete(file)) { + return false; + } + } else if(file.exists()) { + if(!file.delete()) { + return false; + } + } + } + return dir.delete(); + } + return false; + } + + public static void toast(Context context, int messageId) { + toast(context, messageId, true); + } + + public static void toast(Context context, int messageId, boolean shortDuration) { + toast(context, context.getString(messageId), shortDuration); + } + + public static void toast(Context context, String message) { + toast(context, message, true); + } + + public static void toast(Context context, String message, boolean shortDuration) { + if (toast == null) { + toast = Toast.makeText(context, message, shortDuration ? Toast.LENGTH_SHORT : Toast.LENGTH_LONG); + toast.setGravity(Gravity.CENTER, 0, 0); + } else { + toast.setText(message); + toast.setDuration(shortDuration ? Toast.LENGTH_SHORT : Toast.LENGTH_LONG); + } + toast.show(); + } + + /** + * Converts a byte-count to a formatted string suitable for display to the user. + * For instance: + *

    + *
  • format(918) returns "918 B".
  • + *
  • format(98765) returns "96 KB".
  • + *
  • format(1238476) returns "1.2 MB".
  • + *
+ * This method assumes that 1 KB is 1024 bytes. + * To get a localized string, please use formatLocalizedBytes instead. + * + * @param byteCount The number of bytes. + * @return The formatted string. + */ + public static synchronized String formatBytes(long byteCount) { + + // More than 1 GB? + if (byteCount >= 1024 * 1024 * 1024) { + NumberFormat gigaByteFormat = GIGA_BYTE_FORMAT; + return gigaByteFormat.format((double) byteCount / (1024 * 1024 * 1024)); + } + + // More than 1 MB? + if (byteCount >= 1024 * 1024) { + NumberFormat megaByteFormat = MEGA_BYTE_FORMAT; + return megaByteFormat.format((double) byteCount / (1024 * 1024)); + } + + // More than 1 KB? + if (byteCount >= 1024) { + NumberFormat kiloByteFormat = KILO_BYTE_FORMAT; + return kiloByteFormat.format((double) byteCount / 1024); + } + + return byteCount + " B"; + } + + /** + * Converts a byte-count to a formatted string suitable for display to the user. + * For instance: + *
    + *
  • format(918) returns "918 B".
  • + *
  • format(98765) returns "96 KB".
  • + *
  • format(1238476) returns "1.2 MB".
  • + *
+ * This method assumes that 1 KB is 1024 bytes. + * This version of the method returns a localized string. + * + * @param byteCount The number of bytes. + * @return The formatted string. + */ + public static synchronized String formatLocalizedBytes(long byteCount, Context context) { + + // More than 1 GB? + if (byteCount >= 1024 * 1024 * 1024) { + if (GIGA_BYTE_LOCALIZED_FORMAT == null) { + GIGA_BYTE_LOCALIZED_FORMAT = new DecimalFormat(context.getResources().getString(R.string.util_bytes_format_gigabyte)); + } + + return GIGA_BYTE_LOCALIZED_FORMAT.format((double) byteCount / (1024 * 1024 * 1024)); + } + + // More than 1 MB? + if (byteCount >= 1024 * 1024) { + if (MEGA_BYTE_LOCALIZED_FORMAT == null) { + MEGA_BYTE_LOCALIZED_FORMAT = new DecimalFormat(context.getResources().getString(R.string.util_bytes_format_megabyte)); + } + + return MEGA_BYTE_LOCALIZED_FORMAT.format((double) byteCount / (1024 * 1024)); + } + + // More than 1 KB? + if (byteCount >= 1024) { + if (KILO_BYTE_LOCALIZED_FORMAT == null) { + KILO_BYTE_LOCALIZED_FORMAT = new DecimalFormat(context.getResources().getString(R.string.util_bytes_format_kilobyte)); + } + + return KILO_BYTE_LOCALIZED_FORMAT.format((double) byteCount / 1024); + } + + if (BYTE_LOCALIZED_FORMAT == null) { + BYTE_LOCALIZED_FORMAT = new DecimalFormat(context.getResources().getString(R.string.util_bytes_format_byte)); + } + + return BYTE_LOCALIZED_FORMAT.format((double) byteCount); + } + + public static String formatDuration(Integer seconds) { + if (seconds == null) { + return null; + } + + int hours = seconds / 3600; + int minutes = (seconds / 60) % 60; + int secs = seconds % 60; + + StringBuilder builder = new StringBuilder(7); + if(hours > 0) { + builder.append(hours).append(":"); + if(minutes < 10) { + builder.append("0"); + } + } + builder.append(minutes).append(":"); + if (secs < 10) { + builder.append("0"); + } + builder.append(secs); + return builder.toString(); + } + + public static boolean equals(Object object1, Object object2) { + if (object1 == object2) { + return true; + } + if (object1 == null || object2 == null) { + return false; + } + return object1.equals(object2); + + } + + /** + * Encodes the given string by using the hexadecimal representation of its UTF-8 bytes. + * + * @param s The string to encode. + * @return The encoded string. + */ + public static String utf8HexEncode(String s) { + if (s == null) { + return null; + } + byte[] utf8; + try { + utf8 = s.getBytes(Constants.UTF_8); + } catch (UnsupportedEncodingException x) { + throw new RuntimeException(x); + } + return hexEncode(utf8); + } + + /** + * Converts an array of bytes into an array of characters representing the hexadecimal values of each byte in order. + * The returned array will be double the length of the passed array, as it takes two characters to represent any + * given byte. + * + * @param data Bytes to convert to hexadecimal characters. + * @return A string containing hexadecimal characters. + */ + public static String hexEncode(byte[] data) { + int length = data.length; + char[] out = new char[length << 1]; + // two characters form the hex value. + for (int i = 0, j = 0; i < length; i++) { + out[j++] = HEX_DIGITS[(0xF0 & data[i]) >>> 4]; + out[j++] = HEX_DIGITS[0x0F & data[i]]; + } + return new String(out); + } + + /** + * Calculates the MD5 digest and returns the value as a 32 character hex string. + * + * @param s Data to digest. + * @return MD5 digest as a hex string. + */ + public static String md5Hex(String s) { + if (s == null) { + return null; + } + + try { + MessageDigest md5 = MessageDigest.getInstance("MD5"); + return hexEncode(md5.digest(s.getBytes(Constants.UTF_8))); + } catch (Exception x) { + throw new RuntimeException(x.getMessage(), x); + } + } + + public static boolean isNullOrWhiteSpace(String string) { + return string == null || string.isEmpty() || string.trim().isEmpty(); + } + + public static boolean isNetworkConnected(Context context) { + ConnectivityManager manager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + NetworkInfo networkInfo = manager.getActiveNetworkInfo(); + boolean connected = networkInfo != null && networkInfo.isConnected(); + + boolean wifiConnected = connected && networkInfo.getType() == ConnectivityManager.TYPE_WIFI; + boolean wifiRequired = isWifiRequiredForDownload(context); + + return connected && (!wifiRequired || wifiConnected); + } + + public static boolean isExternalStoragePresent() { + return Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()); + } + + private static boolean isWifiRequiredForDownload(Context context) { + SharedPreferences prefs = getPreferences(context); + return prefs.getBoolean(Constants.PREFERENCES_KEY_WIFI_REQUIRED_FOR_DOWNLOAD, false); + } + + public static void info(Context context, int titleId, int messageId) { + showDialog(context, android.R.drawable.ic_dialog_info, titleId, messageId); + } + public static void info(Context context, int titleId, String message) { + showDialog(context, android.R.drawable.ic_dialog_info, titleId, message); + } + + private static void showDialog(Context context, int icon, int titleId, int messageId) { + new AlertDialog.Builder(context) + .setIcon(icon) + .setTitle(titleId) + .setMessage(messageId) + .setPositiveButton(R.string.common_ok, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int i) { + dialog.dismiss(); + } + }) + .show(); + } + private static void showDialog(Context context, int icon, int titleId, String message) { + new AlertDialog.Builder(context) + .setIcon(icon) + .setTitle(titleId) + .setMessage(message) + .setPositiveButton(R.string.common_ok, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int i) { + dialog.dismiss(); + } + }) + .show(); + } + + public static void showPlayingNotification(final Context context, final DownloadServiceImpl downloadService, Handler handler, MusicDirectory.Entry song) { + // Set the icon, scrolling text and timestamp + final Notification notification = new Notification(R.drawable.stat_notify_playing, song.getTitle(), System.currentTimeMillis()); + notification.flags |= Notification.FLAG_NO_CLEAR | Notification.FLAG_ONGOING_EVENT; + + boolean playing = downloadService.getPlayerState() == PlayerState.STARTED; + if (Build.VERSION.SDK_INT>= Build.VERSION_CODES.JELLY_BEAN){ + RemoteViews expandedContentView = new RemoteViews(context.getPackageName(), R.layout.notification_expanded); + setupViews(expandedContentView,context,song, playing); + notification.bigContentView = expandedContentView; + } + + RemoteViews smallContentView = new RemoteViews(context.getPackageName(), R.layout.notification); + setupViews(smallContentView, context, song, playing); + notification.contentView = smallContentView; + + Intent notificationIntent = new Intent(context, MainActivity.class); + notificationIntent.putExtra(Constants.INTENT_EXTRA_NAME_DOWNLOAD, true); + notificationIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); + notification.contentIntent = PendingIntent.getActivity(context, 0, notificationIntent, 0); + + handler.post(new Runnable() { + @Override + public void run() { + downloadService.startForeground(Constants.NOTIFICATION_ID_PLAYING, notification); + } + }); + + // Update widget + DSubWidgetProvider.notifyInstances(context, downloadService, true); + } + + private static void setupViews(RemoteViews rv, Context context, MusicDirectory.Entry song, boolean playing){ + + // Use the same text for the ticker and the expanded notification + String title = song.getTitle(); + String arist = song.getArtist(); + String album = song.getAlbum(); + + // Set the album art. + try { + int size = context.getResources().getDrawable(R.drawable.unknown_album).getIntrinsicHeight(); + Bitmap bitmap = FileUtil.getAlbumArtBitmap(context, song, size); + if (bitmap == null) { + // set default album art + rv.setImageViewResource(R.id.notification_image, R.drawable.unknown_album); + } else { + rv.setImageViewBitmap(R.id.notification_image, bitmap); + } + } catch (Exception x) { + Log.w(TAG, "Failed to get notification cover art", x); + rv.setImageViewResource(R.id.notification_image, R.drawable.unknown_album); + } + + // set the text for the notifications + rv.setTextViewText(R.id.notification_title, title); + rv.setTextViewText(R.id.notification_artist, arist); + rv.setTextViewText(R.id.notification_album, album); + + Pair colors = getNotificationTextColors(context); + if (colors.getFirst() != null) { + rv.setTextColor(R.id.notification_title, colors.getFirst()); + } + if (colors.getSecond() != null) { + rv.setTextColor(R.id.notification_artist, colors.getSecond()); + } + + if(!playing) { + rv.setImageViewResource(R.id.control_pause, R.drawable.notification_play); + rv.setImageViewResource(R.id.control_previous, R.drawable.notification_stop); + } + + // Create actions for media buttons + PendingIntent pendingIntent; + if(playing) { + Intent prevIntent = new Intent("KEYCODE_MEDIA_PREVIOUS"); + prevIntent.setComponent(new ComponentName(context, DownloadServiceImpl.class)); + prevIntent.putExtra(Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MEDIA_PREVIOUS)); + pendingIntent = PendingIntent.getService(context, 0, prevIntent, 0); + rv.setOnClickPendingIntent(R.id.control_previous, pendingIntent); + } else { + Intent prevIntent = new Intent("KEYCODE_MEDIA_STOP"); + prevIntent.setComponent(new ComponentName(context, DownloadServiceImpl.class)); + prevIntent.putExtra(Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MEDIA_STOP)); + pendingIntent = PendingIntent.getService(context, 0, prevIntent, 0); + rv.setOnClickPendingIntent(R.id.control_previous, pendingIntent); + } + + Intent pauseIntent = new Intent("KEYCODE_MEDIA_PLAY_PAUSE"); + pauseIntent.setComponent(new ComponentName(context, DownloadServiceImpl.class)); + pauseIntent.putExtra(Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE)); + pendingIntent = PendingIntent.getService(context, 0, pauseIntent, 0); + rv.setOnClickPendingIntent(R.id.control_pause, pendingIntent); + + Intent nextIntent = new Intent("KEYCODE_MEDIA_NEXT"); + nextIntent.setComponent(new ComponentName(context, DownloadServiceImpl.class)); + nextIntent.putExtra(Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MEDIA_NEXT)); + pendingIntent = PendingIntent.getService(context, 0, nextIntent, 0); + rv.setOnClickPendingIntent(R.id.control_next, pendingIntent); + } + + public static void hidePlayingNotification(final Context context, final DownloadServiceImpl downloadService, Handler handler) { + // Remove notification and remove the service from the foreground + handler.post(new Runnable() { + @Override + public void run() { + downloadService.stopForeground(true); + } + }); + + // Update widget + DSubWidgetProvider.notifyInstances(context, downloadService, false); + } + + public static void sleepQuietly(long millis) { + try { + Thread.sleep(millis); + } catch (InterruptedException x) { + Log.w(TAG, "Interrupted from sleep.", x); + } + } + + public static void startActivityWithoutTransition(Activity currentActivity, Class newActivitiy) { + startActivityWithoutTransition(currentActivity, new Intent(currentActivity, newActivitiy)); + } + + public static void startActivityWithoutTransition(Activity currentActivity, Intent intent) { + currentActivity.startActivity(intent); + disablePendingTransition(currentActivity); + } + + public static void disablePendingTransition(Activity activity) { + + // Activity.overridePendingTransition() was introduced in Android 2.0. Use reflection to maintain + // compatibility with 1.5. + try { + Method method = Activity.class.getMethod("overridePendingTransition", int.class, int.class); + method.invoke(activity, 0, 0); + } catch (Throwable x) { + // Ignored + } + } + + public static Drawable createDrawableFromBitmap(Context context, Bitmap bitmap) { + // BitmapDrawable(Resources, Bitmap) was introduced in Android 1.6. Use reflection to maintain + // compatibility with 1.5. + try { + Constructor constructor = BitmapDrawable.class.getConstructor(Resources.class, Bitmap.class); + return constructor.newInstance(context.getResources(), bitmap); + } catch (Throwable x) { + return new BitmapDrawable(bitmap); + } + } + + public static void registerMediaButtonEventReceiver(Context context) { + + // Only do it if enabled in the settings. + SharedPreferences prefs = getPreferences(context); + boolean enabled = prefs.getBoolean(Constants.PREFERENCES_KEY_MEDIA_BUTTONS, true); + + if (enabled) { + + // AudioManager.registerMediaButtonEventReceiver() was introduced in Android 2.2. + // Use reflection to maintain compatibility with 1.5. + try { + AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); + ComponentName componentName = new ComponentName(context.getPackageName(), MediaButtonIntentReceiver.class.getName()); + Method method = AudioManager.class.getMethod("registerMediaButtonEventReceiver", ComponentName.class); + method.invoke(audioManager, componentName); + } catch (Throwable x) { + // Ignored. + } + } + } + + public static void unregisterMediaButtonEventReceiver(Context context) { + // AudioManager.unregisterMediaButtonEventReceiver() was introduced in Android 2.2. + // Use reflection to maintain compatibility with 1.5. + try { + AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); + ComponentName componentName = new ComponentName(context.getPackageName(), MediaButtonIntentReceiver.class.getName()); + Method method = AudioManager.class.getMethod("unregisterMediaButtonEventReceiver", ComponentName.class); + method.invoke(audioManager, componentName); + } catch (Throwable x) { + // Ignored. + } + } + + @TargetApi(8) + public static void requestAudioFocus(final Context context) { + if (Build.VERSION.SDK_INT >= 8 && !hasFocus) { + final AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); + hasFocus = true; + audioManager.requestAudioFocus(new OnAudioFocusChangeListener() { + public void onAudioFocusChange(int focusChange) { + DownloadServiceImpl downloadService = (DownloadServiceImpl)context; + if((focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT || focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK) && !downloadService.isJukeboxEnabled()) { + if(downloadService.getPlayerState() == PlayerState.STARTED) { + SharedPreferences prefs = getPreferences(context); + int lossPref = Integer.parseInt(prefs.getString(Constants.PREFERENCES_KEY_TEMP_LOSS, "1")); + if(lossPref == 2 || (lossPref == 1 && focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK)) { + lowerFocus = true; + downloadService.setVolume(0.1f); + } else if(lossPref == 0 || (lossPref == 1 && focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT)) { + pauseFocus = true; + downloadService.pause(); + } + } + } else if (focusChange == AudioManager.AUDIOFOCUS_GAIN) { + if(pauseFocus) { + pauseFocus = false; + downloadService.start(); + } else if(lowerFocus) { + lowerFocus = false; + downloadService.setVolume(1.0f); + } + } else if(focusChange == AudioManager.AUDIOFOCUS_LOSS && !downloadService.isJukeboxEnabled()) { + hasFocus = false; + downloadService.pause(); + audioManager.abandonAudioFocus(this); + } + } + }, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN); + } + } + + /** + *

Broadcasts the given song info as the new song being played.

+ */ + public static void broadcastNewTrackInfo(Context context, MusicDirectory.Entry song) { + DownloadService downloadService = (DownloadServiceImpl)context; + Intent intent = new Intent(EVENT_META_CHANGED); + Intent avrcpIntent = new Intent(AVRCP_METADATA_CHANGED); + + if (song != null) { + intent.putExtra("title", song.getTitle()); + intent.putExtra("artist", song.getArtist()); + intent.putExtra("album", song.getAlbum()); + + File albumArtFile = FileUtil.getAlbumArtFile(context, song); + intent.putExtra("coverart", albumArtFile.getAbsolutePath()); + + avrcpIntent.putExtra("playing", true); + avrcpIntent.putExtra("track", song.getTitle()); + avrcpIntent.putExtra("artist", song.getArtist()); + avrcpIntent.putExtra("album", song.getAlbum()); + avrcpIntent.putExtra("ListSize",(long) downloadService.getSongs().size()); + avrcpIntent.putExtra("id", (long) downloadService.getCurrentPlayingIndex()+1); + avrcpIntent.putExtra("duration", (long) downloadService.getPlayerDuration()); + avrcpIntent.putExtra("position", (long) downloadService.getPlayerPosition()); + avrcpIntent.putExtra("coverart", albumArtFile.getAbsolutePath()); + } else { + intent.putExtra("title", ""); + intent.putExtra("artist", ""); + intent.putExtra("album", ""); + intent.putExtra("coverart", ""); + + avrcpIntent.putExtra("playing", false); + avrcpIntent.putExtra("track", ""); + avrcpIntent.putExtra("artist", ""); + avrcpIntent.putExtra("album", ""); + avrcpIntent.putExtra("ListSize",(long)0); + avrcpIntent.putExtra("id", (long) 0); + avrcpIntent.putExtra("duration", (long )0); + avrcpIntent.putExtra("position", (long) 0); + avrcpIntent.putExtra("coverart", ""); + } + + context.sendBroadcast(intent); + context.sendBroadcast(avrcpIntent); + } + + /** + *

Broadcasts the given player state as the one being set.

+ */ + public static void broadcastPlaybackStatusChange(Context context, PlayerState state) { + Intent intent = new Intent(EVENT_PLAYSTATE_CHANGED); + Intent avrcpIntent = new Intent(AVRCP_PLAYSTATE_CHANGED); + + switch (state) { + case STARTED: + intent.putExtra("state", "play"); + avrcpIntent.putExtra("playing", true); + break; + case STOPPED: + intent.putExtra("state", "stop"); + avrcpIntent.putExtra("playing", false); + break; + case PAUSED: + intent.putExtra("state", "pause"); + avrcpIntent.putExtra("playing", false); + break; + case COMPLETED: + intent.putExtra("state", "complete"); + avrcpIntent.putExtra("playing", false); + break; + default: + return; // No need to broadcast. + } + + context.sendBroadcast(intent); + context.sendBroadcast(avrcpIntent); + } + + /** + * Resolves the default text color for notifications. + * + * Based on http://stackoverflow.com/questions/4867338/custom-notification-layouts-and-text-colors/7320604#7320604 + */ + private static Pair getNotificationTextColors(Context context) { + if (NOTIFICATION_TEXT_COLORS.getFirst() == null && NOTIFICATION_TEXT_COLORS.getSecond() == null) { + try { + Notification notification = new Notification(); + String title = "title"; + String content = "content"; + notification.setLatestEventInfo(context, title, content, null); + LinearLayout group = new LinearLayout(context); + ViewGroup event = (ViewGroup) notification.contentView.apply(context, group); + findNotificationTextColors(event, title, content); + group.removeAllViews(); + } catch (Exception x) { + Log.w(TAG, "Failed to resolve notification text colors.", x); + } + } + return NOTIFICATION_TEXT_COLORS; + } + + private static void findNotificationTextColors(ViewGroup group, String title, String content) { + for (int i = 0; i < group.getChildCount(); i++) { + if (group.getChildAt(i) instanceof TextView) { + TextView textView = (TextView) group.getChildAt(i); + String text = textView.getText().toString(); + if (title.equals(text)) { + NOTIFICATION_TEXT_COLORS.setFirst(textView.getTextColors().getDefaultColor()); + } + else if (content.equals(text)) { + NOTIFICATION_TEXT_COLORS.setSecond(textView.getTextColors().getDefaultColor()); + } + } + else if (group.getChildAt(i) instanceof ViewGroup) + findNotificationTextColors((ViewGroup) group.getChildAt(i), title, content); + } + } + + public static WifiManager.WifiLock createWifiLock(Context context, String tag) { + WifiManager wm = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); + int lockType = WifiManager.WIFI_MODE_FULL; + if (Build.VERSION.SDK_INT >= 12) { + lockType = 3; + } + return wm.createWifiLock(lockType, tag); + } +} -- cgit v1.2.3 From e18f29369683f6563164b43c49e9eaa41de3835f Mon Sep 17 00:00:00 2001 From: Scott Jackson Date: Mon, 10 Jun 2013 06:50:54 -0700 Subject: Moved scrobbling into MainFragment when toggling back to online mode --- .../daneren2005/dsub/fragments/MainFragment.java | 62 +++++++++++++++++++++- .../dsub/fragments/SelectDirectoryFragment.java | 57 -------------------- .../dsub/service/OfflineMusicService.java | 1 + 3 files changed, 62 insertions(+), 58 deletions(-) diff --git a/subsonic-android/src/github/daneren2005/dsub/fragments/MainFragment.java b/subsonic-android/src/github/daneren2005/dsub/fragments/MainFragment.java index bbe3c507..9a0f512a 100644 --- a/subsonic-android/src/github/daneren2005/dsub/fragments/MainFragment.java +++ b/subsonic-android/src/github/daneren2005/dsub/fragments/MainFragment.java @@ -1,10 +1,13 @@ package github.daneren2005.dsub.fragments; +import android.app.AlertDialog; import android.content.Context; +import android.content.DialogInterface; import android.content.Intent; import android.net.Uri; import android.os.Bundle; import android.os.StatFs; +import android.util.Log; import android.view.ContextMenu; import android.view.LayoutInflater; import android.view.View; @@ -21,6 +24,8 @@ import github.daneren2005.dsub.util.Util; import com.actionbarsherlock.view.Menu; import com.actionbarsherlock.view.MenuItem; import com.actionbarsherlock.view.MenuInflater; +import github.daneren2005.dsub.service.MusicService; +import github.daneren2005.dsub.service.MusicServiceFactory; import github.daneren2005.dsub.util.ModalBackgroundTask; import github.daneren2005.dsub.view.ChangeLog; import java.io.File; @@ -29,6 +34,7 @@ import java.util.Arrays; import java.util.List; public class MainFragment extends SubsonicFragment { + private static final String TAG = MainFragment.class.getSimpleName(); private LayoutInflater inflater; private static final int MENU_GROUP_SERVER = 10; @@ -192,8 +198,16 @@ public class MainFragment extends SubsonicFragment { } private void toggleOffline() { - Util.setOffline(context, !Util.isOffline(context)); + boolean isOffline = Util.isOffline(context); + Util.setOffline(context, !isOffline); context.getPagerAdapter().invalidate(); + + if(isOffline) { + MusicService musicService = MusicServiceFactory.getMusicService(context); + if(musicService.hasOfflineScrobbles()){ + showOfflineScrobblesDialog(); + } + } } private void showAlbumList(String type) { @@ -211,6 +225,52 @@ public class MainFragment extends SubsonicFragment { replaceFragment(fragment, R.id.home_layout); } } + + private void showOfflineScrobblesDialog() { + AlertDialog.Builder builder = new AlertDialog.Builder(context); + builder.setIcon(android.R.drawable.ic_dialog_info); + + builder.setTitle(R.string.select_album_offline_scrobbles_dialog_title); + builder.setMessage(R.string.select_album_offline_scrobbles_dialog_message); + + //want this on the left and delete on the right hence the backwards button types + builder.setNegativeButton(R.string.select_album_offline_scrobbles_dialog_yes, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + new Thread("Scrobble offline") { + @Override + public void run() { + try { + MusicService musicService = MusicServiceFactory.getMusicService(context); + musicService.processOfflineScrobbles(context, null); + } catch (Exception x) { + Log.i(TAG, "Failed to process offline sc/robbles"); + } + } + }.start(); + } + }); + + builder.setPositiveButton(R.string.select_album_offline_scrobbles_dialog_delete, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + dialogInterface.dismiss(); + FileUtil.getOfflineScrobblesFile().delete(); + } + }); + + builder.setNeutralButton(R.string.select_album_offline_scrobbles_dialog_no, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + dialogInterface.dismiss(); + } + }); + + builder.create().show(); + } private void showAboutDialog() { try { diff --git a/subsonic-android/src/github/daneren2005/dsub/fragments/SelectDirectoryFragment.java b/subsonic-android/src/github/daneren2005/dsub/fragments/SelectDirectoryFragment.java index 971bdd62..82b6c481 100644 --- a/subsonic-android/src/github/daneren2005/dsub/fragments/SelectDirectoryFragment.java +++ b/subsonic-android/src/github/daneren2005/dsub/fragments/SelectDirectoryFragment.java @@ -27,7 +27,6 @@ import github.daneren2005.dsub.service.MusicServiceFactory; import github.daneren2005.dsub.service.OfflineException; import github.daneren2005.dsub.service.ServerTooOldException; import github.daneren2005.dsub.util.Constants; -import github.daneren2005.dsub.util.FileUtil; import github.daneren2005.dsub.util.LoadingTask; import github.daneren2005.dsub.util.Pair; import github.daneren2005.dsub.util.TabBackgroundTask; @@ -273,12 +272,6 @@ public class SelectDirectoryFragment extends SubsonicFragment implements Adapter } else { getMusicDirectory(id, name, refresh); } - - //this may be done better elsewhere but it works for now :) - MusicService musicService = MusicServiceFactory.getMusicService(context); - if(musicService.hasOfflineScrobbles()){ - showOfflineScrobblesDialog(); - } } private void getMusicDirectory(final String id, final String name, final boolean refresh) { @@ -640,56 +633,6 @@ public class SelectDirectoryFragment extends SubsonicFragment implements Adapter builder.create().show(); } - - private void showOfflineScrobblesDialog() { - - - AlertDialog.Builder builder = new AlertDialog.Builder(context); - builder.setIcon(android.R.drawable.ic_dialog_info); - - builder.setTitle(R.string.select_album_offline_scrobbles_dialog_title); - - builder.setMessage(R.string.select_album_offline_scrobbles_dialog_message); - - //want this on the left and delete on the right hence the backwards button types - builder.setNegativeButton(R.string.select_album_offline_scrobbles_dialog_yes, - - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialogInterface, int i) { - new Thread("Scrobble offline") { - @Override - public void run() { - try { - MusicService musicService = MusicServiceFactory.getMusicService(context); - musicService.processOfflineScrobbles(context, null); - } catch (Exception x) { - Log.i(TAG, "Failed to process offline sc/robbles"); - } - } - }.start(); - } - }); - - builder.setPositiveButton(R.string.select_album_offline_scrobbles_dialog_delete, - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialogInterface, int i) { - dialogInterface.dismiss(); - FileUtil.getOfflineScrobblesFile().delete(); - } - }); - - builder.setNeutralButton(R.string.select_album_offline_scrobbles_dialog_no, - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialogInterface, int i) { - dialogInterface.dismiss(); - } - }); - - builder.create().show(); - } private View createHeader(List entries) { View header = entryList.findViewById(R.id.select_album_header); diff --git a/subsonic-android/src/github/daneren2005/dsub/service/OfflineMusicService.java b/subsonic-android/src/github/daneren2005/dsub/service/OfflineMusicService.java index 3efb22dc..85d91b21 100644 --- a/subsonic-android/src/github/daneren2005/dsub/service/OfflineMusicService.java +++ b/subsonic-android/src/github/daneren2005/dsub/service/OfflineMusicService.java @@ -32,6 +32,7 @@ import java.util.Set; import android.content.Context; import android.content.SharedPreferences; import android.graphics.Bitmap; +import android.media.MediaMetadataRetriever; import android.util.Log; import github.daneren2005.dsub.domain.Artist; import github.daneren2005.dsub.domain.Genre; -- cgit v1.2.3 From 67c2d3412f322b6c681a7dd04d4399080aa28b17 Mon Sep 17 00:00:00 2001 From: Scott Jackson Date: Mon, 10 Jun 2013 08:40:07 -0700 Subject: Fix inconsistencies. Make dialog more like others --- subsonic-android/res/values/strings.xml | 10 ++--- .../daneren2005/dsub/fragments/MainFragment.java | 51 +++++++++++----------- 2 files changed, 30 insertions(+), 31 deletions(-) diff --git a/subsonic-android/res/values/strings.xml b/subsonic-android/res/values/strings.xml index a3f8f540..f3126ae0 100644 --- a/subsonic-android/res/values/strings.xml +++ b/subsonic-android/res/values/strings.xml @@ -114,11 +114,11 @@ Now Later Trial period is over - Offline scrobbles file found. - Process offline scrobbles file? - Yes - No - Delete file + + Offline scrobbles file found. + Process offline scrobbles file? + Successfully scrobbled songs + Failed to scrobble songs No genres found diff --git a/subsonic-android/src/github/daneren2005/dsub/fragments/MainFragment.java b/subsonic-android/src/github/daneren2005/dsub/fragments/MainFragment.java index 9a0f512a..645f2ae2 100644 --- a/subsonic-android/src/github/daneren2005/dsub/fragments/MainFragment.java +++ b/subsonic-android/src/github/daneren2005/dsub/fragments/MainFragment.java @@ -27,6 +27,7 @@ import com.actionbarsherlock.view.MenuInflater; import github.daneren2005.dsub.service.MusicService; import github.daneren2005.dsub.service.MusicServiceFactory; import github.daneren2005.dsub.util.ModalBackgroundTask; +import github.daneren2005.dsub.util.SilentBackgroundTask; import github.daneren2005.dsub.view.ChangeLog; import java.io.File; import java.util.ArrayList; @@ -228,44 +229,42 @@ public class MainFragment extends SubsonicFragment { private void showOfflineScrobblesDialog() { AlertDialog.Builder builder = new AlertDialog.Builder(context); - builder.setIcon(android.R.drawable.ic_dialog_info); - - builder.setTitle(R.string.select_album_offline_scrobbles_dialog_title); - builder.setMessage(R.string.select_album_offline_scrobbles_dialog_message); - - //want this on the left and delete on the right hence the backwards button types - builder.setNegativeButton(R.string.select_album_offline_scrobbles_dialog_yes, - new DialogInterface.OnClickListener() { + builder.setIcon(android.R.drawable.ic_dialog_info) + .setTitle(R.string.offline_scrobbles_dialog_title) + .setMessage(R.string.offline_scrobbles_dialog_message) + .setPositiveButton(R.string.common_ok, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialogInterface, int i) { - new Thread("Scrobble offline") { + new SilentBackgroundTask(context) { @Override - public void run() { - try { - MusicService musicService = MusicServiceFactory.getMusicService(context); - musicService.processOfflineScrobbles(context, null); - } catch (Exception x) { - Log.i(TAG, "Failed to process offline sc/robbles"); - } + protected Void doInBackground() throws Throwable { + MusicService musicService = MusicServiceFactory.getMusicService(context); + musicService.processOfflineScrobbles(context, null); + return null; + } + + @Override + protected void done(Void result) { + Util.toast(context, R.string.offline_scrobbles_success); } - }.start(); - } - }); - builder.setPositiveButton(R.string.select_album_offline_scrobbles_dialog_delete, - new DialogInterface.OnClickListener() { + @Override + protected void error(Throwable error) { + String msg = context.getResources().getString(R.string.offline_scrobbles_error) + " " + getErrorMessage(error); + Util.toast(context, msg); + } + }.execute(); + } + }).setNeutralButton(R.string.common_cancel, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialogInterface, int i) { dialogInterface.dismiss(); - FileUtil.getOfflineScrobblesFile().delete(); } - }); - - builder.setNeutralButton(R.string.select_album_offline_scrobbles_dialog_no, - new DialogInterface.OnClickListener() { + }).setNegativeButton(R.string.common_delete, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialogInterface, int i) { dialogInterface.dismiss(); + FileUtil.getOfflineScrobblesFile().delete(); } }); -- cgit v1.2.3 From 5f45683e6937f13107775aa3434f7fcd01ebd9d6 Mon Sep 17 00:00:00 2001 From: Scott Jackson Date: Tue, 11 Jun 2013 12:11:15 -0700 Subject: Fix offline scrobbling in case of deeper trees + non mp3s --- .../src/github/daneren2005/dsub/service/OfflineMusicService.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/subsonic-android/src/github/daneren2005/dsub/service/OfflineMusicService.java b/subsonic-android/src/github/daneren2005/dsub/service/OfflineMusicService.java index 85d91b21..a9d18c5c 100644 --- a/subsonic-android/src/github/daneren2005/dsub/service/OfflineMusicService.java +++ b/subsonic-android/src/github/daneren2005/dsub/service/OfflineMusicService.java @@ -444,12 +444,15 @@ public class OfflineMusicService extends RESTMusicService { scrobbleSearchCriteria = scrobbleSearchCriteria.substring(1); } - scrobbleSearchCriteria = scrobbleSearchCriteria.replace(".complete", "").replace(".partial", "").replace(".mp3", ""); + scrobbleSearchCriteria = scrobbleSearchCriteria.replace(".complete", "").replace(".partial", ""); + int index = scrobbleSearchCriteria.lastIndexOf("."); + scrobbleSearchCriteria = index == -1 ? scrobbleSearchCriteria : scrobbleSearchCriteria.substring(0, index); String[] details = scrobbleSearchCriteria.split("/"); //last.fm only uses artist and track title so broaden the search by just using those. doesn't matter if it find the track on a different album - String artist = "artist:\""+details[0]+"\""; - String title = "title:\""+details[2].substring(details[2].indexOf('-')+1)+"\""; + String artist = "artist:\"" + details[0] + "\""; + String title = details[details.length - 1]; + title = "title:\"" + title.substring(title.indexOf('-') + 1) + "\""; scrobbleSearchCriteria = artist + " AND " + title; -- cgit v1.2.3 From 8dd5b6136e8ae5dd41a9004617238f87a8d27281 Mon Sep 17 00:00:00 2001 From: Scott Jackson Date: Fri, 14 Jun 2013 20:33:58 -0700 Subject: Convert offline scrobbling to SharedPreference, add support for offline mode with online added songs --- subsonic-android/res/values/strings.xml | 3 +- .../daneren2005/dsub/fragments/MainFragment.java | 28 ++++++---- .../dsub/service/CachedMusicService.java | 11 ++-- .../daneren2005/dsub/service/MusicService.java | 4 +- .../dsub/service/OfflineMusicService.java | 59 +++++++++++----------- .../daneren2005/dsub/service/RESTMusicService.java | 59 ++++++++-------------- .../github/daneren2005/dsub/util/Constants.java | 8 ++- .../src/github/daneren2005/dsub/util/FileUtil.java | 5 -- .../src/github/daneren2005/dsub/util/Util.java | 8 +++ 9 files changed, 89 insertions(+), 96 deletions(-) diff --git a/subsonic-android/res/values/strings.xml b/subsonic-android/res/values/strings.xml index f3126ae0..224247fb 100644 --- a/subsonic-android/res/values/strings.xml +++ b/subsonic-android/res/values/strings.xml @@ -117,7 +117,8 @@ Offline scrobbles file found. Process offline scrobbles file? - Successfully scrobbled songs + Successfully scrobbled %1$d songs + Successfully scrobbled %1$d of %2$d songs. Try the rest on a different server. Failed to scrobble songs No genres found diff --git a/subsonic-android/src/github/daneren2005/dsub/fragments/MainFragment.java b/subsonic-android/src/github/daneren2005/dsub/fragments/MainFragment.java index 645f2ae2..45d6eb49 100644 --- a/subsonic-android/src/github/daneren2005/dsub/fragments/MainFragment.java +++ b/subsonic-android/src/github/daneren2005/dsub/fragments/MainFragment.java @@ -4,6 +4,7 @@ import android.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; +import android.content.SharedPreferences; import android.net.Uri; import android.os.Bundle; import android.os.StatFs; @@ -204,9 +205,9 @@ public class MainFragment extends SubsonicFragment { context.getPagerAdapter().invalidate(); if(isOffline) { - MusicService musicService = MusicServiceFactory.getMusicService(context); - if(musicService.hasOfflineScrobbles()){ - showOfflineScrobblesDialog(); + int count = Util.offlineScrobblesCount(context); + if(count > 0){ + showOfflineScrobblesDialog(count); } } } @@ -227,7 +228,7 @@ public class MainFragment extends SubsonicFragment { } } - private void showOfflineScrobblesDialog() { + private void showOfflineScrobblesDialog(final int count) { AlertDialog.Builder builder = new AlertDialog.Builder(context); builder.setIcon(android.R.drawable.ic_dialog_info) .setTitle(R.string.offline_scrobbles_dialog_title) @@ -235,17 +236,20 @@ public class MainFragment extends SubsonicFragment { .setPositiveButton(R.string.common_ok, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialogInterface, int i) { - new SilentBackgroundTask(context) { + new SilentBackgroundTask(context) { @Override - protected Void doInBackground() throws Throwable { + protected Integer doInBackground() throws Throwable { MusicService musicService = MusicServiceFactory.getMusicService(context); - musicService.processOfflineScrobbles(context, null); - return null; + return musicService.processOfflineScrobbles(context, null); } @Override - protected void done(Void result) { - Util.toast(context, R.string.offline_scrobbles_success); + protected void done(Integer result) { + if(result == count) { + Util.toast(context, context.getResources().getString(R.string.offline_scrobbles_success, result)); + } else { + Util.toast(context, context.getResources().getString(R.string.offline_scrobbles_partial, result, count)); + } } @Override @@ -264,7 +268,9 @@ public class MainFragment extends SubsonicFragment { @Override public void onClick(DialogInterface dialogInterface, int i) { dialogInterface.dismiss(); - FileUtil.getOfflineScrobblesFile().delete(); + SharedPreferences.Editor offline = Util.getOfflineSync(context).edit(); + offline.putInt(Constants.OFFLINE_SCROBBLE_COUNT, 0); + offline.commit(); } }); diff --git a/subsonic-android/src/github/daneren2005/dsub/service/CachedMusicService.java b/subsonic-android/src/github/daneren2005/dsub/service/CachedMusicService.java index 218949c4..59e81c3b 100644 --- a/subsonic-android/src/github/daneren2005/dsub/service/CachedMusicService.java +++ b/subsonic-android/src/github/daneren2005/dsub/service/CachedMusicService.java @@ -303,14 +303,9 @@ public class CachedMusicService implements MusicService { } @Override - public boolean hasOfflineScrobbles(){ - return musicService.hasOfflineScrobbles(); - } - - @Override - public void processOfflineScrobbles(final Context context, final ProgressListener progressListener) throws Exception{ - musicService.processOfflineScrobbles(context, progressListener); - } + public int processOfflineScrobbles(final Context context, final ProgressListener progressListener) throws Exception{ + return musicService.processOfflineScrobbles(context, progressListener); + } diff --git a/subsonic-android/src/github/daneren2005/dsub/service/MusicService.java b/subsonic-android/src/github/daneren2005/dsub/service/MusicService.java index 2787b75b..1689835f 100644 --- a/subsonic-android/src/github/daneren2005/dsub/service/MusicService.java +++ b/subsonic-android/src/github/daneren2005/dsub/service/MusicService.java @@ -118,7 +118,5 @@ public interface MusicService { public MusicDirectory getSongsByGenre(String genre, int count, int offset, Context context, ProgressListener progressListener) throws Exception; - boolean hasOfflineScrobbles(); - - void processOfflineScrobbles(final Context context, final ProgressListener progressListener) throws Exception; + int processOfflineScrobbles(final Context context, final ProgressListener progressListener) throws Exception; } \ No newline at end of file diff --git a/subsonic-android/src/github/daneren2005/dsub/service/OfflineMusicService.java b/subsonic-android/src/github/daneren2005/dsub/service/OfflineMusicService.java index a9d18c5c..bda112ff 100644 --- a/subsonic-android/src/github/daneren2005/dsub/service/OfflineMusicService.java +++ b/subsonic-android/src/github/daneren2005/dsub/service/OfflineMusicService.java @@ -431,36 +431,42 @@ public class OfflineMusicService extends RESTMusicService { @Override public void scrobble(String id, boolean submission, Context context, ProgressListener progressListener) throws Exception { if(!submission) { - return; + return; } SharedPreferences prefs = Util.getPreferences(context); String cacheLocn = prefs.getString(Constants.PREFERENCES_KEY_CACHE_LOCATION, null); - File offlineScrobblesFile = FileUtil.getOfflineScrobblesFile(); - - String scrobbleSearchCriteria = id.replace(cacheLocn, ""); - if(scrobbleSearchCriteria.startsWith("/")) { - scrobbleSearchCriteria = scrobbleSearchCriteria.substring(1); - } - - scrobbleSearchCriteria = scrobbleSearchCriteria.replace(".complete", "").replace(".partial", ""); - int index = scrobbleSearchCriteria.lastIndexOf("."); - scrobbleSearchCriteria = index == -1 ? scrobbleSearchCriteria : scrobbleSearchCriteria.substring(0, index); - String[] details = scrobbleSearchCriteria.split("/"); + SharedPreferences offline = Util.getOfflineSync(context); + int scrobbles = offline.getInt(Constants.OFFLINE_SCROBBLE_COUNT, 0); + scrobbles++; + SharedPreferences.Editor offlineEditor = offline.edit(); + + if(id.indexOf(cacheLocn) != -1) { + String scrobbleSearchCriteria = id.replace(cacheLocn, ""); + if(scrobbleSearchCriteria.startsWith("/")) { + scrobbleSearchCriteria = scrobbleSearchCriteria.substring(1); + } - //last.fm only uses artist and track title so broaden the search by just using those. doesn't matter if it find the track on a different album - String artist = "artist:\"" + details[0] + "\""; - String title = details[details.length - 1]; - title = "title:\"" + title.substring(title.indexOf('-') + 1) + "\""; + scrobbleSearchCriteria = scrobbleSearchCriteria.replace(".complete", "").replace(".partial", ""); + int index = scrobbleSearchCriteria.lastIndexOf("."); + scrobbleSearchCriteria = index == -1 ? scrobbleSearchCriteria : scrobbleSearchCriteria.substring(0, index); + String[] details = scrobbleSearchCriteria.split("/"); - scrobbleSearchCriteria = artist + " AND " + title; + //last.fm only uses artist and track title so broaden the search by just using those. doesn't matter if it find the track on a different album + String artist = "artist:\"" + details[0] + "\""; + String title = details[details.length - 1]; + title = "title:\"" + title.substring(title.indexOf('-') + 1) + "\""; - BufferedWriter bw = new BufferedWriter(new FileWriter(offlineScrobblesFile, true)); - bw.write(scrobbleSearchCriteria + "," + System.currentTimeMillis()); - bw.newLine(); - bw.flush(); - bw.close(); + scrobbleSearchCriteria = artist + " AND " + title; + offlineEditor.putString(Constants.OFFLINE_SCROBBLE_SEARCH + scrobbles, scrobbleSearchCriteria); + } else { + offlineEditor.putString(Constants.OFFLINE_SCROBBLE_ID + scrobbles, id); + } + + offlineEditor.putLong(Constants.OFFLINE_SCROBBLE_TIME + scrobbles, System.currentTimeMillis()); + offlineEditor.putInt(Constants.OFFLINE_SCROBBLE_COUNT, scrobbles); + offlineEditor.commit(); } @Override @@ -543,13 +549,8 @@ public class OfflineMusicService extends RESTMusicService { } @Override - public boolean hasOfflineScrobbles(){ - return false; - } - - @Override - public void processOfflineScrobbles(final Context context, final ProgressListener progressListener) throws Exception{ - throw new OfflineException("Offline scrobble cached can not be processes while in offline mode"); + public int processOfflineScrobbles(final Context context, final ProgressListener progressListener) throws Exception{ + throw new OfflineException("Offline scrobble cached can not be processes while in offline mode"); } private void listFilesRecursively(File parent, List children) { diff --git a/subsonic-android/src/github/daneren2005/dsub/service/RESTMusicService.java b/subsonic-android/src/github/daneren2005/dsub/service/RESTMusicService.java index 8a84cd21..53eccf71 100644 --- a/subsonic-android/src/github/daneren2005/dsub/service/RESTMusicService.java +++ b/subsonic-android/src/github/daneren2005/dsub/service/RESTMusicService.java @@ -869,35 +869,23 @@ public class RESTMusicService implements MusicService { } @Override - public boolean hasOfflineScrobbles(){ - return FileUtil.getOfflineScrobblesFile().exists(); - } - - @Override - public void processOfflineScrobbles(final Context context, final ProgressListener progressListener) throws Exception{ - File offlineScrobblesFile = FileUtil.getOfflineScrobblesFile(); - try{ - - BufferedReader br = new BufferedReader(new FileReader(offlineScrobblesFile)); - String line; - - ArrayList lines = new ArrayList(); - while ((line = br.readLine()) != null) { - lines.add(line); - } - br.close(); - offlineScrobblesFile.delete(); - - for(int i = 0; i < lines.size(); i++){ - line = lines.get(i); - String filename = line.substring(0, line.lastIndexOf(',')); - + public int processOfflineScrobbles(final Context context, final ProgressListener progressListener) throws Exception{ + SharedPreferences offline = Util.getOfflineSync(context); + SharedPreferences.Editor offlineEditor = offline.edit(); + int count = offline.getInt(Constants.OFFLINE_SCROBBLE_COUNT, 0); + int retry = 0; + for(int i = 1; i <= count; i++) { + String id = offline.getString(Constants.OFFLINE_SCROBBLE_ID + i, null); + long time = offline.getLong(Constants.OFFLINE_SCROBBLE_TIME + i, 0); + if(id != null) { + scrobble(id, true, time, context, progressListener); + } else { + String search = offline.getString(Constants.OFFLINE_SCROBBLE_SEARCH + i, ""); try{ - long time = Long.parseLong(line.substring(line.lastIndexOf(',')+1)); - SearchCritera critera = new SearchCritera(filename, 0, 0, 1); + SearchCritera critera = new SearchCritera(search, 0, 0, 1); SearchResult result = searchNew(critera, context, progressListener); if(result.getSongs().size() == 1){ - Log.i(TAG, "Query '" + filename + "' returned song " + result.getSongs().get(0).getTitle() + " by " + result.getSongs().get(0).getArtist() + " with id " + result.getSongs().get(0).getId()); + Log.i(TAG, "Query '" + search + "' returned song " + result.getSongs().get(0).getTitle() + " by " + result.getSongs().get(0).getArtist() + " with id " + result.getSongs().get(0).getId()); Log.i(TAG, "Scrobbling " + result.getSongs().get(0).getId() + " with time " + time); scrobble(result.getSongs().get(0).getId(), true, time, context, progressListener); } @@ -907,20 +895,17 @@ public class RESTMusicService implements MusicService { } catch(Exception e){ Log.e(TAG, e.toString()); - BufferedWriter bw = new BufferedWriter(new FileWriter(offlineScrobblesFile, true)); - bw.write(line); - bw.newLine(); - bw.flush(); - bw.close(); + retry++; + offlineEditor.putString(Constants.OFFLINE_SCROBBLE_SEARCH + retry, search); + offlineEditor.putLong(Constants.OFFLINE_SCROBBLE_TIME + retry, time); } } } - catch(FileNotFoundException fnfe){ - //ignore, we dont care - } - catch(Exception e){ - Log.e(TAG, e.toString()); - } + + offlineEditor.putInt(Constants.OFFLINE_SCROBBLE_COUNT, retry); + offlineEditor.commit(); + + return count - retry; } private Reader getReader(Context context, ProgressListener progressListener, String method, HttpParams requestParams) throws Exception { diff --git a/subsonic-android/src/github/daneren2005/dsub/util/Constants.java b/subsonic-android/src/github/daneren2005/dsub/util/Constants.java index d6aa4541..5714b1f1 100644 --- a/subsonic-android/src/github/daneren2005/dsub/util/Constants.java +++ b/subsonic-android/src/github/daneren2005/dsub/util/Constants.java @@ -109,6 +109,11 @@ public final class Constants { public static final String PREFERENCES_KEY_CHAT_ENABLED = "chatEnabled"; public static final String PREFERENCES_KEY_VIDEO_PLAYER = "videoPlayer"; + public static final String OFFLINE_SCROBBLE_COUNT = "scrobbleCount"; + public static final String OFFLINE_SCROBBLE_ID = "scrobbleID"; + public static final String OFFLINE_SCROBBLE_SEARCH = "scrobbleTitle"; + public static final String OFFLINE_SCROBBLE_TIME = "scrobbleTime"; + public static final String CACHE_KEY_IGNORE = "ignoreArticles"; public static final String MAIN_BACK_STACK = "backStackIds"; @@ -118,6 +123,7 @@ public final class Constants { // Name of the preferences file. public static final String PREFERENCES_FILE_NAME = "github.daneren2005.dsub_preferences"; + public static final String OFFLINE_SYNC_NAME = "github.daneren2005.dsub.offline"; // Number of free trial days for non-licensed servers. public static final int FREE_TRIAL_DAYS = 30; @@ -126,8 +132,6 @@ public final class Constants { public static final String DONATION_URL = "http://subsonic.org/pages/android-donation.jsp"; public static final String ALBUM_ART_FILE = "albumart.jpg"; - - public static final String OFFLINE_SCROBBLES_FILE = "offline_scrobbles.log"; private Constants() { } diff --git a/subsonic-android/src/github/daneren2005/dsub/util/FileUtil.java b/subsonic-android/src/github/daneren2005/dsub/util/FileUtil.java index ca06b55f..7ead874c 100644 --- a/subsonic-android/src/github/daneren2005/dsub/util/FileUtil.java +++ b/subsonic-android/src/github/daneren2005/dsub/util/FileUtil.java @@ -149,11 +149,6 @@ public class FileUtil { } return dir; } - - public static File getOfflineScrobblesFile(){ - File offlineScrobblesFile = new File(getSubsonicDirectory(), fileSystemSafe(Constants.OFFLINE_SCROBBLES_FILE)); - return offlineScrobblesFile; - } public static void createDirectoryForParent(File file) { File dir = file.getParentFile(); diff --git a/subsonic-android/src/github/daneren2005/dsub/util/Util.java b/subsonic-android/src/github/daneren2005/dsub/util/Util.java index 269b05d3..76bf6a04 100644 --- a/subsonic-android/src/github/daneren2005/dsub/util/Util.java +++ b/subsonic-android/src/github/daneren2005/dsub/util/Util.java @@ -332,6 +332,14 @@ public final class Util { public static SharedPreferences getPreferences(Context context) { return context.getSharedPreferences(Constants.PREFERENCES_FILE_NAME, 0); } + public static SharedPreferences getOfflineSync(Context context) { + return context.getSharedPreferences(Constants.OFFLINE_SYNC_NAME, 0); + } + + public static int offlineScrobblesCount(Context context) { + SharedPreferences offline = getOfflineSync(context); + return offline.getInt(Constants.OFFLINE_SCROBBLE_COUNT, 0); + } public static String getContentType(HttpEntity entity) { if (entity == null || entity.getContentType() == null) { -- cgit v1.2.3 From 6aa48f1b3faece3d949cc3980ac405a557bcc9c1 Mon Sep 17 00:00:00 2001 From: Scott Jackson Date: Fri, 14 Jun 2013 20:47:39 -0700 Subject: Abstract out search string parsing, use when playing offline mode songs in online mode --- .../daneren2005/dsub/service/OfflineMusicService.java | 19 +++---------------- .../daneren2005/dsub/service/RESTMusicService.java | 14 +++++++++++++- .../src/github/daneren2005/dsub/util/Util.java | 19 +++++++++++++++++++ 3 files changed, 35 insertions(+), 17 deletions(-) diff --git a/subsonic-android/src/github/daneren2005/dsub/service/OfflineMusicService.java b/subsonic-android/src/github/daneren2005/dsub/service/OfflineMusicService.java index bda112ff..1878fecf 100644 --- a/subsonic-android/src/github/daneren2005/dsub/service/OfflineMusicService.java +++ b/subsonic-android/src/github/daneren2005/dsub/service/OfflineMusicService.java @@ -443,25 +443,12 @@ public class OfflineMusicService extends RESTMusicService { SharedPreferences.Editor offlineEditor = offline.edit(); if(id.indexOf(cacheLocn) != -1) { - String scrobbleSearchCriteria = id.replace(cacheLocn, ""); - if(scrobbleSearchCriteria.startsWith("/")) { - scrobbleSearchCriteria = scrobbleSearchCriteria.substring(1); - } - - scrobbleSearchCriteria = scrobbleSearchCriteria.replace(".complete", "").replace(".partial", ""); - int index = scrobbleSearchCriteria.lastIndexOf("."); - scrobbleSearchCriteria = index == -1 ? scrobbleSearchCriteria : scrobbleSearchCriteria.substring(0, index); - String[] details = scrobbleSearchCriteria.split("/"); - - //last.fm only uses artist and track title so broaden the search by just using those. doesn't matter if it find the track on a different album - String artist = "artist:\"" + details[0] + "\""; - String title = details[details.length - 1]; - title = "title:\"" + title.substring(title.indexOf('-') + 1) + "\""; - - scrobbleSearchCriteria = artist + " AND " + title; + String scrobbleSearchCriteria = Util.parseOfflineIDSearch(context, id, cacheLocn); offlineEditor.putString(Constants.OFFLINE_SCROBBLE_SEARCH + scrobbles, scrobbleSearchCriteria); + offlineEditor.remove(Constants.OFFLINE_SCROBBLE_ID + scrobbles); } else { offlineEditor.putString(Constants.OFFLINE_SCROBBLE_ID + scrobbles, id); + offlineEditor.remove(Constants.OFFLINE_SCROBBLE_SEARCH + scrobbles); } offlineEditor.putLong(Constants.OFFLINE_SCROBBLE_TIME + scrobbles, System.currentTimeMillis()); diff --git a/subsonic-android/src/github/daneren2005/dsub/service/RESTMusicService.java b/subsonic-android/src/github/daneren2005/dsub/service/RESTMusicService.java index 53eccf71..249efc10 100644 --- a/subsonic-android/src/github/daneren2005/dsub/service/RESTMusicService.java +++ b/subsonic-android/src/github/daneren2005/dsub/service/RESTMusicService.java @@ -491,7 +491,19 @@ public class RESTMusicService implements MusicService { @Override public void scrobble(String id, boolean submission, Context context, ProgressListener progressListener) throws Exception { - scrobble(id, submission, 0, context, progressListener); + SharedPreferences prefs = Util.getPreferences(context); + String cacheLocn = prefs.getString(Constants.PREFERENCES_KEY_CACHE_LOCATION, null); + + if(id.indexOf(cacheLocn) != -1 && submission) { + String scrobbleSearchCriteria = Util.parseOfflineIDSearch(context, id, cacheLocn); + SearchCritera critera = new SearchCritera(scrobbleSearchCriteria, 0, 0, 1); + SearchResult result = searchNew(critera, context, progressListener); + if(result.getSongs().size() == 1){ + scrobble(result.getSongs().get(0).getId(), true, 0, context, progressListener); + } + } else { + scrobble(id, submission, 0, context, progressListener); + } } public void scrobble(String id, boolean submission, long time, Context context, ProgressListener progressListener) throws Exception { diff --git a/subsonic-android/src/github/daneren2005/dsub/util/Util.java b/subsonic-android/src/github/daneren2005/dsub/util/Util.java index 76bf6a04..25e7c690 100644 --- a/subsonic-android/src/github/daneren2005/dsub/util/Util.java +++ b/subsonic-android/src/github/daneren2005/dsub/util/Util.java @@ -340,6 +340,25 @@ public final class Util { SharedPreferences offline = getOfflineSync(context); return offline.getInt(Constants.OFFLINE_SCROBBLE_COUNT, 0); } + + public static String parseOfflineIDSearch(Context context, String id, String cacheLocation) { + String name = id.replace(cacheLocation, ""); + if(name.startsWith("/")) { + name = name.substring(1); + } + name = name.replace(".complete", "").replace(".partial", ""); + int index = name.lastIndexOf("."); + name = index == -1 ? name : name.substring(0, index); + String[] details = name.split("/"); + + String artist = "artist:\"" + details[0] + "\""; + String title = details[details.length - 1]; + title = "title:\"" + title.substring(title.indexOf('-') + 1) + "\""; + + name = artist + " AND " + title; + + return name; + } public static String getContentType(HttpEntity entity) { if (entity == null || entity.getContentType() == null) { -- cgit v1.2.3