Merge branch 'release/v0.9.6.0'

This commit is contained in:
Jan Christian Grünhage 2017-03-29 00:31:27 +02:00
commit f28e4cbd48
Signed by: jcgruenhage
GPG key ID: 321A67D9EE8BC3E1
23 changed files with 334 additions and 422 deletions

View file

@ -34,15 +34,31 @@ by [Michael Patricios (mpatric)](https://github.com/mpatric).
### Contributing
#### What can you contribute?
Implement features you want to see, or take a look at the issue tracker and fix broken things. Before making bigger changes, see that a contributor is supporting the inclusion, so that you can be sure it will be merged. (See Discuss Ideas below)
#### How to contribute:
If you want to contribute to this project, fork off of develop,
implement what you want to implement, and submit a pull request back into develop.
After testing it, if enough of the collaborators like it, we will merge it.
You might want to discuss your ideas with us first though:
### Discuss Ideas
#### Discuss Ideas
If you have any questions, the easiest way to get help is asking in the #playmusicexporter IRC channel on freenode. You can join that on webchat.freenode.net, or preferably, if you are not really an IRC guy anyway, try the corresponding matrix room [#playmusicexporter:jcg.re](https://matrix.to/#/#playmusicexporter:jcg.re). They are bridged together, so everything that happens in one of the rooms gets automatically transfered to the other one too.
### Supporting this project
If you want to support this project, the best way to do so is contributing source code to the project (see above), or help with illustrations, if that is something you're good at! (I am most certainly not good at creating icons and such things)
If you are unable to do that, but you still want to support this project, you could send me some bitcoins instead: 1NdzpDWPQ53xWT5fraGPZX5F9XrKiPBXjp
I will distribute bitcoins send to me to others helping on the project too, so that other people contributing also have a piece of the cake.
I might later set up [BitHub](https://github.com/WhisperSystems/BitHub) for this purpose later (if there are a lot of donations to distribute), but this project is a little small for that, and BitHub works based on the commit count only, which I would like to modify before setting that up.
### Copyright
Copyright (c) 2016 Jan Christian Grünhage. See LICENSE.txt for details.

View file

@ -42,5 +42,5 @@ android {
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.android.support:appcompat-v7:25.2.0'
compile 'com.android.support:appcompat-v7:25.3.1'
}

View file

@ -29,6 +29,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
@ -309,9 +310,7 @@ public class FileTools {
}
// Remove all blacklisted paths
for (String blacklistPath : mountPointBlacklist) {
storages.remove(blacklistPath);
}
storages.removeAll(Arrays.asList(mountPointBlacklist));
// Sort the list
Collections.sort(storages);

View file

@ -1,6 +1,6 @@
#Sun Jan 22 21:19:51 CET 2017
#Tue Mar 28 12:22:37 CEST 2017
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-3.4.1-all.zip

View file

@ -20,8 +20,6 @@
* THE SOFTWARE.
*/
apply plugin: 'com.android.application'
android {
@ -33,8 +31,8 @@ android {
minSdkVersion 21
targetSdkVersion 25
// TODO Change Version with releases
versionCode 109
versionName '0.9.5.2'
versionCode 110
versionName '0.9.6.0'
vectorDrawables.useSupportLibrary = true
jackOptions {
@ -58,9 +56,10 @@ dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile project(':framework')
compile project(':playmusiclib')
compile 'com.android.support:appcompat-v7:25.2.0'
compile 'com.android.support:support-v4:25.2.0'
compile 'com.android.support:design:25.2.0'
compile 'com.android.support:support-vector-drawable:25.2.0'
compile 'com.android.support:appcompat-v7:25.3.1'
compile 'com.android.support:support-v4:25.3.1'
compile 'com.android.support:design:25.3.1'
compile 'com.android.support:support-vector-drawable:25.3.1'
compile 'com.github.paolorotolo:appintro:4.1.0'
compile 'ly.count.android:sdk:16.12.2'
}

View file

@ -24,8 +24,9 @@
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-permission android:name="android.permission.WAKE_LOCK" />
<application
android:allowBackup="true"
@ -51,7 +52,7 @@
</activity>
<activity
android:name=".activities.Intro"
android:label="Play Music Exporter Intro"/>
android:label="Play Music Exporter Intro" />
<service android:name=".services.ExportService" />
<service android:name=".services.ExportAllService" />

View file

@ -4,7 +4,6 @@ import android.Manifest;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Color;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.Nullable;

View file

@ -43,9 +43,10 @@ import android.widget.EditText;
import android.widget.Toast;
import de.arcus.framework.logger.Logger;
import de.arcus.framework.crashhandler.CrashHandler;
import de.arcus.playmusiclib.exceptions.CouldNotOpenDatabaseException;
import de.arcus.playmusiclib.exceptions.NoSuperUserException;
import ly.count.android.sdk.Countly;
import ly.count.android.sdk.DeviceId;
import re.jcg.playmusicexporter.R;
import re.jcg.playmusicexporter.fragments.MusicTrackListFragment;
import re.jcg.playmusicexporter.fragments.MusicContainerListFragment;
@ -108,8 +109,9 @@ public class MusicContainerListActivity extends AppCompatActivity
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//Adds the crash handler to this class
CrashHandler.addCrashHandler(this);
Countly.sharedInstance().init(this, getString(R.string.countly_url), getString(R.string.countly_token), null, DeviceId.Type.OPEN_UDID);
Countly.sharedInstance().enableCrashReporting();
PlayMusicExporterPreferences.init(this);
if (!PlayMusicExporterPreferences.getSetupDone()) {
@ -195,11 +197,11 @@ public class MusicContainerListActivity extends AppCompatActivity
}
}
}
/**
* Loads the PlayMusicExporter lib and shows the list
*/
private void loadPlayMusicExporter()
{
private void loadPlayMusicExporter() {
// Gets the running instance
mPlayMusicManager = PlayMusicManager.getInstance();
@ -261,7 +263,7 @@ public class MusicContainerListActivity extends AppCompatActivity
MusicContainerListFragment musicTrackListFragment = (MusicContainerListFragment) getSupportFragmentManager()
.findFragmentById(R.id.fragment_main);
switch(mViewType) {
switch (mViewType) {
case Album:
// Load all albums to the list
AlbumDataSource dataSourceAlbum = new AlbumDataSource(mPlayMusicManager);
@ -331,24 +333,21 @@ public class MusicContainerListActivity extends AppCompatActivity
MenuItem itemRefreshLibrary = menu.findItem(R.id.action_refresh);
itemRefreshLibrary.setOnMenuItemClickListener(item ->
{
try
{
try {
mPlayMusicManager.reloadDatabase();
mPlayMusicManager = null;
loadPlayMusicExporter();
Toast.makeText( this, R.string.database_reloaded, Toast.LENGTH_SHORT).show();
}
catch (NoSuperUserException | CouldNotOpenDatabaseException e)
{
Toast.makeText( this, R.string.dialog_superuser_access_denied_title, Toast.LENGTH_SHORT).show();
Toast.makeText(this, R.string.database_reloaded, Toast.LENGTH_SHORT).show();
} catch (NoSuperUserException | CouldNotOpenDatabaseException e) {
Toast.makeText(this, R.string.dialog_superuser_access_denied_title, Toast.LENGTH_SHORT).show();
e.printStackTrace();
}
return true;
});
// Finds the search item and create the search view
MenuItem itemSearch = menu. findItem(R.id.action_search);
mSearchView = (SearchView)MenuItemCompat.getActionView(itemSearch);
MenuItem itemSearch = menu.findItem(R.id.action_search);
mSearchView = (SearchView) MenuItemCompat.getActionView(itemSearch);
if (mSearchView != null) {
// Sets the search listener
@ -376,4 +375,16 @@ public class MusicContainerListActivity extends AppCompatActivity
return false;
}
@Override
public void onStart() {
super.onStart();
Countly.sharedInstance().onStart(this);
}
@Override
public void onStop() {
Countly.sharedInstance().onStop();
super.onStop();
}
}

View file

@ -46,31 +46,28 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
* A preference value change listener that updates the preference's summary
* to reflect its new value.
*/
private static Preference.OnPreferenceChangeListener sBindPreferenceSummaryToValueListener = new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object value) {
String stringValue = value.toString();
private static Preference.OnPreferenceChangeListener sBindPreferenceSummaryToValueListener = (preference, value) -> {
String stringValue = value.toString();
if (preference instanceof ListPreference) {
// For list preferences, look up the correct display value in
// the preference's 'entries' list.
ListPreference listPreference = (ListPreference) preference;
int index = listPreference.findIndexOfValue(stringValue);
if (preference instanceof ListPreference) {
// For list preferences, look up the correct display value in
// the preference's 'entries' list.
ListPreference listPreference = (ListPreference) preference;
int index = listPreference.findIndexOfValue(stringValue);
// Set the summary to reflect the new value.
preference.setSummary(
index >= 0
? listPreference.getEntries()[index]
: null);
// Set the summary to reflect the new value.
preference.setSummary(
index >= 0
? listPreference.getEntries()[index]
: null);
} else {
// For all other preferences, set the summary to the value's
// simple string representation.
preference.setSummary(stringValue);
}
return true;
} else {
// For all other preferences, set the summary to the value's
// simple string representation.
preference.setSummary(stringValue);
}
return true;
};
/**
@ -127,15 +124,8 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public void onBuildHeaders(List<Header> target) {
loadHeadersFromResource(R.xml.pref_headers, target);
// Remove the Debug Fragment
if (!BuildConfig.DEBUG) {
for (int i = 0; i < target.size(); i++) {
if ("Debug".equals(target.get(i).title)) {
target.remove(i);
break;
}
}
if (BuildConfig.DEBUG) {
loadHeadersFromResource(R.xml.pref_debug_header, target);
}
}
@ -160,19 +150,13 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
setHasOptionsMenu(true);
findPreference("preference_alba_export_path").setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
startActivityForResult(new Intent("android.intent.action.OPEN_DOCUMENT_TREE"), REQUEST_CODE_OPEN_DOCUMENT_TREE_ALBA_PATH);
return true;
}
findPreference("preference_alba_export_path").setOnPreferenceClickListener(preference -> {
startActivityForResult(new Intent("android.intent.action.OPEN_DOCUMENT_TREE"), REQUEST_CODE_OPEN_DOCUMENT_TREE_ALBA_PATH);
return true;
});
findPreference("preference_groups_export_path").setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
startActivityForResult(new Intent("android.intent.action.OPEN_DOCUMENT_TREE"), REQUEST_CODE_OPEN_DOCUMENT_TREE_GROUPS_PATH);
return true;
}
findPreference("preference_groups_export_path").setOnPreferenceClickListener(preference -> {
startActivityForResult(new Intent("android.intent.action.OPEN_DOCUMENT_TREE"), REQUEST_CODE_OPEN_DOCUMENT_TREE_GROUPS_PATH);
return true;
});
}
@ -246,12 +230,9 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
}
private void setupOnClickListeners() {
findPreference(PlayMusicExporterPreferences.AUTO_EXPORT_PATH).setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
startActivityForResult(new Intent("android.intent.action.OPEN_DOCUMENT_TREE"), REQUEST_CODE_OPEN_DOCUMENT_TREE_AUTO_PATH);
return true;
}
findPreference(PlayMusicExporterPreferences.AUTO_EXPORT_PATH).setOnPreferenceClickListener(preference -> {
startActivityForResult(new Intent("android.intent.action.OPEN_DOCUMENT_TREE"), REQUEST_CODE_OPEN_DOCUMENT_TREE_AUTO_PATH);
return true;
});
//For every setting that is used to define the export job, setup a listener that
@ -262,12 +243,9 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
}
private void setupRescheduler(String preference) {
findPreference(preference).setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
ExportAllJob.scheduleExport(getContext());
return false;
}
findPreference(preference).setOnPreferenceClickListener(preference1 -> {
ExportAllJob.scheduleExport(getContext());
return false;
});
}

View file

@ -79,10 +79,7 @@ public class MusicContainerListFragment extends ListFragment {
* A dummy implementation of the {@link Callbacks} interface that does
* nothing. Used only when this fragment is not attached to an activity.
*/
private static Callbacks sDummyCallbacks = new Callbacks() {
@Override
public void onItemSelected(MusicTrackList musicTrackList) {
}
private static Callbacks sDummyCallbacks = musicTrackList -> {
};
private MusicContainerListAdapter mMusicTrackListAdapter;
@ -112,9 +109,7 @@ public class MusicContainerListFragment extends ListFragment {
// Null check
if (list != null) {
// Copy the list
for (MusicTrackList musicTrackList : list) {
newList.add(musicTrackList);
}
newList.addAll(list);
}
// Set the list in the adapter
@ -145,7 +140,8 @@ public class MusicContainerListFragment extends ListFragment {
setActivatedPosition(savedInstanceState.getInt(STATE_ACTIVATED_POSITION));
}
}
// DEPRECATED
// DEPRECATED
@Override
public void onAttach(Context context) {
super.onAttach(context);

View file

@ -32,7 +32,6 @@ import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;
@ -197,43 +196,36 @@ public class MusicTrackListFragment extends Fragment {
mListView.setAdapter(mMusicTrackAdapter);
// Click on one list item
mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
// The header is not clicked
if (position > 0) {
// We need to subtract the header view
position -= 1;
mListView.setOnItemClickListener((parent, view, position, id) -> {
// The header is not clicked
if (position > 0) {
// We need to subtract the header view
position -= 1;
// Gets the selected track
MusicTrack musicTrack = mMusicTrackAdapter.getItem(position);
// Gets the selected track
MusicTrack musicTrack = mMusicTrackAdapter.getItem(position);
// Toggle the track
selectTrack(musicTrack, view, TrackSelectionState.Toggle);
}
// Toggle the track
selectTrack(musicTrack, view, TrackSelectionState.Toggle);
}
});
// The floating action button
mFloatingButtonExport = (FloatingActionButton) rootView.findViewById(R.id.floating_button_export);
mFloatingButtonExport.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v)
{
m_CPULock.acquire();
// Export all selected tracks
for (SelectedTrack selectedTrack : SelectedTrackList.getInstance().getSelectedItems()) {
selectedTrack.export(getActivity());
}
if ( m_CPULock.isHeld())
{
m_CPULock.release();
}
// Clear the selection
SelectedTrackList.getInstance().clear(true);
mFloatingButtonExport.setOnClickListener(v -> {
m_CPULock.acquire();
// Export all selected tracks
for (SelectedTrack selectedTrack : SelectedTrackList.getInstance().getSelectedItems()) {
selectedTrack.export(getActivity());
}
if ( m_CPULock.isHeld())
{
m_CPULock.release();
}
// Clear the selection
SelectedTrackList.getInstance().clear(true);
});
updateFloatingButton();
}

View file

@ -22,7 +22,6 @@
package re.jcg.playmusicexporter.fragments;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.res.Configuration;
@ -160,47 +159,24 @@ public class NavigationDrawerFragment extends Fragment {
setViewType(mViewType);
// Click on album
mButtonTypeAlbum.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
setViewType(ViewType.Album);
}
});
mButtonTypeAlbum.setOnClickListener(v -> setViewType(ViewType.Album));
// Click on artist
mButtonTypeArtist.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
setViewType(ViewType.Artist);
}
});
mButtonTypeArtist.setOnClickListener(v -> setViewType(ViewType.Artist));
// Click on playlist
mButtonTypePlaylist.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
setViewType(ViewType.Playlist);
}
});
mButtonTypePlaylist.setOnClickListener(v -> setViewType(ViewType.Playlist));
// Click on rated
mButtonTypeRated.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
setViewType(ViewType.Rated);
}
});
mButtonTypeRated.setOnClickListener(v -> setViewType(ViewType.Rated));
// Click on settings
mButtonSettings.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intentSettings = new Intent(getActivity(), SettingsActivity.class);
startActivity(intentSettings);
mButtonSettings.setOnClickListener(v -> {
Intent intentSettings = new Intent(getActivity(), SettingsActivity.class);
startActivity(intentSettings);
// Close the drawer
mDrawerLayout.closeDrawers();
}
// Close the drawer
mDrawerLayout.closeDrawers();
});
// Color the settings button
@ -307,12 +283,7 @@ public class NavigationDrawerFragment extends Fragment {
}
// Defer code dependent on restoration of previous instance state.
mDrawerLayout.post(new Runnable() {
@Override
public void run() {
mDrawerToggle.syncState();
}
});
mDrawerLayout.post(mDrawerToggle::syncState);
mDrawerLayout.setDrawerListener(mDrawerToggle);
}

