Merge branch 'master' into feature/static_analysis_fixes
This commit is contained in:
commit
45879c571a
@ -23,8 +23,8 @@ THE SOFTWARE.
|
||||
-->
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="ch.blinkenlights.android.vanilla"
|
||||
android:versionName="1.0.20"
|
||||
android:versionCode="1020"
|
||||
android:versionName="1.0.31"
|
||||
android:versionCode="1031"
|
||||
android:installLocation="auto">
|
||||
<uses-sdk android:minSdkVersion="15" android:targetSdkVersion="21" />
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
|
@ -20,12 +20,17 @@ function show(n) {
|
||||
</head>
|
||||
<body>
|
||||
<h1>Vanilla Music</h1>
|
||||
<p><b>Version:</b> 1.0.20 released Jun. 10, 2015<br><br>
|
||||
<p><b>Version:</b> 1.0.31 Released Aug. 20, 2015<br><br>
|
||||
<b>Website:</b> <a href='https://github.com/vanilla-music/vanilla'>https://github.com/vanilla-music/vanilla</a><br>
|
||||
<b>Issue tracker:</b> <a href='https://github.com/vanilla-music/vanilla/issues'>https://github.com/vanilla-music/vanilla/issues</a><br>
|
||||
<br>
|
||||
<b>Changelog</b> [<a href='javascript:show("changelog")'>show</a>]
|
||||
<pre id='changelog'>
|
||||
<b>1.0.30</b>
|
||||
- <b>NEW</b> Display album artwork in listview
|
||||
- <b>NEW</b> Fling-Remove of playlist items
|
||||
- <b>FIX</b> Fixed playlist reordering bug
|
||||
|
||||
<b>1.0.20</b>
|
||||
- <b>NEW</b> Support for heads-up-display notifications
|
||||
- <b>NEW</b> Added 'skip 10 seconds' longpress action
|
||||
|
@ -1,72 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright (C) 2014 Adrian Ulrich <adrian@blinkenlights.ch>
|
||||
|
||||
This program 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.
|
||||
|
||||
This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
-->
|
||||
<ch.blinkenlights.android.vanilla.DraggableRow
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
>
|
||||
|
||||
<LinearLayout
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<View
|
||||
android:id="@+id/pmark"
|
||||
android:layout_width="4dip"
|
||||
android:layout_height="44dip"
|
||||
android:visibility="invisible"
|
||||
android:background="@color/vanillaAccent" />
|
||||
<TextView
|
||||
android:id="@+id/text"
|
||||
android:maxLines="2"
|
||||
android:textColor="?android:textColorPrimary"
|
||||
android:gravity="left|center_vertical"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:paddingTop="2dip"
|
||||
android:paddingBottom="2dip"
|
||||
android:paddingLeft="4dip" />
|
||||
<!-- spacer -->
|
||||
<View
|
||||
android:layout_height="match_parent"
|
||||
android:layout_width="0px"
|
||||
android:layout_weight="1"/>
|
||||
<CheckedTextView
|
||||
android:id="@+id/checkbox"
|
||||
android:visibility="gone"
|
||||
android:checkMark="?android:attr/listChoiceIndicatorMultiple"
|
||||
android:gravity="center_vertical"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_width="44dip"/>
|
||||
<ImageView
|
||||
android:id="@+id/dragger"
|
||||
android:scaleType="center"
|
||||
android:src="@drawable/grabber"
|
||||
android:layout_width="44dip"
|
||||
android:layout_height="44dip" />
|
||||
</LinearLayout>
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1px"
|
||||
android:background="?divider_color"/>
|
||||
|
||||
</ch.blinkenlights.android.vanilla.DraggableRow>
|
@ -30,38 +30,47 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
<View
|
||||
android:id="@+id/pmark"
|
||||
android:visibility="gone"
|
||||
android:layout_width="4dip"
|
||||
android:layout_height="44dip"
|
||||
android:visibility="invisible"
|
||||
android:background="@android:color/holo_blue_dark" />
|
||||
android:background="@color/now_playing_marker" />
|
||||
|
||||
<ch.blinkenlights.android.vanilla.LazyCoverView
|
||||
android:id="@+id/cover"
|
||||
android:visibility="gone"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:longClickable="true"
|
||||
android:scaleType="fitCenter"
|
||||
android:layout_width="44dip"
|
||||
android:layout_height="44dip" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text"
|
||||
android:maxLines="2"
|
||||
android:textColor="?android:textColorPrimary"
|
||||
android:gravity="left|center_vertical"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_width="0px"
|
||||
android:layout_weight="1"
|
||||
android:layout_height="match_parent"
|
||||
android:paddingTop="2dip"
|
||||
android:paddingBottom="2dip"
|
||||
android:paddingLeft="4dip" />
|
||||
<!-- spacer -->
|
||||
<View
|
||||
android:layout_height="match_parent"
|
||||
android:layout_width="0px"
|
||||
android:layout_weight="1"/>
|
||||
<CheckedTextView
|
||||
android:id="@+id/checkbox"
|
||||
android:visibility="gone"
|
||||
android:checkMark="?android:attr/listChoiceIndicatorMultiple"
|
||||
android:gravity="center_vertical"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_width="44dip"/>
|
||||
<ImageView
|
||||
android:id="@+id/dragger"
|
||||
android:scaleType="center"
|
||||
android:src="@drawable/grabber"
|
||||
android:layout_width="44dip"
|
||||
android:layout_height="44dip" />
|
||||
|
||||
<CheckedTextView
|
||||
android:id="@+id/checkbox"
|
||||
android:visibility="gone"
|
||||
android:checkMark="?android:attr/listChoiceIndicatorMultiple"
|
||||
android:gravity="center_vertical"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_width="44dip"/>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/dragger"
|
||||
android:visibility="gone"
|
||||
android:scaleType="center"
|
||||
android:src="@drawable/grabber"
|
||||
android:layout_width="44dip"
|
||||
android:layout_height="44dip" />
|
||||
</LinearLayout>
|
||||
|
||||
<View
|
||||
|
@ -1,32 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright (C) 2012 Christopher Eby <kreed@kreed.org>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
-->
|
||||
<TextView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:maxLines="2"
|
||||
android:textColor="?android:textColorPrimary"
|
||||
android:gravity="left|center_vertical"
|
||||
android:padding="3dip"
|
||||
android:minHeight="44dip" />
|
@ -25,6 +25,15 @@ THE SOFTWARE.
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
<ch.blinkenlights.android.vanilla.LazyCoverView
|
||||
android:id="@+id/cover"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:longClickable="true"
|
||||
android:scaleType="fitCenter"
|
||||
android:layout_width="44dip"
|
||||
android:layout_height="44dip"
|
||||
android:visibility="gone"
|
||||
/>
|
||||
<TextView
|
||||
android:id="@+id/text"
|
||||
android:longClickable="true"
|
||||
@ -40,6 +49,7 @@ THE SOFTWARE.
|
||||
android:id="@+id/divider"
|
||||
android:layout_width="1dip"
|
||||
android:layout_height="fill_parent"
|
||||
android:visibility="gone"
|
||||
android:background="?divider_color" />
|
||||
<ImageView
|
||||
android:id="@+id/arrow"
|
||||
@ -49,5 +59,6 @@ THE SOFTWARE.
|
||||
android:src="@drawable/arrow"
|
||||
android:layout_width="44dip"
|
||||
android:layout_height="44dip"
|
||||
android:visibility="gone"
|
||||
android:contentDescription="@string/expand" />
|
||||
</LinearLayout>
|
||||
|
@ -26,4 +26,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
dslv:drag_enabled="false"
|
||||
dslv:drag_start_mode="onMove"
|
||||
dslv:float_background_color="?float_color"
|
||||
dslv:remove_enabled="true"
|
||||
dslv:remove_mode="flingRemove"
|
||||
dslv:fling_handle_id="@+id/dragger"
|
||||
dslv:drag_handle_id="@+id/dragger"/>
|
||||
|
@ -154,6 +154,8 @@
|
||||
<string name="notification_action_title">Akce upozornění</string>
|
||||
<string name="notification_invert_color_title">Převrátit barvu upozornění</string>
|
||||
<string name="notification_invert_color_summary">Použít bílý text místo černého</string>
|
||||
<string name="notification_nag">Velmi detailní upozornění</string>
|
||||
<string name="notification_nag_summary">Oznamovat změny skladeb za použití \'Heads-Up-Notification\'</string>
|
||||
<string name="playback_screen">Obrazovka přehrávání</string>
|
||||
<string name="playback_on_startup_title">Otevřít při spuštění</string>
|
||||
<string name="playback_on_startup_summary">Při spuštění otevřít náhled přehrávání</string>
|
||||
|
@ -3,34 +3,34 @@
|
||||
<string name="app_name">Vanilla Music</string>
|
||||
<!--Playback Activity (main screen)-->
|
||||
<string name="no_songs">Keine Titel auf dem Gerät gefunden.</string>
|
||||
<string name="empty_queue">Keine Titel ausgewählt. Wählen Sie Titel aus der Bibliothek oder starten Sie den Zufallsmodus durch Antippen dieser Nachricht.</string>
|
||||
<string name="empty_queue">Keine Titel ausgewählt. Wählen Sie Titel aus der Bibliothek oder starten Sie die Zufallswiedergabe durch Antippen dieser Nachricht.</string>
|
||||
<string name="settings">Einstellungen</string>
|
||||
<string name="library">Bibliothek</string>
|
||||
<string name="no_shuffle">Keine zufällige Wiedergabe</string>
|
||||
<string name="shuffle_songs">Titel zufällig wiedergeben</string>
|
||||
<string name="shuffle_songs_continuously">Titel kontinuierlich durchwürfeln</string>
|
||||
<string name="shuffle_albums">Alben zufällig wiedergeben</string>
|
||||
<string name="no_shuffle">Keine Zufallswiedergabe</string>
|
||||
<string name="shuffle_songs">Zufallswiedergabe Titel</string>
|
||||
<string name="shuffle_songs_continuously">Dauerzufallswiedergabe Titel</string>
|
||||
<string name="shuffle_albums">Zufallswiedergabe Alben</string>
|
||||
<string name="no_repeat">Nicht wiederholen</string>
|
||||
<string name="repeat">Wiederholen</string>
|
||||
<string name="repeat_current_song">Aktuellen Titel wiederholen</string>
|
||||
<string name="stop_current_song">Nach aktuellem Titel stoppen</string>
|
||||
<string name="random">Zufällige Wiedergabe</string>
|
||||
<string name="random_enabling">Zufällige Wiedergabe aktiv</string>
|
||||
<string name="song_load_failed">Konnte Titel %s nicht laden. Er ist möglicherweise beschädigt oder fehlt.</string>
|
||||
<string name="queue_cleared">Wiedergabereihe geleert.</string>
|
||||
<string name="random">Zufallswiedergabe</string>
|
||||
<string name="random_enabling">Zufallswiedergabe aktiviert</string>
|
||||
<string name="song_load_failed">Titel %s konnte nicht geladen werden. Er ist möglicherweise beschädigt oder fehlt.</string>
|
||||
<string name="queue_cleared">Warteschlange geleert.</string>
|
||||
<string name="cover_art">Cover</string>
|
||||
<string name="close_notification">Schließen-Benachrichtigung</string>
|
||||
<string name="close_notification">Benachrichtigung beim Schließen</string>
|
||||
<string name="_genre">Genre</string>
|
||||
<string name="_track">Titel</string>
|
||||
<string name="_composer">Komponist</string>
|
||||
<string name="_format">Format</string>
|
||||
<string name="_title">Titel</string>
|
||||
<string name="_artist">Künstler</string>
|
||||
<string name="_artist">Interpret</string>
|
||||
<string name="_album">Album</string>
|
||||
<string name="_year">Jahr</string>
|
||||
<string name="_replaygain">rgain</string>
|
||||
<!--New Playlist Dialog-->
|
||||
<string name="choose_playlist_name">Namen der Wiedergabeliste auswählen</string>
|
||||
<string name="choose_playlist_name">Name der Wiedergabeliste auswählen</string>
|
||||
<string name="create">Erstellen</string>
|
||||
<string name="overwrite">Überschreiben</string>
|
||||
<string name="cancel">Abbrechen</string>
|
||||
@ -41,19 +41,19 @@
|
||||
<string name="play">Abspielen</string>
|
||||
<string name="edit">Bearbeiten</string>
|
||||
<string name="rename">Umbenennen</string>
|
||||
<string name="save_as_playlist">Als Wiedergabeliste speichern…</string>
|
||||
<string name="add_to_playlist">Zur Wiedergabeliste hinzufügen…</string>
|
||||
<string name="add_to_favorites">Zu Favoriten hinzufügen…</string>
|
||||
<string name="save_as_playlist">Als Wiedergabeliste speichern …</string>
|
||||
<string name="add_to_playlist">Zur Wiedergabeliste hinzufügen …</string>
|
||||
<string name="add_to_favorites">Zu Favoriten hinzufügen …</string>
|
||||
<string name="playlist_favorites">Favoriten</string>
|
||||
<string name="new_playlist">Neue Wiedergabeliste…</string>
|
||||
<string name="new_playlist">Neue Wiedergabeliste …</string>
|
||||
<string name="expand">Aufklappen</string>
|
||||
<string name="delete">Löschen</string>
|
||||
<string name="playback_view">Aktueller Titel</string>
|
||||
<string name="sort_by">Sortiere nach</string>
|
||||
<string name="sort_by">Sortieren nach</string>
|
||||
<string name="search">Suche</string>
|
||||
<string name="done">Fertig</string>
|
||||
<string name="remove">Entfernen</string>
|
||||
<string name="clear_search">Suche entfernen</string>
|
||||
<string name="clear_search">Suche leeren</string>
|
||||
<string name="play_or_enqueue">Einreihen - oder abspielen falls pausiert</string>
|
||||
<plurals name="playing">
|
||||
<item quantity="one">1 Titel wird abgespielt.</item>
|
||||
@ -72,9 +72,9 @@
|
||||
<item quantity="other">%d Titel gelöscht.</item>
|
||||
</plurals>
|
||||
<string name="delete_file_failed">Löschen von %s fehlgeschlagen.</string>
|
||||
<string name="deleted_item">\'%s\' wurde gelöscht.</string>
|
||||
<string name="delete_item">\'%s\' löschen?</string>
|
||||
<string name="delete_file">Datei \'%s\' löschen?</string>
|
||||
<string name="deleted_item">\"%s\" wurde gelöscht.</string>
|
||||
<string name="delete_item">\"%s\" löschen?</string>
|
||||
<string name="delete_file">Datei \"%s\" löschen?</string>
|
||||
<string name="artists">Interpreten</string>
|
||||
<string name="albums">Alben</string>
|
||||
<string name="songs">Titel</string>
|
||||
@ -86,7 +86,7 @@
|
||||
<string name="play_all">Alle abspielen</string>
|
||||
<string name="enqueue_all">Alle einreihen</string>
|
||||
<string name="all_songs">Alle Titel</string>
|
||||
<string name="more_from_artist">Mehr vom Künstler</string>
|
||||
<string name="more_from_artist">Mehr vom Interpreten</string>
|
||||
<string name="more_from_album">Mehr aus dem Album</string>
|
||||
<string name="name">Name</string>
|
||||
<string name="number_of_tracks">Anzahl der Titel</string>
|
||||
@ -101,7 +101,7 @@
|
||||
<string name="ascending">Aufsteigend</string>
|
||||
<string name="descending">Absteigend</string>
|
||||
<!--Notification-->
|
||||
<string name="notification_title_paused">%s (Angehalten)</string>
|
||||
<string name="notification_title_paused">%s (angehalten)</string>
|
||||
<!--Preferences-->
|
||||
<plurals name="seconds">
|
||||
<item quantity="one">1 Sekunde</item>
|
||||
@ -117,82 +117,82 @@
|
||||
</plurals>
|
||||
<string name="audio">Audio</string>
|
||||
<string name="volume_title">Lautstärke</string>
|
||||
<string name="media_button_title">Kopfhörer/Bluetooth-Steuerung</string>
|
||||
<string name="media_button_title">Headset-/Bluetooth-Steuerung</string>
|
||||
<string name="media_button_summary">Dies wird auch für die Steuerelemente der ICS-Bildschirmsperre benötigt.</string>
|
||||
<string name="media_button_beep_title">Headset Steuerung</string>
|
||||
<string name="media_button_beep_summary">Piepen nachdem ein Titel via Headset übersprungen wurde</string>
|
||||
<string name="media_button_beep_title">Headset-Steuerung signalisieren</string>
|
||||
<string name="media_button_beep_summary">Akustisches Signal nach dem Überspringen eines Titels via Headset-Steuerung.</string>
|
||||
<string name="headset_only_title">Nur externer Ausgang</string>
|
||||
<string name="headset_only_summary">Verhindert, dass Musik über die internen Lautsprecher wiedergegeben wird.</string>
|
||||
<string name="headset_pause_title">Anhalten wenn nicht angeschlossen</string>
|
||||
<string name="headset_pause_summary">Anhalten, wenn die Kopfhörer entfernt werden.</string>
|
||||
<string name="headset_play_title">Abspielen beim anschließen</string>
|
||||
<string name="headset_play_summary">Abspielen wenn der Kopfhörer angeschlossen wurde. (Nur wenn der Dienst aktiv ist)</string>
|
||||
<string name="headset_pause_title">Pausieren, wenn nicht angeschlossen</string>
|
||||
<string name="headset_pause_summary">Pausieren, wenn der Kopfhörer entfernt wird.</string>
|
||||
<string name="headset_play_title">Abspielen beim Anschließen</string>
|
||||
<string name="headset_play_summary">Abspielen, wenn der Kopfhörer angeschlossen wird (funkioniert nur bei aktivem Dienst).</string>
|
||||
<string name="replaygain">Replay Gain</string>
|
||||
<string name="replaygain_title">Replay Gain aktivieren</string>
|
||||
<string name="replaygain_summary">Replay Gain Einstellungen</string>
|
||||
<string name="replaygain_track_title">Aktiviere per-track Replay Gain</string>
|
||||
<string name="replaygain_track_summary">Titel in einheitlicher Lautstärke abspielen</string>
|
||||
<string name="replaygain_album_title">Aktiviere Album Replay Gain</string>
|
||||
<string name="replaygain_summary">Replay Gain-Einstellungen</string>
|
||||
<string name="replaygain_track_title">Per-Titel-Replay Gain aktivieren</string>
|
||||
<string name="replaygain_track_summary">Alle Titel in einheitlicher Lautstärke abspielen</string>
|
||||
<string name="replaygain_album_title">Album-Replay Gain aktivieren</string>
|
||||
<string name="replaygain_album_summary">Lautstärke von Alben beibehalten</string>
|
||||
<string name="replaygain_bump_title">Replay Gain Vorverstärker</string>
|
||||
<string name="replaygain_untagged_debump_title">Titel ohne Replay Gain Information</string>
|
||||
<string name="replaygain_bump_title">Replay Gain-Vorverstärker</string>
|
||||
<string name="replaygain_untagged_debump_title">Titel ohne Replay Gain-Information</string>
|
||||
<string name="replaygain_untagged_debump_summary">Lautstärke senken um: </string>
|
||||
<string name="readahead">Datei vorauslesen</string>
|
||||
<string name="readahead_summary">Liest den momentan abgespielten Titel im Voraus ein (löst Probleme mit langsamen SD-Karten).</string>
|
||||
<string name="equalizer">Equalizer</string>
|
||||
<string name="cycle_continuous_shuffling">Fortlaufend durchwürfeln</string>
|
||||
<string name="cycle_continuous_shuffling_summary">Im \'Zufällige Wiedergabe\' Modus werden die Titel fortlaufend neu angeordnet</string>
|
||||
<string name="cycle_continuous_shuffling">Dauerzufallswiedergabe</string>
|
||||
<string name="cycle_continuous_shuffling_summary">Titel werden fortlaufend neu durchgemischt</string>
|
||||
<string name="notifications">Benachrichtigungen</string>
|
||||
<string name="notification_mode_title">Benachrichtigungsmodus</string>
|
||||
<string name="notification_action_title">Benachrichtigungsaktion</string>
|
||||
<string name="notification_invert_color_title">Farbe der Benachrichtigung umkehren</string>
|
||||
<string name="notification_invert_color_summary">Text weiß statt schwarz anzeigen</string>
|
||||
<string name="notification_nag">Auffällige Notifikation</string>
|
||||
<string name="notification_nag_summary">Aktuellen Titel als \'Heads-Up-Notifikation\' anzeigen</string>
|
||||
<string name="playback_screen">Wiedergabebildschirm</string>
|
||||
<string name="notification_nag">Auffällige Benachrichtigung</string>
|
||||
<string name="notification_nag_summary">Titelwechsel als \"Heads-Up\"-Benachrichtigung anzeigen</string>
|
||||
<string name="playback_screen">Wiedergabeansicht</string>
|
||||
<string name="playback_on_startup_title">Beim Start öffnen</string>
|
||||
<string name="playback_on_startup_summary">Wiedergabeansicht beim Start anzeigen</string>
|
||||
<string name="playback_on_startup_summary">Wiedergabeansicht beim Start öffnen</string>
|
||||
<string name="display_mode_title">Anzeigemodus</string>
|
||||
<string name="swipe_up_action_title">Hoch-Wisch Aktion</string>
|
||||
<string name="swipe_down_action_title">Runter-Wisch Aktion</string>
|
||||
<string name="cover_press_action_title">Cover-Antippen Aktion</string>
|
||||
<string name="cover_longpress_action_title">Aktion bei langem Cover-Drücken</string>
|
||||
<string name="library_screen">Bibliotheksbildschirm</string>
|
||||
<string name="swipe_up_action_title">Aktion beim Hochwischen</string>
|
||||
<string name="swipe_down_action_title">Aktion beim Runterwischen</string>
|
||||
<string name="cover_press_action_title">Aktion beim Coverantippen</string>
|
||||
<string name="cover_longpress_action_title">Aktion bei langem Coverdrücken</string>
|
||||
<string name="library_screen">Bibliotheksansicht</string>
|
||||
<string name="controls_in_selector_title">Steuerelemente in Bibliotheksansicht</string>
|
||||
<string name="controls_in_selector_summary">Zeige den aktuell gespielten Titel und die Steuerelemente in der Bibliotheksansicht</string>
|
||||
<string name="controls_in_selector_summary">Aktuell abgespielten Titel und Steuerelemente in Bibliotheksansicht anzeigen</string>
|
||||
<string name="default_action_title">Standardaktion</string>
|
||||
<string name="default_playlist_action_title">Standard Wiedergabelisten-Aktion</string>
|
||||
<string name="accelerometer_shake">Beschleunigungssensor-Schütteln</string>
|
||||
<string name="enable_shake_title">Beschleunigungssensor-Schütteln aktivieren</string>
|
||||
<string name="enable_shake_summary">Ist nur aktiv wenn Musik abgespielt wird. Funktioniert auf einigen Geräten nicht bei ausgeschaltetem Bildschirm.</string>
|
||||
<string name="default_playlist_action_title">Wiedergabelisten-Standardaktion</string>
|
||||
<string name="accelerometer_shake">Schütteln</string>
|
||||
<string name="enable_shake_title">Schütteln aktivieren</string>
|
||||
<string name="enable_shake_summary">Ist nur während abgespielter Musik aktiv. Funktioniert auf einigen Geräten nicht bei ausgeschaltetem Bildschirm.</string>
|
||||
<string name="shake_action_title">Aktion beim Schütteln</string>
|
||||
<string name="shake_threshold_title">Schwellwert beim Schütteln</string>
|
||||
<string name="misc_features">Sonstige Funktionen</string>
|
||||
<string name="use_dark_theme_title">Dunkles Theme verwenden</string>
|
||||
<string name="use_dark_theme_summary">Aktiviert das \'Dark Material Design Theme\'</string>
|
||||
<string name="use_dark_theme_title">Dunkles Design verwenden</string>
|
||||
<string name="use_dark_theme_summary">Aktiviert das dunkle \"Material\"-Design</string>
|
||||
<string name="disable_lockscreen_title">Bildschirmsperre deaktivieren</string>
|
||||
<string name="disable_lockscreen_summary">Bildschirmsperre verhindern wenn Bibliothek- oder Wiedergabeansicht aktiv</string>
|
||||
<string name="use_idle_timeout_title">Inaktivitäts-Zeitabschaltung aktivieren</string>
|
||||
<string name="use_idle_timeout_summary">Wenn aktiviert, wird die Wiedergabe nach der angegebenen Inaktivität beendet</string>
|
||||
<string name="idle_timeout_title">Inaktive Zeit</string>
|
||||
<string name="disable_lockscreen_summary">Bildschirmsperre verhindern, wenn Bibliothek- oder Wiedergabeansicht aktiv</string>
|
||||
<string name="use_idle_timeout_title">Inaktivitätszeitabschaltung aktivieren</string>
|
||||
<string name="use_idle_timeout_summary">Falls aktiviert, wird die Wiedergabe nach der angegebenen Inaktivität beendet</string>
|
||||
<string name="idle_timeout_title">Inaktivitätszeitabschaltung</string>
|
||||
<string name="disable_cover_art_title">Cover-Darstellung deaktivieren</string>
|
||||
<string name="disable_cover_art_summary">Jegliches Laden von Cover-Art vermeiden</string>
|
||||
<string name="disable_cover_art_summary">Jegliches Laden von Albumcovern vermeiden</string>
|
||||
<string name="coverloader_android_title">Albumcover von Android verwenden</string>
|
||||
<string name="coverloader_android_summary">Verwendet Androids interne Mediendatenbank um Albumcover zu laden</string>
|
||||
<string name="coverloader_android_summary">Androids interne Mediendatenbank verwenden, um Albumcover zu laden</string>
|
||||
<string name="coverloader_vanilla_title">Cover aus Ordner laden</string>
|
||||
<string name="coverloader_vanilla_summary">Sucht nach Dateien mit den Namen cover.jpg, album.jpg oder artwork.jpg und verwendet jene als Albumcover</string>
|
||||
<string name="coverloader_shadow_title">Cover aus \'verstecktem\' Ordner laden</string>
|
||||
<string name="coverloader_shadow_summary">Verwendet die Datei unter \'/sdcard/Music/.vanilla/ARTIST/ALBUM.jpg\' als Albumcover. (z.B: /sdcard/Music/.vanilla/Jack White/Blunderbuss.jpg)</string>
|
||||
<string name="double_tap_title">Widget-Doppelklick</string>
|
||||
<string name="double_tap_summary">Doppeltes Antippen des 1x1 Widget öffnet die Wiedergabeansicht. Verursacht eine Verzögerung von 400 ms bevor das Widget auf Aktionen reagiert.</string>
|
||||
<string name="scrobble_title">ScrobbleDroid API verwenden</string>
|
||||
<string name="coverloader_vanilla_summary">Nach Dateien mit den Namen cover.jpg, album.jpg oder artwork.jpg suchen und diese als Albumcover anzeigen</string>
|
||||
<string name="coverloader_shadow_title">Cover aus verstecktem Ordner laden</string>
|
||||
<string name="coverloader_shadow_summary">Albumcover von \"/sdcard/Music/.vanilla/ARTIST/ALBUM.jpg\" laden, falls möglich.</string>
|
||||
<string name="double_tap_title">Widget-Doppelantippen</string>
|
||||
<string name="double_tap_summary">Doppeltes Antippen des 1x1-Widgets öffnet die Wiedergabeansicht. Verursacht eine Verzögerung von 400 ms bevor das Widget auf Aktionen reagiert.</string>
|
||||
<string name="scrobble_title">ScrobbleDroid-API verwenden</string>
|
||||
<string name="scrobble_summary">Zu Last.FM scrobbeln mithilfe von ScrobbleDroid oder Simple Last.FM Scrobbler</string>
|
||||
<string name="stock_broadcast_title">Emuliere Standardplayer-Benachrichtigungen</string>
|
||||
<string name="stock_broadcast_summary">Sende Benachrichtigungen, welche den normalen Standardplayer emulieren, um mit externen Bildschirmsperre-Steuerelementen, Widgets etc. kompatibel zu sein.</string>
|
||||
<string name="stock_broadcast_title">Standardplayer-Benachrichtigungen emulieren</string>
|
||||
<string name="stock_broadcast_summary">Benachrichtigungen senden, welche den normalen Standardplayer emulieren, um mit externen Bildschirmsperre-Steuerelementen, Widgets etc. kompatibel zu sein.</string>
|
||||
<string name="media_scan">Bibliothek aktualisieren</string>
|
||||
<string name="tap_to_scan">Antippen, um externem Speicher nach Musik zu durchsuchen</string>
|
||||
<string name="scan_in_progress">Aktualisierung wird durchgeführt…</string>
|
||||
<string name="finished_scanning">Durchsuchen abgeschlossen. Antippen für erneute Suche.</string>
|
||||
<string name="scan_in_progress">Suche wird durchgeführt …</string>
|
||||
<string name="finished_scanning">Durchsuchen abgeschlossen. Für erneute Suche antippen.</string>
|
||||
<string name="about">Über</string>
|
||||
<string name="tabs">Tab-Reihenfolge</string>
|
||||
<string name="customize_tab_order">Anpassen der Reihenfolge und Sichtbarkeit der Bibliothek-Tabs</string>
|
||||
@ -203,21 +203,21 @@
|
||||
<string name="show_when_playing">Bei Wiedergabe anzeigen (Standard)</string>
|
||||
<string name="always_show">Immer anzeigen</string>
|
||||
<string name="open_main_activity">Bibliothek öffnen (Standard)</string>
|
||||
<string name="open_mini_popup">Mini-popup öffnen</string>
|
||||
<string name="open_mini_popup">Mini-Popup öffnen</string>
|
||||
<string name="skip_to_next_song">Zum nächsten Titel springen</string>
|
||||
<string name="open_full_player">Vanilla Music öffnen</string>
|
||||
<string name="info_on_cover">Info auf dem Cover</string>
|
||||
<string name="info_below_cover">Info unter dem Cover</string>
|
||||
<string name="fixed_info">Info fest oben</string>
|
||||
<string name="fixed_info">Info oben fixiert</string>
|
||||
<string name="do_nothing">Keine</string>
|
||||
<string name="open_library">Bibliothek öffnen</string>
|
||||
<string name="play_pause">Wiedergabe/Pause</string>
|
||||
<string name="next_song">Nächster Titel</string>
|
||||
<string name="previous_song">Voriger Titel</string>
|
||||
<string name="previous_song">Vorheriger Titel</string>
|
||||
<string name="next_album">Nächstes Album</string>
|
||||
<string name="previous_album">Voriges Album</string>
|
||||
<string name="cycle_repeat_mode">Wiederholungsart durchwählen</string>
|
||||
<string name="cycle_shuffle_mode">Zufällige Wiedergabearten durchwählen</string>
|
||||
<string name="previous_album">Vorheriges Album</string>
|
||||
<string name="cycle_repeat_mode">Wiederholungsmodi wechseln</string>
|
||||
<string name="cycle_shuffle_mode">Zufallswiedergabemodi wechseln</string>
|
||||
<string name="enqueue_current_album">Album einreihen</string>
|
||||
<string name="enqueue_current_artist">Interpret einreihen</string>
|
||||
<string name="enqueue_current_genre">Genre einreihen</string>
|
||||
@ -226,10 +226,10 @@
|
||||
<string name="show_queue">Warteschlange anzeigen</string>
|
||||
<string name="dequeue_rest">Warteschlange leeren</string>
|
||||
<string name="queue">Warteschlange</string>
|
||||
<string name="toggle_controls">Steuerelemente ein/-ausblenden</string>
|
||||
<string name="toggle_controls">Steuerelemente ein-/ausblenden</string>
|
||||
<string name="seek_10s_backward">10 Sekunden zurück springen</string>
|
||||
<string name="seek_10s_forward">10 Sekunden vorwärts springen</string>
|
||||
<string name="filebrowser_start">Startverzeichnis</string>
|
||||
<string name="customize_filebrowser_start">Dateibrowser startet in diesem Verzeichnis</string>
|
||||
<string name="filebrowser_start">Startordner</string>
|
||||
<string name="customize_filebrowser_start">Dateimanager startet in diesem Ordner</string>
|
||||
<string name="select">Auswählen</string>
|
||||
</resources>
|
||||
|
197
res/values-sr-rRS/translatable.xml
Normal file
197
res/values-sr-rRS/translatable.xml
Normal file
@ -0,0 +1,197 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<resources>
|
||||
<string name="app_name">Ванила музика</string>
|
||||
<!--Playback Activity (main screen)-->
|
||||
<string name="no_songs">Нема песама на вашем уређају.</string>
|
||||
<string name="empty_queue">Ништа није изабрано. Изаберите песме из библиотеке или уђите у насумични режим тапом на ову поруку.</string>
|
||||
<string name="settings">Поставке</string>
|
||||
<string name="library">Библиотека</string>
|
||||
<string name="no_shuffle">Без тумбања</string>
|
||||
<string name="shuffle_songs">Претумбај песме</string>
|
||||
<string name="shuffle_songs_continuously">Претумбавај песме непрестано</string>
|
||||
<string name="shuffle_albums">Претумбај албуме</string>
|
||||
<string name="no_repeat">Не понављај</string>
|
||||
<string name="repeat">Понављај</string>
|
||||
<string name="repeat_current_song">Понови текућу песму</string>
|
||||
<string name="stop_current_song">Заустави након текуће песме</string>
|
||||
<string name="random">Насумично</string>
|
||||
<string name="song_load_failed">Неуспех учитавања песме %s. Можда је фајл оштећен или га нема.</string>
|
||||
<string name="queue_cleared">Ред пуштања очишћен.</string>
|
||||
<string name="cover_art">Омот</string>
|
||||
<string name="close_notification">Затвори обавештење</string>
|
||||
<string name="_genre">жанр</string>
|
||||
<string name="_track">нумера</string>
|
||||
<string name="_composer">композитор</string>
|
||||
<string name="_format">формат</string>
|
||||
<string name="_title">наслов</string>
|
||||
<string name="_artist">извођач</string>
|
||||
<string name="_album">албум</string>
|
||||
<string name="_year">година</string>
|
||||
<!--New Playlist Dialog-->
|
||||
<string name="choose_playlist_name">Наслов листе нумера</string>
|
||||
<string name="create">Направи</string>
|
||||
<string name="overwrite">Пребриши</string>
|
||||
<string name="cancel">Одустани</string>
|
||||
<string name="delete_playlist">Да обришем листу нумера %s?</string>
|
||||
<!--Library-->
|
||||
<string name="enqueue">Стави у ред</string>
|
||||
<string name="enqueue_as_next">Пусти следећу</string>
|
||||
<string name="play">Пусти</string>
|
||||
<string name="edit">Уреди</string>
|
||||
<string name="rename">Преименуј</string>
|
||||
<string name="save_as_playlist">Сачувај као листу нумера…</string>
|
||||
<string name="add_to_playlist">Сачувај у листу нумера…</string>
|
||||
<string name="add_to_favorites">Додај у омиљене</string>
|
||||
<string name="playlist_favorites">Омиљене</string>
|
||||
<string name="new_playlist">Нова листа нумера…</string>
|
||||
<string name="expand">Рашири</string>
|
||||
<string name="delete">Обриши</string>
|
||||
<string name="playback_view">Текућа песма</string>
|
||||
<string name="sort_by">Поређај по</string>
|
||||
<string name="search">Тражи</string>
|
||||
<string name="done">Готово</string>
|
||||
<string name="remove">Уклони</string>
|
||||
<string name="clear_search">Очисти претрагу</string>
|
||||
<string name="play_or_enqueue">У ред ако је пуштено; пусти ако је паузирано</string>
|
||||
<plurals name="playing">
|
||||
<item quantity="one">1 песма пуштена.</item>
|
||||
<item quantity="few">%d песме пуштене.</item>
|
||||
<item quantity="other">%d песама пуштено.</item>
|
||||
</plurals>
|
||||
<plurals name="enqueued">
|
||||
<item quantity="one">1 песма стављена у ред.</item>
|
||||
<item quantity="few">%d песме стављене у ред.</item>
|
||||
<item quantity="other">%d песама стављено у ред.</item>
|
||||
</plurals>
|
||||
<plurals name="added_to_playlist">
|
||||
<item quantity="one">1 песма додата у листу нумера %2$s.</item>
|
||||
<item quantity="few">%1$d песме додате у листу нумера %2$s.</item>
|
||||
<item quantity="other">%1$d песама додато у листу нумера %2$s.</item>
|
||||
</plurals>
|
||||
<plurals name="deleted">
|
||||
<item quantity="one">1 песма обрисана.</item>
|
||||
<item quantity="few">%d песме обрисане.</item>
|
||||
<item quantity="other">%d песама обрисано.</item>
|
||||
</plurals>
|
||||
<string name="delete_file_failed">Брисање %s није успело.</string>
|
||||
<string name="deleted_item">„%s“ обрисана.</string>
|
||||
<string name="delete_item">Да обришем „%s“?</string>
|
||||
<string name="delete_file">Да обришем фајл „%s“?</string>
|
||||
<string name="artists">Извођачи</string>
|
||||
<string name="albums">Албуми</string>
|
||||
<string name="songs">Песме</string>
|
||||
<string name="playlists">Листе нумера</string>
|
||||
<string name="genres">Жанрови</string>
|
||||
<string name="files">Фајлови</string>
|
||||
<string name="none">Ништа</string>
|
||||
<string name="unknown">Непознато</string>
|
||||
<string name="play_all">Пусти све</string>
|
||||
<string name="enqueue_all">Стави све у ред</string>
|
||||
<string name="all_songs">Све песме</string>
|
||||
<string name="more_from_artist">Још од извођача</string>
|
||||
<string name="more_from_album">Још са албума</string>
|
||||
<string name="name">називу</string>
|
||||
<string name="number_of_tracks">броју нумера</string>
|
||||
<string name="year">години</string>
|
||||
<string name="date_added">датуму додавања</string>
|
||||
<string name="artist_album">извођачу, албуму</string>
|
||||
<string name="artist_album_track">извођачу, албуму, нумери</string>
|
||||
<string name="artist_album_title">извођачу, албуму, наслову</string>
|
||||
<string name="artist_year">извођачу, години</string>
|
||||
<string name="album_track">албуму, нумери</string>
|
||||
<string name="song_playcount">броју пуштања</string>
|
||||
<string name="ascending">Растуће</string>
|
||||
<string name="descending">Опадајуће</string>
|
||||
<!--Notification-->
|
||||
<string name="notification_title_paused">%s (паузирана)</string>
|
||||
<!--Preferences-->
|
||||
<plurals name="seconds">
|
||||
<item quantity="one">1 секунда</item>
|
||||
<item quantity="few">%d секунде</item>
|
||||
<item quantity="other">%d секунди</item>
|
||||
</plurals>
|
||||
<plurals name="minutes">
|
||||
<item quantity="one">1 минута</item>
|
||||
<item quantity="few">%d минуте</item>
|
||||
<item quantity="other">%d минута</item>
|
||||
</plurals>
|
||||
<plurals name="hours">
|
||||
<item quantity="one">1 сат</item>
|
||||
<item quantity="few">%d сата</item>
|
||||
<item quantity="other">%d сати</item>
|
||||
</plurals>
|
||||
<string name="audio">Звук</string>
|
||||
<string name="headset_pause_title">Паузирај по искључењу</string>
|
||||
<string name="headset_pause_summary">Пауза по искључењу слушалица.</string>
|
||||
<string name="headset_play_title">Пусти по укључењу </string>
|
||||
<string name="headset_play_summary">Пуштање музике по укључењу слушалица. (Ради само ако је сервис покренут.)</string>
|
||||
<string name="readahead">Укључи предучитавање</string>
|
||||
<string name="equalizer">Еквилајзер</string>
|
||||
<string name="notifications">Обавештења</string>
|
||||
<string name="notification_mode_title">Приказ обавештења</string>
|
||||
<string name="notification_action_title">Радња обавештења</string>
|
||||
<string name="notification_nag">Веома исцрпно обавештење</string>
|
||||
<string name="playback_screen">Екран пуштања</string>
|
||||
<string name="playback_on_startup_title">Отвори по покретању</string>
|
||||
<string name="playback_on_startup_summary">Отвара екран пуштања по покретању</string>
|
||||
<string name="display_mode_title">Приказ информација</string>
|
||||
<string name="swipe_up_action_title">Превлачење на горе</string>
|
||||
<string name="swipe_down_action_title">Превлачење на доле</string>
|
||||
<string name="cover_press_action_title">Тап на омот</string>
|
||||
<string name="cover_longpress_action_title">Дужи притисак на омот</string>
|
||||
<string name="library_screen">Екран библиотеке</string>
|
||||
<string name="default_action_title">Подразумевана радња</string>
|
||||
<string name="default_playlist_action_title">Подразумевана радња листе нумера</string>
|
||||
<string name="accelerometer_shake">Протресање телефона</string>
|
||||
<string name="enable_shake_title">Омогући протресање телефона</string>
|
||||
<string name="enable_shake_summary">Активно само ако је музика пуштена. На неким уређајима не ради када је екран угашен.</string>
|
||||
<string name="shake_action_title">Протресање</string>
|
||||
<string name="shake_threshold_title">Праг силе протресања</string>
|
||||
<string name="misc_features">Разне функције</string>
|
||||
<string name="use_dark_theme_title">Користи тамну тему</string>
|
||||
<string name="use_dark_theme_summary">Тамна материјал тема</string>
|
||||
<string name="double_tap_title">Двотап на виџет</string>
|
||||
<string name="double_tap_summary">Двотап на 1x1 виџет отвара плејер. Опција поставља застој од 400ms пре одзива виџета на радње.</string>
|
||||
<string name="media_scan">Освежи библиотеку</string>
|
||||
<string name="scan_in_progress">Скенирање у току…</string>
|
||||
<string name="finished_scanning">Скенирање завршено. Тапните за поновно скенирање.</string>
|
||||
<string name="about">О програму</string>
|
||||
<string name="tabs">Редослед језичака</string>
|
||||
<string name="customize_tab_order">Прилагођавање редоследа и видљивости језичака библиотеке</string>
|
||||
<string name="restore_default">Врати подразумевано</string>
|
||||
<!--The following are for the list preferences-->
|
||||
<string name="last_used_action">претходно коришћена радња</string>
|
||||
<string name="never_show">никад</string>
|
||||
<string name="show_when_playing">у току пуштања (подраз.)</string>
|
||||
<string name="always_show">увек</string>
|
||||
<string name="open_main_activity">отвара библиотеку (подраз.)</string>
|
||||
<string name="open_mini_popup">отвара искачуће прозорче</string>
|
||||
<string name="skip_to_next_song">пушта следећу песму</string>
|
||||
<string name="open_full_player">отвара цео плејер</string>
|
||||
<string name="info_on_cover">на омоту</string>
|
||||
<string name="info_below_cover">испод омота</string>
|
||||
<string name="fixed_info">фиксиран на врху</string>
|
||||
<string name="do_nothing">не ради ништа</string>
|
||||
<string name="open_library">отвара библиотеку</string>
|
||||
<string name="play_pause">пушта/паузира</string>
|
||||
<string name="next_song">пушта следећу песму</string>
|
||||
<string name="previous_song">пушта претходну песму</string>
|
||||
<string name="next_album">пушта следећи албум</string>
|
||||
<string name="previous_album">пушта претходни албум</string>
|
||||
<string name="cycle_repeat_mode">мења режим понављања</string>
|
||||
<string name="cycle_shuffle_mode">мења режим тумбања</string>
|
||||
<string name="enqueue_current_album">Стави албум у ред</string>
|
||||
<string name="enqueue_current_artist">Стави извођача у ред</string>
|
||||
<string name="enqueue_current_genre">Стави жанр у ред</string>
|
||||
<string name="clear_queue">Очисти ред</string>
|
||||
<string name="empty_the_queue">Испразни ред</string>
|
||||
<string name="show_queue">Прикажи ред</string>
|
||||
<string name="dequeue_rest">Уклони остале из реда</string>
|
||||
<string name="queue">Ред пуштања</string>
|
||||
<string name="toggle_controls">мења приказ контрола</string>
|
||||
<string name="seek_10s_backward">прескаче 10 секунди уназад</string>
|
||||
<string name="seek_10s_forward">прескаче 10 секунди унапред</string>
|
||||
<string name="filebrowser_start">Почетна страница</string>
|
||||
<string name="customize_filebrowser_start">Прегледач фајлова почиње од ове фасцикле</string>
|
||||
<string name="select">Изабери</string>
|
||||
</resources>
|
@ -147,6 +147,8 @@
|
||||
<string name="notification_action_title">Bildirim Eylemi</string>
|
||||
<string name="notification_invert_color_title">Bildirim rengini tersine döndür</string>
|
||||
<string name="notification_invert_color_summary">Siyah metin yerine beyaz metin kullan</string>
|
||||
<string name="notification_nag">Tam sözlü bildirim</string>
|
||||
<string name="notification_nag_summary">Şarkı değişikliklerini \'Ekran Üstü Bildirimi\' ile göster</string>
|
||||
<string name="playback_screen">Oynatma Ekranı</string>
|
||||
<string name="playback_on_startup_title">Başlangıçta Aç</string>
|
||||
<string name="playback_on_startup_summary">Başlangıçta oynatım görünümünü açar</string>
|
||||
|
@ -35,6 +35,9 @@ Copyright (C) 2015 Adrian Ulrich <adrian@blinkenlights.ch>
|
||||
<color name="controls_active">@color/vanillaAccent</color>
|
||||
<color name="controls_normal">@color/material_grey_600</color>
|
||||
|
||||
<!-- showqueue info -->
|
||||
<color name="now_playing_marker">@color/vanillaAccent</color>
|
||||
|
||||
<!-- styles -->
|
||||
<style name="Dialog" parent="android:Theme.Material.Light.Dialog" />
|
||||
<style name="DialogMinWidth" parent="android:Theme.Material.Light.Dialog.MinWidth" />
|
||||
|
228
res/values-zh-rCN/translatable.xml
Normal file
228
res/values-zh-rCN/translatable.xml
Normal file
@ -0,0 +1,228 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<resources>
|
||||
<string name="app_name">Vanilla Music</string>
|
||||
<!--Playback Activity (main screen)-->
|
||||
<string name="no_songs">在你的设备上未找到音乐。</string>
|
||||
<string name="empty_queue">没有选定的音乐。由媒体库或是轻点此信息进入随机模式挑选。</string>
|
||||
<string name="settings">设置</string>
|
||||
<string name="library">媒体库</string>
|
||||
<string name="no_shuffle">不随机播放</string>
|
||||
<string name="shuffle_songs">随机播放音乐</string>
|
||||
<string name="shuffle_songs_continuously">连续随机播放音乐</string>
|
||||
<string name="shuffle_albums">随机播放专辑</string>
|
||||
<string name="no_repeat">不重复播放</string>
|
||||
<string name="repeat">重复播放</string>
|
||||
<string name="repeat_current_song">重播当前曲目</string>
|
||||
<string name="stop_current_song">当前曲目之后停止</string>
|
||||
<string name="random">随机</string>
|
||||
<string name="random_enabling">启用随机</string>
|
||||
<string name="song_load_failed">载入音乐 %s 失败。它可能已经损坏或遗失。</string>
|
||||
<string name="queue_cleared">序列已清除</string>
|
||||
<string name="cover_art">专辑封面</string>
|
||||
<string name="close_notification">关闭通知</string>
|
||||
<string name="_genre">类型</string>
|
||||
<string name="_track">曲目</string>
|
||||
<string name="_composer">作曲</string>
|
||||
<string name="_format">格式</string>
|
||||
<string name="_title">标题</string>
|
||||
<string name="_artist">演出者</string>
|
||||
<string name="_album">专辑</string>
|
||||
<string name="_year">年份</string>
|
||||
<string name="_replaygain">重播增益</string>
|
||||
<!--New Playlist Dialog-->
|
||||
<string name="choose_playlist_name">选择播放列表名字</string>
|
||||
<string name="create">创建</string>
|
||||
<string name="overwrite">覆盖</string>
|
||||
<string name="cancel">取消</string>
|
||||
<string name="delete_playlist">删除播放列表 %s ?</string>
|
||||
<!--Library-->
|
||||
<string name="enqueue">序列</string>
|
||||
<string name="enqueue_as_next">播放下一首</string>
|
||||
<string name="play">播放</string>
|
||||
<string name="edit">编辑</string>
|
||||
<string name="rename">重命名</string>
|
||||
<string name="save_as_playlist">保存播放列表为…</string>
|
||||
<string name="add_to_playlist">添加到播放列表…</string>
|
||||
<string name="add_to_favorites">添加到最爱</string>
|
||||
<string name="playlist_favorites">最爱</string>
|
||||
<string name="new_playlist">新建播放列表…</string>
|
||||
<string name="expand">展开</string>
|
||||
<string name="delete">删除</string>
|
||||
<string name="playback_view">当前播放</string>
|
||||
<string name="sort_by">排序依据</string>
|
||||
<string name="search">搜索</string>
|
||||
<string name="done">完成</string>
|
||||
<string name="remove">移除</string>
|
||||
<string name="clear_search">清空搜索栏</string>
|
||||
<string name="play_or_enqueue">如果播放序列;如果暫停播放</string>
|
||||
<plurals name="playing">
|
||||
<item quantity="other">正在播放 %d 首音乐。</item>
|
||||
</plurals>
|
||||
<plurals name="enqueued">
|
||||
<item quantity="other">%d 首音乐存入序列。</item>
|
||||
</plurals>
|
||||
<plurals name="added_to_playlist">
|
||||
<item quantity="other">%1$d 首音乐已经加入播放列表 %2$s。</item>
|
||||
</plurals>
|
||||
<plurals name="deleted">
|
||||
<item quantity="other">%d 首音乐已删除。</item>
|
||||
</plurals>
|
||||
<string name="delete_file_failed">删除 %s 失败。</string>
|
||||
<string name="deleted_item">“%s”已删除。</string>
|
||||
<string name="delete_item">删除“%s”?</string>
|
||||
<string name="delete_file">删除文件“%s”?</string>
|
||||
<string name="artists">演出者</string>
|
||||
<string name="albums">专辑</string>
|
||||
<string name="songs">音乐</string>
|
||||
<string name="playlists">播放清单</string>
|
||||
<string name="genres">风格</string>
|
||||
<string name="files">文件</string>
|
||||
<string name="none">无</string>
|
||||
<string name="unknown">未知</string>
|
||||
<string name="play_all">全部播放</string>
|
||||
<string name="enqueue_all">全部加入序列</string>
|
||||
<string name="all_songs">全部歌曲</string>
|
||||
<string name="more_from_artist">更多来自演出者</string>
|
||||
<string name="more_from_album">更多来自专辑</string>
|
||||
<string name="name">名称</string>
|
||||
<string name="number_of_tracks">曲目号</string>
|
||||
<string name="year">年份</string>
|
||||
<string name="date_added">添加日期</string>
|
||||
<string name="artist_album">演出者,专辑</string>
|
||||
<string name="artist_album_track">演出者,专辑,曲目号</string>
|
||||
<string name="artist_album_title">演出者,专辑,标题</string>
|
||||
<string name="artist_year">演出者,年份</string>
|
||||
<string name="album_track">演出者,曲目号</string>
|
||||
<string name="song_playcount">播放次数</string>
|
||||
<string name="ascending">升序</string>
|
||||
<string name="descending">绛序</string>
|
||||
<!--Notification-->
|
||||
<string name="notification_title_paused">%s (已暂停)</string>
|
||||
<!--Preferences-->
|
||||
<plurals name="seconds">
|
||||
<item quantity="other">%d 秒</item>
|
||||
</plurals>
|
||||
<plurals name="minutes">
|
||||
<item quantity="other">%d 分钟</item>
|
||||
</plurals>
|
||||
<plurals name="hours">
|
||||
<item quantity="other">%d 小时</item>
|
||||
</plurals>
|
||||
<string name="audio">音频</string>
|
||||
<string name="volume_title">音量</string>
|
||||
<string name="media_button_title">耳机 / 蓝牙控制器</string>
|
||||
<string name="media_button_summary">此特性也需要 ICS 锁屏控制器。</string>
|
||||
<string name="media_button_beep_title">耳机操作时发出哔的一声</string>
|
||||
<string name="media_button_beep_summary">使用耳机控制器连按两次以后切换到下一曲目,并发出哔的一声。</string>
|
||||
<string name="headset_only_title">只有外部输出</string>
|
||||
<string name="headset_only_summary">防止经由内部扬声器播放音乐</string>
|
||||
<string name="headset_pause_title">拔出时暂停播放</string>
|
||||
<string name="headset_pause_summary">当耳机拔出时暂停播放</string>
|
||||
<string name="headset_play_title">插入时播放</string>
|
||||
<string name="headset_play_summary">当耳机插入时播放。(仅当系统运行时)</string>
|
||||
<string name="replaygain">重播增益</string>
|
||||
<string name="replaygain_title">启用重播增益</string>
|
||||
<string name="replaygain_summary">配置重播增益信息</string>
|
||||
<string name="replaygain_track_title">启用每个曲目的播放增益</string>
|
||||
<string name="replaygain_track_summary">以同等响度播放所有音乐</string>
|
||||
<string name="replaygain_album_title">启用专辑重播增益</string>
|
||||
<string name="replaygain_album_summary">维持专辑力度变化</string>
|
||||
<string name="replaygain_bump_title">重播增益的前置放大器</string>
|
||||
<string name="replaygain_untagged_debump_title">禁用重播增益的歌曲标记</string>
|
||||
<string name="replaygain_untagged_debump_summary">降低音量依据</string>
|
||||
<string name="readahead">启用预先读取</string>
|
||||
<string name="readahead_summary">预先读取当前播放的音乐。此功能可以解决“音频滞后”的问题。(由慢速 SD 卡导致的)</string>
|
||||
<string name="equalizer">均衡器</string>
|
||||
<string name="cycle_continuous_shuffling">连续随机播放</string>
|
||||
<string name="cycle_continuous_shuffling_summary">“随机模式”将会以完全随机顺序播放音乐</string>
|
||||
<string name="notifications">通知</string>
|
||||
<string name="notification_mode_title">通知模式</string>
|
||||
<string name="notification_action_title">通知操作</string>
|
||||
<string name="notification_invert_color_title">反转通知颜色</string>
|
||||
<string name="notification_invert_color_summary">用白色文字替代黑色文字</string>
|
||||
<string name="notification_nag">非常详尽的通知</string>
|
||||
<string name="notification_nag_summary">使用“抬头通知”来告知曲面变换</string>
|
||||
<string name="playback_screen">播放界面</string>
|
||||
<string name="playback_on_startup_title">启动时开启</string>
|
||||
<string name="playback_on_startup_summary">启动时打开播放界面</string>
|
||||
<string name="display_mode_title">显示模式</string>
|
||||
<string name="swipe_up_action_title">向上滑动</string>
|
||||
<string name="swipe_down_action_title">向下滑动</string>
|
||||
<string name="cover_press_action_title">轻触专辑封面的操作</string>
|
||||
<string name="cover_longpress_action_title">长按专辑封面的操作</string>
|
||||
<string name="library_screen">媒体库界面</string>
|
||||
<string name="controls_in_selector_title">媒体库中的控制按钮</string>
|
||||
<string name="controls_in_selector_summary">显示当前播放的曲目并在媒体库视图中显示媒体控制按钮</string>
|
||||
<string name="default_action_title">默认动作</string>
|
||||
<string name="default_playlist_action_title">默认播放列表动作</string>
|
||||
<string name="accelerometer_shake">摇一摇</string>
|
||||
<string name="enable_shake_title">启用摇一摇</string>
|
||||
<string name="enable_shake_summary">只在音乐播放时可用。有些设备在屏幕关闭时不可用。</string>
|
||||
<string name="shake_action_title">摇动操作</string>
|
||||
<string name="shake_threshold_title">摇动力道阈值</string>
|
||||
<string name="misc_features">杂项特性</string>
|
||||
<string name="use_dark_theme_title">使用暗色界面</string>
|
||||
<string name="use_dark_theme_summary">使用暗色材质主题</string>
|
||||
<string name="disable_lockscreen_title">禁用锁屏</string>
|
||||
<string name="disable_lockscreen_summary">在媒体库界面或回放屏幕时禁用锁屏</string>
|
||||
<string name="use_idle_timeout_title">启用闲置超时</string>
|
||||
<string name="use_idle_timeout_summary">启用时,闲置一段时间后会停止播放</string>
|
||||
<string name="idle_timeout_title">闲置超时</string>
|
||||
<string name="disable_cover_art_title">禁用专辑封面</string>
|
||||
<string name="disable_cover_art_summary">不要在此应用的任何情况下载入专辑封面</string>
|
||||
<string name="coverloader_android_title">从 Android 设备载入</string>
|
||||
<string name="coverloader_android_summary">查询 Android 内部的媒体数据库来做专辑封面</string>
|
||||
<string name="coverloader_vanilla_title">从文件夹中载入封面</string>
|
||||
<string name="coverloader_vanilla_summary">搜索文件名是 cover.jpg、album.jpg 或 artwork.jpg 的图片文件,以其作为专辑封面</string>
|
||||
<string name="coverloader_shadow_title">从隐藏文件载入封面</string>
|
||||
<string name="coverloader_shadow_summary">尝试从 \'/sdcard/Music/.vanilla/ARTIST/ALBUM.jpg\' 中载入封面图</string>
|
||||
<string name="double_tap_title">双击小工具</string>
|
||||
<string name="double_tap_summary">双击 1 x 1 的小工具将会打开播放器。小工具做出响应之前会有 400 毫秒延迟。</string>
|
||||
<string name="scrobble_title">使用 ScrobbleDroid API</string>
|
||||
<string name="scrobble_summary">Scrobble 透过 ScrobbleDroid 到 Last.FM 或只是 Last.FM Scrobbler</string>
|
||||
<string name="stock_broadcast_title">模拟原生应用广播</string>
|
||||
<string name="stock_broadcast_summary">发送广播以模拟那些由本地原生播放器而产生的效果,例如第三方锁屏控制,小工具等等。</string>
|
||||
<string name="media_scan">刷新媒体库</string>
|
||||
<string name="tap_to_scan">轻触以扫描外部存储的音乐</string>
|
||||
<string name="scan_in_progress">扫描进行中…</string>
|
||||
<string name="finished_scanning">完成扫描。轻触以再次扫描。</string>
|
||||
<string name="about">关于</string>
|
||||
<string name="tabs">标签页排序</string>
|
||||
<string name="customize_tab_order">调整媒体库标签页的顺序及可见性</string>
|
||||
<string name="restore_default">返回默认</string>
|
||||
<!--The following are for the list preferences-->
|
||||
<string name="last_used_action">上次使用的操作</string>
|
||||
<string name="never_show">永不显示</string>
|
||||
<string name="show_when_playing">播放时显示 (默认)</string>
|
||||
<string name="always_show">永远显示</string>
|
||||
<string name="open_main_activity">打开媒体库 (默认)</string>
|
||||
<string name="open_mini_popup">打开迷你弹出框</string>
|
||||
<string name="skip_to_next_song">跳到下一首</string>
|
||||
<string name="open_full_player">打开完整播放器</string>
|
||||
<string name="info_on_cover">信息在封面之上</string>
|
||||
<string name="info_below_cover">信息在封面之下</string>
|
||||
<string name="fixed_info">信息固定在顶部</string>
|
||||
<string name="do_nothing">什么也不做</string>
|
||||
<string name="open_library">打开媒体库</string>
|
||||
<string name="play_pause">播放 / 暂停</string>
|
||||
<string name="next_song">下一曲</string>
|
||||
<string name="previous_song">上一首</string>
|
||||
<string name="next_album">下一专辑</string>
|
||||
<string name="previous_album">上一专辑</string>
|
||||
<string name="cycle_repeat_mode">循环重复模式</string>
|
||||
<string name="cycle_shuffle_mode">循环随机模式</string>
|
||||
<string name="enqueue_current_album">以专辑加入序列</string>
|
||||
<string name="enqueue_current_artist">以演出者加入序列</string>
|
||||
<string name="enqueue_current_genre">以风格加入序列</string>
|
||||
<string name="clear_queue">清空序列</string>
|
||||
<string name="empty_the_queue">空序列</string>
|
||||
<string name="show_queue">显示序列</string>
|
||||
<string name="dequeue_rest">剩余的移出序列</string>
|
||||
<string name="queue">序列</string>
|
||||
<string name="toggle_controls">切换控制器</string>
|
||||
<string name="seek_10s_backward">向后倒退 10 秒</string>
|
||||
<string name="seek_10s_forward">向前快进 10 秒</string>
|
||||
<string name="filebrowser_start">起始目录</string>
|
||||
<string name="customize_filebrowser_start">在此目录开始浏览文件</string>
|
||||
<string name="select">选择</string>
|
||||
</resources>
|
@ -25,6 +25,9 @@ THE SOFTWARE.
|
||||
<color name="controls_active">@android:color/holo_blue_dark</color>
|
||||
<color name="controls_normal">#fff5f5f5</color>
|
||||
|
||||
<!-- showqueue info -->
|
||||
<color name="now_playing_marker">@android:color/holo_blue_dark</color>
|
||||
|
||||
<!-- styles -->
|
||||
<style name="Dialog" parent="android:Theme.Holo.Dialog" />
|
||||
<style name="DialogMinWidth" parent="android:Theme.Holo.Dialog.MinWidth" />
|
||||
|
@ -233,7 +233,7 @@ THE SOFTWARE.
|
||||
<string name="double_tap_title">Double Tap Widget</string>
|
||||
<string name="double_tap_summary">Double-tapping the 1x1 widget will open the player. Incurs a 400ms delay before the widget responds to actions.</string>
|
||||
<string name="scrobble_title">Use ScrobbleDroid API</string>
|
||||
<string name="scrobble_summary">Scrobble to Last.FM through ScrobbleDroid or Simple Last.FM Scrobbler</string>
|
||||
<string name="scrobble_summary">Scrobble to Last.fm through ScrobbleDroid or Simple Last.fm Scrobbler</string>
|
||||
<string name="stock_broadcast_title">Emulate Stock Broadcasts</string>
|
||||
<string name="stock_broadcast_summary">Send broadcasts emulating those sent by the stock music player to work with 3rd party lockscreen controls, widgets, etc.</string>
|
||||
|
||||
|
@ -181,7 +181,7 @@ public final class CoverBitmap {
|
||||
int bitmapWidth = Math.max(coverWidth, boxWidth);
|
||||
int bitmapHeight = Math.max(coverHeight, boxHeight);
|
||||
|
||||
Bitmap bitmap = Bitmap.createBitmap(bitmapWidth, bitmapHeight, Bitmap.Config.RGB_565);
|
||||
Bitmap bitmap = Bitmap.createBitmap(bitmapWidth, bitmapHeight, Bitmap.Config.ARGB_8888);
|
||||
Canvas canvas = new Canvas(bitmap);
|
||||
|
||||
if (cover != null) {
|
||||
@ -267,7 +267,7 @@ public final class CoverBitmap {
|
||||
int bitmapWidth = horizontal ? coverWidth + boxWidth : Math.max(coverWidth, boxWidth);
|
||||
int bitmapHeight = horizontal ? Math.max(coverHeight, boxHeight) : coverHeight + boxHeight;
|
||||
|
||||
Bitmap bitmap = Bitmap.createBitmap(bitmapWidth, bitmapHeight, Bitmap.Config.RGB_565);
|
||||
Bitmap bitmap = Bitmap.createBitmap(bitmapWidth, bitmapHeight, Bitmap.Config.ARGB_8888);
|
||||
Canvas canvas = new Canvas(bitmap);
|
||||
|
||||
if (cover != null) {
|
||||
@ -322,7 +322,7 @@ public final class CoverBitmap {
|
||||
float scale = Math.min((float)width / sourceWidth, (float)height / sourceHeight);
|
||||
sourceWidth *= scale;
|
||||
sourceHeight *= scale;
|
||||
return Bitmap.createScaledBitmap(source, sourceWidth, sourceHeight, false);
|
||||
return Bitmap.createScaledBitmap(source, sourceWidth, sourceHeight, true);
|
||||
}
|
||||
|
||||
/**
|
||||
|
279
src/ch/blinkenlights/android/vanilla/CoverCache.java
Normal file
279
src/ch/blinkenlights/android/vanilla/CoverCache.java
Normal file
@ -0,0 +1,279 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Adrian Ulrich <adrian@blinkenlights.ch>
|
||||
*
|
||||
* This program 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.
|
||||
*
|
||||
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package ch.blinkenlights.android.vanilla;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.net.Uri;
|
||||
import android.util.LruCache;
|
||||
import android.util.Log;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.InputStream;
|
||||
|
||||
|
||||
public class CoverCache {
|
||||
/**
|
||||
* Returned size of small album covers
|
||||
*/
|
||||
public final static int SIZE_SMALL = 96;
|
||||
/**
|
||||
* Returned size of large (cover view) album covers
|
||||
*/
|
||||
public final static int SIZE_LARGE = 600;
|
||||
/**
|
||||
* Use all cover providers to load cover art
|
||||
*/
|
||||
public static final int COVER_MODE_ALL = 0xF;
|
||||
/**
|
||||
* Use androids builtin cover mechanism to load covers
|
||||
*/
|
||||
public static final int COVER_MODE_ANDROID = 0x1;
|
||||
/**
|
||||
* Use vanilla musics cover load mechanism
|
||||
*/
|
||||
public static final int COVER_MODE_VANILLA = 0x2;
|
||||
/**
|
||||
* Use vanilla musics SHADOW cover load mechanism
|
||||
*/
|
||||
public static final int COVER_MODE_SHADOW = 0x4;
|
||||
/**
|
||||
* Shared LRU cache class
|
||||
*/
|
||||
private static BitmapLruCache sBitmapLruCache;
|
||||
/**
|
||||
* Bitmask on how we are going to load coverart
|
||||
*/
|
||||
public static int mCoverLoadMode = 0;
|
||||
|
||||
/**
|
||||
* Constructs a new BitmapCache object
|
||||
* Will initialize the internal LRU cache on first call
|
||||
*
|
||||
* @param context A context to use
|
||||
*/
|
||||
public CoverCache(Context context) {
|
||||
if (sBitmapLruCache == null) {
|
||||
sBitmapLruCache = new BitmapLruCache(context, 6*1024*1024);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a (possibly uncached) cover for the song - may return null if the song has no cover
|
||||
*
|
||||
* @param key The cache key to use for storing a generated cover
|
||||
* @param song The song used to identify the artwork to load
|
||||
*/
|
||||
public Bitmap getCoverFromSong(CoverKey key, Song song) {
|
||||
Bitmap cover = getCachedCover(key);
|
||||
if (cover == null) {
|
||||
cover = sBitmapLruCache.createBitmap(song, key.coverSize*key.coverSize);
|
||||
if (cover != null) {
|
||||
putCover(key, cover);
|
||||
}
|
||||
}
|
||||
return cover;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a cached version of the cover. Will return null if nothing was cached
|
||||
*
|
||||
* @param key The cache key to use
|
||||
*/
|
||||
public Bitmap getCachedCover(CoverKey key) {
|
||||
return sBitmapLruCache.get(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores a new entry in the cache
|
||||
*
|
||||
* @param key The cache key to use
|
||||
* @param cover The bitmap to store
|
||||
*/
|
||||
public void putCover(CoverKey key, Bitmap cover) {
|
||||
sBitmapLruCache.put(key, cover);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes all items hold in the LRU cache
|
||||
*/
|
||||
public static void evictAll() {
|
||||
if (sBitmapLruCache != null) {
|
||||
sBitmapLruCache.evictAll();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Object used as cache key. Objects with the same
|
||||
* media type, id and size are considered to be equal
|
||||
*/
|
||||
public static class CoverKey {
|
||||
public final int coverSize;
|
||||
public final int mediaType;
|
||||
public final long mediaId;
|
||||
|
||||
CoverKey(int mediaType, long mediaId, int coverSize) {
|
||||
this.mediaType = mediaType;
|
||||
this.mediaId = mediaId;
|
||||
this.coverSize = coverSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj instanceof CoverKey
|
||||
&& this.mediaId == ((CoverKey)obj).mediaId
|
||||
&& this.mediaType == ((CoverKey)obj).mediaType
|
||||
&& this.coverSize == ((CoverKey)obj).coverSize) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return this.mediaType*10 + (int)this.mediaId + this.coverSize * (int)1e5;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* The LRU cache implementation, using the CoverKey as key to store Bitmap objects
|
||||
*
|
||||
* Note that the implementation does not override create() in order to enable
|
||||
* the use of fetch-if-cached functions: createBitmap() is therefore called
|
||||
* by CoverCache itself.
|
||||
*/
|
||||
private static class BitmapLruCache extends LruCache<CoverKey, Bitmap> {
|
||||
/**
|
||||
* Context required for content resolver
|
||||
*/
|
||||
private final Context mContext;
|
||||
/**
|
||||
* Filenames we consider to be cover art
|
||||
*/
|
||||
private final static String[] coverNames = { "cover.jpg", "cover.png", "album.jpg", "album.png", "artwork.jpg", "artwork.png", "art.jpg", "art.png" };
|
||||
|
||||
/**
|
||||
* Creates a new in-memory LRU cache
|
||||
*
|
||||
* @param context the application context
|
||||
* @param int the lru cache size in bytes
|
||||
*/
|
||||
public BitmapLruCache(Context context, int size) {
|
||||
super(size);
|
||||
mContext = context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the cache size in bytes, not objects
|
||||
*/
|
||||
@Override
|
||||
protected int sizeOf(CoverKey key, Bitmap value) {
|
||||
return value.getByteCount();
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to create a new bitmap object for given song.
|
||||
* Returns null if no cover art was found
|
||||
*
|
||||
* @param song the function will search for artwork of this object
|
||||
* @param maxPxCount the maximum amount of pixels to return (30*30 = 900)
|
||||
*/
|
||||
public Bitmap createBitmap(Song song, long maxPxCount) {
|
||||
|
||||
if (song.id < 0) {
|
||||
// Unindexed song: return early
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
InputStream inputStream = null;
|
||||
InputStream sampleInputStream = null; // same as inputStream but used for getSampleSize
|
||||
|
||||
if ((CoverCache.mCoverLoadMode & CoverCache.COVER_MODE_VANILLA) != 0) {
|
||||
String basePath = (new File(song.path)).getParentFile().getAbsolutePath(); // parent dir of the currently playing file
|
||||
for (String coverFile: coverNames) {
|
||||
File guessedFile = new File( basePath + "/" + coverFile);
|
||||
if (guessedFile.exists() && !guessedFile.isDirectory()) {
|
||||
inputStream = new FileInputStream(guessedFile);
|
||||
sampleInputStream = new FileInputStream(guessedFile);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (inputStream == null && (CoverCache.mCoverLoadMode & CoverCache.COVER_MODE_SHADOW) != 0) {
|
||||
String shadowPath = "/sdcard/Music/.vanilla/"+(song.artist.replaceAll("/", "_"))+"/"+(song.album.replaceAll("/", "_"))+".jpg";
|
||||
|
||||
File guessedFile = new File(shadowPath);
|
||||
if (guessedFile.exists() && !guessedFile.isDirectory()) {
|
||||
inputStream = new FileInputStream(guessedFile);
|
||||
sampleInputStream = new FileInputStream(guessedFile);
|
||||
}
|
||||
}
|
||||
|
||||
if (inputStream == null && (CoverCache.mCoverLoadMode & CoverCache.COVER_MODE_ANDROID) != 0) {
|
||||
Uri uri = Uri.parse("content://media/external/audio/media/" + song.id + "/albumart");
|
||||
ContentResolver res = mContext.getContentResolver();
|
||||
inputStream = res.openInputStream(uri);
|
||||
sampleInputStream = res.openInputStream(uri);
|
||||
}
|
||||
|
||||
if (inputStream != null) {
|
||||
BitmapFactory.Options bopts = new BitmapFactory.Options();
|
||||
bopts.inPreferredConfig = Bitmap.Config.RGB_565;
|
||||
bopts.inJustDecodeBounds = true;
|
||||
|
||||
final int inSampleSize = getSampleSize(sampleInputStream, bopts, maxPxCount);
|
||||
|
||||
/* reuse bopts: we are now REALLY going to decode the image */
|
||||
bopts.inJustDecodeBounds = false;
|
||||
bopts.inSampleSize = inSampleSize;
|
||||
return BitmapFactory.decodeStream(inputStream, null, bopts);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// no cover art found
|
||||
Log.v("VanillaMusic", "Loading coverart for "+song+" failed with exception "+e);
|
||||
}
|
||||
// failed!
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Guess a good sampleSize value for given inputStream
|
||||
*
|
||||
* @param inputStream the input stream to read from
|
||||
* @param bopts the bitmap options to use
|
||||
* @param maxPxCount how many pixels we are returning at most
|
||||
*/
|
||||
private static int getSampleSize(InputStream inputStream, BitmapFactory.Options bopts, long maxPxCount) {
|
||||
int sampleSize = 1; /* default sample size */
|
||||
|
||||
BitmapFactory.decodeStream(inputStream, null, bopts);
|
||||
long hasPixels = bopts.outHeight * bopts.outWidth;
|
||||
if(hasPixels > maxPxCount) {
|
||||
sampleSize = Math.round((int)Math.sqrt((float) hasPixels / (float) maxPxCount));
|
||||
}
|
||||
return sampleSize;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -365,16 +365,14 @@ public final class CoverView extends View implements Handler.Callback {
|
||||
Context context = getContext();
|
||||
Bitmap cover = song == null ? null : song.getCover(context);
|
||||
|
||||
if (cover == null && style == CoverBitmap.STYLE_NO_INFO) {
|
||||
Bitmap def = mDefaultCover;
|
||||
if (def == null) {
|
||||
mDefaultCover = def = CoverBitmap.generateDefaultCover(context, getWidth(), getHeight());
|
||||
if (cover == null && style != CoverBitmap.STYLE_OVERLAPPING_BOX) {
|
||||
if (mDefaultCover == null) {
|
||||
mDefaultCover = CoverBitmap.generateDefaultCover(context, getWidth(), getHeight());
|
||||
}
|
||||
mBitmaps[i] = def;
|
||||
} else {
|
||||
mBitmaps[i] = CoverBitmap.createBitmap(context, style, cover, song, getWidth(), getHeight());
|
||||
cover = mDefaultCover;
|
||||
}
|
||||
|
||||
mBitmaps[i] = CoverBitmap.createBitmap(context, style, cover, song, getWidth(), getHeight());
|
||||
postInvalidate();
|
||||
}
|
||||
|
||||
|
@ -27,64 +27,64 @@ import android.widget.CheckedTextView;
|
||||
import android.widget.Checkable;
|
||||
|
||||
public class DraggableRow extends LinearLayout implements Checkable {
|
||||
private boolean mShowCheckBox;
|
||||
private boolean mHighlighted;
|
||||
/**
|
||||
* True if the checkbox is checked
|
||||
*/
|
||||
private boolean mChecked;
|
||||
|
||||
private float mDensity;
|
||||
private static int sWidth = -1;
|
||||
/**
|
||||
* True if setupLayout has been called
|
||||
*/
|
||||
private boolean mLayoutSet;
|
||||
|
||||
private TextView mTextView;
|
||||
private CheckedTextView mCheckBox;
|
||||
private View mPmark;
|
||||
private ImageView mDragger;
|
||||
private LazyCoverView mCoverView;
|
||||
|
||||
// Hardcoded sizes of elements in DPI
|
||||
// MUST match definition in draggable_row
|
||||
private final int DPI_PMARK = 4;
|
||||
private final int DPI_CHECKBOX = 44;
|
||||
private final int DPI_DRAGGER = 44;
|
||||
private final int DPI_SLACK = 1; // safety margin
|
||||
/**
|
||||
* Layout types for use with setupLayout
|
||||
*/
|
||||
public static final int LAYOUT_CHECKBOXES = 1;
|
||||
public static final int LAYOUT_COVERVIEW = 2;
|
||||
|
||||
|
||||
public DraggableRow(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
mDensity = context.getResources().getDisplayMetrics().density;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onFinishInflate() {
|
||||
mCheckBox = (CheckedTextView)this.findViewById(R.id.checkbox);
|
||||
mTextView = (TextView)this.findViewById(R.id.text);
|
||||
mPmark = (View)this.findViewById(R.id.pmark);
|
||||
mDragger = (ImageView)this.findViewById(R.id.dragger);
|
||||
setupTextView(false);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void onSizeChanged (int w, int h, int oldw, int oldh) {
|
||||
super.onSizeChanged(w, h, oldw, oldh);
|
||||
if (sWidth != w) {
|
||||
sWidth = w;
|
||||
// This view was drawn with an incorrect with
|
||||
// fix it and force a redraw
|
||||
setupTextView(true);
|
||||
}
|
||||
mCheckBox = (CheckedTextView)this.findViewById(R.id.checkbox);
|
||||
mTextView = (TextView)this.findViewById(R.id.text);
|
||||
mPmark = (View)this.findViewById(R.id.pmark);
|
||||
mDragger = (ImageView)this.findViewById(R.id.dragger);
|
||||
mCoverView = (LazyCoverView)this.findViewById(R.id.cover);
|
||||
super.onFinishInflate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Resizes our textview to the correct size
|
||||
* @param redraw invalidates the current view if TRUE
|
||||
* Sets up commonly used layouts - can only be called once per view
|
||||
*
|
||||
* @param type the layout type to use
|
||||
*/
|
||||
private void setupTextView(boolean redraw) {
|
||||
int pixelUsed = (int)((DPI_SLACK + DPI_PMARK + DPI_DRAGGER + (mShowCheckBox ? DPI_CHECKBOX : 0)) * mDensity);
|
||||
int pixelFree = sWidth - pixelUsed;
|
||||
if (pixelFree > 0)
|
||||
mTextView.setWidth(pixelFree);
|
||||
if (redraw == true)
|
||||
this.invalidate();
|
||||
public void setupLayout(int type) {
|
||||
if (!mLayoutSet) {
|
||||
switch (type) {
|
||||
case LAYOUT_CHECKBOXES:
|
||||
mCheckBox.setVisibility(View.VISIBLE);
|
||||
showDragger(true);
|
||||
break;
|
||||
case LAYOUT_COVERVIEW:
|
||||
mCoverView.setVisibility(View.VISIBLE);
|
||||
showDragger(true);
|
||||
break;
|
||||
default:
|
||||
break; // do not care
|
||||
}
|
||||
mLayoutSet = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -108,26 +108,12 @@ public class DraggableRow extends LinearLayout implements Checkable {
|
||||
* @param state Enable or disable highlighting
|
||||
*/
|
||||
public void highlightRow(boolean state) {
|
||||
if (mHighlighted != state) {
|
||||
mPmark.setVisibility( state ? View.VISIBLE : View.INVISIBLE );
|
||||
mHighlighted = state;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the visibility of the checkbox
|
||||
* @param state show or destroy the checkbox
|
||||
*/
|
||||
public void showCheckBox(boolean state) {
|
||||
if (mShowCheckBox != state) {
|
||||
mCheckBox.setVisibility( state ? View.VISIBLE : View.GONE);
|
||||
mShowCheckBox = state;
|
||||
setupTextView(true);
|
||||
}
|
||||
mPmark.setVisibility( state ? View.VISIBLE : View.INVISIBLE );
|
||||
}
|
||||
|
||||
/**
|
||||
* Change visibility of dragger element
|
||||
*
|
||||
* @param state shows or hides the dragger
|
||||
*/
|
||||
public void showDragger(boolean state) {
|
||||
@ -141,4 +127,11 @@ public class DraggableRow extends LinearLayout implements Checkable {
|
||||
return mTextView;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an instance of our coverview
|
||||
*/
|
||||
public LazyCoverView getCoverView() {
|
||||
return mCoverView;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -188,13 +188,6 @@ public class FileSystemAdapter
|
||||
return pos;
|
||||
}
|
||||
|
||||
private static class ViewHolder {
|
||||
public int id;
|
||||
public TextView text;
|
||||
public View divider;
|
||||
public ImageView arrow;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(int pos, View convertView, ViewGroup parent)
|
||||
{
|
||||
@ -205,7 +198,7 @@ public class FileSystemAdapter
|
||||
view = mInflater.inflate(R.layout.library_row_expandable, null);
|
||||
holder = new ViewHolder();
|
||||
holder.text = (TextView)view.findViewById(R.id.text);
|
||||
holder.divider = view.findViewById(R.id.divider);
|
||||
holder.divider = (View)view.findViewById(R.id.divider);
|
||||
holder.arrow = (ImageView)view.findViewById(R.id.arrow);
|
||||
holder.text.setOnClickListener(this);
|
||||
holder.arrow.setOnClickListener(this);
|
||||
@ -219,8 +212,8 @@ public class FileSystemAdapter
|
||||
boolean isDirectory = file.isDirectory();
|
||||
holder.id = pos;
|
||||
holder.text.setText(file.getName());
|
||||
holder.divider.setVisibility(isDirectory ? View.VISIBLE : View.GONE);
|
||||
holder.arrow.setVisibility(isDirectory ? View.VISIBLE : View.GONE);
|
||||
holder.divider.setVisibility(isDirectory ? View.VISIBLE : View.GONE);
|
||||
holder.text.setCompoundDrawablesWithIntrinsicBounds(isDirectory ? mFolderIcon : null, null, null, null);
|
||||
return view;
|
||||
}
|
||||
@ -295,11 +288,11 @@ public class FileSystemAdapter
|
||||
public Intent createData(View view)
|
||||
{
|
||||
ViewHolder holder = (ViewHolder)view.getTag();
|
||||
File file = mFiles[holder.id];
|
||||
File file = mFiles[(int)holder.id];
|
||||
|
||||
Intent intent = new Intent();
|
||||
intent.putExtra(LibraryAdapter.DATA_TYPE, MediaUtils.TYPE_FILE);
|
||||
intent.putExtra(LibraryAdapter.DATA_ID, (long)holder.id);
|
||||
intent.putExtra(LibraryAdapter.DATA_ID, holder.id);
|
||||
intent.putExtra(LibraryAdapter.DATA_TITLE, holder.text.getText().toString());
|
||||
intent.putExtra(LibraryAdapter.DATA_EXPANDABLE, file.isDirectory());
|
||||
|
||||
|
@ -45,13 +45,6 @@ public class FilebrowserStartAdapter
|
||||
mInflater = (LayoutInflater)activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
}
|
||||
|
||||
private static class ViewHolder {
|
||||
public int id;
|
||||
public TextView text;
|
||||
public View divider;
|
||||
public ImageView arrow;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(int pos, View convertView, ViewGroup parent) {
|
||||
View view;
|
||||
@ -82,7 +75,7 @@ public class FilebrowserStartAdapter
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
ViewHolder holder = (ViewHolder)((View)view.getParent()).getTag();
|
||||
mActivity.onDirectoryClicked(holder.id);
|
||||
mActivity.onDirectoryClicked((int)holder.id);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -428,9 +428,6 @@ public class FullPlaybackActivity extends PlaybackActivity
|
||||
dialog.create().show();
|
||||
}
|
||||
break;
|
||||
case MENU_SHOW_QUEUE:
|
||||
startActivity(new Intent(this, ShowQueueActivity.class));
|
||||
break;
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
217
src/ch/blinkenlights/android/vanilla/LazyCoverView.java
Normal file
217
src/ch/blinkenlights/android/vanilla/LazyCoverView.java
Normal file
@ -0,0 +1,217 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Adrian Ulrich <adrian@blinkenlights.ch>
|
||||
*
|
||||
* This program 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.
|
||||
*
|
||||
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package ch.blinkenlights.android.vanilla;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.drawable.TransitionDrawable;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.Message;
|
||||
|
||||
import android.util.AttributeSet;
|
||||
import android.widget.ImageView;
|
||||
|
||||
/**
|
||||
* LazyCoverView implements a 'song-aware' ImageView
|
||||
*
|
||||
* View updates should be triggered via setCover(type, id) to
|
||||
* instruct the view to load the cover from its own LRU cache.
|
||||
*
|
||||
* The cover will automatically be fetched & scaled in a background
|
||||
* thread on cache miss
|
||||
*/
|
||||
public class LazyCoverView extends ImageView
|
||||
implements Handler.Callback
|
||||
{
|
||||
/**
|
||||
* Context of constructor
|
||||
*/
|
||||
private Context mContext;
|
||||
/**
|
||||
* UI Thread handler
|
||||
*/
|
||||
private static Handler sUiHandler;
|
||||
/**
|
||||
* Worker thread handler
|
||||
*/
|
||||
private static Handler sHandler;
|
||||
/**
|
||||
* The fallback cover image resource encoded as bitmap
|
||||
*/
|
||||
private static Bitmap sFallbackBitmap;
|
||||
/**
|
||||
* Cover LRU cache LRU cache
|
||||
*/
|
||||
private static CoverCache sCoverCache;
|
||||
/**
|
||||
* The cover key we are expected to draw
|
||||
*/
|
||||
private CoverCache.CoverKey mExpectedKey;
|
||||
|
||||
/**
|
||||
* Cover message we are passing around using mHandler
|
||||
*/
|
||||
private static class CoverMsg {
|
||||
public CoverCache.CoverKey key;
|
||||
public LazyCoverView view; // The view we are updating
|
||||
CoverMsg(CoverCache.CoverKey key, LazyCoverView view) {
|
||||
this.key = key;
|
||||
this.view = view;
|
||||
}
|
||||
/**
|
||||
* Returns true if the view still requires updating
|
||||
*/
|
||||
public boolean isRecent() {
|
||||
return this.key.equals(this.view.mExpectedKey);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor of class inflated from XML
|
||||
*
|
||||
* @param context The context of the calling activity
|
||||
* @param attributes attributes passed by the xml inflater
|
||||
*/
|
||||
public LazyCoverView(Context context, AttributeSet attributes) {
|
||||
super(context, attributes);
|
||||
mContext = context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup the handler of this view instance. This function
|
||||
* must be called before calling setCover().
|
||||
*
|
||||
* @param looper The worker thread to use for image scaling
|
||||
*/
|
||||
public void setup(Looper looper) {
|
||||
if (sCoverCache == null) {
|
||||
sCoverCache = new CoverCache(mContext);
|
||||
}
|
||||
if (sFallbackBitmap == null) {
|
||||
sFallbackBitmap = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.fallback_cover);
|
||||
}
|
||||
if (sUiHandler == null) {
|
||||
sUiHandler = new Handler(this);
|
||||
}
|
||||
if (sHandler == null || sHandler.getLooper().equals(looper) == false) {
|
||||
sHandler = new Handler(looper, this);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* mHandler and mUiHandler callbacks
|
||||
*/
|
||||
private static final int MSG_CACHE_COVER = 60;
|
||||
private static final int MSG_DRAW_COVER = 61;
|
||||
|
||||
@Override
|
||||
public boolean handleMessage(Message message) {
|
||||
switch (message.what) {
|
||||
case MSG_CACHE_COVER: {
|
||||
CoverMsg payload = (CoverMsg)message.obj;
|
||||
|
||||
if (payload.isRecent() == false) {
|
||||
// This RPC is already obsoleted: drop it
|
||||
break;
|
||||
}
|
||||
|
||||
Bitmap bitmap = sCoverCache.getCachedCover(payload.key);
|
||||
if (bitmap == null) {
|
||||
Song song = MediaUtils.getSongByTypeId(mContext.getContentResolver(), payload.key.mediaType, payload.key.mediaId);
|
||||
if (song != null) {
|
||||
bitmap = sCoverCache.getCoverFromSong(payload.key, song);
|
||||
}
|
||||
if (bitmap == null) {
|
||||
bitmap = sFallbackBitmap;
|
||||
}
|
||||
sCoverCache.putCover(payload.key, bitmap);
|
||||
}
|
||||
sUiHandler.sendMessage(sUiHandler.obtainMessage(MSG_DRAW_COVER, payload));
|
||||
break;
|
||||
}
|
||||
case MSG_DRAW_COVER: {
|
||||
CoverMsg payload = (CoverMsg)message.obj;
|
||||
// We run in the UI-Thread like setCover()
|
||||
// and do not need locking: checking if the payload
|
||||
// is still recent is sufficient.
|
||||
if (payload.isRecent()) {
|
||||
payload.view.drawFromCache(payload.key, true);
|
||||
}
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to set the image of this cover
|
||||
* Must be called from an UI thread
|
||||
*
|
||||
* @param type The Media type
|
||||
* @param id The id of this media type to query
|
||||
*/
|
||||
public void setCover(int type, long id) {
|
||||
mExpectedKey = new CoverCache.CoverKey(type, id, CoverCache.SIZE_SMALL);
|
||||
if (drawFromCache(mExpectedKey, false) == false) {
|
||||
int delay = 1;
|
||||
if (sHandler.hasMessages(MSG_CACHE_COVER)) {
|
||||
// User is probably scrolling fast as there is already a queued resize job
|
||||
// wait 200ms as this view will most likely be obsolete soon anyway.
|
||||
// This frees us from scaling bitmaps we are never going to show
|
||||
delay = 200;
|
||||
}
|
||||
CoverMsg payload = new CoverMsg(mExpectedKey, this);
|
||||
sHandler.sendMessageDelayed(sHandler.obtainMessage(MSG_CACHE_COVER, payload), delay);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the view with a cached bitmap
|
||||
* A fallback image will be used on cache miss
|
||||
*
|
||||
* @param payload The cover message containing the cache key and view to use
|
||||
*/
|
||||
public boolean drawFromCache(CoverCache.CoverKey key, boolean fadeIn) {
|
||||
boolean cacheHit = true;
|
||||
Bitmap bitmap = sCoverCache.getCachedCover(key);
|
||||
if (bitmap == null) {
|
||||
cacheHit = false;
|
||||
}
|
||||
|
||||
if (fadeIn) {
|
||||
TransitionDrawable td = new TransitionDrawable(new Drawable[] {
|
||||
getDrawable(),
|
||||
(new BitmapDrawable(getResources(), bitmap))
|
||||
});
|
||||
setImageDrawable(td);
|
||||
td.startTransition(120);
|
||||
} else {
|
||||
setImageBitmap(bitmap);
|
||||
}
|
||||
|
||||
return cacheHit;
|
||||
}
|
||||
|
||||
}
|
@ -501,6 +501,23 @@ public class LibraryActivity
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates mCover with the new bitmap, running in the UI thread
|
||||
*
|
||||
* @param cover the cover to set, will use a fallback drawable if null
|
||||
*/
|
||||
private void updateCover(final Bitmap cover) {
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (cover == null)
|
||||
mCover.setImageResource(R.drawable.fallback_cover);
|
||||
else
|
||||
mCover.setImageBitmap(cover);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View view)
|
||||
{
|
||||
@ -778,6 +795,9 @@ public class LibraryActivity
|
||||
controls.setActionView(mActionControls);
|
||||
controls.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
|
||||
|
||||
// Call super after adding the now-playing view as this should be the first item
|
||||
super.onCreateOptionsMenu(menu);
|
||||
|
||||
mSearchMenuItem = menu.add(0, MENU_SEARCH, 0, R.string.search).setIcon(R.drawable.ic_menu_search);
|
||||
mSearchMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW | MenuItem.SHOW_AS_ACTION_ALWAYS);
|
||||
SearchView mSearchView = new SearchView(getActionBar().getThemedContext());
|
||||
@ -785,7 +805,9 @@ public class LibraryActivity
|
||||
mSearchMenuItem.setActionView(mSearchView);
|
||||
|
||||
menu.add(0, MENU_SORT, 0, R.string.sort_by).setIcon(R.drawable.ic_menu_sort_alphabetically);
|
||||
return super.onCreateOptionsMenu(menu);
|
||||
menu.add(0, MENU_SHOW_QUEUE, 0, R.string.show_queue);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -864,7 +886,11 @@ public class LibraryActivity
|
||||
/**
|
||||
* Save the current page, passed in arg1, to SharedPreferences.
|
||||
*/
|
||||
private static final int MSG_SAVE_PAGE = 12;
|
||||
private static final int MSG_SAVE_PAGE = 40;
|
||||
/**
|
||||
* Updates mCover using a background thread
|
||||
*/
|
||||
private static final int MSG_UPDATE_COVER = 41;
|
||||
|
||||
@Override
|
||||
public boolean handleMessage(Message message)
|
||||
@ -876,6 +902,16 @@ public class LibraryActivity
|
||||
editor.commit();
|
||||
break;
|
||||
}
|
||||
case MSG_UPDATE_COVER: {
|
||||
Bitmap cover = null;
|
||||
Song song = (Song)message.obj;
|
||||
if (song != null) {
|
||||
cover = song.getCover(this);
|
||||
}
|
||||
// Dispatch view update to UI thread
|
||||
updateCover(cover);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return super.handleMessage(message);
|
||||
}
|
||||
@ -905,8 +941,6 @@ public class LibraryActivity
|
||||
super.onSongChange(song);
|
||||
|
||||
if (mTitle != null) {
|
||||
Bitmap cover = null;
|
||||
|
||||
if (song == null) {
|
||||
if (mActionControls == null) {
|
||||
mTitle.setText(R.string.none);
|
||||
@ -914,7 +948,6 @@ public class LibraryActivity
|
||||
} else {
|
||||
mTitle.setText(null);
|
||||
mArtist.setText(null);
|
||||
mCover.setImageDrawable(null);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
@ -923,16 +956,12 @@ public class LibraryActivity
|
||||
String artist = song.artist == null ? res.getString(R.string.unknown) : song.artist;
|
||||
mTitle.setText(title);
|
||||
mArtist.setText(artist);
|
||||
cover = song.getCover(this);
|
||||
}
|
||||
|
||||
if (Song.mCoverLoadMode == 0)
|
||||
mCover.setVisibility(View.GONE);
|
||||
else if (cover == null)
|
||||
mCover.setImageResource(R.drawable.fallback_cover);
|
||||
else
|
||||
mCover.setImageBitmap(cover);
|
||||
mCover.setVisibility(CoverCache.mCoverLoadMode == 0 ? View.GONE : View.VISIBLE);
|
||||
mHandler.sendMessage(mHandler.obtainMessage(MSG_UPDATE_COVER, song));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -43,6 +43,7 @@ import android.util.LruCache;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.LinearLayout;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
@ -53,7 +54,7 @@ public class LibraryPagerAdapter
|
||||
implements Handler.Callback
|
||||
, ViewPager.OnPageChangeListener
|
||||
, View.OnCreateContextMenuListener
|
||||
, AdapterView.OnItemClickListener
|
||||
, View.OnClickListener
|
||||
{
|
||||
/**
|
||||
* The number of unique list types. The number of visible lists may be
|
||||
@ -162,9 +163,9 @@ public class LibraryPagerAdapter
|
||||
* song limiters.
|
||||
*/
|
||||
private String mHeaderText;
|
||||
private TextView mArtistHeader;
|
||||
private TextView mAlbumHeader;
|
||||
private TextView mSongHeader;
|
||||
private LinearLayout mArtistHeader;
|
||||
private LinearLayout mAlbumHeader;
|
||||
private LinearLayout mSongHeader;
|
||||
/**
|
||||
* The current filter text, or null if none.
|
||||
*/
|
||||
@ -307,30 +308,31 @@ public class LibraryPagerAdapter
|
||||
LibraryActivity activity = mActivity;
|
||||
LayoutInflater inflater = activity.getLayoutInflater();
|
||||
LibraryAdapter adapter;
|
||||
TextView header = null;
|
||||
LinearLayout header = null;
|
||||
Looper looper = mWorkerHandler.getLooper();
|
||||
|
||||
switch (type) {
|
||||
case MediaUtils.TYPE_ARTIST:
|
||||
adapter = mArtistAdapter = new MediaAdapter(activity, MediaUtils.TYPE_ARTIST, null);
|
||||
adapter = mArtistAdapter = new MediaAdapter(activity, MediaUtils.TYPE_ARTIST, null, looper);
|
||||
mArtistAdapter.setExpandable(mSongsPosition != -1 || mAlbumsPosition != -1);
|
||||
mArtistHeader = header = (TextView)inflater.inflate(R.layout.library_row, null);
|
||||
mArtistHeader = header = (LinearLayout)inflater.inflate(R.layout.library_row_expandable, null);
|
||||
break;
|
||||
case MediaUtils.TYPE_ALBUM:
|
||||
adapter = mAlbumAdapter = new MediaAdapter(activity, MediaUtils.TYPE_ALBUM, mPendingAlbumLimiter);
|
||||
adapter = mAlbumAdapter = new MediaAdapter(activity, MediaUtils.TYPE_ALBUM, mPendingAlbumLimiter, looper);
|
||||
mAlbumAdapter.setExpandable(mSongsPosition != -1);
|
||||
mPendingAlbumLimiter = null;
|
||||
mAlbumHeader = header = (TextView)inflater.inflate(R.layout.library_row, null);
|
||||
mAlbumHeader = header = (LinearLayout)inflater.inflate(R.layout.library_row_expandable, null);
|
||||
break;
|
||||
case MediaUtils.TYPE_SONG:
|
||||
adapter = mSongAdapter = new MediaAdapter(activity, MediaUtils.TYPE_SONG, mPendingSongLimiter);
|
||||
adapter = mSongAdapter = new MediaAdapter(activity, MediaUtils.TYPE_SONG, mPendingSongLimiter, looper);
|
||||
mPendingSongLimiter = null;
|
||||
mSongHeader = header = (TextView)inflater.inflate(R.layout.library_row, null);
|
||||
mSongHeader = header = (LinearLayout)inflater.inflate(R.layout.library_row_expandable, null);
|
||||
break;
|
||||
case MediaUtils.TYPE_PLAYLIST:
|
||||
adapter = mPlaylistAdapter = new MediaAdapter(activity, MediaUtils.TYPE_PLAYLIST, null);
|
||||
adapter = mPlaylistAdapter = new MediaAdapter(activity, MediaUtils.TYPE_PLAYLIST, null, looper);
|
||||
break;
|
||||
case MediaUtils.TYPE_GENRE:
|
||||
adapter = mGenreAdapter = new MediaAdapter(activity, MediaUtils.TYPE_GENRE, null);
|
||||
adapter = mGenreAdapter = new MediaAdapter(activity, MediaUtils.TYPE_GENRE, null, looper);
|
||||
mGenreAdapter.setExpandable(mSongsPosition != -1);
|
||||
break;
|
||||
case MediaUtils.TYPE_FILE:
|
||||
@ -343,11 +345,13 @@ public class LibraryPagerAdapter
|
||||
|
||||
view = (ListView)inflater.inflate(R.layout.listview, null);
|
||||
view.setOnCreateContextMenuListener(this);
|
||||
view.setOnItemClickListener(this);
|
||||
|
||||
view.setTag(type);
|
||||
if (header != null) {
|
||||
header.setText(mHeaderText);
|
||||
header.setTag(type);
|
||||
TextView headerText = (TextView)header.findViewById(R.id.text);
|
||||
headerText.setText(mHeaderText);
|
||||
headerText.setOnClickListener(this);
|
||||
header.setTag(new ViewHolder()); // behave like a normal library row
|
||||
view.addHeaderView(header);
|
||||
}
|
||||
view.setAdapter(adapter);
|
||||
@ -466,11 +470,11 @@ public class LibraryPagerAdapter
|
||||
public void setHeaderText(String text)
|
||||
{
|
||||
if (mArtistHeader != null)
|
||||
mArtistHeader.setText(text);
|
||||
((TextView)mArtistHeader.findViewById(R.id.text)).setText(text);
|
||||
if (mAlbumHeader != null)
|
||||
mAlbumHeader.setText(text);
|
||||
((TextView)mAlbumHeader.findViewById(R.id.text)).setText(text);
|
||||
if (mSongHeader != null)
|
||||
mSongHeader.setText(text);
|
||||
((TextView)mSongHeader.findViewById(R.id.text)).setText(text);
|
||||
mHeaderText = text;
|
||||
}
|
||||
|
||||
@ -819,6 +823,7 @@ public class LibraryPagerAdapter
|
||||
*/
|
||||
private static Intent createHeaderIntent(View header)
|
||||
{
|
||||
header = (View)header.getParent(); // tag is set on parent view of header
|
||||
int type = (Integer)header.getTag();
|
||||
Intent intent = new Intent();
|
||||
intent.putExtra(LibraryAdapter.DATA_ID, LibraryAdapter.HEADER_ID);
|
||||
@ -836,13 +841,12 @@ public class LibraryPagerAdapter
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> list, View view, int position, long id)
|
||||
{
|
||||
Intent intent = id == -1 ? createHeaderIntent(view) : mCurrentAdapter.createData(view);
|
||||
public void onClick(View view) {
|
||||
view = (View)view.getParent(); // get view of linear layout, not the click consumer
|
||||
Intent intent = createHeaderIntent(view);
|
||||
mActivity.onItemClicked(intent);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* LRU implementation for filebrowser position cache
|
||||
*/
|
||||
|
@ -28,6 +28,7 @@ import android.database.Cursor;
|
||||
import android.database.DatabaseUtils;
|
||||
import android.graphics.Color;
|
||||
import android.net.Uri;
|
||||
import android.os.Looper;
|
||||
import android.provider.BaseColumns;
|
||||
import android.provider.MediaStore;
|
||||
import android.text.Spannable;
|
||||
@ -76,6 +77,7 @@ public class MediaAdapter
|
||||
* The current data.
|
||||
*/
|
||||
private Cursor mCursor;
|
||||
private Looper mLooper;
|
||||
/**
|
||||
* The type of media represented by this adapter. Must be one of the
|
||||
* MediaUtils.FIELD_* constants. Determines which content provider to query for
|
||||
@ -134,6 +136,11 @@ public class MediaAdapter
|
||||
* If true, show the expander button on each row.
|
||||
*/
|
||||
private boolean mExpandable;
|
||||
/**
|
||||
* Defines the media type to use for this entry
|
||||
* Setting this to MediaUtils.TYPE_INVALID disables cover artwork
|
||||
*/
|
||||
private int mCoverCacheType;
|
||||
|
||||
/**
|
||||
* Construct a MediaAdapter representing the given <code>type</code> of
|
||||
@ -145,12 +152,16 @@ public class MediaAdapter
|
||||
* and what fields to display in the views.
|
||||
* @param limiter An initial limiter to use
|
||||
*/
|
||||
public MediaAdapter(LibraryActivity activity, int type, Limiter limiter)
|
||||
public MediaAdapter(LibraryActivity activity, int type, Limiter limiter, Looper looper)
|
||||
{
|
||||
mActivity = activity;
|
||||
mType = type;
|
||||
mLimiter = limiter;
|
||||
mInflater = (LayoutInflater) activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
mLooper = looper;
|
||||
|
||||
mCoverCacheType = MediaUtils.TYPE_INVALID;
|
||||
String coverCacheKey = "0"; // SQL dummy entry
|
||||
|
||||
switch (type) {
|
||||
case MediaUtils.TYPE_ARTIST:
|
||||
@ -169,6 +180,8 @@ public class MediaAdapter
|
||||
mSongSort = MediaUtils.ALBUM_SORT;
|
||||
mSortEntries = new int[] { R.string.name, R.string.artist_album, R.string.year, R.string.number_of_tracks, R.string.date_added };
|
||||
mSortValues = new String[] { "album_key %1$s", "artist_key %1$s,album_key %1$s", "minyear %1$s,album_key %1$s", "numsongs %1$s,album_key %1$s", "_id %1$s" };
|
||||
mCoverCacheType = MediaUtils.TYPE_ALBUM;
|
||||
coverCacheKey = BaseColumns._ID;
|
||||
break;
|
||||
case MediaUtils.TYPE_SONG:
|
||||
mStore = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
|
||||
@ -178,6 +191,8 @@ public class MediaAdapter
|
||||
R.string.artist_year, R.string.album_track, R.string.year, R.string.date_added, R.string.song_playcount };
|
||||
mSortValues = new String[] { "title_key %1$s", "artist_key %1$s,album_key %1$s,track %1$s", "artist_key %1$s,album_key %1$s,title_key %1$s",
|
||||
"artist_key %1$s,year %1$s,track %1$s", "album_key %1$s,track %1s", "year %1$s,title_key %1$s", "_id %1$s", SORT_MAGIC_PLAYCOUNT };
|
||||
mCoverCacheType = MediaUtils.TYPE_ALBUM;
|
||||
coverCacheKey = MediaStore.Audio.Albums.ALBUM_ID;
|
||||
break;
|
||||
case MediaUtils.TYPE_PLAYLIST:
|
||||
mStore = MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI;
|
||||
@ -199,9 +214,9 @@ public class MediaAdapter
|
||||
}
|
||||
|
||||
if (mFields.length == 1)
|
||||
mProjection = new String[] { BaseColumns._ID, mFields[0] };
|
||||
mProjection = new String[] { BaseColumns._ID, coverCacheKey, mFields[0] };
|
||||
else
|
||||
mProjection = new String[] { BaseColumns._ID, mFields[mFields.length - 1], mFields[0] };
|
||||
mProjection = new String[] { BaseColumns._ID, coverCacheKey, mFields[mFields.length - 1], mFields[0] };
|
||||
}
|
||||
|
||||
/**
|
||||
@ -252,7 +267,7 @@ public class MediaAdapter
|
||||
|
||||
// Magic sort mode: sort by playcount
|
||||
if (sortStringRaw == SORT_MAGIC_PLAYCOUNT) {
|
||||
ArrayList<Long> topSongs = (new PlayCountsHelper(mActivity)).getTopSongs();
|
||||
ArrayList<Long> topSongs = (new PlayCountsHelper(mActivity)).getTopSongs(4096);
|
||||
int sortWeight = -1 * topSongs.size(); // Sort mode is actually reversed (default: mostplayed -> leastplayed)
|
||||
|
||||
StringBuilder sb = new StringBuilder("CASE WHEN _id=0 THEN 0"); // include dummy statement in initial string -> topSongs may be empty
|
||||
@ -396,15 +411,15 @@ public class MediaAdapter
|
||||
|
||||
switch (mType) {
|
||||
case MediaUtils.TYPE_ARTIST:
|
||||
fields = new String[] { cursor.getString(1) };
|
||||
fields = new String[] { cursor.getString(2) };
|
||||
data = String.format("%s=%d", MediaStore.Audio.Media.ARTIST_ID, id);
|
||||
break;
|
||||
case MediaUtils.TYPE_ALBUM:
|
||||
fields = new String[] { cursor.getString(2), cursor.getString(1) };
|
||||
fields = new String[] { cursor.getString(3), cursor.getString(2) };
|
||||
data = String.format("%s=%d", MediaStore.Audio.Media.ALBUM_ID, id);
|
||||
break;
|
||||
case MediaUtils.TYPE_GENRE:
|
||||
fields = new String[] { cursor.getString(1) };
|
||||
fields = new String[] { cursor.getString(2) };
|
||||
data = id;
|
||||
break;
|
||||
default:
|
||||
@ -433,37 +448,31 @@ public class MediaAdapter
|
||||
}
|
||||
}
|
||||
|
||||
private static class ViewHolder {
|
||||
public long id;
|
||||
public String title;
|
||||
public TextView text;
|
||||
public ImageView arrow;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(int position, View view, ViewGroup parent)
|
||||
{
|
||||
ViewHolder holder;
|
||||
|
||||
if (view == null || mExpandable != view instanceof LinearLayout) {
|
||||
if (view == null) {
|
||||
// We must create a new view if we're not given a recycle view or
|
||||
// if the recycle view has the wrong layout.
|
||||
|
||||
int layout = mExpandable ? R.layout.library_row_expandable : R.layout.library_row;
|
||||
view = mInflater.inflate(layout, null);
|
||||
view = mInflater.inflate(R.layout.library_row_expandable, null);
|
||||
holder = new ViewHolder();
|
||||
view.setTag(holder);
|
||||
|
||||
if (mExpandable) {
|
||||
holder.text = (TextView)view.findViewById(R.id.text);
|
||||
holder.arrow = (ImageView)view.findViewById(R.id.arrow);
|
||||
holder.arrow.setOnClickListener(this);
|
||||
} else {
|
||||
holder.text = (TextView)view;
|
||||
view.setLongClickable(true);
|
||||
}
|
||||
|
||||
holder.text = (TextView)view.findViewById(R.id.text);
|
||||
holder.divider = (View)view.findViewById(R.id.divider);
|
||||
holder.arrow = (ImageView)view.findViewById(R.id.arrow);
|
||||
holder.cover = (LazyCoverView)view.findViewById(R.id.cover);
|
||||
holder.arrow.setOnClickListener(this);
|
||||
holder.text.setOnClickListener(this);
|
||||
holder.cover.setOnClickListener(this);
|
||||
|
||||
holder.divider.setVisibility(mExpandable ? View.VISIBLE : View.GONE);
|
||||
holder.arrow.setVisibility(mExpandable ? View.VISIBLE : View.GONE);
|
||||
holder.cover.setVisibility(mCoverCacheType != MediaUtils.TYPE_INVALID ? View.VISIBLE : View.GONE);
|
||||
holder.cover.setup(mLooper);
|
||||
} else {
|
||||
holder = (ViewHolder)view.getTag();
|
||||
}
|
||||
@ -471,9 +480,10 @@ public class MediaAdapter
|
||||
Cursor cursor = mCursor;
|
||||
cursor.moveToPosition(position);
|
||||
holder.id = cursor.getLong(0);
|
||||
if (mFields.length > 1) {
|
||||
String line1 = cursor.getString(1);
|
||||
String line2 = cursor.getString(2);
|
||||
long cacheId = cursor.getLong(1);
|
||||
if (mFields.length > 2) {
|
||||
String line1 = cursor.getString(2);
|
||||
String line2 = cursor.getString(3);
|
||||
if(line1 == null) { line1 = "???"; }
|
||||
if(line2 == null) { line2 = "???"; }
|
||||
SpannableStringBuilder sb = new SpannableStringBuilder(line1);
|
||||
@ -483,12 +493,16 @@ public class MediaAdapter
|
||||
holder.text.setText(sb);
|
||||
holder.title = line1;
|
||||
} else {
|
||||
String title = cursor.getString(1);
|
||||
String title = cursor.getString(2);
|
||||
if(title == null) { title = "???"; }
|
||||
holder.text.setText(title);
|
||||
holder.title = title;
|
||||
}
|
||||
|
||||
if (mCoverCacheType != MediaUtils.TYPE_INVALID) {
|
||||
holder.cover.setCover(mCoverCacheType, cacheId);
|
||||
}
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@ -565,8 +579,7 @@ public class MediaAdapter
|
||||
public void onClick(View view)
|
||||
{
|
||||
int id = view.getId();
|
||||
if (mExpandable)
|
||||
view = (View)view.getParent();
|
||||
view = (View)view.getParent(); // get view of linear layout, not the click consumer
|
||||
Intent intent = createData(view);
|
||||
if (id == R.id.arrow) {
|
||||
mActivity.onItemExpanded(intent);
|
||||
|
@ -405,6 +405,27 @@ public class MediaUtils {
|
||||
sAllSongs = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the first matching song (or NULL) of given type + id combination
|
||||
*
|
||||
* @param resolver A ContentResolver to use.
|
||||
* @param type The MediaTye to query
|
||||
* @param id The id of given type to query
|
||||
*/
|
||||
public static Song getSongByTypeId(ContentResolver resolver, int type, long id) {
|
||||
Song song = new Song(-1);
|
||||
QueryTask query = buildQuery(type, id, Song.FILLED_PROJECTION, null);
|
||||
Cursor cursor = query.runQuery(resolver);
|
||||
if (cursor != null) {
|
||||
if (cursor.getCount() > 0) {
|
||||
cursor.moveToPosition(0);
|
||||
song.populate(cursor);
|
||||
}
|
||||
cursor.close();
|
||||
}
|
||||
return song.id == -1 ? null : song;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a song randomly selected from all the songs in the Android
|
||||
* MediaStore.
|
||||
|
@ -68,26 +68,31 @@ public class PlayCountsHelper extends SQLiteOpenHelper {
|
||||
public void countSong(Song song) {
|
||||
long id = Song.getId(song);
|
||||
|
||||
SQLiteDatabase dbh = this.getWritableDatabase();
|
||||
SQLiteDatabase dbh = getWritableDatabase();
|
||||
dbh.execSQL("INSERT OR IGNORE INTO "+TABLE_PLAYCOUNTS+" (type, type_id, playcount) VALUES ("+MediaUtils.TYPE_SONG+", "+id+", 0);"); // Creates row if not exists
|
||||
dbh.execSQL("UPDATE "+TABLE_PLAYCOUNTS+" SET playcount=playcount+1 WHERE type="+MediaUtils.TYPE_SONG+" AND type_id="+id+";");
|
||||
performGC(dbh, MediaUtils.TYPE_SONG);
|
||||
dbh.close();
|
||||
|
||||
performGC(MediaUtils.TYPE_SONG);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Returns a sorted array list of most often listen song ids
|
||||
*/
|
||||
public ArrayList<Long> getTopSongs() {
|
||||
public ArrayList<Long> getTopSongs(int limit) {
|
||||
ArrayList<Long> payload = new ArrayList<Long>();
|
||||
SQLiteDatabase dbh = this.getReadableDatabase();
|
||||
Cursor cursor = dbh.rawQuery("SELECT type_id FROM "+TABLE_PLAYCOUNTS+" WHERE type="+MediaUtils.TYPE_SONG+" ORDER BY playcount DESC limit 4096", null);
|
||||
SQLiteDatabase dbh = getReadableDatabase();
|
||||
|
||||
Cursor cursor = dbh.rawQuery("SELECT type_id FROM "+TABLE_PLAYCOUNTS+" WHERE type="+MediaUtils.TYPE_SONG+" AND playcount != 0 ORDER BY playcount DESC limit "+limit, null);
|
||||
|
||||
while (cursor.moveToNext()) {
|
||||
payload.add(cursor.getLong(0));
|
||||
}
|
||||
|
||||
cursor.close();
|
||||
dbh.close();
|
||||
return payload;
|
||||
}
|
||||
|
||||
@ -96,7 +101,8 @@ public class PlayCountsHelper extends SQLiteOpenHelper {
|
||||
* and checks them against Androids media database.
|
||||
* Items not found in the media library are removed from the DBH's database
|
||||
*/
|
||||
private int performGC(SQLiteDatabase dbh, int type) {
|
||||
private int performGC(int type) {
|
||||
SQLiteDatabase dbh = getWritableDatabase();
|
||||
ArrayList<Long> toCheck = new ArrayList<Long>(); // List of songs we are going to check
|
||||
QueryTask query; // Reused query object
|
||||
Cursor cursor; // recycled cursor
|
||||
@ -119,6 +125,7 @@ public class PlayCountsHelper extends SQLiteOpenHelper {
|
||||
cursor.close();
|
||||
}
|
||||
Log.v("VanillaMusic", "performGC: items removed="+removed);
|
||||
dbh.close();
|
||||
return removed;
|
||||
}
|
||||
|
||||
|
@ -183,7 +183,7 @@ public abstract class PlaybackActivity extends Activity
|
||||
PlaybackService service = PlaybackService.get(this);
|
||||
int state = service.playPause();
|
||||
if ((state & PlaybackService.FLAG_ERROR) != 0)
|
||||
Toast.makeText(this, service.getErrorMessage(), Toast.LENGTH_LONG).show();
|
||||
showToast(service.getErrorMessage(), Toast.LENGTH_LONG);
|
||||
setState(state);
|
||||
}
|
||||
|
||||
@ -371,6 +371,9 @@ public abstract class PlaybackActivity extends Activity
|
||||
case MENU_CLEAR_QUEUE:
|
||||
PlaybackService.get(this).clearQueue();
|
||||
break;
|
||||
case MENU_SHOW_QUEUE:
|
||||
startActivity(new Intent(this, ShowQueueActivity.class));
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@ -451,7 +454,7 @@ public abstract class PlaybackActivity extends Activity
|
||||
}
|
||||
|
||||
String message = getResources().getQuantityString(R.plurals.added_to_playlist, count, count, playlistTask.name);
|
||||
Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
|
||||
showToast(message, Toast.LENGTH_SHORT);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -485,9 +488,20 @@ public abstract class PlaybackActivity extends Activity
|
||||
message = res.getString(R.string.deleted_item, intent.getStringExtra("title"));
|
||||
}
|
||||
|
||||
Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
|
||||
showToast(message, Toast.LENGTH_SHORT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and displays a new toast message
|
||||
*/
|
||||
private void showToast(final String message, final int duration) {
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Toast.makeText(getApplicationContext(), message, duration).show();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
|
@ -432,9 +432,9 @@ public final class PlaybackService extends Service
|
||||
mScrobble = settings.getBoolean(PrefKeys.SCROBBLE, false);
|
||||
mIdleTimeout = settings.getBoolean(PrefKeys.USE_IDLE_TIMEOUT, false) ? settings.getInt(PrefKeys.IDLE_TIMEOUT, 3600) : 0;
|
||||
|
||||
Song.mCoverLoadMode = settings.getBoolean(PrefKeys.COVERLOADER_ANDROID, true) ? Song.mCoverLoadMode | Song.COVER_MODE_ANDROID : Song.mCoverLoadMode & ~(Song.COVER_MODE_ANDROID);
|
||||
Song.mCoverLoadMode = settings.getBoolean(PrefKeys.COVERLOADER_VANILLA, true) ? Song.mCoverLoadMode | Song.COVER_MODE_VANILLA : Song.mCoverLoadMode & ~(Song.COVER_MODE_VANILLA);
|
||||
Song.mCoverLoadMode = settings.getBoolean(PrefKeys.COVERLOADER_SHADOW , true) ? Song.mCoverLoadMode | Song.COVER_MODE_SHADOW : Song.mCoverLoadMode & ~(Song.COVER_MODE_SHADOW);
|
||||
CoverCache.mCoverLoadMode = settings.getBoolean(PrefKeys.COVERLOADER_ANDROID, true) ? CoverCache.mCoverLoadMode | CoverCache.COVER_MODE_ANDROID : CoverCache.mCoverLoadMode & ~(CoverCache.COVER_MODE_ANDROID);
|
||||
CoverCache.mCoverLoadMode = settings.getBoolean(PrefKeys.COVERLOADER_VANILLA, true) ? CoverCache.mCoverLoadMode | CoverCache.COVER_MODE_VANILLA : CoverCache.mCoverLoadMode & ~(CoverCache.COVER_MODE_VANILLA);
|
||||
CoverCache.mCoverLoadMode = settings.getBoolean(PrefKeys.COVERLOADER_SHADOW , true) ? CoverCache.mCoverLoadMode | CoverCache.COVER_MODE_SHADOW : CoverCache.mCoverLoadMode & ~(CoverCache.COVER_MODE_SHADOW);
|
||||
|
||||
mHeadsetOnly = settings.getBoolean(PrefKeys.HEADSET_ONLY, false);
|
||||
mCycleContinuousShuffling = settings.getBoolean(PrefKeys.CYCLE_CONTINUOUS_SHUFFLING, false);
|
||||
@ -499,14 +499,14 @@ public final class PlaybackService extends Service
|
||||
play();
|
||||
}
|
||||
} else if (ACTION_TOGGLE_PLAYBACK_DELAYED.equals(action)) {
|
||||
if (mHandler.hasMessages(CALL_GO, Integer.valueOf(0))) {
|
||||
mHandler.removeMessages(CALL_GO, Integer.valueOf(0));
|
||||
if (mHandler.hasMessages(MSG_CALL_GO, Integer.valueOf(0))) {
|
||||
mHandler.removeMessages(MSG_CALL_GO, Integer.valueOf(0));
|
||||
Intent launch = new Intent(this, LibraryActivity.class);
|
||||
launch.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
launch.setAction(Intent.ACTION_MAIN);
|
||||
startActivity(launch);
|
||||
} else {
|
||||
mHandler.sendMessageDelayed(mHandler.obtainMessage(CALL_GO, 0, 0, Integer.valueOf(0)), 400);
|
||||
mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_CALL_GO, 0, 0, Integer.valueOf(0)), 400);
|
||||
}
|
||||
} else if (ACTION_NEXT_SONG.equals(action)) {
|
||||
setCurrentSong(1);
|
||||
@ -515,14 +515,14 @@ public final class PlaybackService extends Service
|
||||
setCurrentSong(1);
|
||||
play();
|
||||
} else if (ACTION_NEXT_SONG_DELAYED.equals(action)) {
|
||||
if (mHandler.hasMessages(CALL_GO, Integer.valueOf(1))) {
|
||||
mHandler.removeMessages(CALL_GO, Integer.valueOf(1));
|
||||
if (mHandler.hasMessages(MSG_CALL_GO, Integer.valueOf(1))) {
|
||||
mHandler.removeMessages(MSG_CALL_GO, Integer.valueOf(1));
|
||||
Intent launch = new Intent(this, LibraryActivity.class);
|
||||
launch.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
launch.setAction(Intent.ACTION_MAIN);
|
||||
startActivity(launch);
|
||||
} else {
|
||||
mHandler.sendMessageDelayed(mHandler.obtainMessage(CALL_GO, 1, 0, Integer.valueOf(1)), 400);
|
||||
mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_CALL_GO, 1, 0, Integer.valueOf(1)), 400);
|
||||
}
|
||||
} else if (ACTION_PREVIOUS_SONG.equals(action)) {
|
||||
setCurrentSong(-1);
|
||||
@ -808,14 +808,14 @@ public final class PlaybackService extends Service
|
||||
mIdleTimeout = settings.getBoolean(PrefKeys.USE_IDLE_TIMEOUT, false) ? settings.getInt(PrefKeys.IDLE_TIMEOUT, 3600) : 0;
|
||||
userActionTriggered();
|
||||
} else if (PrefKeys.COVERLOADER_ANDROID.equals(key)) {
|
||||
Song.mCoverLoadMode = settings.getBoolean(PrefKeys.COVERLOADER_ANDROID, true) ? Song.mCoverLoadMode | Song.COVER_MODE_ANDROID : Song.mCoverLoadMode & ~(Song.COVER_MODE_ANDROID);
|
||||
Song.mFlushCoverCache = true;
|
||||
CoverCache.mCoverLoadMode = settings.getBoolean(PrefKeys.COVERLOADER_ANDROID, true) ? CoverCache.mCoverLoadMode | CoverCache.COVER_MODE_ANDROID : CoverCache.mCoverLoadMode & ~(CoverCache.COVER_MODE_ANDROID);
|
||||
CoverCache.evictAll();
|
||||
} else if (PrefKeys.COVERLOADER_VANILLA.equals(key)) {
|
||||
Song.mCoverLoadMode = settings.getBoolean(PrefKeys.COVERLOADER_VANILLA, true) ? Song.mCoverLoadMode | Song.COVER_MODE_VANILLA : Song.mCoverLoadMode & ~(Song.COVER_MODE_VANILLA);
|
||||
Song.mFlushCoverCache = true;
|
||||
CoverCache.mCoverLoadMode = settings.getBoolean(PrefKeys.COVERLOADER_VANILLA, true) ? CoverCache.mCoverLoadMode | CoverCache.COVER_MODE_VANILLA : CoverCache.mCoverLoadMode & ~(CoverCache.COVER_MODE_VANILLA);
|
||||
CoverCache.evictAll();
|
||||
} else if (PrefKeys.COVERLOADER_SHADOW.equals(key)) {
|
||||
Song.mCoverLoadMode = settings.getBoolean(PrefKeys.COVERLOADER_SHADOW, true) ? Song.mCoverLoadMode | Song.COVER_MODE_SHADOW : Song.mCoverLoadMode & ~(Song.COVER_MODE_SHADOW);
|
||||
Song.mFlushCoverCache = true;
|
||||
CoverCache.mCoverLoadMode = settings.getBoolean(PrefKeys.COVERLOADER_SHADOW, true) ? CoverCache.mCoverLoadMode | CoverCache.COVER_MODE_SHADOW : CoverCache.mCoverLoadMode & ~(CoverCache.COVER_MODE_SHADOW);
|
||||
CoverCache.evictAll();
|
||||
} else if (PrefKeys.HEADSET_ONLY.equals(key)) {
|
||||
mHeadsetOnly = settings.getBoolean(key, false);
|
||||
if (mHeadsetOnly && isSpeakerOn())
|
||||
@ -912,8 +912,8 @@ public final class PlaybackService extends Service
|
||||
mState = state;
|
||||
|
||||
if (state != oldState) {
|
||||
mHandler.sendMessage(mHandler.obtainMessage(PROCESS_STATE, oldState, state));
|
||||
mHandler.sendMessage(mHandler.obtainMessage(BROADCAST_CHANGE, state, 0));
|
||||
mHandler.sendMessage(mHandler.obtainMessage(MSG_PROCESS_STATE, oldState, state));
|
||||
mHandler.sendMessage(mHandler.obtainMessage(MSG_BROADCAST_CHANGE, state, 0));
|
||||
}
|
||||
|
||||
return state;
|
||||
@ -940,7 +940,7 @@ public final class PlaybackService extends Service
|
||||
|
||||
mAudioManager.requestAudioFocus(this, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
|
||||
|
||||
mHandler.removeMessages(ENTER_SLEEP_STATE);
|
||||
mHandler.removeMessages(MSG_ENTER_SLEEP_STATE);
|
||||
try {
|
||||
if (mWakeLock != null && mWakeLock.isHeld() == false)
|
||||
mWakeLock.acquire();
|
||||
@ -961,7 +961,7 @@ public final class PlaybackService extends Service
|
||||
// Delay entering deep sleep. This allows the headset
|
||||
// button to continue to function for a short period after
|
||||
// pausing and keeps the AudioFX session open
|
||||
mHandler.sendEmptyMessageDelayed(ENTER_SLEEP_STATE, SLEEP_STATE_DELAY);
|
||||
mHandler.sendEmptyMessageDelayed(MSG_ENTER_SLEEP_STATE, SLEEP_STATE_DELAY);
|
||||
}
|
||||
|
||||
setupSensor();
|
||||
@ -1228,11 +1228,11 @@ public final class PlaybackService extends Service
|
||||
}
|
||||
}
|
||||
|
||||
mHandler.removeMessages(PROCESS_SONG);
|
||||
mHandler.removeMessages(MSG_PROCESS_SONG);
|
||||
|
||||
mMediaPlayerInitialized = false;
|
||||
mHandler.sendMessage(mHandler.obtainMessage(PROCESS_SONG, song));
|
||||
mHandler.sendMessage(mHandler.obtainMessage(BROADCAST_CHANGE, -1, 0, song));
|
||||
mHandler.sendMessage(mHandler.obtainMessage(MSG_PROCESS_SONG, song));
|
||||
mHandler.sendMessage(mHandler.obtainMessage(MSG_BROADCAST_CHANGE, -1, 0, song));
|
||||
return song;
|
||||
}
|
||||
|
||||
@ -1261,8 +1261,8 @@ public final class PlaybackService extends Service
|
||||
|
||||
mMediaPlayerInitialized = true;
|
||||
// Cancel any pending gapless updates and re-send them
|
||||
mHandler.removeMessages(GAPLESS_UPDATE);
|
||||
mHandler.sendEmptyMessage(GAPLESS_UPDATE);
|
||||
mHandler.removeMessages(MSG_GAPLESS_UPDATE);
|
||||
mHandler.sendEmptyMessage(MSG_GAPLESS_UPDATE);
|
||||
|
||||
if (mPendingSeek != 0 && mPendingSeekSong == song.id) {
|
||||
mMediaPlayer.seekTo(mPendingSeek);
|
||||
@ -1287,7 +1287,7 @@ public final class PlaybackService extends Service
|
||||
* This will stop after skipping 10 songs to avoid endless loops (queue full of broken stuff */
|
||||
if(mTimeline.isEndOfQueue() == false && getSong(1) != null && (playing || (mSkipBroken > 0 && mSkipBroken < 10))) {
|
||||
mSkipBroken++;
|
||||
mHandler.sendMessageDelayed(mHandler.obtainMessage(SKIP_BROKEN_SONG, getTimelinePosition(), 0), 1000);
|
||||
mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_SKIP_BROKEN_SONG, getTimelinePosition(), 0), 1000);
|
||||
}
|
||||
|
||||
}
|
||||
@ -1303,7 +1303,8 @@ public final class PlaybackService extends Service
|
||||
|
||||
// Count this song as played
|
||||
Song song = mTimeline.getSong(0);
|
||||
mPlayCounts.countSong(song);
|
||||
mHandler.sendMessage(mHandler.obtainMessage(MSG_UPDATE_PLAYCOUNTS, song));
|
||||
|
||||
|
||||
if (finishAction(mState) == SongTimeline.FINISH_REPEAT_CURRENT) {
|
||||
setCurrentSong(0);
|
||||
@ -1378,84 +1379,85 @@ public final class PlaybackService extends Service
|
||||
/**
|
||||
* Releases mWakeLock and closes any open AudioFx sessions
|
||||
*/
|
||||
private static final int ENTER_SLEEP_STATE = 1;
|
||||
private static final int MSG_ENTER_SLEEP_STATE = 1;
|
||||
/**
|
||||
* Run the given query and add the results to the timeline.
|
||||
*
|
||||
* obj is the QueryTask. arg1 is the add mode (one of SongTimeline.MODE_*)
|
||||
*/
|
||||
private static final int QUERY = 2;
|
||||
private static final int MSG_QUERY = 2;
|
||||
/**
|
||||
* This message is sent with a delay specified by a user preference. After
|
||||
* this delay, assuming no new IDLE_TIMEOUT messages cancel it, playback
|
||||
* will be stopped.
|
||||
*/
|
||||
private static final int IDLE_TIMEOUT = 4;
|
||||
private static final int MSG_IDLE_TIMEOUT = 4;
|
||||
/**
|
||||
* Decrease the volume gradually over five seconds, pausing when 0 is
|
||||
* reached.
|
||||
*
|
||||
* arg1 should be the progress in the fade as a percentage, 1-100.
|
||||
*/
|
||||
private static final int FADE_OUT = 7;
|
||||
private static final int MSG_FADE_OUT = 7;
|
||||
/**
|
||||
* If arg1 is 0, calls {@link PlaybackService#playPause()}.
|
||||
* Otherwise, calls {@link PlaybackService#setCurrentSong(int)} with arg1.
|
||||
*/
|
||||
private static final int CALL_GO = 8;
|
||||
private static final int BROADCAST_CHANGE = 10;
|
||||
private static final int SAVE_STATE = 12;
|
||||
private static final int PROCESS_SONG = 13;
|
||||
private static final int PROCESS_STATE = 14;
|
||||
private static final int SKIP_BROKEN_SONG = 15;
|
||||
private static final int GAPLESS_UPDATE = 16;
|
||||
private static final int MSG_CALL_GO = 8;
|
||||
private static final int MSG_BROADCAST_CHANGE = 10;
|
||||
private static final int MSG_SAVE_STATE = 12;
|
||||
private static final int MSG_PROCESS_SONG = 13;
|
||||
private static final int MSG_PROCESS_STATE = 14;
|
||||
private static final int MSG_SKIP_BROKEN_SONG = 15;
|
||||
private static final int MSG_GAPLESS_UPDATE = 16;
|
||||
private static final int MSG_UPDATE_PLAYCOUNTS = 17;
|
||||
|
||||
@Override
|
||||
public boolean handleMessage(Message message)
|
||||
{
|
||||
switch (message.what) {
|
||||
case CALL_GO:
|
||||
case MSG_CALL_GO:
|
||||
if (message.arg1 == 0)
|
||||
playPause();
|
||||
else
|
||||
setCurrentSong(message.arg1);
|
||||
break;
|
||||
case SAVE_STATE:
|
||||
case MSG_SAVE_STATE:
|
||||
// For unexpected terminations: crashes, task killers, etc.
|
||||
// In most cases onDestroy will handle this
|
||||
saveState(0);
|
||||
break;
|
||||
case PROCESS_SONG:
|
||||
case MSG_PROCESS_SONG:
|
||||
processSong((Song)message.obj);
|
||||
break;
|
||||
case QUERY:
|
||||
case MSG_QUERY:
|
||||
runQuery((QueryTask)message.obj);
|
||||
break;
|
||||
case IDLE_TIMEOUT:
|
||||
case MSG_IDLE_TIMEOUT:
|
||||
if ((mState & FLAG_PLAYING) != 0) {
|
||||
mHandler.sendMessage(mHandler.obtainMessage(FADE_OUT, 0));
|
||||
mHandler.sendMessage(mHandler.obtainMessage(MSG_FADE_OUT, 0));
|
||||
}
|
||||
break;
|
||||
case FADE_OUT:
|
||||
case MSG_FADE_OUT:
|
||||
if (mFadeOut <= 0.0f) {
|
||||
mIdleStart = SystemClock.elapsedRealtime();
|
||||
unsetFlag(FLAG_PLAYING);
|
||||
} else {
|
||||
mFadeOut -= 0.01f;
|
||||
mHandler.sendMessageDelayed(mHandler.obtainMessage(FADE_OUT, 0), 50);
|
||||
mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_FADE_OUT, 0), 50);
|
||||
}
|
||||
refreshReplayGainValues(); /* Updates the volume using the new mFadeOut value */
|
||||
break;
|
||||
case PROCESS_STATE:
|
||||
case MSG_PROCESS_STATE:
|
||||
processNewState(message.arg1, message.arg2);
|
||||
break;
|
||||
case BROADCAST_CHANGE:
|
||||
case MSG_BROADCAST_CHANGE:
|
||||
broadcastChange(message.arg1, (Song)message.obj, message.getWhen());
|
||||
break;
|
||||
case ENTER_SLEEP_STATE:
|
||||
case MSG_ENTER_SLEEP_STATE:
|
||||
enterSleepState();
|
||||
break;
|
||||
case SKIP_BROKEN_SONG:
|
||||
case MSG_SKIP_BROKEN_SONG:
|
||||
/* Advance to next song if the user didn't already change.
|
||||
* But we are restoring the Playing state in ANY case as we are most
|
||||
* likely still stopped due to the error
|
||||
@ -1467,11 +1469,15 @@ public final class PlaybackService extends Service
|
||||
// Optimistically claim to have recovered from this error
|
||||
mErrorMessage = null;
|
||||
unsetFlag(FLAG_ERROR);
|
||||
mHandler.sendMessage(mHandler.obtainMessage(CALL_GO, 0, 0));
|
||||
mHandler.sendMessage(mHandler.obtainMessage(MSG_CALL_GO, 0, 0));
|
||||
break;
|
||||
case GAPLESS_UPDATE:
|
||||
case MSG_GAPLESS_UPDATE:
|
||||
triggerGaplessUpdate();
|
||||
break;
|
||||
case MSG_UPDATE_PLAYCOUNTS:
|
||||
Song song = (Song)message.obj;
|
||||
mPlayCounts.countSong(song);
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@ -1604,10 +1610,10 @@ public final class PlaybackService extends Service
|
||||
*/
|
||||
public void userActionTriggered()
|
||||
{
|
||||
mHandler.removeMessages(FADE_OUT);
|
||||
mHandler.removeMessages(IDLE_TIMEOUT);
|
||||
mHandler.removeMessages(MSG_FADE_OUT);
|
||||
mHandler.removeMessages(MSG_IDLE_TIMEOUT);
|
||||
if (mIdleTimeout != 0)
|
||||
mHandler.sendEmptyMessageDelayed(IDLE_TIMEOUT, mIdleTimeout * 1000);
|
||||
mHandler.sendEmptyMessageDelayed(MSG_IDLE_TIMEOUT, mIdleTimeout * 1000);
|
||||
|
||||
if (mFadeOut != 1.0f) {
|
||||
mFadeOut = 1.0f;
|
||||
@ -1662,7 +1668,7 @@ public final class PlaybackService extends Service
|
||||
*/
|
||||
public void addSongs(QueryTask query)
|
||||
{
|
||||
mHandler.sendMessage(mHandler.obtainMessage(QUERY, query));
|
||||
mHandler.sendMessage(mHandler.obtainMessage(MSG_QUERY, query));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1731,14 +1737,14 @@ public final class PlaybackService extends Service
|
||||
@Override
|
||||
public void timelineChanged()
|
||||
{
|
||||
mHandler.removeMessages(SAVE_STATE);
|
||||
mHandler.sendEmptyMessageDelayed(SAVE_STATE, SAVE_STATE_DELAY);
|
||||
mHandler.removeMessages(MSG_SAVE_STATE);
|
||||
mHandler.sendEmptyMessageDelayed(MSG_SAVE_STATE, SAVE_STATE_DELAY);
|
||||
|
||||
// Trigger a gappless update for the new timeline
|
||||
// This might get canceled if setCurrentSong() also fired a call
|
||||
// to processSong();
|
||||
mHandler.removeMessages(GAPLESS_UPDATE);
|
||||
mHandler.sendEmptyMessageDelayed(GAPLESS_UPDATE, 100);
|
||||
mHandler.removeMessages(MSG_GAPLESS_UPDATE);
|
||||
mHandler.sendEmptyMessageDelayed(MSG_GAPLESS_UPDATE, 100);
|
||||
|
||||
ArrayList<PlaybackActivity> list = sActivities;
|
||||
for (int i = list.size(); --i != -1; )
|
||||
|
@ -46,6 +46,8 @@ public class PlaylistActivity extends Activity
|
||||
implements View.OnClickListener
|
||||
, AbsListView.OnItemClickListener
|
||||
, DialogInterface.OnClickListener
|
||||
, DragSortListView.DropListener
|
||||
, DragSortListView.RemoveListener
|
||||
{
|
||||
/**
|
||||
* The SongTimeline play mode corresponding to each
|
||||
@ -102,7 +104,8 @@ public class PlaylistActivity extends Activity
|
||||
DragSortListView view = (DragSortListView)findViewById(R.id.list);
|
||||
view.setOnItemClickListener(this);
|
||||
view.setOnCreateContextMenuListener(this);
|
||||
view.setDropListener(onDrop);
|
||||
view.setDropListener(this);
|
||||
view.setRemoveListener(this);
|
||||
mListView = view;
|
||||
|
||||
View header = LayoutInflater.from(this).inflate(R.layout.playlist_buttons, null);
|
||||
@ -287,12 +290,18 @@ public class PlaylistActivity extends Activity
|
||||
* @param from the item index that was dragged
|
||||
* @param to the index where the item was dropped
|
||||
*/
|
||||
private DragSortListView.DropListener onDrop =
|
||||
new DragSortListView.DropListener() {
|
||||
@Override
|
||||
public void drop(int from, int to) {
|
||||
mAdapter.moveItem(from, to);
|
||||
}
|
||||
};
|
||||
@Override
|
||||
public void drop(int from, int to) {
|
||||
mAdapter.moveItem(from, to);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fired from adapter listview if user fling-removed an item
|
||||
* @param position The position of the removed item
|
||||
*/
|
||||
@Override
|
||||
public void remove(int position) {
|
||||
mAdapter.removeItem(position);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Copyright (C) 2011 Christopher Eby <kreed@kreed.org>
|
||||
* Copyright (C) 2015 Adrian Ulrich <adrian@blinkenlights.ch>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
@ -24,6 +25,7 @@ package ch.blinkenlights.android.vanilla;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.ContentUris;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
@ -38,7 +40,6 @@ import android.widget.CursorAdapter;
|
||||
import android.widget.TextView;
|
||||
import android.provider.MediaStore.Audio.Playlists.Members;
|
||||
|
||||
|
||||
/**
|
||||
* CursorAdapter backed by MediaStore playlists.
|
||||
*/
|
||||
@ -48,6 +49,7 @@ public class PlaylistAdapter extends CursorAdapter implements Handler.Callback {
|
||||
MediaStore.Audio.Playlists.Members.TITLE,
|
||||
MediaStore.Audio.Playlists.Members.ARTIST,
|
||||
MediaStore.Audio.Playlists.Members.AUDIO_ID,
|
||||
MediaStore.Audio.Playlists.Members.ALBUM_ID,
|
||||
MediaStore.Audio.Playlists.Members.PLAY_ORDER,
|
||||
};
|
||||
|
||||
@ -106,10 +108,16 @@ public class PlaylistAdapter extends CursorAdapter implements Handler.Callback {
|
||||
public void bindView(View view, Context context, Cursor cursor)
|
||||
{
|
||||
DraggableRow dview = (DraggableRow)view;
|
||||
dview.setupLayout(DraggableRow.LAYOUT_COVERVIEW);
|
||||
dview.showDragger(mEditable);
|
||||
|
||||
TextView textView = dview.getTextView();
|
||||
textView.setText(cursor.getString(1));
|
||||
textView.setTag(cursor.getLong(3));
|
||||
|
||||
LazyCoverView cover = dview.getCoverView();
|
||||
cover.setup(mWorkerHandler.getLooper());
|
||||
cover.setCover(MediaUtils.TYPE_ALBUM, cursor.getLong(4));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -166,15 +174,6 @@ public class PlaylistAdapter extends CursorAdapter implements Handler.Callback {
|
||||
* @param from original position of item
|
||||
* @param to destination of item
|
||||
**/
|
||||
public void moveItem(int from, int to) {
|
||||
if (from == to)
|
||||
return;
|
||||
android.provider.MediaStore.Audio.Playlists.Members.moveItem(mContext.getContentResolver(), mPlaylistId , from, to);
|
||||
mUiHandler.sendEmptyMessage(MSG_RUN_QUERY);
|
||||
}
|
||||
|
||||
|
||||
/* fixme: does the move-after-delete bug still exist in 4.x?
|
||||
public void moveItem(int from, int to)
|
||||
{
|
||||
if (from == to)
|
||||
@ -202,11 +201,11 @@ public class PlaylistAdapter extends CursorAdapter implements Handler.Callback {
|
||||
order = 0;
|
||||
} else {
|
||||
cursor.moveToPosition(start - 1);
|
||||
order = cursor.getLong(4) + 1;
|
||||
order = cursor.getLong(5) + 1;
|
||||
}
|
||||
|
||||
cursor.moveToPosition(end);
|
||||
long endOrder = cursor.getLong(4);
|
||||
long endOrder = cursor.getLong(5);
|
||||
|
||||
// clear the rows we are replacing
|
||||
String[] args = new String[] { Long.toString(order), Long.toString(endOrder) };
|
||||
@ -227,7 +226,7 @@ public class PlaylistAdapter extends CursorAdapter implements Handler.Callback {
|
||||
|
||||
changeCursor(runQuery(resolver));
|
||||
}
|
||||
*/
|
||||
|
||||
public void removeItem(int position)
|
||||
{
|
||||
ContentResolver resolver = mContext.getContentResolver();
|
||||
|
@ -96,6 +96,11 @@ public class RemoteControl {
|
||||
// Create a copy of the cover art, since RemoteControlClient likes
|
||||
// to recycle what we give it.
|
||||
bitmap = bitmap.copy(Bitmap.Config.RGB_565, false);
|
||||
} else {
|
||||
// Some lockscreen implementations fail to clear the cover artwork
|
||||
// if we send a null bitmap. We are creating a 16x16 transparent
|
||||
// bitmap to work around this limitation.
|
||||
bitmap = Bitmap.createBitmap(16, 16, Bitmap.Config.ARGB_8888);
|
||||
}
|
||||
editor.putBitmap(RemoteControlClient.MetadataEditor.BITMAP_KEY_ARTWORK, bitmap);
|
||||
}
|
||||
|
@ -81,11 +81,11 @@ public class SeekBarPreference extends DialogPreference implements SeekBar.OnSee
|
||||
*/
|
||||
private String getSummary(int value)
|
||||
{
|
||||
if ("shake_threshold".equals(getKey())) {
|
||||
if (PrefKeys.SHAKE_THRESHOLD.equals(getKey())) {
|
||||
return String.valueOf(value / 10.0f);
|
||||
} else if("replaygain_bump".equals(getKey())) {
|
||||
} else if(PrefKeys.REPLAYGAIN_BUMP.equals(getKey())) {
|
||||
return String.format("%+.1fdB", 2*(value-75)/10f);
|
||||
} else if("replaygain_untagged_debump".equals(getKey())) {
|
||||
} else if(PrefKeys.REPLAYGAIN_UNTAGGED_DEBUMP.equals(getKey())) {
|
||||
String summary = (String)mContext.getResources().getText(R.string.replaygain_untagged_debump_summary);
|
||||
return String.format("%s %.1fdB", summary, (value-150)/10f);
|
||||
} else {
|
||||
@ -102,7 +102,10 @@ public class SeekBarPreference extends DialogPreference implements SeekBar.OnSee
|
||||
mValueText.setText(getSummary(mValue));
|
||||
|
||||
SeekBar seekBar = (SeekBar)view.findViewById(R.id.seek_bar);
|
||||
seekBar.setMax(150);
|
||||
|
||||
int maxValue = (PrefKeys.SHAKE_THRESHOLD.equals(getKey()) ? 300 : 150);
|
||||
|
||||
seekBar.setMax(maxValue);
|
||||
seekBar.setProgress(mValue);
|
||||
seekBar.setOnSeekBarChangeListener(this);
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2013-2014 Adrian Ulrich <adrian@blinkenlights.ch>
|
||||
* Copyright (C) 2013-2015 Adrian Ulrich <adrian@blinkenlights.ch>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@ -22,6 +22,7 @@ import java.util.ArrayList;
|
||||
import android.app.Activity;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.Bundle;
|
||||
import android.os.Message;
|
||||
import android.view.View;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
@ -32,8 +33,10 @@ import android.widget.ListView;
|
||||
import com.mobeta.android.dslv.DragSortListView;
|
||||
|
||||
public class ShowQueueActivity extends PlaybackActivity
|
||||
implements DialogInterface.OnDismissListener
|
||||
{
|
||||
implements DialogInterface.OnDismissListener,
|
||||
DragSortListView.DropListener,
|
||||
DragSortListView.RemoveListener
|
||||
{
|
||||
private DragSortListView mListView;
|
||||
private ShowQueueAdapter listAdapter;
|
||||
private PlaybackService mService;
|
||||
@ -48,10 +51,10 @@ public class ShowQueueActivity extends PlaybackActivity
|
||||
|
||||
mService = PlaybackService.get(this);
|
||||
mListView = (DragSortListView) findViewById(R.id.list);
|
||||
listAdapter = new ShowQueueAdapter(this, R.layout.draggable_row);
|
||||
listAdapter = new ShowQueueAdapter(this, R.layout.draggable_row, mHandler.getLooper());
|
||||
mListView.setAdapter(listAdapter);
|
||||
mListView.setDropListener(onDrop);
|
||||
mListView.setRemoveListener(onRemove);
|
||||
mListView.setDropListener(this);
|
||||
mListView.setRemoveListener(this);
|
||||
|
||||
mListView.setOnItemClickListener(new OnItemClickListener() {
|
||||
@Override
|
||||
@ -120,38 +123,32 @@ public class ShowQueueActivity extends PlaybackActivity
|
||||
* @param from the item index that was dragged
|
||||
* @param to the index where the item was dropped
|
||||
*/
|
||||
private DragSortListView.DropListener onDrop =
|
||||
new DragSortListView.DropListener() {
|
||||
@Override
|
||||
public void drop(int from, int to) {
|
||||
if (from != to) {
|
||||
mService.moveSongPosition(from, to);
|
||||
}
|
||||
}
|
||||
};
|
||||
@Override
|
||||
public void drop(int from, int to) {
|
||||
if (from != to) {
|
||||
mService.moveSongPosition(from, to);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fired from adapter listview after user removed a song
|
||||
* @param which index to remove from queue
|
||||
*/
|
||||
private DragSortListView.RemoveListener onRemove =
|
||||
new DragSortListView.RemoveListener() {
|
||||
@Override
|
||||
public void remove(int which) {
|
||||
mService.removeSongPosition(which);
|
||||
}
|
||||
};
|
||||
@Override
|
||||
public void remove(int which) {
|
||||
mService.removeSongPosition(which);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fired if user dismisses the create-playlist dialog
|
||||
*
|
||||
* @param dialogInterface the dismissed interface dialog
|
||||
* Saves the current queue as a playlist
|
||||
*/
|
||||
protected static final int MSG_SAVE_QUEUE_AS_PLAYLIST = 30;
|
||||
|
||||
@Override
|
||||
public void onDismiss(DialogInterface dialogInterface) {
|
||||
NewPlaylistDialog dialog = (NewPlaylistDialog)dialogInterface;
|
||||
if (dialog.isAccepted()) {
|
||||
String playlistName = dialog.getText();
|
||||
public boolean handleMessage(Message message) {
|
||||
switch (message.what) {
|
||||
case MSG_SAVE_QUEUE_AS_PLAYLIST:
|
||||
String playlistName = (String)message.obj;
|
||||
long playlistId = Playlist.createPlaylist(getContentResolver(), playlistName);
|
||||
PlaylistTask playlistTask = new PlaylistTask(playlistId, playlistName);
|
||||
playlistTask.audioIds = new ArrayList<Long>();
|
||||
@ -163,7 +160,26 @@ public class ShowQueueActivity extends PlaybackActivity
|
||||
break;
|
||||
playlistTask.audioIds.add(song.id);
|
||||
}
|
||||
mHandler.sendMessage(mHandler.obtainMessage(MSG_ADD_TO_PLAYLIST, playlistTask));
|
||||
|
||||
addToPlaylist(playlistTask);
|
||||
break;
|
||||
default:
|
||||
return super.handleMessage(message);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fired if user dismisses the create-playlist dialog
|
||||
*
|
||||
* @param dialogInterface the dismissed interface dialog
|
||||
*/
|
||||
@Override
|
||||
public void onDismiss(DialogInterface dialogInterface) {
|
||||
NewPlaylistDialog dialog = (NewPlaylistDialog)dialogInterface;
|
||||
if (dialog.isAccepted()) {
|
||||
String playlistName = dialog.getText();
|
||||
mHandler.sendMessage(mHandler.obtainMessage(MSG_SAVE_QUEUE_AS_PLAYLIST, playlistName));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -19,6 +19,7 @@ package ch.blinkenlights.android.vanilla;
|
||||
|
||||
import android.content.Context;
|
||||
import android.app.Activity;
|
||||
import android.os.Looper;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ArrayAdapter;
|
||||
@ -35,14 +36,16 @@ public class ShowQueueAdapter
|
||||
extends ArrayAdapter<Song>
|
||||
{
|
||||
|
||||
int mResource;
|
||||
int mHighlightRow;
|
||||
Context mContext;
|
||||
private int mResource;
|
||||
private int mHighlightRow;
|
||||
private Context mContext;
|
||||
private Looper mLooper;
|
||||
|
||||
public ShowQueueAdapter(Context context, int resource) {
|
||||
public ShowQueueAdapter(Context context, int resource, Looper looper) {
|
||||
super(context, resource);
|
||||
mResource = resource;
|
||||
mContext = context;
|
||||
mLooper = looper;
|
||||
mHighlightRow = -1;
|
||||
}
|
||||
|
||||
@ -64,6 +67,8 @@ public class ShowQueueAdapter
|
||||
} else {
|
||||
LayoutInflater inflater = ((Activity)mContext).getLayoutInflater();
|
||||
row = (DraggableRow)inflater.inflate(mResource, parent, false);
|
||||
row.setupLayout(DraggableRow.LAYOUT_COVERVIEW);
|
||||
row.getCoverView().setup(mLooper);
|
||||
}
|
||||
|
||||
Song song = getItem(position);
|
||||
@ -74,6 +79,7 @@ public class ShowQueueAdapter
|
||||
sb.append(song.album+", "+song.artist);
|
||||
sb.setSpan(new ForegroundColorSpan(Color.GRAY), song.title.length() + 1, sb.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
row.getTextView().setText(sb);
|
||||
row.getCoverView().setCover(MediaUtils.TYPE_ALBUM, song.albumId);
|
||||
}
|
||||
|
||||
row.highlightRow(position == mHighlightRow);
|
||||
|
@ -22,20 +22,10 @@
|
||||
|
||||
package ch.blinkenlights.android.vanilla;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.net.Uri;
|
||||
import android.provider.MediaStore;
|
||||
import android.util.LruCache;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.InputStream;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
/**
|
||||
* Represents a Song backed by the MediaStore. Includes basic metadata and
|
||||
* utilities to retrieve songs from the MediaStore.
|
||||
@ -54,22 +44,6 @@ public class Song implements Comparable<Song> {
|
||||
* The number of flags.
|
||||
*/
|
||||
public static final int FLAG_COUNT = 2;
|
||||
/**
|
||||
* Use all cover providers to load cover art
|
||||
*/
|
||||
public static final int COVER_MODE_ALL = 0xF;
|
||||
/**
|
||||
* Use androids builtin cover mechanism to load covers
|
||||
*/
|
||||
public static final int COVER_MODE_ANDROID = 0x1;
|
||||
/**
|
||||
* Use vanilla musics cover load mechanism
|
||||
*/
|
||||
public static final int COVER_MODE_VANILLA = 0x2;
|
||||
/**
|
||||
* Use vanilla musics SHADOW cover load mechanism
|
||||
*/
|
||||
public static final int COVER_MODE_SHADOW = 0x4;
|
||||
|
||||
|
||||
public static final String[] EMPTY_PROJECTION = {
|
||||
@ -104,158 +78,12 @@ public class Song implements Comparable<Song> {
|
||||
MediaStore.Audio.Playlists.Members.TRACK,
|
||||
};
|
||||
|
||||
private class LruCacheKey {
|
||||
long id;
|
||||
long artistId;
|
||||
long albumId;
|
||||
String path;
|
||||
|
||||
public LruCacheKey(long id, long artistId, long albumId, String path) {
|
||||
this.id = id;
|
||||
this.artistId = artistId;
|
||||
this.albumId = albumId;
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj instanceof LruCacheKey && this.albumId == ((LruCacheKey)obj).albumId && this.artistId == ((LruCacheKey)obj).artistId) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return (int)( 0xFFFFFF & (this.artistId + this.albumId) );
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "LruCacheKey<"+this.id+"> = "+this.path;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* A cache of 6 MiB of covers.
|
||||
*/
|
||||
private static class CoverCache extends LruCache<LruCacheKey, Bitmap> {
|
||||
private final Context mContext;
|
||||
|
||||
// Possible coverart names if we are going to load the cover on our own
|
||||
private static String[] coverNames = { "cover.jpg", "cover.png", "album.jpg", "album.png", "artwork.jpg", "artwork.png", "art.jpg", "art.png" };
|
||||
|
||||
public CoverCache(Context context)
|
||||
{
|
||||
super(6 * 1024 * 1024);
|
||||
mContext = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Bitmap create(LruCacheKey key)
|
||||
{
|
||||
try {
|
||||
InputStream inputStream = null;
|
||||
InputStream sampleInputStream = null; // same as inputStream but used for getSampleSize
|
||||
|
||||
if ((mCoverLoadMode & COVER_MODE_VANILLA) != 0) {
|
||||
String basePath = (new File(key.path)).getParentFile().getAbsolutePath(); // ../ of the currently playing file
|
||||
for (String coverFile: coverNames) {
|
||||
File guessedFile = new File( basePath + "/" + coverFile);
|
||||
if (guessedFile.exists() && !guessedFile.isDirectory()) {
|
||||
inputStream = new FileInputStream(guessedFile);
|
||||
sampleInputStream = new FileInputStream(guessedFile);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (inputStream == null && (mCoverLoadMode & COVER_MODE_SHADOW) != 0) {
|
||||
String[] projection = new String [] { MediaStore.Audio.Media.ARTIST, MediaStore.Audio.Media.ALBUM };
|
||||
QueryTask query = MediaUtils.buildQuery(MediaUtils.TYPE_SONG, key.id, projection, null);
|
||||
Cursor cursor = query.runQuery(mContext.getContentResolver());
|
||||
if (cursor != null) {
|
||||
if (cursor.getCount() > 0) {
|
||||
cursor.moveToNext();
|
||||
String thisArtist = cursor.getString(0);
|
||||
String thisAlbum = cursor.getString(1);
|
||||
String shadowPath = "/sdcard/Music/.vanilla/"+(thisArtist.replaceAll("/", "_"))+"/"+(thisAlbum.replaceAll("/", "_"))+".jpg";
|
||||
|
||||
File guessedFile = new File(shadowPath);
|
||||
if (guessedFile.exists() && !guessedFile.isDirectory()) {
|
||||
inputStream = new FileInputStream(guessedFile);
|
||||
sampleInputStream = new FileInputStream(guessedFile);
|
||||
}
|
||||
}
|
||||
cursor.close();
|
||||
}
|
||||
}
|
||||
|
||||
if (inputStream == null && (mCoverLoadMode & COVER_MODE_ANDROID) != 0 && key.id >= 0) {
|
||||
Uri uri = Uri.parse("content://media/external/audio/media/" + key.id + "/albumart");
|
||||
ContentResolver res = mContext.getContentResolver();
|
||||
inputStream = res.openInputStream(uri);
|
||||
sampleInputStream = res.openInputStream(uri);
|
||||
}
|
||||
|
||||
if (inputStream != null) {
|
||||
BitmapFactory.Options bopts = new BitmapFactory.Options();
|
||||
bopts.inPreferredConfig = Bitmap.Config.RGB_565;
|
||||
bopts.inJustDecodeBounds = true;
|
||||
|
||||
final int inSampleSize = getSampleSize(sampleInputStream, bopts);
|
||||
|
||||
/* reuse bopts: we are now REALLY going to decode the image */
|
||||
bopts.inJustDecodeBounds = false;
|
||||
bopts.inSampleSize = inSampleSize;
|
||||
return BitmapFactory.decodeStream(inputStream, null, bopts);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// no cover art found
|
||||
Log.v("VanillaMusic", "Loading coverart for "+key+" failed with exception "+e);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Guess a good sampleSize value for given inputStream
|
||||
*/
|
||||
private static int getSampleSize(InputStream inputStream, BitmapFactory.Options bopts) {
|
||||
int sampleSize = 1; /* default sample size */
|
||||
long maxVal = 600*600; /* max number of pixels we are accepting */
|
||||
|
||||
BitmapFactory.decodeStream(inputStream, null, bopts);
|
||||
long hasPixels = bopts.outHeight * bopts.outWidth;
|
||||
if(hasPixels > maxVal) {
|
||||
sampleSize = Math.round((int)Math.sqrt((float) hasPixels / (float) maxVal));
|
||||
}
|
||||
return sampleSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int sizeOf(LruCacheKey key, Bitmap value)
|
||||
{
|
||||
return value.getRowBytes() * value.getHeight();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The cache instance.
|
||||
*/
|
||||
private static CoverCache sCoverCache = null;
|
||||
|
||||
/**
|
||||
* Bitmask on how we are going to load coverart
|
||||
*/
|
||||
public static int mCoverLoadMode = 0;
|
||||
|
||||
/**
|
||||
* We will evict our own cache if set to true
|
||||
*/
|
||||
public static boolean mFlushCoverCache = false;
|
||||
|
||||
/**
|
||||
* Id of this song in the MediaStore
|
||||
*/
|
||||
@ -367,19 +195,14 @@ public class Song implements Comparable<Song> {
|
||||
*/
|
||||
public Bitmap getCover(Context context)
|
||||
{
|
||||
if (mCoverLoadMode == 0 || id == -1 || (flags & FLAG_NO_COVER) != 0)
|
||||
if (CoverCache.mCoverLoadMode == 0 || id == -1 || (flags & FLAG_NO_COVER) != 0)
|
||||
return null;
|
||||
|
||||
if (sCoverCache == null)
|
||||
sCoverCache = new CoverCache(context.getApplicationContext());
|
||||
|
||||
if (mFlushCoverCache) {
|
||||
mFlushCoverCache = false;
|
||||
sCoverCache.evictAll();
|
||||
}
|
||||
|
||||
LruCacheKey key = new LruCacheKey(id, artistId, albumId, path);
|
||||
Bitmap cover = sCoverCache.get(key);
|
||||
CoverCache.CoverKey key = new CoverCache.CoverKey(MediaUtils.TYPE_ALBUM, this.albumId, CoverCache.SIZE_LARGE);
|
||||
Bitmap cover = sCoverCache.getCoverFromSong(key, this);
|
||||
|
||||
if (cover == null)
|
||||
flags |= FLAG_NO_COVER;
|
||||
|
@ -34,7 +34,11 @@ import com.mobeta.android.dslv.DragSortListView;
|
||||
/**
|
||||
* The preferences activity in which one can change application preferences.
|
||||
*/
|
||||
public class TabOrderActivity extends Activity implements View.OnClickListener, OnItemClickListener {
|
||||
public class TabOrderActivity extends Activity
|
||||
implements View.OnClickListener,
|
||||
OnItemClickListener,
|
||||
DragSortListView.DropListener
|
||||
{
|
||||
private TabOrderAdapter mAdapter;
|
||||
private DragSortListView mList;
|
||||
|
||||
@ -54,7 +58,7 @@ public class TabOrderActivity extends Activity implements View.OnClickListener,
|
||||
DragSortListView list = (DragSortListView)findViewById(R.id.list);
|
||||
list.setAdapter(mAdapter);
|
||||
list.setOnItemClickListener(this);
|
||||
list.setDropListener(onDrop);
|
||||
list.setDropListener(this);
|
||||
mList = list;
|
||||
load();
|
||||
|
||||
@ -161,28 +165,25 @@ public class TabOrderActivity extends Activity implements View.OnClickListener,
|
||||
* @param from the item index that was dragged
|
||||
* @param to the index where the item was dropped
|
||||
*/
|
||||
private DragSortListView.DropListener onDrop =
|
||||
new DragSortListView.DropListener() {
|
||||
@Override
|
||||
public void drop(int from, int to) {
|
||||
if (from == to)
|
||||
return;
|
||||
@Override
|
||||
public void drop(int from, int to) {
|
||||
if (from == to)
|
||||
return;
|
||||
|
||||
int[] ids = mAdapter.getTabIds();
|
||||
int tempId = ids[from];
|
||||
int[] ids = mAdapter.getTabIds();
|
||||
int tempId = ids[from];
|
||||
|
||||
if (from > to) {
|
||||
System.arraycopy(ids, to, ids, to + 1, from - to);
|
||||
} else {
|
||||
System.arraycopy(ids, from + 1, ids, from, to - from);
|
||||
}
|
||||
if (from > to) {
|
||||
System.arraycopy(ids, to, ids, to + 1, from - to);
|
||||
} else {
|
||||
System.arraycopy(ids, from + 1, ids, from, to - from);
|
||||
}
|
||||
|
||||
ids[to] = tempId;
|
||||
save();
|
||||
mAdapter.notifyDataSetChanged();
|
||||
// no need to update the copy in mAdapter: We worked on a reference
|
||||
}
|
||||
};
|
||||
ids[to] = tempId;
|
||||
save();
|
||||
mAdapter.notifyDataSetChanged();
|
||||
// no need to update the copy in mAdapter: We worked on a reference
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -90,11 +90,11 @@ public class TabOrderAdapter extends BaseAdapter {
|
||||
DraggableRow view;
|
||||
if (convert == null) {
|
||||
view = (DraggableRow)mInflater.inflate(R.layout.draggable_row, null);
|
||||
view.setupLayout(DraggableRow.LAYOUT_CHECKBOXES);
|
||||
} else {
|
||||
view = (DraggableRow)convert;
|
||||
}
|
||||
view.getTextView().setText(LibraryPagerAdapter.TITLES[mTabIds[position]]);
|
||||
view.showCheckBox(true);
|
||||
return view;
|
||||
}
|
||||
|
||||
|
32
src/ch/blinkenlights/android/vanilla/ViewHolder.java
Normal file
32
src/ch/blinkenlights/android/vanilla/ViewHolder.java
Normal file
@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Adrian Ulrich <adrian@blinkenlights.ch>
|
||||
*
|
||||
* This program 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.
|
||||
*
|
||||
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package ch.blinkenlights.android.vanilla;
|
||||
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
import android.widget.ImageView;
|
||||
|
||||
|
||||
public class ViewHolder {
|
||||
public long id;
|
||||
public String title;
|
||||
public TextView text;
|
||||
public View divider;
|
||||
public ImageView arrow;
|
||||
public LazyCoverView cover;
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user