From 204829ac5b395f87c5ab45e89b5b273a76687e58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?yJan=20Christian=20Gr=C3=BCnhage?= Date: Sat, 22 Apr 2017 15:40:29 +0200 Subject: [PATCH] Fixes for #30 and #25, as well as some other little things: - Disabled JACK Toolchain, moved to native Java 8 support instead. - Updated gradle plugin from 2.3 to 2.4-alpha6 - Removed the paragraph about bithub from the README - Rephrased some things about why 5.0 is minimum - Made analytics opt-in on first launch - Extracted strings from the intro - Added [LeakCanary](https://github.com/square/leakcanary) --- README.md | 7 +- build.gradle | 2 +- .../SuperUserPermissionRequestListener.java | 4 - .../de/arcus/framework/utils/FileTools.java | 35 +++++--- playmusicexporter/build.gradle | 6 +- .../src/main/AndroidManifest.xml | 3 +- .../playmusicexporter/PlayMusicExporter.java | 24 ++++++ .../playmusicexporter/activities/Intro.java | 86 +++++++++++-------- .../MusicContainerListActivity.java | 16 ++-- .../services/ExportAllService.java | 6 +- .../services/ExportService.java | 6 +- .../PlayMusicExporterPreferences.java | 13 +++ .../src/main/res/drawable/ic_error_white.xml | 9 ++ .../src/main/res/values/strings.xml | 24 +++++- .../src/main/res/xml/pref_about.xml | 11 ++- 15 files changed, 179 insertions(+), 73 deletions(-) create mode 100644 playmusicexporter/src/main/java/re/jcg/playmusicexporter/PlayMusicExporter.java create mode 100644 playmusicexporter/src/main/res/drawable/ic_error_white.xml diff --git a/README.md b/README.md index c59e165..5a986a7 100644 --- a/README.md +++ b/README.md @@ -19,8 +19,9 @@ There is also a nice library you can simply use in your projects. **This app and the included library will require root access to your device! If your device is not rooted you can neither use this app nor the library.** -This app uses API Level 21, which has been introduced by Android Lollipop. If you use KitKat -or lower, this app will not work. +This app uses multiple features introduced in API Level 21 (Android 5.0 Lollipop), the JobScheduler +and the the folder selection of the storage access framework. +If you use KitKat or lower, this app will not work. ### Credits @@ -57,8 +58,6 @@ If you are unable to do that, but you still want to support this project, you co 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. diff --git a/build.gradle b/build.gradle index 34e6830..f2c0b11 100644 --- a/build.gradle +++ b/build.gradle @@ -27,7 +27,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:2.3.0' + classpath 'com.android.tools.build:gradle:2.4.0-alpha6' } } diff --git a/framework/src/main/java/de/arcus/framework/superuser/SuperUserPermissionRequestListener.java b/framework/src/main/java/de/arcus/framework/superuser/SuperUserPermissionRequestListener.java index a70534a..8d0331c 100644 --- a/framework/src/main/java/de/arcus/framework/superuser/SuperUserPermissionRequestListener.java +++ b/framework/src/main/java/de/arcus/framework/superuser/SuperUserPermissionRequestListener.java @@ -1,9 +1,5 @@ package de.arcus.framework.superuser; -/** - * Created by jcgruenhage on 1/18/17. - */ - public interface SuperUserPermissionRequestListener { void superUserGranted(boolean granted); } diff --git a/framework/src/main/java/de/arcus/framework/utils/FileTools.java b/framework/src/main/java/de/arcus/framework/utils/FileTools.java index b6a0c45..9589ce6 100644 --- a/framework/src/main/java/de/arcus/framework/utils/FileTools.java +++ b/framework/src/main/java/de/arcus/framework/utils/FileTools.java @@ -22,6 +22,8 @@ package de.arcus.framework.utils; +import android.os.Environment; + import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; @@ -42,10 +44,12 @@ public class FileTools { /** * Private constructor */ - private FileTools() {} + private FileTools() { + } /** * Creates a directory if it not exists + * * @param dir Directory path * @return Returns true if the directory was created or already exists */ @@ -75,6 +79,7 @@ public class FileTools { /** * Checks if the directory exists + * * @param dir Path of the file * @return Return whether the directory exists */ @@ -87,6 +92,7 @@ public class FileTools { /** * Creates an empty file + * * @param file File path * @return Returns true if the file was successfully created */ @@ -98,7 +104,7 @@ public class FileTools { return (new File(file)).createNewFile(); } catch (IOException e) { // Failed - Logger.getInstance().logError("FileCreate", "Could not create file: " + e.getMessage()); + Logger.getInstance().logError("FileCreate", "Could not create file: " + e.getMessage()); return false; } @@ -106,7 +112,8 @@ public class FileTools { /** * Moves a file - * @param src Soruce path + * + * @param src Soruce path * @param dest Destination path * @return Return whether the moving was successful */ @@ -122,7 +129,8 @@ public class FileTools { /** * Copies a stream - * @param inputStream Source stream + * + * @param inputStream Source stream * @param outputStream Destination stream * @return Return whether the stream was copied successful */ @@ -155,7 +163,8 @@ public class FileTools { /** * Copies a file - * @param src Source path + * + * @param src Source path * @param dest Destination path * @return Return whether the file was copied successful */ @@ -195,6 +204,7 @@ public class FileTools { /** * Deletes a file + * * @param file Path of the file * @return Returns whether the deleting was successful */ @@ -205,6 +215,7 @@ public class FileTools { /** * Checks if the file exists + * * @param file Path of the file * @return Return whether the file exists */ @@ -217,6 +228,7 @@ public class FileTools { /** * Checks whether the file or directory is a link + * * @param path Path of the file / directory * @return Returns whether the file or directory is a link */ @@ -235,6 +247,7 @@ public class FileTools { /** * Gets the root canonical file of a symbolic link + * * @param path The path * @return The root file */ @@ -244,6 +257,7 @@ public class FileTools { /** * Gets the root canonical file of a symbolic link + * * @param file The file * @return The root file */ @@ -269,19 +283,20 @@ public class FileTools { /** * Gets all storages; eg. all sdcards + * * @return List of all storages */ public static String[] getStorages() { List storages = new ArrayList<>(); // Hard coded mount points - final String[] mountPointBlacklist = new String[] { "/mnt/tmp", "/mnt/factory", "/mnt/obb", "/mnt/asec", "/mnt/secure", "/mnt/media_rw", "/mnt/shell", "/storage/emulated" }; - final String[] mountPointDirectories = new String[] { "/mnt", "/storage" }; - final String[] mountPoints = new String[] { "/sdcard", "/external_sd" }; + final String[] mountPointBlacklist = new String[]{"/mnt/tmp", "/mnt/factory", "/mnt/obb", "/mnt/asec", "/mnt/secure", "/mnt/media_rw", "/mnt/shell", "/storage/emulated"}; + final String[] mountPointDirectories = new String[]{"/mnt", "/storage"}; + final String[] mountPoints = new String[]{Environment.getExternalStorageDirectory().getAbsolutePath(), "/external_sd"}; // Adds all mount point directories - for(String mountPointDirectory : mountPointDirectories) { + for (String mountPointDirectory : mountPointDirectories) { // Checks all subdirectories File dir = getRootCanonicalFile(mountPointDirectory); if (dir.exists() && dir.isDirectory()) { @@ -301,7 +316,7 @@ public class FileTools { } // Adds all direct mount points - for(String mountPoint : mountPoints) { + for (String mountPoint : mountPoints) { File file = getRootCanonicalFile(mountPoint); if (file.isDirectory() && file.canRead()) { if (!storages.contains(file.getAbsolutePath())) diff --git a/playmusicexporter/build.gradle b/playmusicexporter/build.gradle index 954e749..4d383fb 100644 --- a/playmusicexporter/build.gradle +++ b/playmusicexporter/build.gradle @@ -35,9 +35,6 @@ android { versionName '0.9.6.0' vectorDrawables.useSupportLibrary = true - jackOptions { - enabled true - } buildConfigField "java.util.Date", "BUILD_TIME", "new java.util.Date(" + System.currentTimeMillis() + "L)" } buildTypes { @@ -62,4 +59,7 @@ dependencies { 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' + debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5' + releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5' + testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5' } diff --git a/playmusicexporter/src/main/AndroidManifest.xml b/playmusicexporter/src/main/AndroidManifest.xml index 09c1b32..4c4aa17 100644 --- a/playmusicexporter/src/main/AndroidManifest.xml +++ b/playmusicexporter/src/main/AndroidManifest.xml @@ -33,7 +33,8 @@ android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:supportsRtl="true" - android:theme="@style/AppTheme"> + android:theme="@style/AppTheme" + android:name=".PlayMusicExporter"> diff --git a/playmusicexporter/src/main/java/re/jcg/playmusicexporter/PlayMusicExporter.java b/playmusicexporter/src/main/java/re/jcg/playmusicexporter/PlayMusicExporter.java new file mode 100644 index 0000000..8df607a --- /dev/null +++ b/playmusicexporter/src/main/java/re/jcg/playmusicexporter/PlayMusicExporter.java @@ -0,0 +1,24 @@ +package re.jcg.playmusicexporter; + +import android.app.Application; + +import com.squareup.leakcanary.LeakCanary; + + +/** + * Android Application. + * Normally, we would not need to extend this, but it is required for + * the leak detection library we use. + * See {@link LeakCanary} + */ +public class PlayMusicExporter extends Application { + @Override public void onCreate() { + super.onCreate(); + if (LeakCanary.isInAnalyzerProcess(this)) { + // This process is dedicated to LeakCanary for heap analysis. + // You should not init your app in this process. + return; + } + LeakCanary.install(this); + } +} diff --git a/playmusicexporter/src/main/java/re/jcg/playmusicexporter/activities/Intro.java b/playmusicexporter/src/main/java/re/jcg/playmusicexporter/activities/Intro.java index fa1d05a..6ec3d74 100644 --- a/playmusicexporter/src/main/java/re/jcg/playmusicexporter/activities/Intro.java +++ b/playmusicexporter/src/main/java/re/jcg/playmusicexporter/activities/Intro.java @@ -1,9 +1,9 @@ package re.jcg.playmusicexporter.activities; import android.Manifest; +import android.app.Dialog; import android.content.Intent; import android.content.pm.PackageManager; -import android.graphics.Color; import android.os.Build; import android.os.Bundle; import android.support.annotation.Nullable; @@ -29,49 +29,41 @@ public class Intro extends AppIntro { Fragment warning; Fragment storage; Fragment superuser; + Fragment error; Fragment finish; private void initFragments() { + int color = ContextCompat.getColor(this, R.color.application_main); welcome = AppIntroFragment.newInstance( - "Welcome!", - "This is the Play Music Exporter. It can export songs from Play Music " + - "and save them as MP3 files where you want them to be.", + getString(R.string.intro_welcome_title), + getString(R.string.intro_welcome_description), R.drawable.ic_launcher_transparent, - Color.parseColor("#ef6c00")); + color); warning = AppIntroFragment.newInstance( - "Warning!", - "You are responsible for what you do with this app. Depending on where you live " + - "it might be illegal to use this app. We discourage piracy of music " + - "and other intellectual property. Sharing music you exported with " + - "this tool might be a very bad idea, Google could put an invisible " + - "watermark on the music, so that people can trace the MP3s back to " + - "the owner of the Google account that was used.", + getString(R.string.intro_warning_title), + getString(R.string.intro_warning_description), R.drawable.ic_warning_white, - Color.parseColor("#ef6c00")); + color); storage = AppIntroFragment.newInstance( - "We need access to your storage.", - "We need to access the external storage, " + - "for copying the Play Music database to a folder," + - "where we have the right to work with it. " + - "We also need access to the external storage," + - "to finish up the MP3s, from encrypted without ID3 tags," + - "to decrypted with ID3 tags, before we save them to your export path.", + getString(R.string.intro_storage_title), + getString(R.string.intro_storage_description), R.drawable.ic_folder_white, - Color.parseColor("#ef6c00")); + color); superuser = AppIntroFragment.newInstance( - "We need root access.", - "Some of the files we need to access are in the private folders of Play Music. " + - "Android prevents apps from accessing the private folders " + - "of other apps, but luckily, you can circumvent this protection " + - "with root access. Without root access this app can't do anything.", + getString(R.string.intro_superuser_title), + getString(R.string.intro_superuser_description), R.drawable.ic_superuser, - Color.parseColor("#ef6c00")); + color); + error = AppIntroFragment.newInstance( + getString(R.string.intro_error_title), + getString(R.string.intro_error_description), + R.drawable.ic_error_white, + color); finish = AppIntroFragment.newInstance( - "Tutorial finished!", - "One note: Should you revoke any of these permission, the tutorial will be " + - "shown again on the next launch.", + getString(R.string.intro_finish_title), + getString(R.string.intro_finish_description), R.drawable.ic_launcher_transparent, - Color.parseColor("#ef6c00")); + color); } @Override @@ -87,6 +79,7 @@ public class Intro extends AppIntro { addSlide(warning); addSlide(storage); addSlide(superuser); + addSlide(error); addSlide(finish); pager.setPagingEnabled(true); @@ -100,7 +93,7 @@ public class Intro extends AppIntro { promptAcceptWarning(); } else if (storage.equals(oldFragment) && superuser.equals(newFragment)) { requestStoragePermission(); - } else if (superuser.equals(oldFragment) && finish.equals(newFragment)) { + } else if (superuser.equals(oldFragment) && error.equals(newFragment)) { SuperUser.askForPermissionInBackground(granted -> { if (!granted) { AlertDialog.Builder builder = @@ -113,9 +106,30 @@ public class Intro extends AppIntro { builder.show(); } }); + } else if (error.equals(oldFragment) && finish.equals(newFragment)) { + promptEnableErrorReporting(); } } + private void promptEnableErrorReporting() { + AlertDialog.Builder builder = new AlertDialog.Builder(this); + Dialog.OnClickListener enable = (dialog, which) -> { + PlayMusicExporterPreferences.setReportStats(true); + dialog.dismiss(); + }; + Dialog.OnClickListener disable = (dialog, which) -> { + PlayMusicExporterPreferences.setReportStats(false); + dialog.dismiss(); + }; + builder.setTitle(R.string.error_alert_dialog_title); + builder.setMessage(R.string.error_alert_dialog_message); + builder.setCancelable(false); + builder.setNegativeButton(R.string.no, disable); + builder.setNeutralButton(R.string.whatever, enable); + builder.setPositiveButton(R.string.yes, enable); + builder.show(); + } + private void requestStoragePermission() { if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) @@ -151,12 +165,12 @@ public class Intro extends AppIntro { private void promptAcceptWarning() { AlertDialog.Builder builder = new AlertDialog.Builder(this); - builder.setTitle("Understood?"); - builder.setMessage("Have you read and understood this?"); + builder.setTitle(R.string.warning_alert_dialog_title); + builder.setMessage(R.string.warning_alert_dialog_message); builder.setCancelable(false); - builder.setNegativeButton("No", ((dialog, which) + builder.setNegativeButton(getString(R.string.no), ((dialog, which) -> pager.setCurrentItem(pager.getCurrentItem() - 1))); - builder.setPositiveButton("Yes", (((dialog, which) -> dialog.dismiss()))); + builder.setPositiveButton(getString(R.string.no), (((dialog, which) -> dialog.dismiss()))); builder.show(); } diff --git a/playmusicexporter/src/main/java/re/jcg/playmusicexporter/activities/MusicContainerListActivity.java b/playmusicexporter/src/main/java/re/jcg/playmusicexporter/activities/MusicContainerListActivity.java index bf49109..8f0e87c 100644 --- a/playmusicexporter/src/main/java/re/jcg/playmusicexporter/activities/MusicContainerListActivity.java +++ b/playmusicexporter/src/main/java/re/jcg/playmusicexporter/activities/MusicContainerListActivity.java @@ -108,17 +108,15 @@ public class MusicContainerListActivity extends AppCompatActivity @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - - 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()) { startActivity(new Intent(this, Intro.class)); finish(); } else { - + if (PlayMusicExporterPreferences.getReportStats()) { + Countly.sharedInstance().init(this, getString(R.string.countly_url), getString(R.string.countly_token), null, DeviceId.Type.OPEN_UDID); + Countly.sharedInstance().enableCrashReporting(); + } setContentView(R.layout.activity_track_list); @@ -379,12 +377,14 @@ public class MusicContainerListActivity extends AppCompatActivity @Override public void onStart() { super.onStart(); - Countly.sharedInstance().onStart(this); + if (PlayMusicExporterPreferences.getReportStats()) + Countly.sharedInstance().onStart(this); } @Override public void onStop() { - Countly.sharedInstance().onStop(); + if (PlayMusicExporterPreferences.getReportStats()) + Countly.sharedInstance().onStop(); super.onStop(); } } diff --git a/playmusicexporter/src/main/java/re/jcg/playmusicexporter/services/ExportAllService.java b/playmusicexporter/src/main/java/re/jcg/playmusicexporter/services/ExportAllService.java index f136677..cd2600d 100644 --- a/playmusicexporter/src/main/java/re/jcg/playmusicexporter/services/ExportAllService.java +++ b/playmusicexporter/src/main/java/re/jcg/playmusicexporter/services/ExportAllService.java @@ -75,7 +75,8 @@ 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); + if (PlayMusicExporterPreferences.getReportStats()) + Countly.sharedInstance().recordEvent("Exported Song", 1); } else { Log.i(TAG, "Failed to export Music Track: " + getStringForTrack(lTrack)); } @@ -90,7 +91,8 @@ public class ExportAllService extends IntentService { Log.i(TAG, "Automatic export failed, because the URI is invalid."); } else throw e; } catch (Exception e) { - Countly.sharedInstance().logException(e); + if (PlayMusicExporterPreferences.getReportStats()) + Countly.sharedInstance().logException(e); e.printStackTrace(); } } diff --git a/playmusicexporter/src/main/java/re/jcg/playmusicexporter/services/ExportService.java b/playmusicexporter/src/main/java/re/jcg/playmusicexporter/services/ExportService.java index b151357..42d723a 100644 --- a/playmusicexporter/src/main/java/re/jcg/playmusicexporter/services/ExportService.java +++ b/playmusicexporter/src/main/java/re/jcg/playmusicexporter/services/ExportService.java @@ -203,13 +203,15 @@ public class ExportService extends IntentService { // Exports the song try { if (playMusicManager.exportMusicTrack(mTrackCurrent, uri, path, PlayMusicExporterPreferences.getFileOverwritePreference())) { - Countly.sharedInstance().recordEvent("Exported Song", 1); + if (PlayMusicExporterPreferences.getReportStats()) + Countly.sharedInstance().recordEvent("Exported Song", 1); } else { // Export failed mTracksFailed ++; } } catch (Exception e) { - Countly.sharedInstance().logException(e); + if (PlayMusicExporterPreferences.getReportStats()) + Countly.sharedInstance().logException(e); e.printStackTrace(); } } else { diff --git a/playmusicexporter/src/main/java/re/jcg/playmusicexporter/settings/PlayMusicExporterPreferences.java b/playmusicexporter/src/main/java/re/jcg/playmusicexporter/settings/PlayMusicExporterPreferences.java index cad79d5..cd95eab 100644 --- a/playmusicexporter/src/main/java/re/jcg/playmusicexporter/settings/PlayMusicExporterPreferences.java +++ b/playmusicexporter/src/main/java/re/jcg/playmusicexporter/settings/PlayMusicExporterPreferences.java @@ -7,6 +7,7 @@ import android.net.Uri; import android.os.Environment; import android.preference.PreferenceManager; +import re.jcg.playmusicexporter.BuildConfig; import re.jcg.playmusicexporter.fragments.NavigationDrawerFragment; public class PlayMusicExporterPreferences { @@ -49,6 +50,9 @@ public class PlayMusicExporterPreferences { public static final String SETUP_DONE = "preference_setup_done"; public static final boolean SETUP_DONE_DEFAULT = false; + public static final String REPORT_STATS = "preference_report_stats"; + public static final boolean REPORT_STATS_DEFAULT = true; + private PlayMusicExporterPreferences() { } @@ -175,4 +179,13 @@ public class PlayMusicExporterPreferences { public static void setAlbumArtSize(int size) { preferences.edit().putString(EXPORT_ALBUM_ART_SIZE, "" + size).apply(); } + + public static boolean getReportStats() { + //Never report stats in debug builds + return preferences.getBoolean(REPORT_STATS, REPORT_STATS_DEFAULT) && !BuildConfig.DEBUG; + } + + public static void setReportStats(boolean reportStats) { + preferences.edit().putBoolean(REPORT_STATS, reportStats).apply(); + } } diff --git a/playmusicexporter/src/main/res/drawable/ic_error_white.xml b/playmusicexporter/src/main/res/drawable/ic_error_white.xml new file mode 100644 index 0000000..0ed3ff1 --- /dev/null +++ b/playmusicexporter/src/main/res/drawable/ic_error_white.xml @@ -0,0 +1,9 @@ + + + diff --git a/playmusicexporter/src/main/res/values/strings.xml b/playmusicexporter/src/main/res/values/strings.xml index ef35a24..0321fcd 100644 --- a/playmusicexporter/src/main/res/values/strings.xml +++ b/playmusicexporter/src/main/res/values/strings.xml @@ -122,7 +122,7 @@ Website - + Mp3agic ID3 Libary Michael Patricios © 2006–2013 @@ -149,4 +149,26 @@ Refresh Music Database Reloaded Test Crash Handler + Support me + Donate to the current developer via Bitcoin. This requires that you have installed a bitcoin wallet on your phone. For donations from a computer, see the homepage. + + Welcome! + This is the Play Music Exporter. It can export songs from Play Music and save them as MP3 files where you want them to be. + Warning! + You are responsible for what you do with this app. Depending on where you live it might be illegal to use this app. We discourage piracy of music and other intellectual property. Sharing music you exported with this tool might be a very bad idea, Google could put an invisible watermark on the music, so that people can trace the MP3s back to the owner of the Google account that was used. + We need access to your storage. + We need to access the external storage, for copying the Play Music database to a folder, where we have the right to work with it. We also need access to the external storage, to finish up the MP3s, from encrypted without ID3 tags, to decrypted with ID3 tags, before we save them to your export path. + Some of the files we need to access are in the private folders of Play Music. Android prevents apps from accessing the private folders of other apps, but luckily, you can circumvent this protection with root access. Without root access this app can\'t do anything. + We need root access. + No single piece of software is perfect. To get this closer though, we have added automated error reporting to this app. If you would like to help make this app better, let us enable that. If you don\'t enable this and something does not work, you are on your own, since we can\'t help you without having the logs this provides us with. The data collected by this does not include anything that could be used to identify you. (You will be prompted to either accept or deny that when you go to the next slide) + Anonymous error reporting. + One note: Should you revoke any of these permission, the tutorial will be shown again on the next launch. + Introduction finished! + Anonymous error reporting? + In order to make this app better, please enable automatic crash reporting. You wont ever get any help, if you disable this, since we can\'t fix bugs without the logs provided by this. The data collected by this does not include anything that could be used to identify you. + No + Yes + I don\'t care, just let me use the app! + Understood? + Have you read and understood this? diff --git a/playmusicexporter/src/main/res/xml/pref_about.xml b/playmusicexporter/src/main/res/xml/pref_about.xml index 8536818..6be9309 100644 --- a/playmusicexporter/src/main/res/xml/pref_about.xml +++ b/playmusicexporter/src/main/res/xml/pref_about.xml @@ -12,7 +12,7 @@ - + + + + + +