View file

@ -9,6 +9,7 @@ import android.util.Log;
import java.util.List;
import ly.count.android.sdk.Countly;
import re.jcg.playmusicexporter.settings.PlayMusicExporterPreferences;
import re.jcg.playmusicexporter.utils.MusicPathBuilder;
import de.arcus.playmusiclib.PlayMusicManager;
@ -34,8 +35,7 @@ public class ExportAllService extends IntentService {
Log.i(TAG, "Intent sent!");
}
public ExportAllService()
{
public ExportAllService() {
super("AutoGPME-ExportService");
}
@ -75,10 +75,11 @@ public class ExportAllService extends IntentService {
try {
if (lPlayMusicManager.exportMusicTrack(lTrack, lUri, lPath, PlayMusicExporterPreferences.getFileOverwritePreference())) {
Log.i(TAG, "Exported Music Track: " + getStringForTrack(lTrack));
Countly.sharedInstance().recordEvent("Exported Song", 1);
} else {
Log.i(TAG, "Failed to export Music Track: " + getStringForTrack(lTrack));
}
} catch (IllegalArgumentException e) {
if (e.getMessage().contains("Invalid URI:")) {
/*
@ -88,19 +89,14 @@ public class ExportAllService extends IntentService {
*/
Log.i(TAG, "Automatic export failed, because the URI is invalid.");
} else throw e;
}
finally
{
if ( CPULock.isHeld())
{
CPULock.release();
}
} catch (Exception e) {
Countly.sharedInstance().logException(e);
e.printStackTrace();
}
}
}
}
if ( CPULock.isHeld())
{
if (CPULock.isHeld()) {
CPULock.release();
}
}

