Added the playmusiclib

This commit is contained in:
David Schulte 2015-01-24 15:08:38 +01:00
parent e85e97d141
commit 3b0eb97f51
26 changed files with 2187 additions and 6 deletions

View file

@ -79,12 +79,14 @@
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/rs" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/symbols" />
<excludeFolder url="file://$MODULE_DIR$/build/outputs" />
<excludeFolder url="file://$MODULE_DIR$/build/tmp" />
</content>
<orderEntry type="jdk" jdkName="Android API 21 Platform" jdkType="Android SDK" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" exported="" name="appcompat-v7-21.0.3" level="project" />
<orderEntry type="library" exported="" name="support-annotations-21.0.3" level="project" />
<orderEntry type="library" exported="" name="support-v4-21.0.3" level="project" />
<orderEntry type="module" module-name="playmusiclib" exported="" />
<orderEntry type="module" module-name="framework" exported="" />
</component>
</module>

View file

@ -48,4 +48,5 @@ dependencies {
compile 'com.android.support:appcompat-v7:21.0.3'
compile 'com.android.support:support-v4:21.0.3'
compile project(':framework')
compile project(':playmusiclib')
}

View file

@ -26,10 +26,21 @@ import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.ActionBar;
import android.support.v7.app.ActionBarActivity;
import android.util.Log;
import java.util.List;
import de.arcus.framework.logger.Logger;
import de.arcus.framework.crashhandler.CrashHandler;
import de.arcus.framework.superuser.SuperUser;
import de.arcus.playmusiclib.PlayMusicManager;
import de.arcus.playmusiclib.datasources.AlbumDataSource;
import de.arcus.playmusiclib.datasources.MusicTrackDataSource;
import de.arcus.playmusiclib.exceptions.CouldNotOpenDatabase;
import de.arcus.playmusiclib.exceptions.NoSuperUserException;
import de.arcus.playmusiclib.exceptions.PlayMusicNotFound;
import de.arcus.playmusiclib.items.Album;
import de.arcus.playmusiclib.items.MusicTrack;
/**
* An activity representing a list of Tracks. This activity
@ -87,7 +98,44 @@ public class TrackListActivity extends ActionBarActivity
}
SuperUser.askForPermissions();
PlayMusicManager playMusicManager = new PlayMusicManager(this);
try {
// Simple play ground
playMusicManager.startUp();
playMusicManager.setOfflineOnly(true);
AlbumDataSource albumDataSource = new AlbumDataSource(playMusicManager);
albumDataSource.setSerchKey("A bird story");
// Load all albums
List<Album> albums = albumDataSource.getAll();
if (albums.size() > 0) {
// Gets the first album
Album album = albums.get(0);
// Load tracks from album
List<MusicTrack> tracks = album.getMusicTrackList();
if (tracks.size() > 0) {
// Gets the first track
MusicTrack track = tracks.get(0);
String fileMusic = track.getSourceFile();
String artwork = track.getArtworkPath();
Log.d("Debug", "Breakpoint");
}
}
} catch (PlayMusicNotFound playMusicNotFound) {
playMusicNotFound.printStackTrace();
} catch (NoSuperUserException e) {
e.printStackTrace();
} catch (CouldNotOpenDatabase couldNotOpenDatabase) {
couldNotOpenDatabase.printStackTrace();
}
// TODO: If exposing deep links into your app, handle intents here.

View file

@ -113,7 +113,7 @@ public class SuperUser {
// Starts the process
if (sessionStart()) {
// Test for superuser
SuperUserCommand superUserCommand = new SuperUserCommand("whoami");
SuperUserCommand superUserCommand = new SuperUserCommand("echo 'root'");
Logger.getInstance().logInfo("SuperUser", "askForPermissions");
if (superUserCommand.execute()) {

View file

@ -26,19 +26,35 @@ package de.arcus.framework.superuser;
* Tools for the superuser
*/
public class SuperUserTools {
/**
* Private constructor
*/
private SuperUserTools() {}
/**
* Copy a file with root permissions
* @param src Source file
* @param dest Destination file
* @return Returns whether the command was successful
*/
public static boolean copyFile(String src, String dest) {
public static boolean fileCopy(String src, String dest) {
SuperUserCommand superUserCommand = new SuperUserCommand(new String[] {
"rm -f '" + dest + "'", // Remove destination file
"cat '" + src + "' >> '" + dest + "'", // Using cat to copy file instead of cp, because you can use it without busybox
"chmod 0777 '" + dest + "'" // Change the access mode to all users (chown sdcard_r will fail on some devices)
"chmod 0777 '" + dest + "'", // Change the access mode to all users (chown sdcard_r will fail on some devices)
"echo 'done'" // Fix to prevent the 'no output' bug in SuperUserCommand
});
// Execute the command
// Executes the command
superUserCommand.execute();
// Superuser permissions and command are successful
return superUserCommand.commandWasSuccessful();
}
public static boolean fileExists(String filename) {
SuperUserCommand superUserCommand = new SuperUserCommand("ls '" + filename + "'");
// Executes the command
superUserCommand.execute();
// Superuser permissions and command are successful

1
playmusiclib/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/build

46
playmusiclib/build.gradle Normal file
View file

@ -0,0 +1,46 @@
/*
* 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.
*/
apply plugin: 'com.android.library'
android {
compileSdkVersion 21
buildToolsVersion "21.1.2"
defaultConfig {
minSdkVersion 8
targetSdkVersion 21
versionCode 1
versionName "1.0"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile project(':framework')
}

View file

@ -0,0 +1,91 @@
<?xml version="1.0" encoding="UTF-8"?>
<module external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$/.." external.system.id="GRADLE" type="JAVA_MODULE" version="4">
<component name="FacetManager">
<facet type="android-gradle" name="Android-Gradle">
<configuration>
<option name="GRADLE_PROJECT_PATH" value=":playmusiclib" />
</configuration>
</facet>
<facet type="android" name="Android">
<configuration>
<option name="SELECTED_BUILD_VARIANT" value="debug" />
<option name="ASSEMBLE_TASK_NAME" value="assembleDebug" />
<option name="COMPILE_JAVA_TASK_NAME" value="compileDebugSources" />
<option name="ASSEMBLE_TEST_TASK_NAME" value="assembleDebugTest" />
<option name="SOURCE_GEN_TASK_NAME" value="generateDebugSources" />
<option name="TEST_SOURCE_GEN_TASK_NAME" value="generateDebugTestSources" />
<option name="ALLOW_USER_CONFIGURATION" value="false" />
<option name="MANIFEST_FILE_RELATIVE_PATH" value="/src/main/AndroidManifest.xml" />
<option name="RES_FOLDER_RELATIVE_PATH" value="/src/main/res" />
<option name="RES_FOLDERS_RELATIVE_PATH" value="file://$MODULE_DIR$/src/main/res" />
<option name="ASSETS_FOLDER_RELATIVE_PATH" value="/src/main/assets" />
<option name="LIBRARY_PROJECT" value="true" />
</configuration>
</facet>
</component>
<component name="NewModuleRootManager" inherit-compiler-output="false">
<output url="file://$MODULE_DIR$/build/intermediates/classes/debug" />
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/r/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/aidl/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/buildConfig/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/rs/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/rs/debug" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/generated/debug" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/r/test/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/aidl/test/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/buildConfig/test/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/rs/test/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/rs/test/debug" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/generated/test/debug" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/res" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/resources" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/assets" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/aidl" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/java" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/jni" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/rs" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/res" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/main/assets" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/main/aidl" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/jni" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/rs" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/res" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/resources" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/assets" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/aidl" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/java" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/jni" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/rs" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/assets" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/bundles" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/classes" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/coverage-instrumented-classes" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/dependency-cache" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/dex" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/dex-cache" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/jacoco" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/javaResources" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/libs" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/lint" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/manifests" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/ndk" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/pre-dexed" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/proguard" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/res" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/rs" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/symbols" />
<excludeFolder url="file://$MODULE_DIR$/build/outputs" />
<excludeFolder url="file://$MODULE_DIR$/build/tmp" />
</content>
<orderEntry type="jdk" jdkName="Android API 21 Platform" jdkType="Android SDK" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" exported="" name="support-annotations-21.0.3" level="project" />
<orderEntry type="module" module-name="framework" exported="" />
</component>
</module>

17
playmusiclib/proguard-rules.pro vendored Normal file
View file

@ -0,0 +1,17 @@
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in /home/david/android-sdks/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}

View file

@ -0,0 +1,35 @@
/*
* 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.
*/
package de.arcus.playmusiclib;
import android.app.Application;
import android.test.ApplicationTestCase;
/**
* <a href="http://d.android.com/tools/testing/testing_android.html">Testing Fundamentals</a>
*/
public class ApplicationTest extends ApplicationTestCase<Application> {
public ApplicationTest() {
super(Application.class);
}
}

View file

@ -0,0 +1,30 @@
<!--
~ 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.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="de.arcus.playmusiclib">
<application android:allowBackup="true">
</application>
</manifest>

View file

@ -0,0 +1,204 @@
/*
* 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.
*/
package de.arcus.playmusiclib;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.NoSuchAlgorithmException;
import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
/**
* Exports encrypted music files from Google Music All Access
*/
public class AllAccessExporter {
private String mFileName;
private byte[] mCpData;
private Cipher mCipher;
private SecretKeySpec mKeySpec;
private InputStream mInput;
private OutputStream mOutput;
private byte[] mMagicNumber;
static final int BUFFER_SIZE = 1024;
static final byte[] MAGIC_NUMBER = { 18, -45, 21, 39 };
/**
* Creates a new exporter for AllAccess files
*
* @param input The encrypted music file (Copy the file into a readable directory before you use them.)
* @param cpData The 16 byte long key (CpData-Blob in MUSIC table)
* @throws NoSuchAlgorithmException Encryption method is not supported on this device
* @throws NoSuchPaddingException Padding mode is not supported on this device
* @throws IOException File could not be read (This class does not support SU commands; first copy your file into a readable directory)
*/
public AllAccessExporter(String input, byte[] cpData) throws NoSuchAlgorithmException, NoSuchPaddingException, IOException
{
mFileName = input;
mCpData = cpData;
// Select encryption mode
mCipher = Cipher.getInstance("AES/CTR/NoPadding");
mKeySpec = new SecretKeySpec(mCpData, "AES");
// Opens the source file
mInput = new FileInputStream(mFileName);
// Reads the first 4 bytes = MagicNumber
mMagicNumber = new byte[4];
if (mInput.read(mMagicNumber) != 4)
mMagicNumber = null;
}
/**
* Checks whether the magic number of the file is correct
*
* @return Returns whether the Magic Number is valid
*/
public boolean hasValidMagicNumber()
{
if (mMagicNumber == null)
return false;
if (mMagicNumber.length != 4)
return false;
for(int i=0; i<4; i++)
if (mMagicNumber[i] != MAGIC_NUMBER[i])
return false;
return true;
}
/**
* Saves an unencrypted copy of the music file as an Mp3
* @param filename The path to the target file
* @return Returns whether the file was successfully saved
*/
public boolean save(String filename)
{
try {
// Opens the target file
mOutput = new FileOutputStream(filename);
byte[] output = new byte[BUFFER_SIZE];
// Reads all blocks of the file
while(mInput.available() > 0) {
int size = read(output);
if (size > 0) {
mOutput.write(output, 0, size);
} else
break;
}
// Close the files
mInput.close();
mOutput.close();
// Everything went according to plan
return true;
} catch (Exception e) {
e.printStackTrace();
}
// Try to close the files if an error occurs
try {
if (mInput != null)
mInput.close();
if (mOutput != null)
mOutput.close();
} catch (IOException e) {
e.printStackTrace();
}
// An error has Occurred
return false;
}
/**
* Reads a block and decrypts it
* @param output output buffer
* @return Returns the length of the output buffer
* @throws Exception
*/
private int read(byte[] output) throws Exception
{
byte[] mBuffer = new byte[BUFFER_SIZE];
byte[] mIvBuffer = new byte[16];
int pos = 0;
// Fills the buffer
while (pos < BUFFER_SIZE)
{
int size = mInput.read(mBuffer, pos, BUFFER_SIZE - pos);
// There is nothing more to read
if (size == -1)
{
// Error, there was nothing more to read
if (pos == 0)
return -1;
// Buffer is filled
if (pos > 16)
break;
// Error, block is smaller than 16 bytes
return -1;
}
pos += size;
}
// The first 16 bytes of the block are the initialization vector
System.arraycopy(mBuffer, 0, mIvBuffer, 0, 16);
IvParameterSpec ivParameter = new IvParameterSpec(mIvBuffer);
// The remaining bytes are the encrypted data
int decodeSize = pos - 16;
try
{
// Initializes the encryption method
mCipher.init(Cipher.DECRYPT_MODE, mKeySpec, ivParameter);
// Decrypts the block
if (mCipher.doFinal(mBuffer, 16, decodeSize, output, 0) != decodeSize)
throw new IllegalStateException("Wrong block size");
}
catch (Exception ex)
{
// Unexpected error
throw new Exception("Unexpected error while decrypting", ex);
}
// Returns the block size
return decodeSize;
}
}

View file

@ -0,0 +1,262 @@
/*
* 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.
*/
package de.arcus.playmusiclib;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.text.TextUtils;
import de.arcus.framework.superuser.SuperUser;
import de.arcus.framework.superuser.SuperUserTools;
import de.arcus.playmusiclib.exceptions.CouldNotOpenDatabase;
import de.arcus.playmusiclib.exceptions.NoSuperUserException;
import de.arcus.playmusiclib.exceptions.PlayMusicNotFound;
import de.arcus.playmusiclib.items.MusicTrack;
/**
* Connects to the PlayMusic data
*/
public class PlayMusicManager {
/**
* PlayMusic package id
*/
public static final String PLAYMUSIC_PACKAGE_ID = "com.google.android.music";
/**
* Context of the app, needed to access to the package manager
*/
private Context mContext;
/**
* @return Gets the app context
*/
public Context getContext() {
return mContext;
}
/**
* Play Music database
*/
private SQLiteDatabase mDatabase;
/**
* @return Gets the database
*/
public SQLiteDatabase getDatabase() {
return mDatabase;
}
/**
* Path to the private app data
* Eg.: /data/data/com.google.android.music/
*/
private String mPathPrivateData;
/**
* Paths to all possible public app data
* Eg.: /sdcard/Android/data/com.google.android.music/
*/
private String[] mPathPublicData;
/**
* Application info from PlayMusic
*/
private ApplicationInfo mPlayMusicApplicationInfo;
/**
* @return Gets the path to the database
*/
private String getDatabasePath() {
return mPathPrivateData + "/databases/music.db";
}
/**
* 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() {
return getTempPath() + "/music.db";
}
/**
* @return Gets the temp path to the exported music
*/
private String getTempPath() {
return mContext.getCacheDir().getAbsolutePath();
}
/**
* If this is set the data source will only load offline tracks
*/
private boolean mOfflineOnly;
/**
* @return Returns whether the data source should only load offline tracks
*/
public boolean getOfflineOnly() {
return mOfflineOnly;
}
/**
* @param offlineOnly Sets whether the data source should only load offline tracks
*/
public void setOfflineOnly(boolean offlineOnly) {
mOfflineOnly = offlineOnly;
}
/**
* Creates a new PlayMusic manager
* @param context App context
*/
public PlayMusicManager(Context context) {
mContext = context;
}
/**
* Loads all needed information
*/
public void startUp() throws PlayMusicNotFound, NoSuperUserException, CouldNotOpenDatabase {
// Gets the package manager
PackageManager packageManager = mContext.getPackageManager();
try {
// Loads the application info
mPlayMusicApplicationInfo = packageManager.getApplicationInfo(PLAYMUSIC_PACKAGE_ID, 0);
} catch (PackageManager.NameNotFoundException e) {
// No PlayMusic
throw new PlayMusicNotFound();
}
// Path to the private data
mPathPrivateData = mPlayMusicApplicationInfo.dataDir;
mPathPublicData = new String[] {}; // TODO: Get sdcards
// Ask for super user
if (!SuperUser.askForPermissions())
throw new NoSuperUserException();
// Copy the database to the temp folder
if (!SuperUserTools.fileCopy(getDatabasePath(), getTempDatabasePath()))
throw new CouldNotOpenDatabase();
// Opens the database
try {
// Close the database
closeDatabase();
mDatabase = SQLiteDatabase.openDatabase(getTempDatabasePath(), null, SQLiteDatabase.OPEN_READONLY);
} catch (SQLException e) {
throw new CouldNotOpenDatabase();
}
}
/**
* Closes the database if it's open
*/
private void closeDatabase() {
if (mDatabase == null) return;
mDatabase.close();
}
/**
* @return Gets the path to the private music
*/
public String getPrivateMusicPath() {
return mPathPrivateData + "/files/music";
}
/**
* Gets the path to the music track
* @param localCopyPath The local copy path
* @return The path to the music file
*/
public String getMusicFile(String localCopyPath) {
// LocalCopyPath is empty
if (TextUtils.isEmpty(localCopyPath)) return null;
// Private music path
String path = getPrivateMusicPath() + "/" + localCopyPath;
// Music file exists
if (SuperUserTools.fileExists(path)) return path;
return null;
}
/**
* @return Gets the path to the private files
*/
public String getPrivateFilesPath() {
return mPathPrivateData + "/files";
}
/**
* Gets the full path to the artwork
* @param artworkPath The artwork path
* @return The full path to the artwork
*/
public String getArtworkPath(String artworkPath) {
// Artwork path is empty
if (TextUtils.isEmpty(artworkPath)) return null;
// Private music path
String path = getPrivateFilesPath() + "/" + artworkPath;
// Music file exists
if (SuperUserTools.fileExists(path)) return path;
return null;
}
/**
* Exports a track to the sd card
* @param musicTrack The music track you want to export
* @param dest The destination path
* @return Returns whether the export was successful
*/
public boolean exportMusicTrack(MusicTrack musicTrack, String dest) {
// Check for null
if (musicTrack == null) return false;
String srcFile = musicTrack.getSourceFile();
// Could not find the source file
if (srcFile == null) return false;
String fileTmp = getTempPath() + "/tmp.mp3";
// Copy to temp path failed
if (!SuperUserTools.fileCopy(srcFile, fileTmp))
return false;
// TODO
return true;
}
}

View file

@ -0,0 +1,164 @@
/*
* 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.
*/
package de.arcus.playmusiclib.datasources;
import android.database.Cursor;
import android.text.TextUtils;
import java.util.List;
import de.arcus.playmusiclib.PlayMusicManager;
import de.arcus.playmusiclib.items.Album;
/**
* Data source for albums
*/
public class AlbumDataSource extends DataSource<Album> {
private final static String TABLE_MUSIC = "MUSIC";
private final static String COLUMN_ALBUMID = "AlbumId";
private final static String COLUMN_ALBUM = "Album";
private final static String COLUMN_ALBUMARTIST = "AlbumArtist";
private final static String COLUMN_ALBUM_ARTWORKFILE = "(SELECT ARTWORK_CACHE.LocalLocation FROM MUSIC AS MUSIC2 LEFT JOIN ARTWORK_CACHE ON MUSIC2.AlbumArtLocation = ARTWORK_CACHE.RemoteLocation WHERE MUSIC2.AlbumID = MUSIC.AlbumID AND ARTWORK_CACHE.RemoteLocation IS NOT NULL LIMIT 1) AS ArtistArtworkPath";
private final static String COLUMN_TITLE = "Title";
private final static String COLUMN_ARTIST = "Artist";
private final static String[] COLUMNS_ALL = { COLUMN_ALBUMID, COLUMN_ALBUM,
COLUMN_ALBUMARTIST, COLUMN_ALBUM_ARTWORKFILE};
/**
* If this is set the data source will only load offline tracks
*/
private boolean mOfflineOnly;
/**
* If the search key is set, this data source will only load items which contains this text
*/
private String mSearchKey;
/**
* @return Returns whether the data source should only load offline tracks
*/
public boolean getOfflineOnly() {
return mOfflineOnly;
}
/**
* @param offlineOnly Sets whether the data source should only load offline tracks
*/
public void setOfflineOnly(boolean offlineOnly) {
mOfflineOnly = offlineOnly;
}
/**
* @return Gets the search key
*/
public String getSearchKey() {
return mSearchKey;
}
/**
* @param searchKey Sets the search key
*/
public void setSerchKey(String searchKey) {
mSearchKey = searchKey;
}
/**
* Creates a new data source
* @param playMusicManager The manager
*/
public AlbumDataSource(PlayMusicManager playMusicManager) {
super(playMusicManager);
// Load global settings
setOfflineOnly(playMusicManager.getOfflineOnly());
}
/**
* Prepare the where command and adds the global settings
* @param where The where command
* @return The new where command
*/
private String prepareWhere(String where) {
// The new where
String newWhere = "LocalCopyType != 300";
// Loads only offline tracks
if (mOfflineOnly)
newWhere += " AND LocalCopyPath IS NOT NULL";
// Search only items which contains the key
if (!TextUtils.isEmpty(mSearchKey)) {
String searchKey = mSearchKey.replace("'", "''");
newWhere += " AND (" + COLUMN_ALBUM + " LIKE '%" + searchKey + "%'";
newWhere += " OR " + COLUMN_TITLE + " LIKE '%" + searchKey + "%'";
newWhere += " OR " + COLUMN_ALBUMARTIST + " LIKE '%" + searchKey + "%'";
newWhere += " OR " + COLUMN_ARTIST + " LIKE '%" + searchKey + "%')";
}
// Adds an 'and' if needed
if (!TextUtils.isEmpty(where)) where = "(" + where + ") AND ";
where += newWhere;
return where;
}
@Override
/**
* Gets the data object from a data row
* @param cursor Data row
* @return Data object
*/
protected Album getDataObject(Cursor cursor) {
Album instance = new Album(mPlayMusicManager);
// Read all properties from the data row
instance.setAlbumId(cursor.getLong(getColumnsIndex(COLUMNS_ALL, COLUMN_ALBUMID)));
instance.setAlbum(cursor.getString(getColumnsIndex(COLUMNS_ALL, COLUMN_ALBUM)));
instance.setAlbumArtist(cursor.getString(getColumnsIndex(COLUMNS_ALL, COLUMN_ALBUMARTIST)));
instance.setArtworkFile(cursor.getString(getColumnsIndex(COLUMNS_ALL, COLUMN_ALBUM_ARTWORKFILE)));
return instance;
}
/**
* Loads an album by Id
* @param id The album id
* @return Returns the album or null
*/
public Album getById(long id) {
return getItem(TABLE_MUSIC, COLUMNS_ALL, prepareWhere("AlbumID = " + id));
}
/**
* Gets a list of all albums
* @return Returns all albums
*/
public List<Album> getAll() {
return getItems(TABLE_MUSIC, COLUMNS_ALL, prepareWhere(""), COLUMN_ALBUM);
}
}

View file

@ -0,0 +1,153 @@
/*
* 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.
*/
package de.arcus.playmusiclib.datasources;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import java.util.LinkedList;
import java.util.List;
import de.arcus.playmusiclib.PlayMusicManager;
/**
* Universal data source
*/
public abstract class DataSource<T> {
/**
* The manager
*/
protected PlayMusicManager mPlayMusicManager;
/**
* Creates a new data source
* @param playMusicManager The manager
*/
public DataSource(PlayMusicManager playMusicManager) {
mPlayMusicManager = playMusicManager;
}
/**
* Gets the index of the column
* @param columns Table header
* @param column Column
* @return Index
*/
protected static int getColumnsIndex(String[] columns, String column) {
for (int n=0; n<columns.length; n++) {
// Found column
if (columns[n].equals(column))
return n;
}
// Not found
return -1;
}
protected abstract T getDataObject(Cursor cursor);
/**
* Loads all items from the database
* @param table The table
* @param columns All columns
* @param where The where-command
* @return Returns a list with all items
*/
protected List<T> getItems(String table, String[] columns, String where) {
return getItems(table, columns, where, null);
}
/**
* Loads all items from the database
* @param table The table
* @param columns All columns
* @param where The where-command
* @param orderBy Order
* @return Returns a list with all items
*/
protected List<T> getItems(String table, String[] columns, String where, String orderBy) {
// No connection; abort
if (!mPlayMusicManager.getDatabase().isOpen()) return null;
// Creates the list
List<T> items = new LinkedList<>();
try {
// Gets the first data row
Cursor cursor = mPlayMusicManager.getDatabase().query(table, columns, where, null, null, null, orderBy);
// SQL error
if (cursor == null) return null;
cursor.moveToFirst();
int pos = 0;
// Reads the table
while (!cursor.isAfterLast()) {
pos ++;
// Adds the object
items.add(getDataObject(cursor));
// Go to next data row
cursor.moveToNext();
}
} catch (Exception e) {
e.printStackTrace();
return null;
}
return items;
}
/**
* Loads one item from the database
* @param table The table
* @param columns All columns
* @param where The where-command
* @return Returns the item
*/
protected T getItem(String table, String[] columns, String where) {
return getItem(table, columns, where, null);
}
/**
* Loads one item from the database
* @param table The table
* @param columns All columns
* @param where The where-command
* @param orderBy Order
* @return Returns the item
*/
protected T getItem(String table, String[] columns, String where, String orderBy) {
// Loads the list
List<T> items = getItems(table, columns, where, orderBy);
// Gets the first item
if (items.size() > 0)
return items.get(0);
else
return null;
}
}

View file

@ -0,0 +1,210 @@
/*
* 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.
*/
package de.arcus.playmusiclib.datasources;
import android.database.Cursor;
import android.text.TextUtils;
import java.util.List;
import de.arcus.playmusiclib.PlayMusicManager;
import de.arcus.playmusiclib.items.Album;
import de.arcus.playmusiclib.items.MusicTrack;
import de.arcus.playmusiclib.items.Playlist;
/**
* Data source for music tracks
*/
public class MusicTrackDataSource extends DataSource<MusicTrack> {
// Tables
private final static String TABLE_MUSIC = "MUSIC";
private final static String TABLE_MUSIC_PLAYLIST = "MUSIC LEFT JOIN LISTITEMS ON MUSIC.ID = LISTITEMS.MusicID";
// All fields
private final static String COLUMN_ID = "Id";
private final static String COLUMN_SIZE = "Size";
private final static String COLUMN_LOCALCOPYPATH = "LocalCopyPath";
private final static String COLUMN_LOCALCOPYTYPE = "LocalCopyType";
private final static String COLUMN_LOCALCOPYSTORAGETYPE = "LocalCopyStorageType";
private final static String COLUMN_TITLE = "Title";
private final static String COLUMN_ARTIST = "Artist";
private final static String COLUMN_ALBUMARTIST = "AlbumArtist";
private final static String COLUMN_ALBUM = "Album";
private final static String COLUMN_GENRE = "Genre";
private final static String COLUMN_YEAR = "Year";
private final static String COLUMN_TRACKNUMBER = "TrackNumber";
private final static String COLUMN_DISCNUMBER = "DiscNumber";
private final static String COLUMN_DURATION = "Duration";
private final static String COLUMN_ALBUMID = "AlbumId";
private final static String COLUMN_CLIENTID = "ClientId";
private final static String COLUMN_SOURCEID = "SourceId";
private final static String COLUMN_CPDATA = "CpData";
private final static String COLUMN_ARTWORKFILE = "(SELECT LocalLocation FROM artwork_cache WHERE artwork_cache.RemoteLocation = AlbumArtLocation) AS ArtworkFile";
// All columns
private final static String[] COLUMNS_ALL = { COLUMN_ID, COLUMN_SIZE,
COLUMN_LOCALCOPYPATH, COLUMN_LOCALCOPYTYPE, COLUMN_LOCALCOPYSTORAGETYPE, COLUMN_TITLE, COLUMN_ARTIST, COLUMN_ALBUMARTIST,
COLUMN_ALBUM, COLUMN_GENRE, COLUMN_YEAR, COLUMN_TRACKNUMBER, COLUMN_DISCNUMBER, COLUMN_DURATION,
COLUMN_ALBUMID, COLUMN_CLIENTID, COLUMN_SOURCEID, COLUMN_ARTWORKFILE, COLUMN_CPDATA };
/**
* If this is set the data source will only load offline tracks
*/
private boolean mOfflineOnly;
/**
* If the search key is set, this data source will only load items which contains this text
*/
private String mSearchKey;
/**
* @return Returns whether the data source should only load offline tracks
*/
public boolean getOfflineOnly() {
return mOfflineOnly;
}
/**
* @param offlineOnly Sets whether the data source should only load offline tracks
*/
public void setOfflineOnly(boolean offlineOnly) {
mOfflineOnly = offlineOnly;
}
/**
* @return Gets the search key
*/
public String getSearchKey() {
return mSearchKey;
}
/**
* @param searchKey Sets the search key
*/
public void setSerchKey(String searchKey) {
mSearchKey = searchKey;
}
/**
* Creates a new data source
* @param playMusicManager The manager
*/
public MusicTrackDataSource(PlayMusicManager playMusicManager) {
super(playMusicManager);
// Load global settings
setOfflineOnly(playMusicManager.getOfflineOnly());
}
/**
* Prepare the where command and adds the global settings
* @param where The where command
* @return The new where command
*/
private String prepareWhere(String where) {
// The new where
String newWhere = "LocalCopyType != 300";
// Loads only offline tracks
if (mOfflineOnly)
newWhere += " AND LocalCopyPath IS NOT NULL";
// Search only items which contains the key
if (!TextUtils.isEmpty(mSearchKey)) {
String searchKey = mSearchKey.replace("'", "''");
newWhere += " AND (" + COLUMN_ALBUM + " LIKE '%" + searchKey + "%'";
newWhere += " OR " + COLUMN_TITLE + " LIKE '%" + searchKey + "%'";
newWhere += " OR " + COLUMN_ALBUMARTIST + " LIKE '%" + searchKey + "%'";
newWhere += " OR " + COLUMN_ARTIST + " LIKE '%" + searchKey + "%')";
}
// Adds an 'and' if needed
if (!TextUtils.isEmpty(where)) where = "(" + where + ") AND ";
where += newWhere;
return where;
}
@Override
/**
* Gets the data object from a data row
* @param cursor Data row
* @return Data object
*/
protected MusicTrack getDataObject(Cursor cursor) {
MusicTrack instance = new MusicTrack(mPlayMusicManager);
// Read all properties from the data row
instance.setId(cursor.getLong(getColumnsIndex(COLUMNS_ALL, COLUMN_ID)));
instance.setSize(cursor.getLong(getColumnsIndex(COLUMNS_ALL, COLUMN_SIZE)));
instance.setLocalCopyPath(cursor.getString(getColumnsIndex(COLUMNS_ALL, COLUMN_LOCALCOPYPATH)));
instance.setLocalCopyType(cursor.getLong(getColumnsIndex(COLUMNS_ALL, COLUMN_LOCALCOPYTYPE)));
instance.setLocalCopyStorageType(cursor.getLong(getColumnsIndex(COLUMNS_ALL, COLUMN_LOCALCOPYSTORAGETYPE)));
instance.setTitle(cursor.getString(getColumnsIndex(COLUMNS_ALL, COLUMN_TITLE)));
instance.setArtist(cursor.getString(getColumnsIndex(COLUMNS_ALL, COLUMN_ARTIST)));
instance.setAlbumArtist(cursor.getString(getColumnsIndex(COLUMNS_ALL, COLUMN_ALBUMARTIST)));
instance.setAlbum(cursor.getString(getColumnsIndex(COLUMNS_ALL, COLUMN_ALBUM)));
instance.setGenre(cursor.getString(getColumnsIndex(COLUMNS_ALL, COLUMN_GENRE)));
instance.setYear(cursor.getString(getColumnsIndex(COLUMNS_ALL, COLUMN_YEAR)));
instance.setTrackNumber(cursor.getLong(getColumnsIndex(COLUMNS_ALL, COLUMN_TRACKNUMBER)));
instance.setDiscNumber(cursor.getLong(getColumnsIndex(COLUMNS_ALL, COLUMN_DISCNUMBER)));
instance.setDuration(cursor.getLong(getColumnsIndex(COLUMNS_ALL, COLUMN_DURATION)));
instance.setAlbumId(cursor.getLong(getColumnsIndex(COLUMNS_ALL, COLUMN_ALBUMID)));
instance.setClientId(cursor.getString(getColumnsIndex(COLUMNS_ALL, COLUMN_CLIENTID)));
instance.setSourceId(cursor.getString(getColumnsIndex(COLUMNS_ALL, COLUMN_SOURCEID)));
instance.setCpData(cursor.getBlob(getColumnsIndex(COLUMNS_ALL, COLUMN_CPDATA)));
instance.setArtworkFile(cursor.getString(getColumnsIndex(COLUMNS_ALL, COLUMN_ARTWORKFILE)));
return instance;
}
/**
* Loads a track by Id
* @param id The track id
* @return Returns the music track or null
*/
public MusicTrack getById(long id) {
return getItem(TABLE_MUSIC, COLUMNS_ALL, prepareWhere("Id = " + id));
}
/**
* Gets a list of tracks by an album
* @param album The album
* @return Returns the track list
*/
public List<MusicTrack> getByAlbum(Album album) {
return getItems(TABLE_MUSIC, COLUMNS_ALL, prepareWhere("AlbumId = " + album.getAlbumId()), COLUMN_DISCNUMBER + ", " + COLUMN_TRACKNUMBER);
}
/**
* Gets a list of tracks by an playlist
* @param playlist The playlist
* @return Returns the track list
*/
public List<MusicTrack> getByPlaylist(Playlist playlist) {
return getItems(TABLE_MUSIC, COLUMNS_ALL, prepareWhere("ListId = " + playlist.getId()), "LISTITEMS.ID");
}
}

View file

@ -0,0 +1,29 @@
/*
* 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.
*/
package de.arcus.playmusiclib.exceptions;
/**
* Exception will thrown if the database could not be copied or opened
*/
public class CouldNotOpenDatabase extends Exception {
}

View file

@ -0,0 +1,29 @@
/*
* 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.
*/
package de.arcus.playmusiclib.exceptions;
/**
* Exception will thrown if the app dosen't have super user permissions
*/
public class NoSuperUserException extends Exception {
}

View file

@ -0,0 +1,29 @@
/*
* 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.
*/
package de.arcus.playmusiclib.exceptions;
/**
* Exception will thrown if PlayMusic is not installed
*/
public class PlayMusicNotFound extends Exception {
}

View file

@ -0,0 +1,140 @@
/*
* 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.
*/
package de.arcus.playmusiclib.items;
import de.arcus.playmusiclib.PlayMusicManager;
import de.arcus.playmusiclib.datasources.MusicTrackDataSource;
/**
* An Album
*/
public class Album extends MusicList {
// Variables
private long mAlbumId;
private String mAlbum, mAlbumArtist, mArtworkFile;
private String mArtworkPath;
/**
* Creates a data item
* @param playMusicManager The manager
*/
public Album(PlayMusicManager playMusicManager) {
super(playMusicManager);
}
/**
* @return Gets the album id
*/
public long getAlbumId() {
return mAlbumId;
}
/**
* @param albumId Sets the album id
*/
public void setAlbumId(long albumId) {
this.mAlbumId = albumId;
}
/**
* @return Gets the name of the album
*/
public String getAlbum() {
return mAlbum;
}
/**
* @param album Sets the name of the album
*/
public void setAlbum(String album) {
this.mAlbum = album;
}
/**
* @return gets the album artist
*/
public String getAlbumArtist() {
return mAlbumArtist;
}
/**
* @param albumArtist Sets the album artist
*/
public void setAlbumArtist(String albumArtist) {
this.mAlbumArtist = albumArtist;
}
/**
* Gets the artwork file name
*/
public String getArtworkFile() {
return mArtworkFile;
}
/**
* @param artworkFile Sets the artwork file name
*/
public void setArtworkFile(String artworkFile) {
mArtworkFile = artworkFile;
}
@Override
/**
* Gets the album name
*/
public String getTitle() {
return getAlbum();
}
@Override
/**
* Gets the album artist
*/
public String getDescription() {
return getAlbumArtist();
}
@Override
/**
* Loads all tracks from this album
*/
protected void fetchTrackList() {
// Music track data source
MusicTrackDataSource musicTrackDataSource = new MusicTrackDataSource(mPlayMusicManager);
// Load the track list
mMusicTrackList = musicTrackDataSource.getByAlbum(this);
}
@Override
/**
* return Gets the full path to the artwork
*/
public String getArtworkPath() {
// Search for the artwork path
if (mArtworkPath == null)
mArtworkPath = mPlayMusicManager.getArtworkPath(mArtworkFile);
return mArtworkPath;
}
}

View file

@ -0,0 +1,94 @@
/*
* 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.
*/
package de.arcus.playmusiclib.items;
import android.content.Context;
import java.util.List;
import de.arcus.playmusiclib.PlayMusicManager;
/**
* List of {@link de.arcus.playmusiclib.items.MusicTrack MusicTracks}.
* Eg. albums, playlists, etc...
*/
public abstract class MusicList {
/**
* The manager
*/
protected PlayMusicManager mPlayMusicManager;
/**
* Creates a data item
* @param playMusicManager The manager
*/
public MusicList(PlayMusicManager playMusicManager) {
mPlayMusicManager = playMusicManager;
}
/**
* List of all loaded tracks in this list.
* This list will only be loaded if {@link #getMusicTrackList()} was called
*/
protected List<MusicTrack> mMusicTrackList;
public List<MusicTrack> getMusicTrackList() {
// List is requested for the fist time
if (mMusicTrackList == null)
fetchTrackList();
// Return list
return mMusicTrackList;
}
/**
* Loads all tracks from this list.
* Must be overwritten in all extended classes
*/
protected abstract void fetchTrackList();
/**
* Gets the artwork path
* @return Path to the artwork
*/
public abstract String getArtworkPath();
/**
* Gets the title of the list
* @return Title
*/
public abstract String getTitle();
/**
* Gets the description of the list.
* Eg. the artist of the album
* @return Description
*/
public abstract String getDescription();
@Override
public String toString() {
return getTitle();
}
}

View file

@ -0,0 +1,350 @@
/*
* 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.
*/
package de.arcus.playmusiclib.items;
import de.arcus.playmusiclib.PlayMusicManager;
/**
* A single music track from Play Music
*/
public class MusicTrack {
// Variables
private long mId, mSize, mTrackNumber, mDiscNumber, mAlbumId, mLocalCopyType, mLocalCopyStorageType, mDuration;
private String mTitle, mArtist, mAlbum, mAlbumArtist, mLocalCopyPath, mGenre, mYear, mClientId, mSourceId, mArtworkFile;
private byte[] mCpData;
private String mSourceFile;
private String mArtworkPath;
/**
* The manager
*/
private PlayMusicManager mPlayMusicManager;
/**
* Creates a data item
* @param playMusicManager The manager
*/
public MusicTrack(PlayMusicManager playMusicManager) {
mPlayMusicManager = playMusicManager;
}
/**
* @return Gets the track is
*/
public long getId() {
return mId;
}
/**
* @param id Sets the track id
*/
public void setId(long id) {
this.mId = mId;
}
/**
* @return Gets the file size
*/
public long getSize() {
return mSize;
}
/**
* @param size Sets the file size
*/
public void setSize(long size) {
this.mSize = size;
}
/**
* @return Get the track number in the album
*/
public long getTrackNumber() {
return mTrackNumber;
}
/**
* @param trackNumber Sets the track number in the album
*/
public void setTrackNumber(long trackNumber) {
this.mTrackNumber = trackNumber;
}
/**
* @return Gets the disc number in the album
*/
public long getDiscNumber() {
return mDiscNumber;
}
/**
* @param discNumber Sets the disc number in the album
*/
public void setDiscNumber(long discNumber) {
this.mDiscNumber = discNumber;
}
/**
* @return Gets the duration of the track
*/
public long getDuration() {
return mDuration;
}
/**
* @param duration Sets the duration of the track
*/
public void setDuration(long duration) {
this.mDuration = duration;
}
/**
* @return Gets the local copy type
*/
public long getLocalCopyType() {
return mLocalCopyType;
}
/**
* @param localCopyType Sets the local copy type
*/
public void setLocalCopyType(long localCopyType) {
this.mLocalCopyType = localCopyType;
}
/**
* @return Gets the local copy storage type
*/
public long getLocalCopyStorageType() {
return mLocalCopyStorageType;
}
/**
* @param localCopyStorageType Sets the local copy storage type
*/
public void setLocalCopyStorageType(long localCopyStorageType) {
this.mLocalCopyStorageType = localCopyStorageType;
}
/**
* @return Gets the release year of the song
*/
public String getYear() {
return mYear;
}
/**
* @param year Sets the release year of the song
*/
public void setYear(String year) {
this.mYear = year;
}
/**
* @return Gets the album id
*/
public long getAlbumId() {
return mAlbumId;
}
/**
* @param albumId Sets the album id
*/
public void setAlbumId(long albumId) {
this.mAlbumId = albumId;
}
/**
* @return Gets the track title
*/
public String getTitle() {
return mTitle;
}
/**
* @param title Sets the track title
*/
public void setTitle(String title) {
this.mTitle = title;
}
/**
* @return Gets the artist
*/
public String getArtist() {
return mArtist;
}
/**
* @param artist Sets the artist
*/
public void setArtist(String artist) {
this.mArtist = artist;
}
/**
* @return Gets the album artist
*/
public String getAlbumArtist() {
return mAlbumArtist;
}
/**
* @param albumArtist Sets the album artist
*/
public void setAlbumArtist(String albumArtist) {
this.mAlbumArtist = albumArtist;
}
/**
* @return Gets the album title
*/
public String getAlbum() {
return mAlbum;
}
/**
* @param album Sets the album title
*/
public void setAlbum(String album) {
this.mAlbum = album;
}
/**
* @return Gets the local copy path
*/
public String getLocalCopyPath() {
return mLocalCopyPath;
}
/**
* @param localCopyPath Sets the local copy path
*/
public void setLocalCopyPath(String localCopyPath) {
this.mLocalCopyPath = localCopyPath;
}
/**
* @return Gets the genre
*/
public String getGenre() {
return mGenre;
}
/**
* @param genre Sets the genre
*/
public void setGenre(String genre) {
this.mGenre = genre;
}
/**
* @return Gets the client id
*/
public String getClientId() {
return mClientId;
}
/**
* @param clientId Sets the client id
*/
public void setClientId(String clientId) {
this.mClientId = clientId;
}
/**
* @return Gets the source id
*/
public String getSourceId() {
return mSourceId;
}
/**
* @param sourceId Sets the source id
*/
public void setSourceId(String sourceId) {
this.mSourceId = sourceId;
}
/**
* @return Gets the AllAccess decryption key
*/
public byte[] getCpData() {
return mCpData;
}
/**
* @param cpData Sets the AllAccess decryption key
*/
public void setCpData(byte[] cpData) {
this.mCpData = cpData;
}
/**
* @return Gets the artwork file name
*/
public String getArtworkFile() {
return mArtworkFile;
}
/**
* @param artworkFile Sets the artwork file name
*/
public void setArtworkFile(String artworkFile) {
this.mArtworkFile = artworkFile;
}
/**
* @return Returns the source file path
*/
public String getSourceFile() {
// Search for the source file
if (mSourceFile == null)
mSourceFile = mPlayMusicManager.getMusicFile(mLocalCopyPath);
return mSourceFile;
}
/**
* @return Returns the full album artwork path
*/
public String getArtworkPath() {
// Search for the artwork path
if (mArtworkPath == null)
mArtworkPath = mPlayMusicManager.getArtworkPath(mArtworkFile);
return mArtworkPath;
}
/**
* @return Returns if this file is encoded from AllAccess
*/
public boolean isEncoded() {
return (mCpData != null);
}
@Override
public String toString() {
return mTitle;
}
}

View file

@ -0,0 +1,174 @@
/*
* 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.
*/
package de.arcus.playmusiclib.items;
import de.arcus.playmusiclib.PlayMusicManager;
import de.arcus.playmusiclib.R;
import de.arcus.playmusiclib.datasources.MusicTrackDataSource;
/**
* Created by david on 24.01.15.
*/
public class Playlist extends MusicList {
// Variables
private long mId, mListType;
private String mName;
private String mOwnerName;
private String mArtworkFile;
private final static long TYPE_QUEUE = 10;
private final static long TYPE_RADIO = 50;
private String mArtworkPath;
/**
* Creates a data item
* @param playMusicManager The manager
*/
public Playlist(PlayMusicManager playMusicManager) {
super(playMusicManager);
}
/**
* @return Gets the playlist id
*/
public long getId() {
return mId;
}
/**
* @param id Sets the playlist id
*/
public void setId(long id) {
this.mId = id;
}
/**
* @return Gets the playlist type
*/
long getListType() {
return mListType;
}
/**
* @param listType Sets the playlist type
*/
public void setListType(long listType) {
this.mListType = listType;
}
/**
* @return Gets the playlist name
*/
public String getName() {
return mName;
}
/**
* @param name Sets the name of the playlist
*/
public void setName(String name) {
this.mName = name;
}
/**
* @return Gets the name of the playlist owner
*/
public String getOwnerName() {
return mOwnerName;
}
/**
* @param ownerName Sets the name of the playlist owner
*/
public void setOwnerName(String ownerName) {
this.mOwnerName = ownerName;
}
/**
* @return Gets the artwork file name
*/
public String getArtworFile() {
return mArtworkFile;
}
/**
* @param artworkfile Sets the artwork file name
*/
public void setArtworkFile(String artworkfile) {
this.mArtworkFile = artworkfile;
}
@Override
/**
* Gets the album name
*/
public String getTitle() {
// Play queue
if (getListType() == TYPE_QUEUE) {
return mPlayMusicManager.getContext().getString(R.string.music_playlist_queue);
}
return getName();
}
@Override
/**
* Gets the playlist name
*/
public String getDescription() {
// Play queue
if (getListType() == TYPE_QUEUE) {
return mPlayMusicManager.getContext().getString(R.string.music_playlist_queue_description);
}
// Radio
if (getListType() == TYPE_RADIO) {
return mPlayMusicManager.getContext().getString(R.string.music_playlist_radio);
}
return getOwnerName();
}
@Override
/**
* Loads all tracks from this playlist
*/
protected void fetchTrackList() {
// Music track data source
MusicTrackDataSource musicTrackDataSource = new MusicTrackDataSource(mPlayMusicManager);
// Load the track list
mMusicTrackList = musicTrackDataSource.getByPlaylist(this);
}
@Override
/**
* return Gets the full path to the artwork
*/
public String getArtworkPath() {
// Search for the artwork path
if (mArtworkPath == null)
mArtworkPath = mPlayMusicManager.getArtworkPath(mArtworkFile);
return mArtworkPath;
}
}

View file

@ -0,0 +1,28 @@
<?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.
-->
<resources>
<string name="music_playlist_radio">Radiosender</string>
<string name="music_playlist_queue_description">Zuletzt angehört</string>
<string name="music_playlist_queue">Wiedergabeliste</string>
</resources>

View file

@ -0,0 +1,28 @@
<?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.
-->
<resources>
<string name="music_playlist_queue">Play queue</string>
<string name="music_playlist_queue_description">Recently played</string>
<string name="music_playlist_radio">Radio</string>
</resources>

View file

@ -22,4 +22,4 @@
include ':app', ':framework'
include ':app', ':framework', ':playmusiclib'