View file

@ -31,6 +31,7 @@ import android.os.Bundle;
import android.support.v4.app.NotificationCompat;
import de.arcus.framework.logger.Logger;
import ly.count.android.sdk.Countly;
import re.jcg.playmusicexporter.R;
import de.arcus.playmusiclib.PlayMusicManager;
import de.arcus.playmusiclib.datasources.MusicTrackDataSource;
@ -200,9 +201,16 @@ public class ExportService extends IntentService {
updateNotification();
// Exports the song
if(!playMusicManager.exportMusicTrack(mTrackCurrent, uri, path, PlayMusicExporterPreferences.getFileOverwritePreference())) {
// Export failed
mTracksFailed ++;
try {
if (playMusicManager.exportMusicTrack(mTrackCurrent, uri, path, PlayMusicExporterPreferences.getFileOverwritePreference())) {
Countly.sharedInstance().recordEvent("Exported Song", 1);
} else {
// Export failed
mTracksFailed ++;
}
} catch (Exception e) {
Countly.sharedInstance().logException(e);
e.printStackTrace();
}
} else {
// Export failed

View file

@ -22,14 +22,12 @@
package re.jcg.playmusicexporter.utils;
import android.graphics.Bitmap;
import android.widget.ImageView;
import java.lang.ref.WeakReference;
import re.jcg.playmusicexporter.R;
import de.arcus.playmusiclib.ArtworkLoader;
import de.arcus.playmusiclib.ArtworkLoaderCallback;
import de.arcus.playmusiclib.items.ArtworkEntry;
/**
@ -121,13 +119,10 @@ public class ArtworkViewLoader {
maximalArtworkSize = imageViewDefault.getContext().getResources().getDimensionPixelSize(R.dimen.music_track_artwork_loading_size);
// Sets the bitmap in the UI thread
Runnable runnable = new Runnable() {
@Override
public void run() {
// Default icon
imageViewDefault.setImageResource(mDefaultImage);
Runnable runnable = () -> {
// Default icon
imageViewDefault.setImageResource(mDefaultImage);
}
};
imageViewDefault.post(runnable);
}
@ -138,36 +133,30 @@ public class ArtworkViewLoader {
mIsLoading = true;
// Load the artwork
ArtworkLoader.loadArtworkAsync(mArtworkEntry, maximalArtworkSize, new ArtworkLoaderCallback() {
@Override
public void onFinished(final Bitmap bitmap) {
final ImageView imageView = mImageView.get();
ArtworkLoader.loadArtworkAsync(mArtworkEntry, maximalArtworkSize, bitmap -> {
final ImageView imageView = mImageView.get();
if (imageViewDefault != null) {
// Sets the bitmap in the UI thread
Runnable runnable = new Runnable() {
@Override
public void run() {
// Bitmap is valid
if (bitmap != null)
imageView.setImageBitmap(bitmap);
else
imageView.setImageResource(mDefaultImage);
}
};
imageView.post(runnable);
}
if (imageViewDefault != null) {
// Sets the bitmap in the UI thread
Runnable runnable = () -> {
// Bitmap is valid
if (bitmap != null)
imageView.setImageBitmap(bitmap);
else
imageView.setImageResource(mDefaultImage);
};
imageView.post(runnable);
}
// Loading is done
mIsLoading = false;
// Loading is done
mIsLoading = false;
// Loads the next image
if (mNewArtworkEntry != null) {
mArtworkEntry = mNewArtworkEntry;
mNewArtworkEntry = null;
// Loads the next image
if (mNewArtworkEntry != null) {
mArtworkEntry = mNewArtworkEntry;
mNewArtworkEntry = null;
loadImage();
}
loadImage();
}
});
}

View file

@ -1,31 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (c) 2015 David Schulte
~
~ 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.
-->
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="@color/button_navigation_drawer_hover">
<item>
<shape android:shape="rectangle">
<solid android:color="@color/button_navigation_drawer_normal"/>
</shape>
</item>
</ripple>

View file

@ -1,31 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (c) 2015 David Schulte
~
~ 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.
-->
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="@color/button_navigation_drawer_hover">
<item>
<shape android:shape="rectangle">
<solid android:color="@color/button_navigation_drawer_selected"/>
</shape>
</item>
</ripple>

View file

@ -21,7 +21,11 @@
~ THE SOFTWARE.
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@color/button_navigation_drawer_normal"/>
</shape>
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="@color/button_navigation_drawer_hover">
<item>
<shape android:shape="rectangle">
<solid android:color="@color/button_navigation_drawer_normal"/>
</shape>
</item>
</ripple>

View file

@ -21,7 +21,11 @@
~ THE SOFTWARE.
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@color/button_navigation_drawer_selected"/>
</shape>
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="@color/button_navigation_drawer_hover">
<item>
<shape android:shape="rectangle">
<solid android:color="@color/button_navigation_drawer_selected"/>
</shape>
</item>
</ripple>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="countly_url">https://countly.jcg.re/</string>
<string name="countly_token">82a1d9405388c4dd73dc9835f84c59cf4274086d</string>
</resources>

View file

@ -0,0 +1,7 @@
<preference-headers xmlns:android="http://schemas.android.com/apk/res/android">
<header
android:fragment="re.jcg.playmusicexporter.activities.SettingsActivity$DebugPreferenceFragment"
android:icon="@drawable/ic_action_settings"
android:title="@string/pref_header_debug" />
</preference-headers>

View file

@ -16,9 +16,5 @@
android:fragment="re.jcg.playmusicexporter.activities.SettingsActivity$AboutPreferenceFragment"
android:icon="@drawable/ic_info_black_24dp"
android:title="@string/pref_header_about" />
<header
android:fragment="re.jcg.playmusicexporter.activities.SettingsActivity$DebugPreferenceFragment"
android:icon="@drawable/ic_action_settings"
android:title="@string/pref_header_debug" />
</preference-headers>

View file

@ -25,7 +25,6 @@ package de.arcus.playmusiclib;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.graphics.Bitmap;
@ -33,10 +32,8 @@ import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.os.ParcelFileDescriptor;
import android.support.v4.content.FileProvider;
import android.support.v4.provider.DocumentFile;
import android.text.TextUtils;
import android.util.Log;
import com.mpatric.mp3agic.ID3v1Genres;
import com.mpatric.mp3agic.ID3v1Tag;
@ -44,7 +41,10 @@ import com.mpatric.mp3agic.ID3v2;
import com.mpatric.mp3agic.ID3v22Tag;
import com.mpatric.mp3agic.ID3v23Tag;
import com.mpatric.mp3agic.ID3v24Tag;
import com.mpatric.mp3agic.InvalidDataException;
import com.mpatric.mp3agic.Mp3File;
import com.mpatric.mp3agic.NotSupportedException;
import com.mpatric.mp3agic.UnsupportedTagException;
import java.io.ByteArrayOutputStream;
import java.io.File;
@ -138,6 +138,7 @@ public class PlayMusicManager {
/**
* The database will be copied to a temp folder to access from the app
*
* @return Gets the temp path to the database
*/
private String getTempDatabasePath() {
@ -297,6 +298,7 @@ public class PlayMusicManager {
/**
* Creates a new PlayMusic manager
*
* @param context App context
*/
public PlayMusicManager(Context context) {
@ -307,8 +309,9 @@ public class PlayMusicManager {
/**
* Loads all needed information and opens the database
* @throws PlayMusicNotFoundException PlayMusic is not installed
* @throws NoSuperUserException No super user permissions
*
* @throws PlayMusicNotFoundException PlayMusic is not installed
* @throws NoSuperUserException No super user permissions
* @throws CouldNotOpenDatabaseException Could not open the database
*/
public void startUp() throws PlayMusicNotFoundException, NoSuperUserException, CouldNotOpenDatabaseException {
@ -345,7 +348,8 @@ public class PlayMusicManager {
/**
* Copies the database to a temp directory and opens it
* @throws NoSuperUserException No super user permissions
*
* @throws NoSuperUserException No super user permissions
* @throws de.arcus.playmusiclib.exceptions.CouldNotOpenDatabaseException Could not open the database
*/
private void loadDatabase() throws NoSuperUserException, CouldNotOpenDatabaseException {
@ -370,7 +374,8 @@ public class PlayMusicManager {
/**
* Reloads the database from PlayMusic
* @throws NoSuperUserException No super user permissions
*
* @throws NoSuperUserException No super user permissions
* @throws CouldNotOpenDatabaseException Could not open the database
*/
public void reloadDatabase() throws NoSuperUserException, CouldNotOpenDatabaseException {
@ -403,6 +408,7 @@ public class PlayMusicManager {
/**
* Gets the path to the music track
*
* @param localCopyPath The local copy path
* @return The path to the music file
*/
@ -437,6 +443,7 @@ public class PlayMusicManager {
/**
* Gets the full path to the artwork
*
* @param artworkPath The artwork path
* @return The full path to the artwork
*/
@ -469,12 +476,13 @@ public class PlayMusicManager {
/**
* Exports a track to the sd card
* @param musicTrack The music track you want to export
* @param dest The destination path
* @param forceOverwrite Forces overwrite of the destination file
*
* @param musicTrack The music track you want to export
* @param dest The destination path
* @param forceOverwrite Forces overwrite of the destination file
* @return Returns whether the export was successful
*/
public boolean exportMusicTrack(MusicTrack musicTrack, String dest, boolean forceOverwrite ) {
public boolean exportMusicTrack(MusicTrack musicTrack, String dest, boolean forceOverwrite) throws InvalidDataException, IOException, UnsupportedTagException, NotSupportedException {
// Creates the destination directory
File directory = new File(dest).getParentFile();
@ -487,12 +495,13 @@ public class PlayMusicManager {
/**
* Exports a track to the sd card
* @param musicTrack The music track you want to export
* @param uri The document tree
* @param forceOverwrite Forces overwrite of the destination file
*
* @param musicTrack The music track you want to export
* @param uri The document tree
* @param forceOverwrite Forces overwrite of the destination file
* @return Returns whether the export was successful
*/
public boolean exportMusicTrack(MusicTrack musicTrack, Uri uri, String path, boolean forceOverwrite) {
public boolean exportMusicTrack(MusicTrack musicTrack, Uri uri, String path, boolean forceOverwrite) throws InvalidDataException, IOException, UnsupportedTagException, NotSupportedException {
// Check for null
if (musicTrack == null) return false;
@ -504,10 +513,9 @@ public class PlayMusicManager {
String uniqueID = UUID.randomUUID().toString();
if ( forceOverwrite || !isAlreadyThere(uri, path) )
{
if (forceOverwrite || !isAlreadyThere(uri, path)) {
String fileTmp = getTempPath() + uniqueID +"_tmp.mp3";
String fileTmp = getTempPath() + uniqueID + "_tmp.mp3";
// Copy to temp path failed
if (!SuperUserTools.fileCopy(srcFile, fileTmp))
@ -515,7 +523,7 @@ public class PlayMusicManager {
// Encrypt the file
if (musicTrack.isEncoded()) {
String fileTmpCrypt = getTempPath() + uniqueID +"_crypt.mp3";
String fileTmpCrypt = getTempPath() + uniqueID + "_crypt.mp3";
// Encrypts the file
if (trackEncrypt(musicTrack, fileTmp, fileTmpCrypt)) {
@ -530,7 +538,6 @@ public class PlayMusicManager {
}
String dest;
Uri copyUri = null;
if (uri.toString().startsWith("file://")) {
@ -541,19 +548,19 @@ public class PlayMusicManager {
FileTools.directoryCreate(parentDirectory);
} else {
// Complex uri (Lollipop)
dest = getTempPath() + uniqueID +"_final.mp3";
dest = getTempPath() + uniqueID + "_final.mp3";
// The root
DocumentFile document = DocumentFile.fromTreeUri(mContext, uri);
// Creates the subdirectories
String[] directories = path.split("\\/");
for(int i=0; i<directories.length - 1; i++) {
String[] directories = path.split("/");
for (int i = 0; i < directories.length - 1; i++) {
String directoryName = directories[i];
boolean found = false;
// Search all sub elements
for (DocumentFile subDocument: document.listFiles()) {
for (DocumentFile subDocument : document.listFiles()) {
// Directory exists
if (subDocument.isDirectory() && subDocument.getName().equalsIgnoreCase(directoryName)) {
document = subDocument;
@ -571,12 +578,12 @@ public class PlayMusicManager {
// Gets the filename
String filename = directories[directories.length - 1];
for (DocumentFile subDocument: document.listFiles()) {
for (DocumentFile subDocument : document.listFiles()) {
// Directory exists
if (subDocument.isFile() ){
if ( filename != null && subDocument.getName().equalsIgnoreCase(filename)) {
if (subDocument.isFile()) {
if (filename != null && subDocument.getName().equalsIgnoreCase(filename)) {
// Delete the file
if ( forceOverwrite ) {
if (forceOverwrite) {
Logger.getInstance().logWarning("ExportMusicTrack", "(forceOverwrite) Deleting original file: " + filename);
}
subDocument.delete();
@ -660,7 +667,7 @@ public class PlayMusicManager {
//new MediaScanner(mContext, dest);
} else {
Logger.getInstance().logInfo("exportMusicTrack", path + " already exists, skipping." );
Logger.getInstance().logInfo("exportMusicTrack", path + " already exists, skipping.");
}
// Done
@ -669,9 +676,10 @@ public class PlayMusicManager {
/**
* Checks if the destination file already exists
* @param pUri the source file
*
* @param pUri the source file
* @param pPath The destination
* return true if the file already exists
* return true if the file already exists
*/
private boolean isAlreadyThere(Uri pUri, String pPath) {
if (pUri.toString().startsWith("file://")) {
@ -683,8 +691,8 @@ public class PlayMusicManager {
for (String lDisplayName : pPath.split("/")) {
if (lDocumentFile.findFile(lDisplayName) != null) {
lDocumentFile = lDocumentFile.findFile(lDisplayName);
if ( lDocumentFile.length() == 0 ) {
if ( !lDocumentFile.isDirectory() ) {
if (lDocumentFile.length() == 0) {
if (!lDocumentFile.isDirectory()) {
Logger.getInstance().logInfo("isAlreadyThere", pPath + " File exists, but is 0 bytes in size.");
}
}
@ -699,122 +707,117 @@ public class PlayMusicManager {
/**
* Copies the music file to a new path and adds the mp3 meta data
*
* @param musicTrack Track information
* @param src The source mp3 file
* @param dest The destination path
* return Return if the operation was successful
* @param src The source mp3 file
* @param dest The destination path
* return Return if the operation was successful
*/
private boolean trackWriteID3(MusicTrack musicTrack, String src, String dest) {
try {
// Opens the mp3
Mp3File mp3File = new Mp3File(src);
private boolean trackWriteID3(MusicTrack musicTrack, String src, String dest) throws InvalidDataException, IOException, UnsupportedTagException, NotSupportedException {
// Opens the mp3
Mp3File mp3File = new Mp3File(src);
// Removes all existing tags
mp3File.removeId3v1Tag();
mp3File.removeId3v2Tag();
mp3File.removeCustomTag();
// We want to add a fallback ID3v1 tag
if (mID3EnableFallback) {
// Create a new tag with ID3v1
ID3v1Tag tagID3v1 = new ID3v1Tag();
// Set all tag values
tagID3v1.setTrack(musicTrack.getTitle());
tagID3v1.setArtist(musicTrack.getArtist());
tagID3v1.setAlbum(musicTrack.getAlbum());
tagID3v1.setYear(musicTrack.getYear());
// Search the genre
for(int n=0; n<ID3v1Genres.GENRES.length; n++) {
// Genre found
if (ID3v1Genres.GENRES[n].equals(musicTrack.getGenre())) {
tagID3v1.setGenre(n);
break;
}
}
mp3File.setId3v1Tag(tagID3v1);
}
// It can't be null
final ID3v2 tagID3v2;
// Creates the requested version
switch(mID3v2Version) {
case ID3v22:
tagID3v2 = new ID3v22Tag();
break;
case ID3v23:
tagID3v2 = new ID3v23Tag();
break;
case ID3v24:
tagID3v2 = new ID3v24Tag();
break;
default:
tagID3v2 = null;
break;
}
// Removes all existing tags
mp3File.removeId3v1Tag();
mp3File.removeId3v2Tag();
mp3File.removeCustomTag();
// We want to add a fallback ID3v1 tag
if (mID3EnableFallback) {
// Create a new tag with ID3v1
ID3v1Tag tagID3v1 = new ID3v1Tag();
// Set all tag values
tagID3v2.setTitle(musicTrack.getTitle());
tagID3v2.setArtist(musicTrack.getArtist());
tagID3v2.setAlbum(musicTrack.getAlbum());
tagID3v2.setAlbumArtist(musicTrack.getAlbumArtist());
tagID3v2.setTrack("" + musicTrack.getTrackNumber());
tagID3v2.setPartOfSet("" + musicTrack.getDiscNumber());
tagID3v2.setYear(musicTrack.getYear());
tagID3v1.setTrack(musicTrack.getTitle());
tagID3v1.setArtist(musicTrack.getArtist());
tagID3v1.setAlbum(musicTrack.getAlbum());
tagID3v1.setYear(musicTrack.getYear());
if (!TextUtils.isEmpty(musicTrack.getGenre())) {
try {
// Maybe the genre is not supported
tagID3v2.setGenreDescription(musicTrack.getGenre());
} catch (IllegalArgumentException e) {
Logger.getInstance().logWarning("TrackWriteID3", e.getMessage());
// Search the genre
for (int n = 0; n < ID3v1Genres.GENRES.length; n++) {
// Genre found
if (ID3v1Genres.GENRES[n].equals(musicTrack.getGenre())) {
tagID3v1.setGenre(n);
break;
}
}
// Add the artwork to the meta data
if (mID3EnableArtwork) {
// Load the artwork
Bitmap bitmap = ArtworkLoader.loadArtwork(musicTrack, mID3ArtworkMaximumSize);
if (bitmap != null) {
// JPEG is default
String mimeType = "image/jpeg";
// Load the bitmap into a byte array
ByteArrayOutputStream artworkDataStream = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.JPEG, 90, artworkDataStream);
// Adds the artwork to the meta data
tagID3v2.setAlbumImage(artworkDataStream.toByteArray(), mimeType);
}
}
mp3File.setId3v2Tag(tagID3v2);
// Save the file
mp3File.save(dest);
// Done
return true;
} catch (Exception e) {
Logger.getInstance().logError("TrackWriteId3", e.toString());
mp3File.setId3v1Tag(tagID3v1);
}
// Failed
return false;
// It can't be null
final ID3v2 tagID3v2;
// Creates the requested version
switch (mID3v2Version) {
case ID3v22:
tagID3v2 = new ID3v22Tag();
break;
case ID3v23:
tagID3v2 = new ID3v23Tag();
break;
case ID3v24:
tagID3v2 = new ID3v24Tag();
break;
default:
tagID3v2 = null;
break;
}
// Set all tag values
tagID3v2.setTitle(musicTrack.getTitle());
tagID3v2.setArtist(musicTrack.getArtist());
tagID3v2.setAlbum(musicTrack.getAlbum());
tagID3v2.setAlbumArtist(musicTrack.getAlbumArtist());
tagID3v2.setTrack("" + musicTrack.getTrackNumber());
tagID3v2.setPartOfSet("" + musicTrack.getDiscNumber());
tagID3v2.setYear(musicTrack.getYear());
if (!TextUtils.isEmpty(musicTrack.getGenre())) {
try {
// Maybe the genre is not supported
tagID3v2.setGenreDescription(musicTrack.getGenre());
} catch (IllegalArgumentException e) {
Logger.getInstance().logWarning("TrackWriteID3", e.getMessage());
}
}
// Add the artwork to the meta data
if (mID3EnableArtwork) {
// Load the artwork
Bitmap bitmap = ArtworkLoader.loadArtwork(musicTrack, mID3ArtworkMaximumSize);
if (bitmap != null) {
// JPEG is default
String mimeType = "image/jpeg";
// Load the bitmap into a byte array
ByteArrayOutputStream artworkDataStream = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.JPEG, 90, artworkDataStream);
// Adds the artwork to the meta data
tagID3v2.setAlbumImage(artworkDataStream.toByteArray(), mimeType);
}
}
mp3File.setId3v2Tag(tagID3v2);
// Save the file
mp3File.save(dest);
// Done
return true;
}
/**
* Encrypts a track and save it to a new path
*
* @param musicTrack The music track
* @param src The source mp3 file
* @param dest The destination path
* @param src The source mp3 file
* @param dest The destination path
* @return Return if the operation was successful
*/
private boolean trackEncrypt(MusicTrack musicTrack, String src, String dest) {
@ -843,8 +846,8 @@ public class PlayMusicManager {
* Deletes all cache files
*/
private void cleanUp(String theUniqueID) {
FileTools.fileDelete( getTempPath() + theUniqueID +"_final.mp3");
FileTools.fileDelete( getTempPath() + theUniqueID +"_tmp.mp3");
FileTools.fileDelete( getTempPath() + theUniqueID +"_crypt.mp3");
FileTools.fileDelete(getTempPath() + theUniqueID + "_final.mp3");
FileTools.fileDelete(getTempPath() + theUniqueID + "_tmp.mp3");
FileTools.fileDelete(getTempPath() + theUniqueID + "_crypt.mp3");
}
}