[
  {
    "path": ".gitignore",
    "content": ".gradle\n/local.properties\n.idea\n.DS_Store\n/build\n/captures\n/*.iml\n\n/app/app-release.apk\napp/app.iml\napp/manifest-merger-release-report.txt\n\nsecret.xml\ngoogle-services.json\n"
  },
  {
    "path": ".travis.yml",
    "content": "language: android\njdk: oraclejdk8\n\nbranches:\n  only:\n    - master\n\nscript: \"./gradlew clean assembleDebug\"\n\nandroid:\n  components:\n      - platform-tools\n      - tools\n      - build-tools-28.0.3\n      - android-28\n      - extra-android-m2repository\n"
  },
  {
    "path": "Changelog.md",
    "content": "#Changelog\n\nv0.21b-\n\n* Add Homescreen widget\n* View playlists in list, grid and default\n* Delete playlists, clear auto playlists and hide auto playlists\n* Remove songs from playlists, reorder playlists\n* Delete songs from device\n\nv0.14b-\n\n* Added Theme engine\n* Performance improvements\n* Many bugs fixed\n\nv0.13b-\n\n* Create playlists and add songs to playlists\n* Go to artist, album, add to queue, play next in song popup menu\n* Pause music playback when headphones removed\n* Fixed equalizer not working\n* Fixed track numbers being wrong\n* Bluetooth controls\n* Show song duration in Timber3 now playing style\n* Added Chinese,French,German and Italian translation\n* Bug fixes and improvements\n\nv0.122b-\n* Fix crash in search activity\n* Fix one time crash in marshmallow after allowing permission\n* Added albums view options(list,grid)\n\nv0.12b-\n* Fix crash in artist detail and search\n* Add intent for equalizer\n* Ability to sort songs, albums and artists according to year,duration,track,number of songs, a-z, z-a etc\n\n\nv0.11b-\n* Fix crash on asynctask onpostexecute\n* Fix crash on onTouchEvent in NavigationView\n* Updated more material launcher and notification icon\n* Reduced Apk size, enabled Proguard\n* Added option to choose default start page\n* Added option to remember the last page opened\n* Fixed blurred albumart not changing if albumart not available\n\nv0.1b\n* Initial Release\n"
  },
  {
    "path": "README.md",
    "content": "# Timber\n[![Build Status](https://travis-ci.org/naman14/Timber.svg?branch=master)](https://travis-ci.org/naman14/Timber)\n\nMaterial Design Music Player\n\nThis project is no longer in active development. Please refer to [TimberX](https://github.com/naman14/TimberX) instead\n\n<a href='https://play.google.com/store/apps/details?id=naman14.timber&pcampaignid=MKT-Other-global-all-co-prtnr-py-PartBadge-Mar2515-1'><img alt='Get it on Google Play' src='https://play.google.com/intl/en_us/badges/images/generic/en_badge_web_generic.png' height=90px/></a>\n\n<a href='https://f-droid.org/repository/browse/?fdid=naman14.timber'><img alt='Get it on F-Droid' src='https://guardianproject.info/wp-content/uploads/2014/07/logo-fdroid.png' height=60px/></a>\n\n## Screenshots\n\n<img src=\"https://raw.githubusercontent.com/naman14/Timber/master/graphics/screen2.png\" width=\"360\" height=\"640\">\n<img src=\"https://raw.githubusercontent.com/naman14/Timber/master/graphics/screen3.png\" width=\"360\" height=\"640\">\n<img src=\"https://raw.githubusercontent.com/naman14/Timber/master/graphics/screen7.png\" width=\"360\" height=\"640\">\n<img src=\"https://raw.githubusercontent.com/naman14/Timber/master/graphics/screen8.png\" width=\"360\" height=\"640\">\n<img src=\"https://raw.githubusercontent.com/naman14/Timber/master/graphics/screen9.jpg\" width=\"330\" height=\"640\">\n<img src=\"https://raw.githubusercontent.com/naman14/Timber/master/graphics/screen10.jpg\" width=\"330\" height=\"640\">\n<img src=\"https://raw.githubusercontent.com/naman14/Timber/master/graphics/screen1.png\" width=\"360\" height=\"640\">\n<img src=\"https://raw.githubusercontent.com/naman14/Timber/master/graphics/screen5.png\" width=\"360\" height=\"640\">\n<img src=\"https://raw.githubusercontent.com/naman14/Timber/master/graphics/screen12.jpg\" width=\"360\" height=\"640\">\n<img src=\"https://raw.githubusercontent.com/naman14/Timber/master/graphics/screen4.png\" width=\"360\" height=\"640\">\n<img src=\"https://raw.githubusercontent.com/naman14/Timber/master/graphics/screen6.png\" width=\"360\" height=\"640\">\n\n## Features\n- Material design\n- Browse Songs, Albums, Artists\n- Create and edit playlists\n- 6 different now playing styles\n- Homescreen widgets\n- Browse device folders\n- Dark theme and UI customisability\n- Gestures for track switching\n- LastFM scrobble\n- Android Wear and Android Auto support\n- Playing queue in notification (Xposed)\n- Lyrics support\n- Chromecast support\n\n## Changelog\n\nChangelog is available [here](https://github.com/naman14/Timber/blob/master/Changelog.md)\n\n## Credits\n\n* CyanogenMod's [Eleven Music Player](https://github.com/CyanogenMod/android_packages_apps_Eleven)\n* [TimelyTextView](https://github.com/adnan-SM/TimelyTextView)\n* [MultiViewPager](https://github.com/Pixplicity/MultiViewPager)\n* [PlayPauseButton](https://github.com/recruit-lifestyle/PlayPauseButton)\n* [CircularSeekBar](https://github.com/devadvance/circularseekbar)\n* [Nammu](https://github.com/tajchert/Nammu)\n\n# Donate\nPaypal donation email-\nnamandwivedi14@gmail.com\n\n\n## License\n\n>(c) 2015 Naman Dwivedi \n\n>This is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. \n\n>This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. \n\n>You should have received a copy of the GNU General Public License along with this app. If not, see <https://www.gnu.org/licenses/>.\n"
  },
  {
    "path": "app/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "app/build.gradle",
    "content": "apply plugin: 'com.android.application'\napply plugin: 'com.google.gms.google-services'\napply plugin: 'com.google.firebase.crashlytics'\n\nandroid {\n    compileSdkVersion rootProject.compileSdkVersion\n\n    defaultConfig {\n        applicationId \"naman14.timber\"\n        minSdkVersion rootProject.minSdkVersion\n        targetSdkVersion rootProject.targetSdkVersion\n        versionCode 22\n        versionName \"1.8\"\n        //renderscript support mode is not supported for 21+ with gradle version 2.0\n        renderscriptTargetApi 16\n        renderscriptSupportModeEnabled false\n        multiDexEnabled true\n    }\n\n    buildTypes {\n        release {\n            minifyEnabled false\n            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n\n        }\n        debug {\n            versionNameSuffix \"-debug\"\n            minifyEnabled false\n            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n        }\n    }\n\n    lintOptions {\n        disable 'MissingTranslation'\n        disable 'ExtraTranslation'\n    }\n\n}\n\nrepositories {\n    jcenter()\n}\n\ndependencies {\n    implementation fileTree(dir: 'libs', include: ['*.jar'])\n    implementation \"androidx.appcompat:appcompat:1.0.2\"\n    implementation \"com.google.android.material:material:1.0.0\"\n    implementation \"androidx.cardview:cardview:1.0.0\"\n    implementation \"androidx.recyclerview:recyclerview:1.1.0\"\n    implementation \"androidx.palette:palette:1.0.0\"\n    implementation \"androidx.percentlayout:percentlayout:1.0.0\"\n    implementation 'androidx.multidex:multidex:2.0.1'\n\n    implementation \"androidx.mediarouter:mediarouter:1.1.0\"\n    implementation 'com.google.android.gms:play-services-cast-framework:16.1.2'\n\n    implementation 'com.nostra13.universalimageloader:universal-image-loader:1.9.4'\n    implementation 'net.steamcrafted:materialiconlib:1.1.4'\n    implementation 'com.squareup.retrofit:retrofit:1.9.0'\n    implementation 'com.squareup.okhttp:okhttp-urlconnection:2.3.0'\n    implementation 'com.squareup.okhttp:okhttp:2.3.0'\n    implementation 'com.google.code.gson:gson:2.3'\n    implementation 'de.Maxr1998:track-selector-lib:1.2'\n\n    implementation 'com.afollestad.material-dialogs:core:0.9.0.2'\n    implementation 'com.afollestad.material-dialogs:commons:0.9.0.2'\n\n    implementation('com.github.naman14:app-theme-engine:0.5.2@aar') {\n        transitive = true\n    }\n    implementation 'com.google.firebase:firebase-crashlytics:17.2.2'\n\n    implementation 'com.anjlab.android.iab.v3:library:1.0.+'\n    implementation 'org.nanohttpd:nanohttpd:2.3.1'\n}\n\napply from: '../mock.gradle'\n"
  },
  {
    "path": "app/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# By default, the flags in this file are appended to flags specified\n# in /Users/naman/sdk/tools/proguard/proguard-android.txt\n# You can edit the include path and order by changing the proguardFiles\n# directive in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# Add any project specific keep options here:\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n\n# retrofit\n-keep class com.squareup.okhttp.** { *; }\n-keep class retrofit.** { *; }\n-keep interface com.squareup.okhttp.** { *; }\n-keep class com.naman14.timber.lastfmapi.models.** { *; }\n\n-keep class android.support.v4.app.** { *; }\n-keep interface android.support.v4.app.** { *; }\n-keep class android.support.v7.app.** { *; }\n-keep interface android.support.v7.app.** { *; }\n\n-dontwarn com.squareup.okhttp.**\n-dontwarn okio.**\n-dontwarn retrofit.**\n-dontwarn rx.**\n\n-keepclasseswithmembers class * {\n    @retrofit.http.* <methods>;\n}\n\n-keepattributes Exceptions\n-keepattributes Signature\n-keepattributes *Annotation*\n\n#renderscript\n-keep class android.support.v8.renderscript.** { *; }\n\n#searchview\n-keep class android.support.v7.widget.SearchView { *; }\n"
  },
  {
    "path": "app/src/androidTest/java/com/naman14/timber/ApplicationTest.java",
    "content": "package com.naman14.timber;\n\nimport android.app.Application;\nimport android.test.ApplicationTestCase;\n\n/**\n * <a href=\"http://d.android.com/tools/testing/testing_android.html\">Testing Fundamentals</a>\n */\npublic class ApplicationTest extends ApplicationTestCase<Application> {\n    public ApplicationTest() {\n        super(Application.class);\n    }\n}"
  },
  {
    "path": "app/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    package=\"com.naman14.timber\">\n\n    <uses-sdk tools:overrideLibrary=\"de.Maxr1998.trackselectorlib\" />\n\n    <uses-permission android:name=\"android.permission.WRITE_EXTERNAL_STORAGE\" />\n    <uses-permission android:name=\"android.permission.READ_EXTERNAL_STORAGE\" />\n    <uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\" />\n    <uses-permission android:name=\"android.permission.INTERNET\" />\n    <uses-permission android:name=\"android.permission.WAKE_LOCK\" />\n    <uses-permission android:name=\"android.permission.BROADCAST_STICKY\" />\n    <uses-permission android:name=\"com.android.vending.BILLING\" />\n\n\n    <application\n        android:name=\".TimberApp\"\n        android:allowBackup=\"false\"\n        android:icon=\"@mipmap/ic_launcher\"\n        android:label=\"Timber\"\n        android:theme=\"@style/AppTheme.FullScreen.Light\"\n        tools:replace=\"android:allowBackup, android:label\">\n\n        <activity\n            android:name=\".activities.MainActivity\"\n            android:label=\"Timber\"\n            android:screenOrientation=\"portrait\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\" />\n\n                <category android:name=\"android.intent.category.CATEGORY_APP_MUSIC\" />\n                <category android:name=\"android.intent.category.LAUNCHER\" />\n            </intent-filter>\n            <intent-filter>\n                <action android:name=\"android.intent.action.VIEW\" />\n                <category android:name=\"android.intent.category.DEFAULT\" />\n\n                <data android:scheme=\"file\" />\n                <data android:mimeType=\"audio/*\" />\n                <data android:mimeType=\"application/ogg\" />\n                <data android:mimeType=\"application/x-ogg\" />\n                <data android:mimeType=\"application/itunes\" />\n            </intent-filter>\n            <intent-filter>\n                <action android:name=\"android.intent.action.VIEW\" />\n\n                <category android:name=\"android.intent.category.DEFAULT\" />\n                <category android:name=\"android.intent.category.BROWSABLE\" />\n\n                <data android:scheme=\"http\" />\n                <data android:mimeType=\"audio/*\" />\n                <data android:mimeType=\"application/ogg\" />\n                <data android:mimeType=\"application/x-ogg\" />\n                <data android:mimeType=\"application/itunes\" />\n            </intent-filter>\n            <intent-filter android:priority=\"-1\">\n                <action android:name=\"android.intent.action.VIEW\" />\n\n                <category android:name=\"android.intent.category.DEFAULT\" />\n                <category android:name=\"android.intent.category.BROWSABLE\" />\n\n                <data android:scheme=\"content\" />\n                <data android:mimeType=\"audio/*\" />\n                <data android:mimeType=\"application/ogg\" />\n                <data android:mimeType=\"application/x-ogg\" />\n                <data android:mimeType=\"application/itunes\" />\n            </intent-filter>\n        </activity>\n\n        <activity\n            android:name=\".activities.SettingsActivity\"\n            android:theme=\"@style/AppThemeNormalLight\" />\n\n        <activity\n            android:name=\".activities.PlaylistDetailActivity\"\n            android:theme=\"@style/AppTheme.FullScreen.Light\" />\n\n        <activity\n            android:name=\".activities.SearchActivity\"\n            android:theme=\"@style/AppThemeNormalLight\" />\n\n        <activity\n            android:name=\".activities.NowPlayingActivity\"\n            android:theme=\"@style/AppTheme.FullScreen.Light\" />\n\n        <activity android:name=\".activities.DonateActivity\"/>\n\n        <activity\n            android:name=\".cast.ExpandedControlsActivity\"\n            android:label=\"@string/app_name\"\n            android:theme=\"@style/AppTheme.ExpandedCastController\"\n            android:screenOrientation=\"portrait\">\n\n        </activity>\n\n        <receiver android:name=\".helpers.MediaButtonIntentReceiver\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.MEDIA_BUTTON\" />\n                <action android:name=\"android.media.AUDIO_BECOMING_NOISY\" />\n            </intent-filter>\n        </receiver>\n        <!-- Music service -->\n        <service\n            android:name=\".MusicService\"\n            android:label=\"@string/app_name\"\n            android:process=\":main\" />\n\n        <service\n            android:name=\".WearBrowserService\"\n            android:exported=\"true\">\n            <intent-filter>\n                <action android:name=\"android.media.browse.MediaBrowserService\" />\n            </intent-filter>\n        </service>\n\n\n        <meta-data\n            android:name=\n                \"com.google.android.gms.cast.framework.OPTIONS_PROVIDER_CLASS_NAME\"\n            android:value=\"com.naman14.timber.cast.CastOptionsProvider\" />\n\n        <receiver android:name=\".widgets.desktop.StandardWidget\" android:label=\"@string/widget_standard\">\n            <intent-filter>\n                <action android:name=\"android.appwidget.action.APPWIDGET_UPDATE\" />\n                <action android:name=\"com.naman14.timber.metachanged\" />\n                <action android:name=\"com.naman14.timber.playstatechanged\" />\n            </intent-filter>\n            <meta-data android:name=\"android.appwidget.provider\"\n                android:resource=\"@xml/widget_standard\" />\n        </receiver>\n\n        <receiver android:name=\".widgets.desktop.WhiteWidget\" android:label=\"@string/widget_white\">\n            <intent-filter>\n                <action android:name=\"android.appwidget.action.APPWIDGET_UPDATE\" />\n                <action android:name=\"com.naman14.timber.metachanged\" />\n                <action android:name=\"com.naman14.timber.playstatechanged\" />\n            </intent-filter>\n            <meta-data android:name=\"android.appwidget.provider\"\n                android:resource=\"@xml/widget_white\" />\n        </receiver>\n\n        <receiver android:name=\".widgets.desktop.SmallWidget\" android:label=\"@string/widget_small\">\n            <intent-filter>\n                <action android:name=\"android.appwidget.action.APPWIDGET_UPDATE\" />\n                <action android:name=\"com.naman14.timber.metachanged\" />\n                <action android:name=\"com.naman14.timber.playstatechanged\" />\n            </intent-filter>\n            <meta-data android:name=\"android.appwidget.provider\"\n                android:resource=\"@xml/widget_small\" />\n        </receiver>\n\n    </application>\n\n</manifest>\n"
  },
  {
    "path": "app/src/main/aidl/com/naman14/timber/ITimberService.aidl",
    "content": "package com.naman14.timber;\n\nimport com.naman14.timber.helpers.MusicPlaybackTrack;\n\ninterface ITimberService\n{\n    void openFile(String path);\n    void open(in long [] list, int position, long sourceId, int sourceType);\n    void stop();\n    void pause();\n    void play();\n    void prev(boolean forcePrevious);\n    void next();\n    void enqueue(in long [] list, int action, long sourceId, int sourceType);\n    void setQueuePosition(int index);\n    void setShuffleMode(int shufflemode);\n    void setRepeatMode(int repeatmode);\n    void moveQueueItem(int from, int to);\n    void refresh();\n    void playlistChanged();\n    boolean isPlaying();\n    long [] getQueue();\n    long getQueueItemAtPosition(int position);\n    int getQueueSize();\n    int getQueuePosition();\n    int getQueueHistoryPosition(int position);\n    int getQueueHistorySize();\n    int[] getQueueHistoryList();\n    long duration();\n    long position();\n    long seek(long pos);\n    void seekRelative(long deltaInMs);\n    long getAudioId();\n    MusicPlaybackTrack getCurrentTrack();\n    MusicPlaybackTrack getTrack(int index);\n    long getNextAudioId();\n    long getPreviousAudioId();\n    long getArtistId();\n    long getAlbumId();\n    String getArtistName();\n    String getTrackName();\n    String getAlbumName();\n    String getPath();\n    int getShuffleMode();\n    int removeTracks(int first, int last);\n    int removeTrack(long id);\n    boolean removeTrackAtPosition(long id, int position);\n    int getRepeatMode();\n    int getMediaMountedCount();\n    int getAudioSessionId();\n}\n\n"
  },
  {
    "path": "app/src/main/aidl/com/naman14/timber/helpers/MusicPlaybackTrack.aidl",
    "content": "package com.naman14.timber.helpers;\n\nparcelable MusicPlaybackTrack;"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/MusicPlayer.java",
    "content": "/*\n * Copyright (C) 2012 Andrew Neal\n * Copyright (C) 2014 The CyanogenMod Project\n * Copyright (C) 2015 Naman Dwivedi\n *\n * Licensed under the Apache License, Version 2.0\n * (the \"License\"); you may not use this file except in compliance with the\n * License. You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law\n * or agreed to in writing, software distributed under the License is\n * distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n * KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\npackage com.naman14.timber;\n\nimport android.app.Activity;\nimport android.content.ComponentName;\nimport android.content.ContentResolver;\nimport android.content.ContentUris;\nimport android.content.ContentValues;\nimport android.content.Context;\nimport android.content.ContextWrapper;\nimport android.content.Intent;\nimport android.content.ServiceConnection;\nimport android.database.Cursor;\nimport android.net.Uri;\nimport android.os.IBinder;\nimport android.os.RemoteException;\nimport android.provider.BaseColumns;\nimport android.provider.MediaStore;\nimport android.widget.Toast;\n\nimport com.naman14.timber.dataloaders.SongLoader;\nimport com.naman14.timber.helpers.MusicPlaybackTrack;\nimport com.naman14.timber.utils.TimberUtils.IdType;\n\nimport java.util.Arrays;\nimport java.util.WeakHashMap;\n\npublic class MusicPlayer {\n\n    private static final WeakHashMap<Context, ServiceBinder> mConnectionMap;\n    private static final long[] sEmptyList;\n    public static ITimberService mService = null;\n    private static ContentValues[] mContentValuesCache = null;\n\n    static {\n        mConnectionMap = new WeakHashMap<Context, ServiceBinder>();\n        sEmptyList = new long[0];\n    }\n\n    public static final ServiceToken bindToService(final Context context,\n                                                   final ServiceConnection callback) {\n\n        Activity realActivity = ((Activity) context).getParent();\n        if (realActivity == null) {\n            realActivity = (Activity) context;\n        }\n        final ContextWrapper contextWrapper = new ContextWrapper(realActivity);\n        contextWrapper.startService(new Intent(contextWrapper, MusicService.class));\n        final ServiceBinder binder = new ServiceBinder(callback,\n                contextWrapper.getApplicationContext());\n        if (contextWrapper.bindService(\n                new Intent().setClass(contextWrapper, MusicService.class), binder, 0)) {\n            mConnectionMap.put(contextWrapper, binder);\n            return new ServiceToken(contextWrapper);\n        }\n        return null;\n    }\n\n    public static void unbindFromService(final ServiceToken token) {\n        if (token == null) {\n            return;\n        }\n        final ContextWrapper mContextWrapper = token.mWrappedContext;\n        final ServiceBinder mBinder = mConnectionMap.remove(mContextWrapper);\n        if (mBinder == null) {\n            return;\n        }\n        mContextWrapper.unbindService(mBinder);\n        if (mConnectionMap.isEmpty()) {\n            mService = null;\n        }\n    }\n\n    public static final boolean isPlaybackServiceConnected() {\n        return mService != null;\n    }\n\n    public static void next() {\n        try {\n            if (mService != null) {\n                mService.next();\n            }\n        } catch (final RemoteException ignored) {\n        }\n    }\n\n    public static void initPlaybackServiceWithSettings(final Context context) {\n\n    }\n\n    public static void asyncNext(final Context context) {\n        final Intent previous = new Intent(context, MusicService.class);\n        previous.setAction(MusicService.NEXT_ACTION);\n        context.startService(previous);\n    }\n\n    public static void previous(final Context context, final boolean force) {\n        final Intent previous = new Intent(context, MusicService.class);\n        if (force) {\n            previous.setAction(MusicService.PREVIOUS_FORCE_ACTION);\n        } else {\n            previous.setAction(MusicService.PREVIOUS_ACTION);\n        }\n        context.startService(previous);\n    }\n\n    public static void playOrPause() {\n        try {\n            if (mService != null) {\n                if (mService.isPlaying()) {\n                    mService.pause();\n                } else {\n                    mService.play();\n                }\n            }\n        } catch (final Exception ignored) {\n        }\n    }\n\n    public static void cycleRepeat() {\n        try {\n            if (mService != null) {\n                switch (mService.getRepeatMode()) {\n                    case MusicService.REPEAT_NONE:\n                        mService.setRepeatMode(MusicService.REPEAT_ALL);\n                        break;\n                    case MusicService.REPEAT_ALL:\n                        mService.setRepeatMode(MusicService.REPEAT_CURRENT);\n                        if (mService.getShuffleMode() != MusicService.SHUFFLE_NONE) {\n                            mService.setShuffleMode(MusicService.SHUFFLE_NONE);\n                        }\n                        break;\n                    default:\n                        mService.setRepeatMode(MusicService.REPEAT_NONE);\n                        break;\n                }\n            }\n        } catch (final RemoteException ignored) {\n        }\n    }\n\n    public static void cycleShuffle() {\n        try {\n            if (mService != null) {\n                switch (mService.getShuffleMode()) {\n                    case MusicService.SHUFFLE_NONE:\n                        mService.setShuffleMode(MusicService.SHUFFLE_NORMAL);\n                        if (mService.getRepeatMode() == MusicService.REPEAT_CURRENT) {\n                            mService.setRepeatMode(MusicService.REPEAT_ALL);\n                        }\n                        break;\n                    case MusicService.SHUFFLE_NORMAL:\n                        mService.setShuffleMode(MusicService.SHUFFLE_NONE);\n                        break;\n                    case MusicService.SHUFFLE_AUTO:\n                        mService.setShuffleMode(MusicService.SHUFFLE_NONE);\n                        break;\n                    default:\n                        break;\n                }\n            }\n        } catch (final RemoteException ignored) {\n        }\n    }\n\n    public static final boolean isPlaying() {\n        if (mService != null) {\n            try {\n                return mService.isPlaying();\n            } catch (final RemoteException ignored) {\n            }\n        }\n        return false;\n    }\n\n    public static final int getShuffleMode() {\n        if (mService != null) {\n            try {\n                return mService.getShuffleMode();\n            } catch (final RemoteException ignored) {\n            }\n        }\n        return 0;\n    }\n\n    public static void setShuffleMode(int mode) {\n        try {\n            if (mService != null) {\n                mService.setShuffleMode(mode);\n            }\n        } catch (RemoteException ignored) {\n\n        }\n    }\n\n    public static final int getRepeatMode() {\n        if (mService != null) {\n            try {\n                return mService.getRepeatMode();\n            } catch (final RemoteException ignored) {\n            }\n        }\n        return 0;\n    }\n\n    public static final String getTrackName() {\n        if (mService != null) {\n            try {\n                return mService.getTrackName();\n            } catch (final RemoteException ignored) {\n            }\n        }\n        return null;\n    }\n\n    public static final String getArtistName() {\n        if (mService != null) {\n            try {\n                return mService.getArtistName();\n            } catch (final RemoteException ignored) {\n            }\n        }\n        return null;\n    }\n\n    public static final String getAlbumName() {\n        if (mService != null) {\n            try {\n                return mService.getAlbumName();\n            } catch (final RemoteException ignored) {\n            }\n        }\n        return null;\n    }\n\n    public static final long getCurrentAlbumId() {\n        if (mService != null) {\n            try {\n                return mService.getAlbumId();\n            } catch (final RemoteException ignored) {\n            }\n        }\n        return -1;\n    }\n\n    public static final long getCurrentAudioId() {\n        if (mService != null) {\n            try {\n                return mService.getAudioId();\n            } catch (final RemoteException ignored) {\n            }\n        }\n        return -1;\n    }\n\n    public static final MusicPlaybackTrack getCurrentTrack() {\n        if (mService != null) {\n            try {\n                return mService.getCurrentTrack();\n            } catch (final RemoteException ignored) {\n            }\n        }\n        return null;\n    }\n\n    public static final MusicPlaybackTrack getTrack(int index) {\n        if (mService != null) {\n            try {\n                return mService.getTrack(index);\n            } catch (final RemoteException ignored) {\n            }\n        }\n        return null;\n    }\n\n    public static final long getNextAudioId() {\n        if (mService != null) {\n            try {\n                return mService.getNextAudioId();\n            } catch (final RemoteException ignored) {\n            }\n        }\n        return -1;\n    }\n\n    public static final long getPreviousAudioId() {\n        if (mService != null) {\n            try {\n                return mService.getPreviousAudioId();\n            } catch (final RemoteException ignored) {\n            }\n        }\n        return -1;\n    }\n\n    public static final long getCurrentArtistId() {\n        if (mService != null) {\n            try {\n                return mService.getArtistId();\n            } catch (final RemoteException ignored) {\n            }\n        }\n        return -1;\n    }\n\n    public static final int getAudioSessionId() {\n        if (mService != null) {\n            try {\n                return mService.getAudioSessionId();\n            } catch (final RemoteException ignored) {\n            }\n        }\n        return -1;\n    }\n\n    public static final long[] getQueue() {\n        try {\n            if (mService != null) {\n                return mService.getQueue();\n            } else {\n            }\n        } catch (final RemoteException ignored) {\n        }\n        return sEmptyList;\n    }\n\n    public static final long getQueueItemAtPosition(int position) {\n        try {\n            if (mService != null) {\n                return mService.getQueueItemAtPosition(position);\n            } else {\n            }\n        } catch (final RemoteException ignored) {\n        }\n        return -1;\n    }\n\n    public static final int getQueueSize() {\n        try {\n            if (mService != null) {\n                return mService.getQueueSize();\n            } else {\n            }\n        } catch (final RemoteException ignored) {\n        }\n        return 0;\n    }\n\n    public static final int getQueuePosition() {\n        try {\n            if (mService != null) {\n                return mService.getQueuePosition();\n            }\n        } catch (final RemoteException ignored) {\n        }\n        return 0;\n    }\n\n    public static void setQueuePosition(final int position) {\n        if (mService != null) {\n            try {\n                mService.setQueuePosition(position);\n            } catch (final RemoteException ignored) {\n            }\n        }\n    }\n\n    public static void refresh() {\n        try {\n            if (mService != null) {\n                mService.refresh();\n            }\n        } catch (final RemoteException ignored) {\n        }\n    }\n\n    public static final int getQueueHistorySize() {\n        if (mService != null) {\n            try {\n                return mService.getQueueHistorySize();\n            } catch (final RemoteException ignored) {\n            }\n        }\n        return 0;\n    }\n\n    public static final int getQueueHistoryPosition(int position) {\n        if (mService != null) {\n            try {\n                return mService.getQueueHistoryPosition(position);\n            } catch (final RemoteException ignored) {\n            }\n        }\n        return -1;\n    }\n\n    public static final int[] getQueueHistoryList() {\n        if (mService != null) {\n            try {\n                return mService.getQueueHistoryList();\n            } catch (final RemoteException ignored) {\n            }\n        }\n        return null;\n    }\n\n    public static final int removeTrack(final long id) {\n        try {\n            if (mService != null) {\n                return mService.removeTrack(id);\n            }\n        } catch (final RemoteException ingored) {\n        }\n        return 0;\n    }\n\n    public static final boolean removeTrackAtPosition(final long id, final int position) {\n        try {\n            if (mService != null) {\n                return mService.removeTrackAtPosition(id, position);\n            }\n        } catch (final RemoteException ingored) {\n        }\n        return false;\n    }\n\n    public static void moveQueueItem(final int from, final int to) {\n        try {\n            if (mService != null) {\n                mService.moveQueueItem(from, to);\n            } else {\n            }\n        } catch (final RemoteException ignored) {\n        }\n    }\n\n    public static void playArtist(final Context context, final long artistId, int position, boolean shuffle) {\n        final long[] artistList = getSongListForArtist(context, artistId);\n        if (artistList != null) {\n            playAll(context, artistList, position, artistId, IdType.Artist, shuffle);\n        }\n    }\n\n    public static void playAlbum(final Context context, final long albumId, int position, boolean shuffle) {\n        final long[] albumList = getSongListForAlbum(context, albumId);\n        if (albumList != null) {\n            playAll(context, albumList, position, albumId, IdType.Album, shuffle);\n        }\n    }\n\n    public static void playAll(final Context context, final long[] list, int position,\n                               final long sourceId, final IdType sourceType,\n                               final boolean forceShuffle) {\n        if (list == null || list.length == 0 || mService == null) {\n            return;\n        }\n        try {\n            if (forceShuffle) {\n                mService.setShuffleMode(MusicService.SHUFFLE_NORMAL);\n            }\n            final long currentId = mService.getAudioId();\n            final int currentQueuePosition = getQueuePosition();\n            if (position != -1 && currentQueuePosition == position && currentId == list[position]) {\n                final long[] playlist = getQueue();\n                if (Arrays.equals(list, playlist)) {\n                    mService.play();\n                    return;\n                }\n            }\n            if (position < 0) {\n                position = 0;\n            }\n            mService.open(list, forceShuffle ? -1 : position, sourceId, sourceType.mId);\n            mService.play();\n        } catch (final RemoteException ignored) {\n        } catch (IllegalStateException e) {\n            e.printStackTrace();\n        }\n    }\n\n    public static void playNext(Context context, final long[] list, final long sourceId, final IdType sourceType) {\n        if (mService == null) {\n            return;\n        }\n        try {\n            mService.enqueue(list, MusicService.NEXT, sourceId, sourceType.mId);\n            final String message = makeLabel(context, R.plurals.NNNtrackstoqueue, list.length);\n            Toast.makeText(context, message, Toast.LENGTH_SHORT).show();\n        } catch (final RemoteException ignored) {\n        }\n    }\n\n    public static void shuffleAll(final Context context) {\n        Cursor cursor = SongLoader.makeSongCursor(context, null, null);\n        final long[] trackList = SongLoader.getSongListForCursor(cursor);\n        if (trackList.length == 0 || mService == null) {\n            return;\n        }\n        try {\n            mService.setShuffleMode(MusicService.SHUFFLE_NORMAL);\n            if (getQueuePosition() == 0 && mService.getAudioId() == trackList[0] && Arrays.equals(trackList, getQueue())) {\n                    mService.play();\n                    return;\n            }\n            mService.open(trackList, -1, -1, IdType.NA.mId);\n            mService.play();\n            cursor.close();\n        } catch (final RemoteException ignored) {\n        }\n    }\n\n    public static final long[] getSongListForArtist(final Context context, final long id) {\n        final String[] projection = new String[]{\n                BaseColumns._ID\n        };\n        final String selection = MediaStore.Audio.AudioColumns.ARTIST_ID + \"=\" + id + \" AND \"\n                + MediaStore.Audio.AudioColumns.IS_MUSIC + \"=1\";\n        Cursor cursor = context.getContentResolver().query(\n                MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, projection, selection, null,\n                MediaStore.Audio.AudioColumns.ALBUM_KEY + \",\" + MediaStore.Audio.AudioColumns.TRACK);\n        if (cursor != null) {\n            final long[] mList = SongLoader.getSongListForCursor(cursor);\n            cursor.close();\n            cursor = null;\n            return mList;\n        }\n        return sEmptyList;\n    }\n\n    public static final long[] getSongListForAlbum(final Context context, final long id) {\n        final String[] projection = new String[]{\n                BaseColumns._ID\n        };\n        final String selection = MediaStore.Audio.AudioColumns.ALBUM_ID + \"=\" + id + \" AND \" + MediaStore.Audio.AudioColumns.IS_MUSIC\n                + \"=1\";\n        Cursor cursor = context.getContentResolver().query(\n                MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, projection, selection, null,\n                MediaStore.Audio.AudioColumns.TRACK + \", \" + MediaStore.Audio.Media.DEFAULT_SORT_ORDER);\n        if (cursor != null) {\n            final long[] mList = SongLoader.getSongListForCursor(cursor);\n            cursor.close();\n            cursor = null;\n            return mList;\n        }\n        return sEmptyList;\n    }\n\n    public static final int getSongCountForAlbumInt(final Context context, final long id) {\n        int songCount = 0;\n        if (id == -1) {\n            return songCount;\n        }\n\n        Uri uri = ContentUris.withAppendedId(MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI, id);\n        Cursor cursor = context.getContentResolver().query(uri,\n                new String[]{MediaStore.Audio.AlbumColumns.NUMBER_OF_SONGS}, null, null, null);\n        if (cursor != null) {\n            cursor.moveToFirst();\n            if (!cursor.isAfterLast()) {\n                if (!cursor.isNull(0)) {\n                    songCount = cursor.getInt(0);\n                }\n            }\n            cursor.close();\n            cursor = null;\n        }\n\n        return songCount;\n    }\n\n    public static final String getReleaseDateForAlbum(final Context context, final long id) {\n        if (id == -1) {\n            return null;\n        }\n        Uri uri = ContentUris.withAppendedId(MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI, id);\n        Cursor cursor = context.getContentResolver().query(uri, new String[]{\n                MediaStore.Audio.AlbumColumns.FIRST_YEAR\n        }, null, null, null);\n        String releaseDate = null;\n        if (cursor != null) {\n            cursor.moveToFirst();\n            if (!cursor.isAfterLast()) {\n                releaseDate = cursor.getString(0);\n            }\n            cursor.close();\n            cursor = null;\n        }\n        return releaseDate;\n    }\n\n    public static void seek(final long position) {\n        if (mService != null) {\n            try {\n                mService.seek(position);\n            } catch (final RemoteException ignored) {\n            } catch (IllegalStateException ignored) {\n\n            }\n        }\n    }\n\n    public static void seekRelative(final long deltaInMs) {\n        if (mService != null) {\n            try {\n                mService.seekRelative(deltaInMs);\n            } catch (final RemoteException ignored) {\n            } catch (final IllegalStateException ignored) {\n\n            }\n        }\n    }\n\n    public static final long position() {\n        if (mService != null) {\n            try {\n                return mService.position();\n            } catch (final RemoteException ignored) {\n            } catch (final IllegalStateException ex) {\n\n            }\n        }\n        return 0;\n    }\n\n    public static final long duration() {\n        if (mService != null) {\n            try {\n                return mService.duration();\n            } catch (final RemoteException ignored) {\n            } catch (final IllegalStateException ignored) {\n\n            }\n        }\n        return 0;\n    }\n\n    public static void clearQueue() {\n        if (mService!=null) {\n            try {\n                mService.removeTracks(0, Integer.MAX_VALUE);\n            } catch (final RemoteException ignored) {\n            }\n        }\n    }\n\n    public static void addToQueue(final Context context, final long[] list, long sourceId,\n                                  IdType sourceType) {\n        if (mService == null) {\n            return;\n        }\n        try {\n            mService.enqueue(list, MusicService.LAST, sourceId, sourceType.mId);\n            final String message = makeLabel(context, R.plurals.NNNtrackstoqueue, list.length);\n            Toast.makeText(context, message, Toast.LENGTH_SHORT).show();\n        } catch (final RemoteException ignored) {\n        }\n    }\n\n    public static final String makeLabel(final Context context, final int pluralInt,\n                                         final int number) {\n        return context.getResources().getQuantityString(pluralInt, number, number);\n    }\n\n    public static void addToPlaylist(final Context context, final long[] ids, final long playlistid) {\n        final int size = ids.length;\n        final ContentResolver resolver = context.getContentResolver();\n        final String[] projection = new String[]{\n                \"max(\" + \"play_order\" + \")\",\n        };\n        final Uri uri = MediaStore.Audio.Playlists.Members.getContentUri(\"external\", playlistid);\n        Cursor cursor = null;\n        int base = 0;\n\n        try {\n            cursor = resolver.query(uri, projection, null, null, null);\n\n            if (cursor != null && cursor.moveToFirst()) {\n                base = cursor.getInt(0) + 1;\n            }\n        } finally {\n            if (cursor != null) {\n                cursor.close();\n                cursor = null;\n            }\n        }\n\n        int numinserted = 0;\n        for (int offSet = 0; offSet < size; offSet += 1000) {\n            makeInsertItems(ids, offSet, 1000, base);\n            numinserted += resolver.bulkInsert(uri, mContentValuesCache);\n        }\n        final String message = context.getResources().getQuantityString(\n                R.plurals.NNNtrackstoplaylist, numinserted, numinserted);\n        Toast.makeText(context, message, Toast.LENGTH_SHORT).show();\n    }\n\n    public static void makeInsertItems(final long[] ids, final int offset, int len, final int base) {\n        if (offset + len > ids.length) {\n            len = ids.length - offset;\n        }\n\n        if (mContentValuesCache == null || mContentValuesCache.length != len) {\n            mContentValuesCache = new ContentValues[len];\n        }\n        for (int i = 0; i < len; i++) {\n            if (mContentValuesCache[i] == null) {\n                mContentValuesCache[i] = new ContentValues();\n            }\n            mContentValuesCache[i].put(MediaStore.Audio.Playlists.Members.PLAY_ORDER, base + offset + i);\n            mContentValuesCache[i].put(MediaStore.Audio.Playlists.Members.AUDIO_ID, ids[offset + i]);\n        }\n    }\n\n    public static final long createPlaylist(final Context context, final String name) {\n        if (name != null && name.length() > 0) {\n            final ContentResolver resolver = context.getContentResolver();\n            final String[] projection = new String[]{\n                    MediaStore.Audio.PlaylistsColumns.NAME\n            };\n            final String selection = MediaStore.Audio.PlaylistsColumns.NAME + \" = '\" + name + \"'\";\n            Cursor cursor = resolver.query(MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI,\n                    projection, selection, null, null);\n            if (cursor.getCount() <= 0) {\n                final ContentValues values = new ContentValues(1);\n                values.put(MediaStore.Audio.PlaylistsColumns.NAME, name);\n                final Uri uri = resolver.insert(MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI,\n                        values);\n                return Long.parseLong(uri.getLastPathSegment());\n            }\n            if (cursor != null) {\n                cursor.close();\n                cursor = null;\n            }\n            return -1;\n        }\n        return -1;\n    }\n\n    public static final void openFile(final String path) {\n        if (mService != null) {\n            try {\n                mService.openFile(path);\n            } catch (final RemoteException ignored) {\n            }\n        }\n    }\n\n    public static final class ServiceBinder implements ServiceConnection {\n        private final ServiceConnection mCallback;\n        private final Context mContext;\n\n\n        public ServiceBinder(final ServiceConnection callback, final Context context) {\n            mCallback = callback;\n            mContext = context;\n        }\n\n        @Override\n        public void onServiceConnected(final ComponentName className, final IBinder service) {\n            mService = ITimberService.Stub.asInterface(service);\n            if (mCallback != null) {\n                mCallback.onServiceConnected(className, service);\n            }\n            initPlaybackServiceWithSettings(mContext);\n        }\n\n        @Override\n        public void onServiceDisconnected(final ComponentName className) {\n            if (mCallback != null) {\n                mCallback.onServiceDisconnected(className);\n            }\n            mService = null;\n        }\n    }\n\n    public static final class ServiceToken {\n        public ContextWrapper mWrappedContext;\n\n        public ServiceToken(final ContextWrapper context) {\n            mWrappedContext = context;\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/MusicService.java",
    "content": "/*\n * Copyright (C) 2012 Andrew Neal\n * Copyright (C) 2014 The CyanogenMod Project\n * Copyright (C) 2015 Naman Dwivedi\n *\n * Licensed under the Apache License, Version 2.0\n * (the \"License\"); you may not use this file except in compliance with the\n * License. You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law\n * or agreed to in writing, software distributed under the License is\n * distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n * KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\npackage com.naman14.timber;\n\nimport android.Manifest;\nimport android.annotation.SuppressLint;\nimport android.annotation.TargetApi;\nimport android.app.AlarmManager;\nimport android.app.Notification;\nimport android.app.NotificationChannel;\nimport android.app.NotificationManager;\nimport android.app.PendingIntent;\nimport android.app.Service;\nimport android.content.BroadcastReceiver;\nimport android.content.ComponentName;\nimport android.content.ContentResolver;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.content.IntentFilter;\nimport android.content.SharedPreferences;\nimport android.database.ContentObserver;\nimport android.database.Cursor;\nimport android.database.MatrixCursor;\nimport android.graphics.Bitmap;\nimport android.graphics.Color;\nimport android.media.AudioManager;\nimport android.media.AudioManager.OnAudioFocusChangeListener;\nimport android.media.MediaMetadataEditor;\nimport android.media.MediaMetadataRetriever;\nimport android.media.MediaPlayer;\nimport android.media.RemoteControlClient;\nimport android.media.audiofx.AudioEffect;\nimport android.net.Uri;\nimport android.os.Build;\nimport android.os.Bundle;\nimport android.os.Handler;\nimport android.os.HandlerThread;\nimport android.os.IBinder;\nimport android.os.Looper;\nimport android.os.Message;\nimport android.os.PowerManager;\nimport android.os.PowerManager.WakeLock;\nimport android.os.RemoteException;\nimport android.os.SystemClock;\nimport android.provider.MediaStore;\nimport android.provider.MediaStore.Audio.AlbumColumns;\nimport android.provider.MediaStore.Audio.AudioColumns;\nimport androidx.core.app.NotificationManagerCompat;\nimport android.support.v4.media.MediaMetadataCompat;\nimport android.support.v4.media.session.MediaSessionCompat;\nimport android.support.v4.media.session.PlaybackStateCompat;\n\nimport androidx.media.app.NotificationCompat;\nimport androidx.palette.graphics.Palette;\nimport android.text.TextUtils;\nimport android.util.Log;\n\nimport com.naman14.timber.helpers.MediaButtonIntentReceiver;\nimport com.naman14.timber.helpers.MusicPlaybackTrack;\nimport com.naman14.timber.lastfmapi.LastFmClient;\nimport com.naman14.timber.lastfmapi.models.LastfmUserSession;\nimport com.naman14.timber.lastfmapi.models.ScrobbleQuery;\nimport com.naman14.timber.permissions.Nammu;\nimport com.naman14.timber.provider.MusicPlaybackState;\nimport com.naman14.timber.provider.RecentStore;\nimport com.naman14.timber.provider.SongPlayCount;\nimport com.naman14.timber.utils.NavigationUtils;\nimport com.naman14.timber.utils.PreferencesUtility;\nimport com.naman14.timber.utils.TimberUtils;\nimport com.naman14.timber.utils.TimberUtils.IdType;\nimport com.nostra13.universalimageloader.core.ImageLoader;\n\nimport java.io.IOException;\nimport java.lang.ref.WeakReference;\nimport java.util.ArrayList;\nimport java.util.LinkedList;\nimport java.util.ListIterator;\nimport java.util.Random;\nimport java.util.TreeSet;\n\nimport de.Maxr1998.trackselectorlib.ModNotInstalledException;\nimport de.Maxr1998.trackselectorlib.NotificationHelper;\nimport de.Maxr1998.trackselectorlib.TrackItem;\n\n@SuppressLint(\"NewApi\")\npublic class MusicService extends Service {\n    public static final String PLAYSTATE_CHANGED = \"com.naman14.timber.playstatechanged\";\n    public static final String POSITION_CHANGED = \"com.naman14.timber.positionchanged\";\n    public static final String META_CHANGED = \"com.naman14.timber.metachanged\";\n    public static final String QUEUE_CHANGED = \"com.naman14.timber.queuechanged\";\n    public static final String PLAYLIST_CHANGED = \"com.naman14.timber.playlistchanged\";\n    public static final String REPEATMODE_CHANGED = \"com.naman14.timber.repeatmodechanged\";\n    public static final String SHUFFLEMODE_CHANGED = \"com.naman14.timber.shufflemodechanged\";\n    public static final String TRACK_ERROR = \"com.naman14.timber.trackerror\";\n    public static final String TIMBER_PACKAGE_NAME = \"com.naman14.timber\";\n    public static final String MUSIC_PACKAGE_NAME = \"com.android.music\";\n    public static final String SERVICECMD = \"com.naman14.timber.musicservicecommand\";\n    public static final String TOGGLEPAUSE_ACTION = \"com.naman14.timber.togglepause\";\n    public static final String PAUSE_ACTION = \"com.naman14.timber.pause\";\n    public static final String STOP_ACTION = \"com.naman14.timber.stop\";\n    public static final String PREVIOUS_ACTION = \"com.naman14.timber.previous\";\n    public static final String PREVIOUS_FORCE_ACTION = \"com.naman14.timber.previous.force\";\n    public static final String NEXT_ACTION = \"fcom.naman14.timber.next\";\n    public static final String REPEAT_ACTION = \"com.naman14.timber.repeat\";\n    public static final String SHUFFLE_ACTION = \"com.naman14.timber.shuffle\";\n    public static final String FROM_MEDIA_BUTTON = \"frommediabutton\";\n    public static final String REFRESH = \"com.naman14.timber.refresh\";\n    public static final String UPDATE_LOCKSCREEN = \"com.naman14.timber.updatelockscreen\";\n    public static final String CMDNAME = \"command\";\n    public static final String CMDTOGGLEPAUSE = \"togglepause\";\n    public static final String CMDSTOP = \"stop\";\n    public static final String CMDPAUSE = \"pause\";\n    public static final String CMDPLAY = \"play\";\n    public static final String CMDPREVIOUS = \"previous\";\n    public static final String CMDNEXT = \"next\";\n    public static final String CMDNOTIF = \"buttonId\";\n    public static final String UPDATE_PREFERENCES = \"updatepreferences\";\n    public static final String CHANNEL_ID = \"timber_channel_01\";\n    public static final int NEXT = 2;\n    public static final int LAST = 3;\n    public static final int SHUFFLE_NONE = 0;\n    public static final int SHUFFLE_NORMAL = 1;\n    public static final int SHUFFLE_AUTO = 2;\n    public static final int REPEAT_NONE = 0;\n    public static final int REPEAT_CURRENT = 1;\n    public static final int REPEAT_ALL = 2;\n    public static final int MAX_HISTORY_SIZE = 1000;\n    private static final String TAG = \"MusicPlaybackService\";\n    private static final boolean D = false;\n    private static final String SHUTDOWN = \"com.naman14.timber.shutdown\";\n    private static final int IDCOLIDX = 0;\n    private static final int TRACK_ENDED = 1;\n    private static final int TRACK_WENT_TO_NEXT = 2;\n    private static final int RELEASE_WAKELOCK = 3;\n    private static final int SERVER_DIED = 4;\n    private static final int FOCUSCHANGE = 5;\n    private static final int FADEDOWN = 6;\n    private static final int FADEUP = 7;\n    private static final int IDLE_DELAY = 5 * 60 * 1000;\n    private static final long REWIND_INSTEAD_PREVIOUS_THRESHOLD = 3000;\n    private static final String[] PROJECTION = new String[]{\n            \"audio._id AS _id\", MediaStore.Audio.Media.ARTIST, MediaStore.Audio.Media.ALBUM,\n            MediaStore.Audio.Media.TITLE, MediaStore.Audio.Media.DATA,\n            MediaStore.Audio.Media.MIME_TYPE, MediaStore.Audio.Media.ALBUM_ID,\n            MediaStore.Audio.Media.ARTIST_ID\n    };\n    private static final String[] ALBUM_PROJECTION = new String[]{\n            MediaStore.Audio.Albums.ALBUM, MediaStore.Audio.Albums.ARTIST,\n            MediaStore.Audio.Albums.LAST_YEAR\n    };\n    private static final String[] NOTIFICATION_PROJECTION = new String[]{\n            \"audio._id AS _id\", AudioColumns.ALBUM_ID, AudioColumns.TITLE,\n            AudioColumns.ARTIST, AudioColumns.DURATION\n    };\n    private static final Shuffler mShuffler = new Shuffler();\n    private static final int NOTIFY_MODE_NONE = 0;\n    private static final int NOTIFY_MODE_FOREGROUND = 1;\n    private static final int NOTIFY_MODE_BACKGROUND = 2;\n    private static final String[] PROJECTION_MATRIX = new String[]{\n            \"_id\", MediaStore.Audio.Media.ARTIST, MediaStore.Audio.Media.ALBUM,\n            MediaStore.Audio.Media.TITLE, MediaStore.Audio.Media.DATA,\n            MediaStore.Audio.Media.MIME_TYPE, MediaStore.Audio.Media.ALBUM_ID,\n            MediaStore.Audio.Media.ARTIST_ID\n    };\n    private static LinkedList<Integer> mHistory = new LinkedList<>();\n    private final IBinder mBinder = new ServiceStub(this);\n    private MultiPlayer mPlayer;\n    private String mFileToPlay;\n    private WakeLock mWakeLock;\n    private AlarmManager mAlarmManager;\n    private PendingIntent mShutdownIntent;\n    private boolean mShutdownScheduled;\n    private NotificationManagerCompat mNotificationManager;\n    private Cursor mCursor;\n    private Cursor mAlbumCursor;\n    private AudioManager mAudioManager;\n    private SharedPreferences mPreferences;\n    private boolean mServiceInUse = false;\n    private boolean mIsSupposedToBePlaying = false;\n    private long mLastPlayedTime;\n    private int mNotifyMode = NOTIFY_MODE_NONE;\n    private long mNotificationPostTime = 0;\n    private boolean mQueueIsSaveable = true;\n    private boolean mPausedByTransientLossOfFocus = false;\n\n    private MediaSessionCompat mSession;\n    @SuppressWarnings(\"deprecation\")\n    private RemoteControlClient mRemoteControlClient;\n\n    private ComponentName mMediaButtonReceiverComponent;\n\n    private int mCardId;\n\n    private int mPlayPos = -1;\n\n    private int mNextPlayPos = -1;\n\n    private int mOpenFailedCounter = 0;\n\n    private int mMediaMountedCount = 0;\n\n    private int mShuffleMode = SHUFFLE_NONE;\n\n    private int mRepeatMode = REPEAT_NONE;\n\n    private int mServiceStartId = -1;\n\n    private ArrayList<MusicPlaybackTrack> mPlaylist = new ArrayList<MusicPlaybackTrack>(100);\n\n    private long[] mAutoShuffleList = null;\n\n    private MusicPlayerHandler mPlayerHandler;\n    private final OnAudioFocusChangeListener mAudioFocusListener = new OnAudioFocusChangeListener() {\n\n        @Override\n        public void onAudioFocusChange(final int focusChange) {\n            mPlayerHandler.obtainMessage(FOCUSCHANGE, focusChange, 0).sendToTarget();\n        }\n    };\n    private HandlerThread mHandlerThread;\n    private BroadcastReceiver mUnmountReceiver = null;\n    private MusicPlaybackState mPlaybackStateStore;\n    private boolean mShowAlbumArtOnLockscreen;\n    private boolean mActivateXTrackSelector;\n    private SongPlayCount mSongPlayCount;\n    private RecentStore mRecentStore;\n    private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {\n\n        @Override\n        public void onReceive(final Context context, final Intent intent) {\n            final String command = intent.getStringExtra(CMDNAME);\n\n\n            handleCommandIntent(intent);\n\n        }\n    };\n    private ContentObserver mMediaStoreObserver;\n\n    @Override\n    public IBinder onBind(final Intent intent) {\n        if (D) Log.d(TAG, \"Service bound, intent = \" + intent);\n        cancelShutdown();\n        mServiceInUse = true;\n        return mBinder;\n    }\n\n    @Override\n    public boolean onUnbind(final Intent intent) {\n        if (D) Log.d(TAG, \"Service unbound\");\n        mServiceInUse = false;\n        saveQueue(true);\n\n        if (mIsSupposedToBePlaying || mPausedByTransientLossOfFocus) {\n\n            return true;\n\n        } else if (mPlaylist.size() > 0 || mPlayerHandler.hasMessages(TRACK_ENDED)) {\n            scheduleDelayedShutdown();\n            return true;\n        }\n        stopSelf(mServiceStartId);\n\n        return true;\n    }\n\n    @Override\n    public void onRebind(final Intent intent) {\n        cancelShutdown();\n        mServiceInUse = true;\n    }\n\n    @Override\n    public void onCreate() {\n        if (D) Log.d(TAG, \"Creating service\");\n        super.onCreate();\n\n        mNotificationManager = NotificationManagerCompat.from(this);\n        createNotificationChannel();\n\n        // gets a pointer to the playback state store\n        mPlaybackStateStore = MusicPlaybackState.getInstance(this);\n        mSongPlayCount = SongPlayCount.getInstance(this);\n        mRecentStore = RecentStore.getInstance(this);\n\n\n        mHandlerThread = new HandlerThread(\"MusicPlayerHandler\",\n                android.os.Process.THREAD_PRIORITY_BACKGROUND);\n        mHandlerThread.start();\n\n\n        mPlayerHandler = new MusicPlayerHandler(this, mHandlerThread.getLooper());\n\n\n        mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);\n        mMediaButtonReceiverComponent = new ComponentName(getPackageName(),\n                MediaButtonIntentReceiver.class.getName());\n        mAudioManager.registerMediaButtonEventReceiver(mMediaButtonReceiverComponent);\n\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)\n            setUpMediaSession();\n        else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH)\n            setUpRemoteControlClient();\n\n        mPreferences = getSharedPreferences(\"Service\", 0);\n        mCardId = getCardId();\n\n        registerExternalStorageListener();\n\n        mPlayer = new MultiPlayer(this);\n        mPlayer.setHandler(mPlayerHandler);\n\n        // Initialize the intent filter and each action\n        final IntentFilter filter = new IntentFilter();\n        filter.addAction(SERVICECMD);\n        filter.addAction(TOGGLEPAUSE_ACTION);\n        filter.addAction(PAUSE_ACTION);\n        filter.addAction(STOP_ACTION);\n        filter.addAction(NEXT_ACTION);\n        filter.addAction(PREVIOUS_ACTION);\n        filter.addAction(PREVIOUS_FORCE_ACTION);\n        filter.addAction(REPEAT_ACTION);\n        filter.addAction(SHUFFLE_ACTION);\n        filter.addAction(AudioManager.ACTION_AUDIO_BECOMING_NOISY);\n        filter.addAction(Intent.ACTION_SCREEN_ON);\n        // Attach the broadcast listener\n        registerReceiver(mIntentReceiver, filter);\n\n        mMediaStoreObserver = new MediaStoreObserver(mPlayerHandler);\n        getContentResolver().registerContentObserver(\n                MediaStore.Audio.Media.INTERNAL_CONTENT_URI, true, mMediaStoreObserver);\n        getContentResolver().registerContentObserver(\n                MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, true, mMediaStoreObserver);\n\n        // Initialize the wake lock\n        final PowerManager powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);\n        mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, getClass().getName());\n        mWakeLock.setReferenceCounted(false);\n\n\n        final Intent shutdownIntent = new Intent(this, MusicService.class);\n        shutdownIntent.setAction(SHUTDOWN);\n\n        mAlarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);\n        mShutdownIntent = PendingIntent.getService(this, 0, shutdownIntent, 0);\n\n        scheduleDelayedShutdown();\n\n        reloadQueueAfterPermissionCheck();\n        notifyChange(QUEUE_CHANGED);\n        notifyChange(META_CHANGED);\n        //Try to push LastFMCache\n        if (LastfmUserSession.getSession(this) != null) {\n            LastFmClient.getInstance(this).Scrobble(null);\n        }\n        PreferencesUtility pref = PreferencesUtility.getInstance(this);\n        mShowAlbumArtOnLockscreen = pref.getSetAlbumartLockscreen();\n        mActivateXTrackSelector = pref.getXPosedTrackselectorEnabled();\n    }\n\n    @SuppressWarnings(\"deprecation\")\n    @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)\n    private void setUpRemoteControlClient() {\n        //Legacy for ICS\n        if (mRemoteControlClient == null) {\n            Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);\n            mediaButtonIntent.setComponent(mMediaButtonReceiverComponent);\n            PendingIntent mediaPendingIntent = PendingIntent.getBroadcast(this, 0, mediaButtonIntent, 0);\n\n            // create and register the remote control client\n            mRemoteControlClient = new RemoteControlClient(mediaPendingIntent);\n            mAudioManager.registerRemoteControlClient(mRemoteControlClient);\n        }\n\n        mRemoteControlClient.setTransportControlFlags(\n                RemoteControlClient.FLAG_KEY_MEDIA_PLAY |\n                        RemoteControlClient.FLAG_KEY_MEDIA_PAUSE |\n                        RemoteControlClient.FLAG_KEY_MEDIA_PREVIOUS |\n                        RemoteControlClient.FLAG_KEY_MEDIA_NEXT |\n                        RemoteControlClient.FLAG_KEY_MEDIA_STOP);\n    }\n\n    private void setUpMediaSession() {\n        mSession = new MediaSessionCompat(this, \"Timber\");\n        mSession.setCallback(new MediaSessionCompat.Callback() {\n            @Override\n            public void onPause() {\n                pause();\n                mPausedByTransientLossOfFocus = false;\n            }\n\n            @Override\n            public void onPlay() {\n                play();\n            }\n\n            @Override\n            public void onSeekTo(long pos) {\n                seek(pos);\n            }\n\n            @Override\n            public void onSkipToNext() {\n                gotoNext(true);\n            }\n\n            @Override\n            public void onSkipToPrevious() {\n                prev(false);\n            }\n\n            @Override\n            public void onStop() {\n                pause();\n                mPausedByTransientLossOfFocus = false;\n                seek(0);\n                releaseServiceUiAndStop();\n            }\n        });\n        mSession.setFlags(MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS\n                          | MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS);\n    }\n\n    @Override\n    public void onDestroy() {\n        if (D) Log.d(TAG, \"Destroying service\");\n        super.onDestroy();\n        //Try to push LastFMCache\n        if (LastfmUserSession.getSession(this).isLogedin()) {\n            LastFmClient.getInstance(this).Scrobble(null);\n        }\n        // Remove any sound effects\n        final Intent audioEffectsIntent = new Intent(\n                AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION);\n        audioEffectsIntent.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, getAudioSessionId());\n        audioEffectsIntent.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, getPackageName());\n        sendBroadcast(audioEffectsIntent);\n\n\n        mAlarmManager.cancel(mShutdownIntent);\n\n        mPlayerHandler.removeCallbacksAndMessages(null);\n\n        if (TimberUtils.isJellyBeanMR2())\n            mHandlerThread.quitSafely();\n        else mHandlerThread.quit();\n\n        mPlayer.release();\n        mPlayer = null;\n\n        mAudioManager.abandonAudioFocus(mAudioFocusListener);\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)\n            mSession.release();\n\n        getContentResolver().unregisterContentObserver(mMediaStoreObserver);\n\n        closeCursor();\n\n        unregisterReceiver(mIntentReceiver);\n        if (mUnmountReceiver != null) {\n            unregisterReceiver(mUnmountReceiver);\n            mUnmountReceiver = null;\n        }\n\n        mWakeLock.release();\n    }\n\n    @Override\n    public int onStartCommand(final Intent intent, final int flags, final int startId) {\n        if (D) Log.d(TAG, \"Got new intent \" + intent + \", startId = \" + startId);\n        mServiceStartId = startId;\n\n        if (intent != null) {\n            final String action = intent.getAction();\n\n            if (SHUTDOWN.equals(action)) {\n                mShutdownScheduled = false;\n                releaseServiceUiAndStop();\n                return START_NOT_STICKY;\n            }\n\n            handleCommandIntent(intent);\n        }\n\n        scheduleDelayedShutdown();\n\n        if (intent != null && intent.getBooleanExtra(FROM_MEDIA_BUTTON, false)) {\n            MediaButtonIntentReceiver.completeWakefulIntent(intent);\n        }\n\n        return START_NOT_STICKY; //no sense to use START_STICKY with using startForeground\n    }\n\n    void scrobble() {\n        if (LastfmUserSession.getSession(this).isLogedin()) {\n            Log.d(\"Scrobble\", \"to LastFM\");\n            String trackname = getTrackName();\n            if (trackname != null)\n                LastFmClient.getInstance(this).Scrobble(new ScrobbleQuery(getArtistName(), trackname, System.currentTimeMillis() / 1000));\n        }\n    }\n\n    private void releaseServiceUiAndStop() {\n        if (isPlaying()\n                || mPausedByTransientLossOfFocus\n                || mPlayerHandler.hasMessages(TRACK_ENDED)) {\n            return;\n        }\n\n        if (D) Log.d(TAG, \"Nothing is playing anymore, releasing notification\");\n        cancelNotification();\n        mAudioManager.abandonAudioFocus(mAudioFocusListener);\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)\n            mSession.setActive(false);\n\n        if (!mServiceInUse) {\n            saveQueue(true);\n            stopSelf(mServiceStartId);\n        }\n    }\n\n    private void handleCommandIntent(Intent intent) {\n        final String action = intent.getAction();\n        final String command = SERVICECMD.equals(action) ? intent.getStringExtra(CMDNAME) : null;\n\n        if (D) Log.d(TAG, \"handleCommandIntent: action = \" + action + \", command = \" + command);\n\n        if (NotificationHelper.checkIntent(intent)) {\n            goToPosition(mPlayPos + NotificationHelper.getPosition(intent));\n            return;\n        }\n\n        if (CMDNEXT.equals(command) || NEXT_ACTION.equals(action)) {\n            gotoNext(true);\n        } else if (CMDPREVIOUS.equals(command) || PREVIOUS_ACTION.equals(action)\n                || PREVIOUS_FORCE_ACTION.equals(action)) {\n            prev(PREVIOUS_FORCE_ACTION.equals(action));\n        } else if (CMDTOGGLEPAUSE.equals(command) || TOGGLEPAUSE_ACTION.equals(action)) {\n            if (isPlaying()) {\n                pause();\n                mPausedByTransientLossOfFocus = false;\n            } else {\n                play();\n            }\n        } else if (CMDPAUSE.equals(command) || PAUSE_ACTION.equals(action)) {\n            pause();\n            mPausedByTransientLossOfFocus = false;\n        } else if (CMDPLAY.equals(command)) {\n            play();\n        } else if (CMDSTOP.equals(command) || STOP_ACTION.equals(action)) {\n            pause();\n            mPausedByTransientLossOfFocus = false;\n            seek(0);\n            releaseServiceUiAndStop();\n        } else if (REPEAT_ACTION.equals(action)) {\n            cycleRepeat();\n        } else if (SHUFFLE_ACTION.equals(action)) {\n            cycleShuffle();\n        } else if (UPDATE_PREFERENCES.equals(action)) {\n            onPreferencesUpdate(intent.getExtras());\n        }\n        else if (AudioManager.ACTION_AUDIO_BECOMING_NOISY.equals(action)) {\n            if (PreferencesUtility.getInstance(getApplicationContext()).pauseEnabledOnDetach()) {\n                pause();\n            }\n        }\n    }\n\n    private void onPreferencesUpdate(Bundle extras) {\n        mShowAlbumArtOnLockscreen = extras.getBoolean(\"lockscreen\", mShowAlbumArtOnLockscreen);\n        mActivateXTrackSelector = extras.getBoolean(\"xtrack\",mActivateXTrackSelector);\n        LastfmUserSession session = LastfmUserSession.getSession(this);\n        session.mToken = extras.getString(\"lf_token\", session.mToken);\n        session.mUsername = extras.getString(\"lf_user\", session.mUsername);\n        if (\"logout\".equals(session.mToken)) {\n            session.mToken = null;\n            session.mUsername = null;\n        }\n        notifyChange(META_CHANGED);\n\n    }\n\n    private void updateNotification() {\n        final int newNotifyMode;\n        if (isPlaying()) {\n            newNotifyMode = NOTIFY_MODE_FOREGROUND;\n        } else if (recentlyPlayed()) {\n            newNotifyMode = NOTIFY_MODE_BACKGROUND;\n        } else {\n            newNotifyMode = NOTIFY_MODE_NONE;\n        }\n\n        int notificationId = hashCode();\n        if (mNotifyMode != newNotifyMode) {\n            if (mNotifyMode == NOTIFY_MODE_FOREGROUND) {\n                if (TimberUtils.isLollipop())\n                    stopForeground(newNotifyMode == NOTIFY_MODE_NONE);\n                else\n                    stopForeground(newNotifyMode == NOTIFY_MODE_NONE || newNotifyMode == NOTIFY_MODE_BACKGROUND);\n            } else if (newNotifyMode == NOTIFY_MODE_NONE) {\n                mNotificationManager.cancel(notificationId);\n                mNotificationPostTime = 0;\n            }\n        }\n\n        if (newNotifyMode == NOTIFY_MODE_FOREGROUND) {\n            startForeground(notificationId, buildNotification());\n        } else if (newNotifyMode == NOTIFY_MODE_BACKGROUND) {\n            mNotificationManager.notify(notificationId, buildNotification());\n        }\n\n        mNotifyMode = newNotifyMode;\n    }\n\n    private void cancelNotification() {\n        stopForeground(true);\n        mNotificationManager.cancel(hashCode());\n        mNotificationPostTime = 0;\n        mNotifyMode = NOTIFY_MODE_NONE;\n    }\n\n    private int getCardId() {\n        if (TimberUtils.isMarshmallow()) {\n            if (Nammu.checkPermission(Manifest.permission.READ_EXTERNAL_STORAGE)) {\n                return getmCardId();\n            } else return 0;\n        } else {\n            return getmCardId();\n        }\n    }\n\n    private int getmCardId() {\n        final ContentResolver resolver = getContentResolver();\n        Cursor cursor = resolver.query(Uri.parse(\"content://media/external/fs_id\"), null, null,\n                null, null);\n        int mCardId = -1;\n        if (cursor != null && cursor.moveToFirst()) {\n            mCardId = cursor.getInt(0);\n            cursor.close();\n            cursor = null;\n        }\n        return mCardId;\n    }\n\n    public void closeExternalStorageFiles(final String storagePath) {\n        stop(true);\n        notifyChange(QUEUE_CHANGED);\n        notifyChange(META_CHANGED);\n    }\n\n    public void registerExternalStorageListener() {\n        if (mUnmountReceiver == null) {\n            mUnmountReceiver = new BroadcastReceiver() {\n\n\n                @Override\n                public void onReceive(final Context context, final Intent intent) {\n                    final String action = intent.getAction();\n                    if (action.equals(Intent.ACTION_MEDIA_EJECT)) {\n                        saveQueue(true);\n                        mQueueIsSaveable = false;\n                        closeExternalStorageFiles(intent.getData().getPath());\n                    } else if (action.equals(Intent.ACTION_MEDIA_MOUNTED)) {\n                        mMediaMountedCount++;\n                        mCardId = getCardId();\n                        reloadQueueAfterPermissionCheck();\n                        mQueueIsSaveable = true;\n                        notifyChange(QUEUE_CHANGED);\n                        notifyChange(META_CHANGED);\n                    }\n                }\n            };\n            final IntentFilter filter = new IntentFilter();\n            filter.addAction(Intent.ACTION_MEDIA_EJECT);\n            filter.addAction(Intent.ACTION_MEDIA_MOUNTED);\n            filter.addDataScheme(\"file\");\n            registerReceiver(mUnmountReceiver, filter);\n        }\n    }\n\n    private void scheduleDelayedShutdown() {\n        if (D) Log.v(TAG, \"Scheduling shutdown in \" + IDLE_DELAY + \" ms\");\n        mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,\n                SystemClock.elapsedRealtime() + IDLE_DELAY, mShutdownIntent);\n        mShutdownScheduled = true;\n    }\n\n    private void cancelShutdown() {\n        if (D) Log.d(TAG, \"Cancelling delayed shutdown, scheduled = \" + mShutdownScheduled);\n        if (mShutdownScheduled) {\n            mAlarmManager.cancel(mShutdownIntent);\n            mShutdownScheduled = false;\n        }\n    }\n\n    private void stop(final boolean goToIdle) {\n        if (D) Log.d(TAG, \"Stopping playback, goToIdle = \" + goToIdle);\n        long duration = this.duration();\n        long position = this.position();\n        if (duration > 30000 && (position >= duration / 2) || position > 240000) {\n            scrobble();\n        }\n\n        if (mPlayer.isInitialized()) {\n            mPlayer.stop();\n        }\n        mFileToPlay = null;\n        closeCursor();\n        if (goToIdle) {\n            setIsSupposedToBePlaying(false, false);\n        } else {\n            if (TimberUtils.isLollipop())\n                stopForeground(false);\n            else stopForeground(true);\n        }\n    }\n\n    private int removeTracksInternal(int first, int last) {\n        synchronized (this) {\n            if (last < first) {\n                return 0;\n            } else if (first < 0) {\n                first = 0;\n            } else if (last >= mPlaylist.size()) {\n                last = mPlaylist.size() - 1;\n            }\n\n            boolean gotonext = false;\n            if (first <= mPlayPos && mPlayPos <= last) {\n                mPlayPos = first;\n                gotonext = true;\n            } else if (mPlayPos > last) {\n                mPlayPos -= last - first + 1;\n            }\n            final int numToRemove = last - first + 1;\n\n            if (first == 0 && last == mPlaylist.size() - 1) {\n                mPlayPos = -1;\n                mNextPlayPos = -1;\n                mPlaylist.clear();\n                mHistory.clear();\n            } else {\n                for (int i = 0; i < numToRemove; i++) {\n                    mPlaylist.remove(first);\n                }\n\n                ListIterator<Integer> positionIterator = mHistory.listIterator();\n                while (positionIterator.hasNext()) {\n                    int pos = positionIterator.next();\n                    if (pos >= first && pos <= last) {\n                        positionIterator.remove();\n                    } else if (pos > last) {\n                        positionIterator.set(pos - numToRemove);\n                    }\n                }\n            }\n            if (gotonext) {\n                if (mPlaylist.size() == 0) {\n                    stop(true);\n                    mPlayPos = -1;\n                    closeCursor();\n                } else {\n                    if (mShuffleMode != SHUFFLE_NONE) {\n                        mPlayPos = getNextPosition(true);\n                    } else if (mPlayPos >= mPlaylist.size()) {\n                        mPlayPos = 0;\n                    }\n                    final boolean wasPlaying = isPlaying();\n                    stop(false);\n                    openCurrentAndNext();\n                    if (wasPlaying) {\n                        play();\n                    }\n                }\n                notifyChange(META_CHANGED);\n            }\n            return last - first + 1;\n        }\n    }\n\n    private void addToPlayList(final long[] list, int position, long sourceId, TimberUtils.IdType sourceType) {\n        final int addlen = list.length;\n        if (position < 0) {\n            mPlaylist.clear();\n            position = 0;\n        }\n\n        mPlaylist.ensureCapacity(mPlaylist.size() + addlen);\n        if (position > mPlaylist.size()) {\n            position = mPlaylist.size();\n        }\n\n        final ArrayList<MusicPlaybackTrack> arrayList = new ArrayList<MusicPlaybackTrack>(addlen);\n        for (int i = 0; i < list.length; i++) {\n            arrayList.add(new MusicPlaybackTrack(list[i], sourceId, sourceType, i));\n        }\n\n        mPlaylist.addAll(position, arrayList);\n\n        if (mPlaylist.size() == 0) {\n            closeCursor();\n            notifyChange(META_CHANGED);\n        }\n    }\n\n    private void updateCursor(final long trackId) {\n        updateCursor(\"_id=\" + trackId, null);\n    }\n\n    private void updateCursor(final String selection, final String[] selectionArgs) {\n        synchronized (this) {\n            closeCursor();\n            mCursor = openCursorAndGoToFirst(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,\n                    PROJECTION, selection, selectionArgs);\n        }\n        updateAlbumCursor();\n    }\n\n    private void updateCursor(final Uri uri) {\n        synchronized (this) {\n            closeCursor();\n            mCursor = openCursorAndGoToFirst(uri, PROJECTION, null, null);\n        }\n        updateAlbumCursor();\n    }\n\n    private void updateAlbumCursor() {\n        long albumId = getAlbumId();\n        if (albumId >= 0) {\n            mAlbumCursor = openCursorAndGoToFirst(MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI,\n                    ALBUM_PROJECTION, \"_id=\" + albumId, null);\n        } else {\n            mAlbumCursor = null;\n        }\n    }\n\n    private Cursor openCursorAndGoToFirst(Uri uri, String[] projection,\n                                          String selection, String[] selectionArgs) {\n        Cursor c = getContentResolver().query(uri, projection,\n                selection, selectionArgs, null);\n        if (c == null) {\n            return null;\n        }\n        if (!c.moveToFirst()) {\n            c.close();\n            return null;\n        }\n        return c;\n    }\n\n    private synchronized void closeCursor() {\n        if (mCursor != null) {\n            mCursor.close();\n            mCursor = null;\n        }\n        if (mAlbumCursor != null) {\n            mAlbumCursor.close();\n            mAlbumCursor = null;\n        }\n    }\n\n    private void openCurrentAndNext() {\n        openCurrentAndMaybeNext(true);\n    }\n\n    private void openCurrentAndMaybeNext(final boolean openNext) {\n        synchronized (this) {\n            closeCursor();\n\n            if (mPlaylist.size() == 0) {\n                return;\n            }\n            stop(false);\n\n            boolean shutdown = false;\n\n            updateCursor(mPlaylist.get(mPlayPos).mId);\n            while (true) {\n                if (mCursor != null\n                        && openFile(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI + \"/\"\n                        + mCursor.getLong(IDCOLIDX))) {\n                    break;\n                }\n\n                closeCursor();\n                if (mOpenFailedCounter++ < 10 && mPlaylist.size() > 1) {\n                    final int pos = getNextPosition(false);\n                    if (pos < 0) {\n                        shutdown = true;\n                        break;\n                    }\n                    mPlayPos = pos;\n                    stop(false);\n                    mPlayPos = pos;\n                    updateCursor(mPlaylist.get(mPlayPos).mId);\n                } else {\n                    mOpenFailedCounter = 0;\n                    Log.w(TAG, \"Failed to open file for playback\");\n                    shutdown = true;\n                    break;\n                }\n            }\n\n            if (shutdown) {\n                scheduleDelayedShutdown();\n                if (mIsSupposedToBePlaying) {\n                    mIsSupposedToBePlaying = false;\n                    notifyChange(PLAYSTATE_CHANGED);\n                }\n            } else if (openNext) {\n                setNextTrack();\n            }\n        }\n    }\n\n    private void sendErrorMessage(final String trackName) {\n        final Intent i = new Intent(TRACK_ERROR);\n        i.putExtra(TrackErrorExtra.TRACK_NAME, trackName);\n        sendBroadcast(i);\n    }\n\n    private int getNextPosition(final boolean force) {\n        if (mPlaylist == null || mPlaylist.isEmpty()) {\n            return -1;\n        }\n        if (!force && mRepeatMode == REPEAT_CURRENT) {\n            if (mPlayPos < 0) {\n                return 0;\n            }\n            return mPlayPos;\n        } else if (mShuffleMode == SHUFFLE_NORMAL) {\n            final int numTracks = mPlaylist.size();\n\n\n            final int[] trackNumPlays = new int[numTracks];\n            for (int i = 0; i < numTracks; i++) {\n                trackNumPlays[i] = 0;\n            }\n\n\n            final int numHistory = mHistory.size();\n            for (int i = 0; i < numHistory; i++) {\n                final int idx = mHistory.get(i).intValue();\n                if (idx >= 0 && idx < numTracks) {\n                    trackNumPlays[idx]++;\n                }\n            }\n\n            if (mPlayPos >= 0 && mPlayPos < numTracks) {\n                trackNumPlays[mPlayPos]++;\n            }\n\n            int minNumPlays = Integer.MAX_VALUE;\n            int numTracksWithMinNumPlays = 0;\n            for (int i = 0; i < trackNumPlays.length; i++) {\n                if (trackNumPlays[i] < minNumPlays) {\n                    minNumPlays = trackNumPlays[i];\n                    numTracksWithMinNumPlays = 1;\n                } else if (trackNumPlays[i] == minNumPlays) {\n                    numTracksWithMinNumPlays++;\n                }\n            }\n\n\n            if (minNumPlays > 0 && numTracksWithMinNumPlays == numTracks\n                    && mRepeatMode != REPEAT_ALL && !force) {\n                return -1;\n            }\n\n\n            int skip = mShuffler.nextInt(numTracksWithMinNumPlays);\n            for (int i = 0; i < trackNumPlays.length; i++) {\n                if (trackNumPlays[i] == minNumPlays) {\n                    if (skip == 0) {\n                        return i;\n                    } else {\n                        skip--;\n                    }\n                }\n            }\n\n            if (D)\n                Log.e(TAG, \"Getting the next position resulted did not get a result when it should have\");\n            return -1;\n        } else if (mShuffleMode == SHUFFLE_AUTO) {\n            doAutoShuffleUpdate();\n            return mPlayPos + 1;\n        } else {\n            if (mPlayPos >= mPlaylist.size() - 1) {\n                if (mRepeatMode == REPEAT_NONE && !force) {\n                    return -1;\n                } else if (mRepeatMode == REPEAT_ALL || force) {\n                    return 0;\n                }\n                return -1;\n            } else {\n                return mPlayPos + 1;\n            }\n        }\n    }\n\n    private void setNextTrack() {\n        setNextTrack(getNextPosition(false));\n    }\n\n    private void setNextTrack(int position) {\n        mNextPlayPos = position;\n        if (D) Log.d(TAG, \"setNextTrack: next play position = \" + mNextPlayPos);\n        if (mNextPlayPos >= 0 && mPlaylist != null && mNextPlayPos < mPlaylist.size()) {\n            final long id = mPlaylist.get(mNextPlayPos).mId;\n            mPlayer.setNextDataSource(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI + \"/\" + id);\n        } else {\n            mPlayer.setNextDataSource(null);\n        }\n    }\n\n    private boolean makeAutoShuffleList() {\n        Cursor cursor = null;\n        try {\n            cursor = getContentResolver().query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,\n                    new String[]{\n                            MediaStore.Audio.Media._ID\n                    }, MediaStore.Audio.Media.IS_MUSIC + \"=1\", null, null);\n            if (cursor == null || cursor.getCount() == 0) {\n                return false;\n            }\n            final int len = cursor.getCount();\n            final long[] list = new long[len];\n            for (int i = 0; i < len; i++) {\n                cursor.moveToNext();\n                list[i] = cursor.getLong(0);\n            }\n            mAutoShuffleList = list;\n            return true;\n        } catch (final RuntimeException e) {\n        } finally {\n            if (cursor != null) {\n                cursor.close();\n                cursor = null;\n            }\n        }\n        return false;\n    }\n\n    private void doAutoShuffleUpdate() {\n        boolean notify = false;\n        if (mPlayPos > 10) {\n            removeTracks(0, mPlayPos - 9);\n            notify = true;\n        }\n        final int toAdd = 7 - (mPlaylist.size() - (mPlayPos < 0 ? -1 : mPlayPos));\n        for (int i = 0; i < toAdd; i++) {\n            int lookback = mHistory.size();\n            int idx = -1;\n            while (true) {\n                idx = mShuffler.nextInt(mAutoShuffleList.length);\n                if (!wasRecentlyUsed(idx, lookback)) {\n                    break;\n                }\n                lookback /= 2;\n            }\n            mHistory.add(idx);\n            if (mHistory.size() > MAX_HISTORY_SIZE) {\n                mHistory.remove(0);\n            }\n            mPlaylist.add(new MusicPlaybackTrack(mAutoShuffleList[idx], -1, TimberUtils.IdType.NA, -1));\n            notify = true;\n        }\n        if (notify) {\n            notifyChange(QUEUE_CHANGED);\n        }\n    }\n\n    private boolean wasRecentlyUsed(final int idx, int lookbacksize) {\n        if (lookbacksize == 0) {\n            return false;\n        }\n        final int histsize = mHistory.size();\n        if (histsize < lookbacksize) {\n            lookbacksize = histsize;\n        }\n        final int maxidx = histsize - 1;\n        for (int i = 0; i < lookbacksize; i++) {\n            final long entry = mHistory.get(maxidx - i);\n            if (entry == idx) {\n                return true;\n            }\n        }\n        return false;\n    }\n\n    private void notifyChange(final String what) {\n        if (D) Log.d(TAG, \"notifyChange: what = \" + what);\n\n        // Update the lockscreen controls\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)\n            updateMediaSession(what);\n        else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH)\n            updateRemoteControlClient(what);\n\n        if (what.equals(POSITION_CHANGED)) {\n            return;\n        }\n\n        final Intent intent = new Intent(what);\n        intent.putExtra(\"id\", getAudioId());\n        intent.putExtra(\"artist\", getArtistName());\n        intent.putExtra(\"album\", getAlbumName());\n        intent.putExtra(\"albumid\", getAlbumId());\n        intent.putExtra(\"track\", getTrackName());\n        intent.putExtra(\"playing\", isPlaying());\n\n        sendStickyBroadcast(intent);\n\n        final Intent musicIntent = new Intent(intent);\n        musicIntent.setAction(what.replace(TIMBER_PACKAGE_NAME, MUSIC_PACKAGE_NAME));\n        sendStickyBroadcast(musicIntent);\n\n        if (what.equals(META_CHANGED)) {\n\n            mRecentStore.addSongId(getAudioId());\n            mSongPlayCount.bumpSongCount(getAudioId());\n\n        } else if (what.equals(QUEUE_CHANGED)) {\n            saveQueue(true);\n            if (isPlaying()) {\n\n                if (mNextPlayPos >= 0 && mNextPlayPos < mPlaylist.size()\n                        && getShuffleMode() != SHUFFLE_NONE) {\n                    setNextTrack(mNextPlayPos);\n                } else {\n                    setNextTrack();\n                }\n            }\n        } else {\n            saveQueue(false);\n        }\n\n        if (what.equals(PLAYSTATE_CHANGED)) {\n            updateNotification();\n        }\n\n    }\n\n    @SuppressWarnings(\"deprecation\")\n    @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)\n    private void updateRemoteControlClient(final String what) {\n        //Legacy for ICS\n        if (mRemoteControlClient != null) {\n            int playState = mIsSupposedToBePlaying\n                    ? RemoteControlClient.PLAYSTATE_PLAYING\n                    : RemoteControlClient.PLAYSTATE_PAUSED;\n            if (what.equals(META_CHANGED) || what.equals(QUEUE_CHANGED)) {\n                Bitmap albumArt = null;\n                if (mShowAlbumArtOnLockscreen) {\n                    albumArt = ImageLoader.getInstance().loadImageSync(TimberUtils.getAlbumArtUri(getAlbumId()).toString());\n                    if (albumArt != null) {\n\n                        Bitmap.Config config = albumArt.getConfig();\n                        if (config == null) {\n                            config = Bitmap.Config.ARGB_8888;\n                        }\n                        albumArt = albumArt.copy(config, false);\n                    }\n                }\n\n                RemoteControlClient.MetadataEditor editor = mRemoteControlClient.editMetadata(true);\n                editor.putString(MediaMetadataRetriever.METADATA_KEY_ALBUM, getAlbumName());\n                editor.putString(MediaMetadataRetriever.METADATA_KEY_ARTIST, getArtistName());\n                editor.putString(MediaMetadataRetriever.METADATA_KEY_TITLE, getTrackName());\n                editor.putLong(MediaMetadataRetriever.METADATA_KEY_DURATION, duration());\n                editor.putBitmap(MediaMetadataEditor.BITMAP_KEY_ARTWORK, albumArt);\n                editor.apply();\n\n            }\n            mRemoteControlClient.setPlaybackState(playState);\n        }\n    }\n\n\n    private void updateMediaSession(final String what) {\n        int playState = mIsSupposedToBePlaying\n                ? PlaybackStateCompat.STATE_PLAYING\n                : PlaybackStateCompat.STATE_PAUSED;\n\n        if (what.equals(PLAYSTATE_CHANGED) || what.equals(POSITION_CHANGED)) {\n            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {\n                mSession.setPlaybackState(new PlaybackStateCompat.Builder()\n                        .setState(playState, position(), 1.0f)\n                        .setActions(PlaybackStateCompat.ACTION_PLAY | PlaybackStateCompat.ACTION_PAUSE | PlaybackStateCompat.ACTION_PLAY_PAUSE |\n                                PlaybackStateCompat.ACTION_SKIP_TO_NEXT | PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS)\n                        .build());\n            }\n        } else if (what.equals(META_CHANGED) || what.equals(QUEUE_CHANGED)) {\n            Bitmap albumArt = null;\n            if (mShowAlbumArtOnLockscreen) {\n                albumArt = ImageLoader.getInstance().loadImageSync(TimberUtils.getAlbumArtUri(getAlbumId()).toString());\n                if (albumArt != null) {\n\n                    Bitmap.Config config = albumArt.getConfig();\n                    if (config == null) {\n                        config = Bitmap.Config.ARGB_8888;\n                    }\n                    albumArt = albumArt.copy(config, false);\n                }\n            }\n            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {\n                mSession.setMetadata(new MediaMetadataCompat.Builder()\n                        .putString(MediaMetadataCompat.METADATA_KEY_ARTIST, getArtistName())\n                        .putString(MediaMetadataCompat.METADATA_KEY_ALBUM_ARTIST, getAlbumArtistName())\n                        .putString(MediaMetadataCompat.METADATA_KEY_ALBUM, getAlbumName())\n                        .putString(MediaMetadataCompat.METADATA_KEY_TITLE, getTrackName())\n                        .putLong(MediaMetadataCompat.METADATA_KEY_DURATION, duration())\n                        .putLong(MediaMetadataCompat.METADATA_KEY_TRACK_NUMBER, getQueuePosition() + 1)\n                        .putLong(MediaMetadataCompat.METADATA_KEY_NUM_TRACKS, getQueue().length)\n                        .putString(MediaMetadataCompat.METADATA_KEY_GENRE, getGenreName())\n                        .putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, albumArt)\n                        .build());\n\n                mSession.setPlaybackState(new PlaybackStateCompat.Builder()\n                        .setState(playState, position(), 1.0f)\n                        .setActions(PlaybackStateCompat.ACTION_PLAY | PlaybackStateCompat.ACTION_PAUSE | PlaybackStateCompat.ACTION_PLAY_PAUSE |\n                                PlaybackStateCompat.ACTION_SKIP_TO_NEXT | PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS)\n                        .build());\n            }\n        }\n    }\n\n    private void createNotificationChannel() {\n        if (TimberUtils.isOreo()) {\n            CharSequence name = \"Timber\";\n            int importance = NotificationManager.IMPORTANCE_LOW;\n            NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);\n            NotificationChannel mChannel = new NotificationChannel(CHANNEL_ID, name, importance);\n            manager.createNotificationChannel(mChannel);\n        }\n    }\n\n    private Notification buildNotification() {\n        final String albumName = getAlbumName();\n        final String artistName = getArtistName();\n        final boolean isPlaying = isPlaying();\n        String text = TextUtils.isEmpty(albumName)\n                ? artistName : artistName + \" - \" + albumName;\n\n        int playButtonResId = isPlaying\n                ? R.drawable.ic_pause_white_36dp : R.drawable.ic_play_white_36dp;\n\n        Intent nowPlayingIntent = NavigationUtils.getNowPlayingIntent(this);\n        PendingIntent clickIntent = PendingIntent.getActivity(this, 0, nowPlayingIntent, PendingIntent.FLAG_UPDATE_CURRENT);\n        Bitmap artwork;\n        artwork = ImageLoader.getInstance().loadImageSync(TimberUtils.getAlbumArtUri(getAlbumId()).toString());\n\n        if (artwork == null) {\n            artwork = ImageLoader.getInstance().loadImageSync(\"drawable://\" + R.drawable.ic_empty_music2);\n        }\n\n        if (mNotificationPostTime == 0) {\n            mNotificationPostTime = System.currentTimeMillis();\n        }\n\n        androidx.core.app.NotificationCompat.Builder builder = new androidx.core.app.NotificationCompat.Builder(this, CHANNEL_ID)\n                .setSmallIcon(R.drawable.ic_notification)\n                .setLargeIcon(artwork)\n                .setContentIntent(clickIntent)\n                .setContentTitle(getTrackName())\n                .setContentText(text)\n                .setWhen(mNotificationPostTime)\n                .addAction(R.drawable.ic_skip_previous_white_36dp,\n                        \"\",\n                        retrievePlaybackAction(PREVIOUS_ACTION))\n                .addAction(playButtonResId, \"\",\n                        retrievePlaybackAction(TOGGLEPAUSE_ACTION))\n                .addAction(R.drawable.ic_skip_next_white_36dp,\n                        \"\",\n                        retrievePlaybackAction(NEXT_ACTION));\n\n        if (TimberUtils.isJellyBeanMR1()) {\n            builder.setShowWhen(false);\n        }\n\n        if (TimberUtils.isLollipop()) {\n            builder.setVisibility(Notification.VISIBILITY_PUBLIC);\n            NotificationCompat.MediaStyle style = new NotificationCompat.MediaStyle()\n                    .setMediaSession(mSession.getSessionToken())\n                    .setShowActionsInCompactView(0, 1, 2, 3);\n            builder.setStyle(style);\n        }\n        if (artwork != null && TimberUtils.isLollipop()) {\n            builder.setColor(Palette.from(artwork).generate().getVibrantColor(Color.parseColor(\"#403f4d\")));\n        }\n\n        if (TimberUtils.isOreo()) {\n            builder.setColorized(true);\n        }\n\n        Notification n = builder.build();\n\n        if (mActivateXTrackSelector) {\n            addXTrackSelector(n);\n        }\n\n        return n;\n    }\n\n    private void addXTrackSelector(Notification n) {\n        if (NotificationHelper.isSupported(n)) {\n            StringBuilder selection = new StringBuilder();\n            StringBuilder order = new StringBuilder().append(\"CASE _id \\n\");\n            for (int i = 0; i < mPlaylist.size(); i++) {\n                selection.append(\"_id=\").append(mPlaylist.get(i).mId).append(\" OR \");\n                order.append(\"WHEN \").append(mPlaylist.get(i).mId).append(\" THEN \").append(i).append(\"\\n\");\n            }\n            order.append(\"END\");\n            Cursor c = getContentResolver().query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, NOTIFICATION_PROJECTION, selection.substring(0, selection.length() - 3), null, order.toString());\n            if (c != null && c.getCount() != 0) {\n                c.moveToFirst();\n                ArrayList<Bundle> list = new ArrayList<>();\n                do {\n                    TrackItem t = new TrackItem()\n                            .setArt(TimberUtils.getAlbumArtUri(c.getLong(c.getColumnIndexOrThrow(AudioColumns.ALBUM_ID))))\n                            .setTitle(c.getString(c.getColumnIndexOrThrow(AudioColumns.TITLE)))\n                            .setArtist(c.getString(c.getColumnIndexOrThrow(AudioColumns.ARTIST)))\n                            .setDuration(TimberUtils.makeShortTimeString(this, c.getInt(c.getColumnIndexOrThrow(AudioColumns.DURATION)) / 1000));\n                    list.add(t.get());\n                } while (c.moveToNext());\n                try {\n                    NotificationHelper.insertToNotification(n, list, this, getQueuePosition());\n                } catch (ModNotInstalledException e) {\n                    e.printStackTrace();\n                }\n                c.close();\n            }\n        }\n    }\n\n    private final PendingIntent retrievePlaybackAction(final String action) {\n        final ComponentName serviceName = new ComponentName(this, MusicService.class);\n        Intent intent = new Intent(action);\n        intent.setComponent(serviceName);\n\n        return PendingIntent.getService(this, 0, intent, 0);\n    }\n\n    private void saveQueue(final boolean full) {\n        if (!mQueueIsSaveable) {\n            return;\n        }\n\n        final SharedPreferences.Editor editor = mPreferences.edit();\n        if (full) {\n            mPlaybackStateStore.saveState(mPlaylist,\n                    mShuffleMode != SHUFFLE_NONE ? mHistory : null);\n            editor.putInt(\"cardid\", mCardId);\n        }\n        editor.putInt(\"curpos\", mPlayPos);\n        if (mPlayer.isInitialized()) {\n            editor.putLong(\"seekpos\", mPlayer.position());\n        }\n        editor.putInt(\"repeatmode\", mRepeatMode);\n        editor.putInt(\"shufflemode\", mShuffleMode);\n        editor.apply();\n    }\n\n    private void reloadQueueAfterPermissionCheck() {\n        if (TimberUtils.isMarshmallow()) {\n            if (Nammu.checkPermission(Manifest.permission.READ_EXTERNAL_STORAGE)) {\n                reloadQueue();\n            }\n        } else {\n            reloadQueue();\n        }\n    }\n\n    private void reloadQueue() {\n        int id = mCardId;\n        if (mPreferences.contains(\"cardid\")) {\n            id = mPreferences.getInt(\"cardid\", ~mCardId);\n        }\n        if (id == mCardId) {\n            mPlaylist = mPlaybackStateStore.getQueue();\n        }\n        if (mPlaylist.size() > 0) {\n            final int pos = mPreferences.getInt(\"curpos\", 0);\n            if (pos < 0 || pos >= mPlaylist.size()) {\n                mPlaylist.clear();\n                return;\n            }\n            mPlayPos = pos;\n            updateCursor(mPlaylist.get(mPlayPos).mId);\n            if (mCursor == null) {\n                SystemClock.sleep(3000);\n                updateCursor(mPlaylist.get(mPlayPos).mId);\n            }\n            synchronized (this) {\n                closeCursor();\n                mOpenFailedCounter = 20;\n                openCurrentAndNext();\n            }\n            if (!mPlayer.isInitialized()) {\n                mPlaylist.clear();\n                return;\n            }\n\n            final long seekpos = mPreferences.getLong(\"seekpos\", 0);\n            seek(seekpos >= 0 && seekpos < duration() ? seekpos : 0);\n\n            if (D) {\n                Log.d(TAG, \"restored queue, currently at position \"\n                        + position() + \"/\" + duration()\n                        + \" (requested \" + seekpos + \")\");\n            }\n\n            int repmode = mPreferences.getInt(\"repeatmode\", REPEAT_NONE);\n            if (repmode != REPEAT_ALL && repmode != REPEAT_CURRENT) {\n                repmode = REPEAT_NONE;\n            }\n            mRepeatMode = repmode;\n\n            int shufmode = mPreferences.getInt(\"shufflemode\", SHUFFLE_NONE);\n            if (shufmode != SHUFFLE_AUTO && shufmode != SHUFFLE_NORMAL) {\n                shufmode = SHUFFLE_NONE;\n            }\n            if (shufmode != SHUFFLE_NONE) {\n                mHistory = mPlaybackStateStore.getHistory(mPlaylist.size());\n            }\n            if (shufmode == SHUFFLE_AUTO) {\n                if (!makeAutoShuffleList()) {\n                    shufmode = SHUFFLE_NONE;\n                }\n            }\n            mShuffleMode = shufmode;\n        }\n    }\n\n    public boolean openFile(final String path) {\n        if (D) Log.d(TAG, \"openFile: path = \" + path);\n        synchronized (this) {\n            if (path == null) {\n                return false;\n            }\n\n            if (mCursor == null) {\n                Uri uri = Uri.parse(path);\n                boolean shouldAddToPlaylist = true;\n                long id = -1;\n                try {\n                    id = Long.valueOf(uri.getLastPathSegment());\n                } catch (NumberFormatException ex) {\n                    // Ignore\n                }\n\n                if (id != -1 && path.startsWith(\n                        MediaStore.Audio.Media.EXTERNAL_CONTENT_URI.toString())) {\n                    updateCursor(uri);\n\n                } else if (id != -1 && path.startsWith(\n                        MediaStore.Files.getContentUri(\"external\").toString())) {\n                    updateCursor(id);\n\n                } else if (path.startsWith(\"content://downloads/\")) {\n\n                    String mpUri = getValueForDownloadedFile(this, uri, \"mediaprovider_uri\");\n                    if (D) Log.i(TAG, \"Downloaded file's MP uri : \" + mpUri);\n                    if (!TextUtils.isEmpty(mpUri)) {\n                        if (openFile(mpUri)) {\n                            notifyChange(META_CHANGED);\n                            return true;\n                        } else {\n                            return false;\n                        }\n                    } else {\n                        updateCursorForDownloadedFile(this, uri);\n                        shouldAddToPlaylist = false;\n                    }\n\n                } else {\n                    String where = MediaStore.Audio.Media.DATA + \"=?\";\n                    String[] selectionArgs = new String[]{path};\n                    updateCursor(where, selectionArgs);\n                }\n                try {\n                    if (mCursor != null && shouldAddToPlaylist) {\n                        mPlaylist.clear();\n                        mPlaylist.add(new MusicPlaybackTrack(\n                                mCursor.getLong(IDCOLIDX), -1, TimberUtils.IdType.NA, -1));\n                        notifyChange(QUEUE_CHANGED);\n                        mPlayPos = 0;\n                        mHistory.clear();\n                    }\n                } catch (final UnsupportedOperationException ex) {\n                    // Ignore\n                }\n            }\n\n            mFileToPlay = path;\n            mPlayer.setDataSource(mFileToPlay);\n            if (mPlayer.isInitialized()) {\n                mOpenFailedCounter = 0;\n                return true;\n            }\n\n            String trackName = getTrackName();\n            if (TextUtils.isEmpty(trackName)) {\n                trackName = path;\n            }\n            sendErrorMessage(trackName);\n\n            stop(true);\n            return false;\n        }\n    }\n\n    private void updateCursorForDownloadedFile(Context context, Uri uri) {\n        synchronized (this) {\n            closeCursor();\n            MatrixCursor cursor = new MatrixCursor(PROJECTION_MATRIX);\n            String title = getValueForDownloadedFile(this, uri, \"title\");\n            cursor.addRow(new Object[]{\n                    null,\n                    null,\n                    null,\n                    title,\n                    null,\n                    null,\n                    null,\n                    null\n            });\n            mCursor = cursor;\n            mCursor.moveToFirst();\n        }\n    }\n\n    private String getValueForDownloadedFile(Context context, Uri uri, String column) {\n\n        Cursor cursor = null;\n        final String[] projection = {\n                column\n        };\n\n        try {\n            cursor = context.getContentResolver().query(uri, projection, null, null, null);\n            if (cursor != null && cursor.moveToFirst()) {\n                return cursor.getString(0);\n            }\n        } finally {\n            if (cursor != null) {\n                cursor.close();\n            }\n        }\n        return null;\n    }\n\n    public int getAudioSessionId() {\n        synchronized (this) {\n            return mPlayer.getAudioSessionId();\n        }\n    }\n\n    public int getMediaMountedCount() {\n        return mMediaMountedCount;\n    }\n\n    public int getShuffleMode() {\n        return mShuffleMode;\n    }\n\n    public void setShuffleMode(final int shufflemode) {\n        synchronized (this) {\n            if (mShuffleMode == shufflemode && mPlaylist.size() > 0) {\n                return;\n            }\n\n            mShuffleMode = shufflemode;\n            if (mShuffleMode == SHUFFLE_AUTO) {\n                if (makeAutoShuffleList()) {\n                    mPlaylist.clear();\n                    doAutoShuffleUpdate();\n                    mPlayPos = 0;\n                    openCurrentAndNext();\n                    play();\n                    notifyChange(META_CHANGED);\n                    return;\n                } else {\n                    mShuffleMode = SHUFFLE_NONE;\n                }\n            } else {\n                setNextTrack();\n            }\n            saveQueue(false);\n            notifyChange(SHUFFLEMODE_CHANGED);\n        }\n    }\n\n    public int getRepeatMode() {\n        return mRepeatMode;\n    }\n\n    public void setRepeatMode(final int repeatmode) {\n        synchronized (this) {\n            mRepeatMode = repeatmode;\n            setNextTrack();\n            saveQueue(false);\n            notifyChange(REPEATMODE_CHANGED);\n        }\n    }\n\n    public int removeTrack(final long id) {\n        int numremoved = 0;\n        synchronized (this) {\n            for (int i = 0; i < mPlaylist.size(); i++) {\n                if (mPlaylist.get(i).mId == id) {\n                    numremoved += removeTracksInternal(i, i);\n                    i--;\n                }\n            }\n        }\n        if (numremoved > 0) {\n            notifyChange(QUEUE_CHANGED);\n        }\n        return numremoved;\n    }\n\n    public boolean removeTrackAtPosition(final long id, final int position) {\n        synchronized (this) {\n            if (position >= 0 &&\n                    position < mPlaylist.size() &&\n                    mPlaylist.get(position).mId == id) {\n\n                return removeTracks(position, position) > 0;\n            }\n        }\n        return false;\n    }\n\n    public int removeTracks(final int first, final int last) {\n        final int numremoved = removeTracksInternal(first, last);\n        if (numremoved > 0) {\n            notifyChange(QUEUE_CHANGED);\n        }\n        return numremoved;\n    }\n\n    public int getQueuePosition() {\n        synchronized (this) {\n            return mPlayPos;\n        }\n    }\n\n    public void setQueuePosition(final int index) {\n        synchronized (this) {\n            stop(false);\n            mPlayPos = index;\n            openCurrentAndNext();\n            play();\n            notifyChange(META_CHANGED);\n            if (mShuffleMode == SHUFFLE_AUTO) {\n                doAutoShuffleUpdate();\n            }\n        }\n    }\n\n    public int getQueueHistorySize() {\n        synchronized (this) {\n            return mHistory.size();\n        }\n    }\n\n    public int getQueueHistoryPosition(int position) {\n        synchronized (this) {\n            if (position >= 0 && position < mHistory.size()) {\n                return mHistory.get(position);\n            }\n        }\n\n        return -1;\n    }\n\n    public int[] getQueueHistoryList() {\n        synchronized (this) {\n            int[] history = new int[mHistory.size()];\n            for (int i = 0; i < mHistory.size(); i++) {\n                history[i] = mHistory.get(i);\n            }\n\n            return history;\n        }\n    }\n\n    public String getPath() {\n        synchronized (this) {\n            if (mCursor == null) {\n                return null;\n            }\n            return mCursor.getString(mCursor.getColumnIndexOrThrow(AudioColumns.DATA));\n        }\n    }\n\n    public String getAlbumName() {\n        synchronized (this) {\n            if (mCursor == null) {\n                return null;\n            }\n            return mCursor.getString(mCursor.getColumnIndexOrThrow(AudioColumns.ALBUM));\n        }\n    }\n\n    public String getTrackName() {\n        synchronized (this) {\n            if (mCursor == null) {\n                return null;\n            }\n            return mCursor.getString(mCursor.getColumnIndexOrThrow(AudioColumns.TITLE));\n        }\n    }\n\n    public String getGenreName() {\n        synchronized (this) {\n            if (mCursor == null || mPlayPos < 0 || mPlayPos >= mPlaylist.size()) {\n                return null;\n            }\n            String[] genreProjection = {MediaStore.Audio.Genres.NAME};\n            Uri genreUri = MediaStore.Audio.Genres.getContentUriForAudioId(\"external\",\n                    (int) mPlaylist.get(mPlayPos).mId);\n            Cursor genreCursor = getContentResolver().query(genreUri, genreProjection,\n                    null, null, null);\n            if (genreCursor != null) {\n                try {\n                    if (genreCursor.moveToFirst()) {\n                        return genreCursor.getString(\n                                genreCursor.getColumnIndexOrThrow(MediaStore.Audio.Genres.NAME));\n                    }\n                } finally {\n                    genreCursor.close();\n                }\n            }\n            return null;\n        }\n    }\n\n    public String getArtistName() {\n        synchronized (this) {\n            if (mCursor == null) {\n                return null;\n            }\n            return mCursor.getString(mCursor.getColumnIndexOrThrow(AudioColumns.ARTIST));\n        }\n    }\n\n    public String getAlbumArtistName() {\n        synchronized (this) {\n            if (mAlbumCursor == null) {\n                return null;\n            }\n            return mAlbumCursor.getString(mAlbumCursor.getColumnIndexOrThrow(AlbumColumns.ARTIST));\n        }\n    }\n\n    public long getAlbumId() {\n        synchronized (this) {\n            if (mCursor == null) {\n                return -1;\n            }\n            return mCursor.getLong(mCursor.getColumnIndexOrThrow(AudioColumns.ALBUM_ID));\n        }\n    }\n\n    public long getArtistId() {\n        synchronized (this) {\n            if (mCursor == null) {\n                return -1;\n            }\n            return mCursor.getLong(mCursor.getColumnIndexOrThrow(AudioColumns.ARTIST_ID));\n        }\n    }\n\n    public long getAudioId() {\n        MusicPlaybackTrack track = getCurrentTrack();\n        if (track != null) {\n            return track.mId;\n        }\n\n        return -1;\n    }\n\n    public MusicPlaybackTrack getCurrentTrack() {\n        return getTrack(mPlayPos);\n    }\n\n    public synchronized MusicPlaybackTrack getTrack(int index) {\n        if (index >= 0 && index < mPlaylist.size() && mPlayer.isInitialized()) {\n            return mPlaylist.get(index);\n        }\n\n        return null;\n    }\n\n    public long getNextAudioId() {\n        synchronized (this) {\n            if (mNextPlayPos >= 0 && mNextPlayPos < mPlaylist.size() && mPlayer.isInitialized()) {\n                return mPlaylist.get(mNextPlayPos).mId;\n            }\n        }\n        return -1;\n    }\n\n    public long getPreviousAudioId() {\n        synchronized (this) {\n            if (mPlayer.isInitialized()) {\n                int pos = getPreviousPlayPosition(false);\n                if (pos >= 0 && pos < mPlaylist.size()) {\n                    return mPlaylist.get(pos).mId;\n                }\n            }\n        }\n        return -1;\n    }\n\n    public long seek(long position) {\n        if (mPlayer.isInitialized()) {\n            if (position < 0) {\n                position = 0;\n            } else if (position > mPlayer.duration()) {\n                position = mPlayer.duration();\n            }\n            long result = mPlayer.seek(position);\n            notifyChange(POSITION_CHANGED);\n            return result;\n        }\n        return -1;\n    }\n\n    public void seekRelative(long deltaInMs) {\n        synchronized (this) {\n            if (mPlayer.isInitialized()) {\n                final long newPos = position() + deltaInMs;\n                final long duration = duration();\n                if (newPos < 0) {\n                    prev(true);\n                    // seek to the new duration + the leftover position\n                    seek(duration() + newPos);\n                } else if (newPos >= duration) {\n                    gotoNext(true);\n                    // seek to the leftover duration\n                    seek(newPos - duration);\n                } else {\n                    seek(newPos);\n                }\n            }\n        }\n    }\n\n    public long position() {\n        if (mPlayer.isInitialized()) {\n            return mPlayer.position();\n        }\n        return -1;\n    }\n\n    public long duration() {\n        if (mPlayer.isInitialized()) {\n            return mPlayer.duration();\n        }\n        return -1;\n    }\n\n    public long[] getQueue() {\n        synchronized (this) {\n            final int len = mPlaylist.size();\n            final long[] list = new long[len];\n            for (int i = 0; i < len; i++) {\n                list[i] = mPlaylist.get(i).mId;\n            }\n            return list;\n        }\n    }\n\n    public long getQueueItemAtPosition(int position) {\n        synchronized (this) {\n            if (position >= 0 && position < mPlaylist.size()) {\n                return mPlaylist.get(position).mId;\n            }\n        }\n\n        return -1;\n    }\n\n    public int getQueueSize() {\n        synchronized (this) {\n            return mPlaylist.size();\n        }\n    }\n\n    public boolean isPlaying() {\n        return mIsSupposedToBePlaying;\n    }\n\n    private void setIsSupposedToBePlaying(boolean value, boolean notify) {\n        if (mIsSupposedToBePlaying != value) {\n            mIsSupposedToBePlaying = value;\n\n\n            if (!mIsSupposedToBePlaying) {\n                scheduleDelayedShutdown();\n                mLastPlayedTime = System.currentTimeMillis();\n            }\n\n            if (notify) {\n                notifyChange(PLAYSTATE_CHANGED);\n            }\n        }\n    }\n\n    private boolean recentlyPlayed() {\n        return isPlaying() || System.currentTimeMillis() - mLastPlayedTime < IDLE_DELAY;\n    }\n\n    public void open(final long[] list, final int position, long sourceId, TimberUtils.IdType sourceType) {\n        synchronized (this) {\n            if (mShuffleMode == SHUFFLE_AUTO) {\n                mShuffleMode = SHUFFLE_NORMAL;\n            }\n            final long oldId = getAudioId();\n            final int listlength = list.length;\n            boolean newlist = true;\n            if (mPlaylist.size() == listlength) {\n                newlist = false;\n                for (int i = 0; i < listlength; i++) {\n                    if (list[i] != mPlaylist.get(i).mId) {\n                        newlist = true;\n                        break;\n                    }\n                }\n            }\n            if (newlist) {\n                addToPlayList(list, -1, sourceId, sourceType);\n                notifyChange(QUEUE_CHANGED);\n            }\n            if (position >= 0) {\n                mPlayPos = position;\n            } else {\n                mPlayPos = mShuffler.nextInt(mPlaylist.size());\n            }\n            mHistory.clear();\n            openCurrentAndNext();\n            if (oldId != getAudioId()) {\n                notifyChange(META_CHANGED);\n            }\n        }\n    }\n\n    public void stop() {\n        stop(true);\n    }\n\n    public void play() {\n        play(true);\n    }\n\n    public void play(boolean createNewNextTrack) {\n        int status = mAudioManager.requestAudioFocus(mAudioFocusListener,\n                AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);\n\n        if (D) Log.d(TAG, \"Starting playback: audio focus request status = \" + status);\n\n        if (status != AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {\n            return;\n        }\n\n        final Intent intent = new Intent(AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION);\n        intent.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, getAudioSessionId());\n        intent.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, getPackageName());\n        sendBroadcast(intent);\n\n        mAudioManager.registerMediaButtonEventReceiver(new ComponentName(getPackageName(),\n                MediaButtonIntentReceiver.class.getName()));\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)\n            mSession.setActive(true);\n\n        if (createNewNextTrack) {\n            setNextTrack();\n        } else {\n            setNextTrack(mNextPlayPos);\n        }\n\n        if (mPlayer.isInitialized()) {\n            final long duration = mPlayer.duration();\n            if (mRepeatMode != REPEAT_CURRENT && duration > 2000\n                    && mPlayer.position() >= duration - 2000) {\n                gotoNext(true);\n            }\n\n            mPlayer.start();\n            mPlayerHandler.removeMessages(FADEDOWN);\n            mPlayerHandler.sendEmptyMessage(FADEUP);\n\n            setIsSupposedToBePlaying(true, true);\n\n            cancelShutdown();\n            updateNotification();\n            notifyChange(META_CHANGED);\n        } else if (mPlaylist.size() <= 0) {\n            setShuffleMode(SHUFFLE_AUTO);\n        }\n    }\n\n    public void pause() {\n        if (D) Log.d(TAG, \"Pausing playback\");\n        synchronized (this) {\n            mPlayerHandler.removeMessages(FADEUP);\n            if (mIsSupposedToBePlaying) {\n                final Intent intent = new Intent(\n                        AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION);\n                intent.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, getAudioSessionId());\n                intent.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, getPackageName());\n                sendBroadcast(intent);\n\n                mPlayer.pause();\n                notifyChange(META_CHANGED);\n                setIsSupposedToBePlaying(false, true);\n            }\n        }\n    }\n\n    public void gotoNext(final boolean force) {\n        if (D) Log.d(TAG, \"Going to next track\");\n        synchronized (this) {\n            if (mPlaylist.size() <= 0) {\n                if (D) Log.d(TAG, \"No play queue\");\n                scheduleDelayedShutdown();\n                return;\n            }\n            int pos = mNextPlayPos;\n            if (pos < 0) {\n                pos = getNextPosition(force);\n            }\n\n            if (pos < 0) {\n                setIsSupposedToBePlaying(false, true);\n                return;\n            }\n\n            stop(false);\n            setAndRecordPlayPos(pos);\n            openCurrentAndNext();\n            play();\n            notifyChange(META_CHANGED);\n        }\n    }\n\n    public void goToPosition(int pos) {\n        synchronized (this) {\n            if (mPlaylist.size() <= 0) {\n                if (D) Log.d(TAG, \"No play queue\");\n                scheduleDelayedShutdown();\n                return;\n            }\n            if (pos < 0) {\n                return;\n            }\n            if (pos == mPlayPos) {\n                if (!isPlaying()) {\n                    play();\n                }\n                return;\n            }\n            stop(false);\n            setAndRecordPlayPos(pos);\n            openCurrentAndNext();\n            play();\n            notifyChange(META_CHANGED);\n        }\n    }\n\n    public void setAndRecordPlayPos(int nextPos) {\n        synchronized (this) {\n\n            if (mShuffleMode != SHUFFLE_NONE) {\n                mHistory.add(mPlayPos);\n                if (mHistory.size() > MAX_HISTORY_SIZE) {\n                    mHistory.remove(0);\n                }\n            }\n\n            mPlayPos = nextPos;\n        }\n    }\n\n    public void prev(boolean forcePrevious) {\n        synchronized (this) {\n            boolean goPrevious = getRepeatMode() != REPEAT_CURRENT &&\n                    (position() < REWIND_INSTEAD_PREVIOUS_THRESHOLD || forcePrevious);\n\n            if (goPrevious) {\n                if (D) Log.d(TAG, \"Going to previous track\");\n                int pos = getPreviousPlayPosition(true);\n\n                if (pos < 0) {\n                    return;\n                }\n                mNextPlayPos = mPlayPos;\n                mPlayPos = pos;\n                stop(false);\n                openCurrent();\n                play(false);\n                notifyChange(META_CHANGED);\n            } else {\n                if (D) Log.d(TAG, \"Going to beginning of track\");\n                seek(0);\n                play(false);\n            }\n        }\n    }\n\n    public int getPreviousPlayPosition(boolean removeFromHistory) {\n        synchronized (this) {\n            if (mShuffleMode == SHUFFLE_NORMAL) {\n\n                final int histsize = mHistory.size();\n                if (histsize == 0) {\n                    return -1;\n                }\n                final Integer pos = mHistory.get(histsize - 1);\n                if (removeFromHistory) {\n                    mHistory.remove(histsize - 1);\n                }\n                return pos.intValue();\n            } else {\n                if (mPlayPos > 0) {\n                    return mPlayPos - 1;\n                } else {\n                    return mPlaylist.size() - 1;\n                }\n            }\n        }\n    }\n\n    private void openCurrent() {\n        openCurrentAndMaybeNext(false);\n    }\n\n    public void moveQueueItem(int index1, int index2) {\n        synchronized (this) {\n            if (index1 >= mPlaylist.size()) {\n                index1 = mPlaylist.size() - 1;\n            }\n            if (index2 >= mPlaylist.size()) {\n                index2 = mPlaylist.size() - 1;\n            }\n\n            if (index1 == index2) {\n                return;\n            }\n\n            final MusicPlaybackTrack track = mPlaylist.remove(index1);\n            if (index1 < index2) {\n                mPlaylist.add(index2, track);\n                if (mPlayPos == index1) {\n                    mPlayPos = index2;\n                } else if (mPlayPos >= index1 && mPlayPos <= index2) {\n                    mPlayPos--;\n                }\n            } else if (index2 < index1) {\n                mPlaylist.add(index2, track);\n                if (mPlayPos == index1) {\n                    mPlayPos = index2;\n                } else if (mPlayPos >= index2 && mPlayPos <= index1) {\n                    mPlayPos++;\n                }\n            }\n            notifyChange(QUEUE_CHANGED);\n        }\n    }\n\n    public void enqueue(final long[] list, final int action, long sourceId, IdType sourceType) {\n        synchronized (this) {\n            if (action == NEXT && mPlayPos + 1 < mPlaylist.size()) {\n                addToPlayList(list, mPlayPos + 1, sourceId, sourceType);\n                mNextPlayPos = mPlayPos + 1;\n                notifyChange(QUEUE_CHANGED);\n            } else {\n                addToPlayList(list, Integer.MAX_VALUE, sourceId, sourceType);\n                notifyChange(QUEUE_CHANGED);\n            }\n\n            if (mPlayPos < 0) {\n                mPlayPos = 0;\n                openCurrentAndNext();\n                play();\n                notifyChange(META_CHANGED);\n            }\n        }\n    }\n\n    private void cycleRepeat() {\n        if (mRepeatMode == REPEAT_NONE) {\n            setRepeatMode(REPEAT_CURRENT);\n            if (mShuffleMode != SHUFFLE_NONE) {\n                setShuffleMode(SHUFFLE_NONE);\n            }\n        } else {\n            setRepeatMode(REPEAT_NONE);\n        }\n    }\n\n    private void cycleShuffle() {\n        if (mShuffleMode == SHUFFLE_NONE) {\n            setShuffleMode(SHUFFLE_NORMAL);\n//            if (mRepeatMode == REPEAT_CURRENT) {\n//                setRepeatMode(REPEAT_ALL);\n//            }\n        } else if (mShuffleMode == SHUFFLE_NORMAL || mShuffleMode == SHUFFLE_AUTO) {\n            setShuffleMode(SHUFFLE_NONE);\n        }\n    }\n\n    public void refresh() {\n        notifyChange(REFRESH);\n    }\n\n    public void playlistChanged() {\n        notifyChange(PLAYLIST_CHANGED);\n    }\n\n    public interface TrackErrorExtra {\n\n        String TRACK_NAME = \"trackname\";\n    }\n\n    private static final class MusicPlayerHandler extends Handler {\n        private final WeakReference<MusicService> mService;\n        private float mCurrentVolume = 1.0f;\n\n\n        public MusicPlayerHandler(final MusicService service, final Looper looper) {\n            super(looper);\n            mService = new WeakReference<MusicService>(service);\n        }\n\n\n        @Override\n        public void handleMessage(final Message msg) {\n            final MusicService service = mService.get();\n            if (service == null) {\n                return;\n            }\n\n            synchronized (service) {\n                switch (msg.what) {\n                    case FADEDOWN:\n                        mCurrentVolume -= .05f;\n                        if (mCurrentVolume > .2f) {\n                            sendEmptyMessageDelayed(FADEDOWN, 10);\n                        } else {\n                            mCurrentVolume = .2f;\n                        }\n                        service.mPlayer.setVolume(mCurrentVolume);\n                        break;\n                    case FADEUP:\n                        mCurrentVolume += .01f;\n                        if (mCurrentVolume < 1.0f) {\n                            sendEmptyMessageDelayed(FADEUP, 10);\n                        } else {\n                            mCurrentVolume = 1.0f;\n                        }\n                        service.mPlayer.setVolume(mCurrentVolume);\n                        break;\n                    case SERVER_DIED:\n                        if (service.isPlaying()) {\n                            final TrackErrorInfo info = (TrackErrorInfo) msg.obj;\n                            service.sendErrorMessage(info.mTrackName);\n\n\n                            service.removeTrack(info.mId);\n                        } else {\n                            service.openCurrentAndNext();\n                        }\n                        break;\n                    case TRACK_WENT_TO_NEXT:\n                        mService.get().scrobble();\n                        service.setAndRecordPlayPos(service.mNextPlayPos);\n                        service.setNextTrack();\n                        if (service.mCursor != null) {\n                            service.mCursor.close();\n                            service.mCursor = null;\n                        }\n                        service.updateCursor(service.mPlaylist.get(service.mPlayPos).mId);\n                        service.notifyChange(META_CHANGED);\n                        service.updateNotification();\n                        break;\n                    case TRACK_ENDED:\n                        if (service.mRepeatMode == REPEAT_CURRENT) {\n                            service.seek(0);\n                            service.play();\n                        } else {\n                            service.gotoNext(false);\n                        }\n                        break;\n                    case RELEASE_WAKELOCK:\n                        service.mWakeLock.release();\n                        break;\n                    case FOCUSCHANGE:\n                        if (D) Log.d(TAG, \"Received audio focus change event \" + msg.arg1);\n                        switch (msg.arg1) {\n                            case AudioManager.AUDIOFOCUS_LOSS:\n                            case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:\n                                if (service.isPlaying()) {\n                                    service.mPausedByTransientLossOfFocus =\n                                            msg.arg1 == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT;\n                                }\n                                service.pause();\n                                break;\n                            case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:\n                                removeMessages(FADEUP);\n                                sendEmptyMessage(FADEDOWN);\n                                break;\n                            case AudioManager.AUDIOFOCUS_GAIN:\n                                if (!service.isPlaying()\n                                        && service.mPausedByTransientLossOfFocus) {\n                                    service.mPausedByTransientLossOfFocus = false;\n                                    mCurrentVolume = 0f;\n                                    service.mPlayer.setVolume(mCurrentVolume);\n                                    service.play();\n                                } else {\n                                    removeMessages(FADEDOWN);\n                                    sendEmptyMessage(FADEUP);\n                                }\n                                break;\n                            default:\n                        }\n                        break;\n                    default:\n                        break;\n                }\n            }\n        }\n    }\n\n    private static final class Shuffler {\n\n        private final LinkedList<Integer> mHistoryOfNumbers = new LinkedList<Integer>();\n\n        private final TreeSet<Integer> mPreviousNumbers = new TreeSet<Integer>();\n\n        private final Random mRandom = new Random();\n\n        private int mPrevious;\n\n\n        public Shuffler() {\n            super();\n        }\n\n\n        public int nextInt(final int interval) {\n            int next;\n            do {\n                next = mRandom.nextInt(interval);\n            } while (next == mPrevious && interval > 1\n                    && !mPreviousNumbers.contains(Integer.valueOf(next)));\n            mPrevious = next;\n            mHistoryOfNumbers.add(mPrevious);\n            mPreviousNumbers.add(mPrevious);\n            cleanUpHistory();\n            return next;\n        }\n\n\n        private void cleanUpHistory() {\n            if (!mHistoryOfNumbers.isEmpty() && mHistoryOfNumbers.size() >= MAX_HISTORY_SIZE) {\n                for (int i = 0; i < Math.max(1, MAX_HISTORY_SIZE / 2); i++) {\n                    mPreviousNumbers.remove(mHistoryOfNumbers.removeFirst());\n                }\n            }\n        }\n    }\n\n    private static final class TrackErrorInfo {\n        public long mId;\n        public String mTrackName;\n\n        public TrackErrorInfo(long id, String trackName) {\n            mId = id;\n            mTrackName = trackName;\n        }\n    }\n\n    private static final class MultiPlayer implements MediaPlayer.OnErrorListener,\n            MediaPlayer.OnCompletionListener {\n\n        private final WeakReference<MusicService> mService;\n\n        private MediaPlayer mCurrentMediaPlayer = new MediaPlayer();\n\n        private MediaPlayer mNextMediaPlayer;\n\n        private Handler mHandler;\n\n        private boolean mIsInitialized = false;\n\n        private String mNextMediaPath;\n\n\n        public MultiPlayer(final MusicService service) {\n            mService = new WeakReference<MusicService>(service);\n            mCurrentMediaPlayer.setWakeMode(mService.get(), PowerManager.PARTIAL_WAKE_LOCK);\n\n        }\n\n\n        public void setDataSource(final String path) {\n            try {\n                mIsInitialized = setDataSourceImpl(mCurrentMediaPlayer, path);\n                if (mIsInitialized) {\n                    setNextDataSource(null);\n                }\n            } catch (IllegalStateException e) {\n                e.printStackTrace();\n            }\n        }\n\n\n        private boolean setDataSourceImpl(final MediaPlayer player, final String path) {\n            try {\n                player.reset();\n                player.setOnPreparedListener(null);\n                if (path.startsWith(\"content://\")) {\n                    player.setDataSource(mService.get(), Uri.parse(path));\n                } else {\n                    player.setDataSource(path);\n                }\n                player.setAudioStreamType(AudioManager.STREAM_MUSIC);\n\n                player.prepare();\n            } catch (final IOException todo) {\n\n                return false;\n            } catch (final IllegalArgumentException todo) {\n\n                return false;\n            }\n            player.setOnCompletionListener(this);\n            player.setOnErrorListener(this);\n            return true;\n        }\n\n\n        public void setNextDataSource(final String path) {\n            mNextMediaPath = null;\n            try {\n                mCurrentMediaPlayer.setNextMediaPlayer(null);\n            } catch (IllegalArgumentException e) {\n                Log.i(TAG, \"Next media player is current one, continuing\");\n            } catch (IllegalStateException e) {\n                Log.e(TAG, \"Media player not initialized!\");\n                return;\n            }\n            if (mNextMediaPlayer != null) {\n                mNextMediaPlayer.release();\n                mNextMediaPlayer = null;\n            }\n            if (path == null) {\n                return;\n            }\n            mNextMediaPlayer = new MediaPlayer();\n            mNextMediaPlayer.setWakeMode(mService.get(), PowerManager.PARTIAL_WAKE_LOCK);\n            mNextMediaPlayer.setAudioSessionId(getAudioSessionId());\n            try {\n                if (setDataSourceImpl(mNextMediaPlayer, path)) {\n                    mNextMediaPath = path;\n                    mCurrentMediaPlayer.setNextMediaPlayer(mNextMediaPlayer);\n                } else {\n                    if (mNextMediaPlayer != null) {\n                        mNextMediaPlayer.release();\n                        mNextMediaPlayer = null;\n                    }\n                }\n            } catch (IllegalStateException e) {\n                e.printStackTrace();\n            }\n        }\n\n\n        public void setHandler(final Handler handler) {\n            mHandler = handler;\n        }\n\n\n        public boolean isInitialized() {\n            return mIsInitialized;\n        }\n\n\n        public void start() {\n            mCurrentMediaPlayer.start();\n        }\n\n\n        public void stop() {\n            mCurrentMediaPlayer.reset();\n            mIsInitialized = false;\n        }\n\n\n        public void release() {\n            mCurrentMediaPlayer.release();\n        }\n\n\n        public void pause() {\n            mCurrentMediaPlayer.pause();\n        }\n\n\n        public long duration() {\n            return mCurrentMediaPlayer.getDuration();\n        }\n\n\n        public long position() {\n            return mCurrentMediaPlayer.getCurrentPosition();\n        }\n\n\n        public long seek(final long whereto) {\n            mCurrentMediaPlayer.seekTo((int) whereto);\n            return whereto;\n        }\n\n\n        public void setVolume(final float vol) {\n            try {\n                mCurrentMediaPlayer.setVolume(vol, vol);\n            } catch (IllegalStateException e) {\n                e.printStackTrace();\n            }\n        }\n\n        public int getAudioSessionId() {\n            return mCurrentMediaPlayer.getAudioSessionId();\n        }\n\n        public void setAudioSessionId(final int sessionId) {\n            mCurrentMediaPlayer.setAudioSessionId(sessionId);\n        }\n\n        @Override\n        public boolean onError(final MediaPlayer mp, final int what, final int extra) {\n            Log.w(TAG, \"Music Server Error what: \" + what + \" extra: \" + extra);\n            switch (what) {\n                case MediaPlayer.MEDIA_ERROR_SERVER_DIED:\n                    final MusicService service = mService.get();\n                    final TrackErrorInfo errorInfo = new TrackErrorInfo(service.getAudioId(),\n                            service.getTrackName());\n\n                    mIsInitialized = false;\n                    mCurrentMediaPlayer.release();\n                    mCurrentMediaPlayer = new MediaPlayer();\n                    mCurrentMediaPlayer.setWakeMode(service, PowerManager.PARTIAL_WAKE_LOCK);\n                    Message msg = mHandler.obtainMessage(SERVER_DIED, errorInfo);\n                    mHandler.sendMessageDelayed(msg, 2000);\n                    return true;\n                default:\n                    break;\n            }\n            return false;\n        }\n\n\n        @Override\n        public void onCompletion(final MediaPlayer mp) {\n            if (mp == mCurrentMediaPlayer && mNextMediaPlayer != null) {\n                mCurrentMediaPlayer.release();\n                mCurrentMediaPlayer = mNextMediaPlayer;\n                mNextMediaPath = null;\n                mNextMediaPlayer = null;\n                mHandler.sendEmptyMessage(TRACK_WENT_TO_NEXT);\n            } else {\n                mService.get().mWakeLock.acquire(30000);\n                mHandler.sendEmptyMessage(TRACK_ENDED);\n                mHandler.sendEmptyMessage(RELEASE_WAKELOCK);\n            }\n        }\n    }\n\n    private static final class ServiceStub extends ITimberService.Stub {\n\n        private final WeakReference<MusicService> mService;\n\n        private ServiceStub(final MusicService service) {\n            mService = new WeakReference<MusicService>(service);\n        }\n\n\n        @Override\n        public void openFile(final String path) throws RemoteException {\n            mService.get().openFile(path);\n        }\n\n        @Override\n        public void open(final long[] list, final int position, long sourceId, int sourceType)\n                throws RemoteException {\n            mService.get().open(list, position, sourceId, IdType.getTypeById(sourceType));\n        }\n\n        @Override\n        public void stop() throws RemoteException {\n            mService.get().stop();\n        }\n\n        @Override\n        public void pause() throws RemoteException {\n            mService.get().pause();\n        }\n\n\n        @Override\n        public void play() throws RemoteException {\n            mService.get().play();\n        }\n\n        @Override\n        public void prev(boolean forcePrevious) throws RemoteException {\n            mService.get().prev(forcePrevious);\n        }\n\n        @Override\n        public void next() throws RemoteException {\n            mService.get().gotoNext(true);\n        }\n\n        @Override\n        public void enqueue(final long[] list, final int action, long sourceId, int sourceType)\n                throws RemoteException {\n            mService.get().enqueue(list, action, sourceId, IdType.getTypeById(sourceType));\n        }\n\n        @Override\n        public void moveQueueItem(final int from, final int to) throws RemoteException {\n            mService.get().moveQueueItem(from, to);\n        }\n\n        @Override\n        public void refresh() throws RemoteException {\n            mService.get().refresh();\n        }\n\n        @Override\n        public void playlistChanged() throws RemoteException {\n            mService.get().playlistChanged();\n        }\n\n        @Override\n        public boolean isPlaying() throws RemoteException {\n            return mService.get().isPlaying();\n        }\n\n        @Override\n        public long[] getQueue() throws RemoteException {\n            return mService.get().getQueue();\n        }\n\n        @Override\n        public long getQueueItemAtPosition(int position) throws RemoteException {\n            return mService.get().getQueueItemAtPosition(position);\n        }\n\n        @Override\n        public int getQueueSize() throws RemoteException {\n            return mService.get().getQueueSize();\n        }\n\n        @Override\n        public int getQueueHistoryPosition(int position) throws RemoteException {\n            return mService.get().getQueueHistoryPosition(position);\n        }\n\n        @Override\n        public int getQueueHistorySize() throws RemoteException {\n            return mService.get().getQueueHistorySize();\n        }\n\n        @Override\n        public int[] getQueueHistoryList() throws RemoteException {\n            return mService.get().getQueueHistoryList();\n        }\n\n        @Override\n        public long duration() throws RemoteException {\n            return mService.get().duration();\n        }\n\n        @Override\n        public long position() throws RemoteException {\n            return mService.get().position();\n        }\n\n        @Override\n        public long seek(final long position) throws RemoteException {\n            return mService.get().seek(position);\n        }\n\n        @Override\n        public void seekRelative(final long deltaInMs) throws RemoteException {\n            mService.get().seekRelative(deltaInMs);\n        }\n\n        @Override\n        public long getAudioId() throws RemoteException {\n            return mService.get().getAudioId();\n        }\n\n        @Override\n        public MusicPlaybackTrack getCurrentTrack() throws RemoteException {\n            return mService.get().getCurrentTrack();\n        }\n\n        @Override\n        public MusicPlaybackTrack getTrack(int index) throws RemoteException {\n            return mService.get().getTrack(index);\n        }\n\n        @Override\n        public long getNextAudioId() throws RemoteException {\n            return mService.get().getNextAudioId();\n        }\n\n        @Override\n        public long getPreviousAudioId() throws RemoteException {\n            return mService.get().getPreviousAudioId();\n        }\n\n        @Override\n        public long getArtistId() throws RemoteException {\n            return mService.get().getArtistId();\n        }\n\n        @Override\n        public long getAlbumId() throws RemoteException {\n            return mService.get().getAlbumId();\n        }\n\n        @Override\n        public String getArtistName() throws RemoteException {\n            return mService.get().getArtistName();\n        }\n\n        @Override\n        public String getTrackName() throws RemoteException {\n            return mService.get().getTrackName();\n        }\n\n        @Override\n        public String getAlbumName() throws RemoteException {\n            return mService.get().getAlbumName();\n        }\n\n        @Override\n        public String getPath() throws RemoteException {\n            return mService.get().getPath();\n        }\n\n        @Override\n        public int getQueuePosition() throws RemoteException {\n            return mService.get().getQueuePosition();\n        }\n\n        @Override\n        public void setQueuePosition(final int index) throws RemoteException {\n            mService.get().setQueuePosition(index);\n        }\n\n        @Override\n        public int getShuffleMode() throws RemoteException {\n            return mService.get().getShuffleMode();\n        }\n\n        @Override\n        public void setShuffleMode(final int shufflemode) throws RemoteException {\n            mService.get().setShuffleMode(shufflemode);\n        }\n\n        @Override\n        public int getRepeatMode() throws RemoteException {\n            return mService.get().getRepeatMode();\n        }\n\n        @Override\n        public void setRepeatMode(final int repeatmode) throws RemoteException {\n            mService.get().setRepeatMode(repeatmode);\n        }\n\n        @Override\n        public int removeTracks(final int first, final int last) throws RemoteException {\n            return mService.get().removeTracks(first, last);\n        }\n\n\n        @Override\n        public int removeTrack(final long id) throws RemoteException {\n            return mService.get().removeTrack(id);\n        }\n\n\n        @Override\n        public boolean removeTrackAtPosition(final long id, final int position)\n                throws RemoteException {\n            return mService.get().removeTrackAtPosition(id, position);\n        }\n\n\n        @Override\n        public int getMediaMountedCount() throws RemoteException {\n            return mService.get().getMediaMountedCount();\n        }\n\n\n        @Override\n        public int getAudioSessionId() throws RemoteException {\n            return mService.get().getAudioSessionId();\n        }\n\n    }\n\n    private class MediaStoreObserver extends ContentObserver implements Runnable {\n\n        private static final long REFRESH_DELAY = 500;\n        private Handler mHandler;\n\n        public MediaStoreObserver(Handler handler) {\n            super(handler);\n            mHandler = handler;\n        }\n\n        @Override\n        public void onChange(boolean selfChange) {\n\n\n            mHandler.removeCallbacks(this);\n            mHandler.postDelayed(this, REFRESH_DELAY);\n        }\n\n        @Override\n        public void run() {\n\n            Log.e(\"ELEVEN\", \"calling refresh!\");\n            refresh();\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/TimberApp.java",
    "content": "/*\n * Copyright (C) 2015 Naman Dwivedi\n *\n * Licensed under the GNU General Public License v3\n *\n * This is free software: you can redistribute it and/or modify it\n * under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;\n * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n * See the GNU General Public License for more details.\n */\n\npackage com.naman14.timber;\n\nimport androidx.multidex.MultiDexApplication;\n\nimport com.afollestad.appthemeengine.ATE;\nimport com.naman14.timber.permissions.Nammu;\nimport com.naman14.timber.utils.PreferencesUtility;\nimport com.nostra13.universalimageloader.core.ImageLoader;\nimport com.nostra13.universalimageloader.core.ImageLoaderConfiguration;\nimport com.nostra13.universalimageloader.core.download.BaseImageDownloader;\nimport com.nostra13.universalimageloader.utils.L;\n\nimport java.io.IOException;\nimport java.io.InputStream;\n\npublic class TimberApp extends MultiDexApplication {\n\n    private static TimberApp mInstance;\n\n    public static synchronized TimberApp getInstance() {\n        return mInstance;\n    }\n\n    @Override\n    public void onCreate() {\n        super.onCreate();\n        mInstance = this;\n\n        ImageLoaderConfiguration localImageLoaderConfiguration = new ImageLoaderConfiguration.Builder(this).imageDownloader(new BaseImageDownloader(this) {\n            PreferencesUtility prefs = PreferencesUtility.getInstance(TimberApp.this);\n\n            @Override\n            protected InputStream getStreamFromNetwork(String imageUri, Object extra) throws IOException {\n                if (prefs.loadArtistAndAlbumImages())\n                    return super.getStreamFromNetwork(imageUri, extra);\n                throw new IOException();\n            }\n        }).build();\n\n        ImageLoader.getInstance().init(localImageLoaderConfiguration);\n        L.writeLogs(false);\n        L.disableLogging();\n        L.writeDebugLogs(false);\n        Nammu.init(this);\n\n        if (!ATE.config(this, \"light_theme\").isConfigured()) {\n            ATE.config(this, \"light_theme\")\n                    .activityTheme(R.style.AppThemeLight)\n                    .primaryColorRes(R.color.colorPrimaryLightDefault)\n                    .accentColorRes(R.color.colorAccentLightDefault)\n                    .coloredNavigationBar(false)\n                    .usingMaterialDialogs(true)\n                    .commit();\n        }\n        if (!ATE.config(this, \"dark_theme\").isConfigured()) {\n            ATE.config(this, \"dark_theme\")\n                    .activityTheme(R.style.AppThemeDark)\n                    .primaryColorRes(R.color.colorPrimaryDarkDefault)\n                    .accentColorRes(R.color.colorAccentDarkDefault)\n                    .coloredNavigationBar(false)\n                    .usingMaterialDialogs(true)\n                    .commit();\n        }\n        if (!ATE.config(this, \"light_theme_notoolbar\").isConfigured()) {\n            ATE.config(this, \"light_theme_notoolbar\")\n                    .activityTheme(R.style.AppThemeLight)\n                    .coloredActionBar(false)\n                    .primaryColorRes(R.color.colorPrimaryLightDefault)\n                    .accentColorRes(R.color.colorAccentLightDefault)\n                    .coloredNavigationBar(false)\n                    .usingMaterialDialogs(true)\n                    .commit();\n        }\n        if (!ATE.config(this, \"dark_theme_notoolbar\").isConfigured()) {\n            ATE.config(this, \"dark_theme_notoolbar\")\n                    .activityTheme(R.style.AppThemeDark)\n                    .coloredActionBar(false)\n                    .primaryColorRes(R.color.colorPrimaryDarkDefault)\n                    .accentColorRes(R.color.colorAccentDarkDefault)\n                    .coloredNavigationBar(true)\n                    .usingMaterialDialogs(true)\n                    .commit();\n        }\n\n    }\n\n\n}\n"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/WearBrowserService.java",
    "content": "/*\n * Copyright (C) 2015 Naman Dwivedi\n *\n * Licensed under the GNU General Public License v3\n *\n * This is free software: you can redistribute it and/or modify it\n * under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;\n * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n * See the GNU General Public License for more details.\n */\n\npackage com.naman14.timber;\n\nimport android.annotation.TargetApi;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.media.MediaDescription;\nimport android.media.browse.MediaBrowser;\nimport android.media.session.MediaSession;\nimport android.net.Uri;\nimport android.os.AsyncTask;\nimport android.os.Bundle;\nimport android.service.media.MediaBrowserService;\nimport androidx.annotation.Nullable;\n\nimport com.naman14.timber.dataloaders.AlbumLoader;\nimport com.naman14.timber.dataloaders.AlbumSongLoader;\nimport com.naman14.timber.dataloaders.ArtistAlbumLoader;\nimport com.naman14.timber.dataloaders.ArtistLoader;\nimport com.naman14.timber.dataloaders.ArtistSongLoader;\nimport com.naman14.timber.dataloaders.PlaylistLoader;\nimport com.naman14.timber.dataloaders.PlaylistSongLoader;\nimport com.naman14.timber.dataloaders.SongLoader;\nimport com.naman14.timber.models.Album;\nimport com.naman14.timber.models.Artist;\nimport com.naman14.timber.models.Playlist;\nimport com.naman14.timber.models.Song;\nimport com.naman14.timber.utils.TimberUtils;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n@TargetApi(21)\npublic class WearBrowserService extends MediaBrowserService {\n\n    public static final String MEDIA_ID_ROOT = \"__ROOT__\";\n    public static final int TYPE_ARTIST = 0;\n    public static final int TYPE_ALBUM = 1;\n    public static final int TYPE_SONG = 2;\n    public static final int TYPE_PLAYLIST = 3;\n    public static final int TYPE_ARTIST_SONG_ALBUMS = 4;\n    public static final int TYPE_ALBUM_SONGS = 5;\n    public static final int TYPE_ARTIST_ALL_SONGS = 6;\n    public static final int TYPE_PLAYLIST_ALL_SONGS = 7;\n\n    MediaSession mSession;\n    public static WearBrowserService sInstance;\n\n    private Context mContext;\n    private boolean mServiceStarted;\n\n    public static WearBrowserService getInstance() {\n        return sInstance;\n    }\n\n    @Override\n    public void onCreate() {\n        super.onCreate();\n        sInstance = this;\n        mContext = this;\n        mSession = new MediaSession(this, \"WearBrowserService\");\n        setSessionToken(mSession.getSessionToken());\n        mSession.setCallback(new MediaSessionCallback());\n        mSession.setFlags(MediaSession.FLAG_HANDLES_MEDIA_BUTTONS | MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS);\n\n    }\n\n    @Override\n    public int onStartCommand(Intent startIntent, int flags, int startId) {\n        return START_STICKY;\n    }\n\n    @Override\n    public void onDestroy() {\n        mServiceStarted = false;\n        mSession.release();\n    }\n\n    @Override\n    public void onLoadChildren(String parentId, Result<List<MediaBrowser.MediaItem>> result) {\n\n        result.detach();\n        loadChildren(parentId, result);\n\n    }\n\n    @Nullable\n    @Override\n    public BrowserRoot onGetRoot(String clientPackageName, int clientUid, Bundle rootHints) {\n        return new BrowserRoot(MEDIA_ID_ROOT, null);\n    }\n\n    private final class MediaSessionCallback extends MediaSession.Callback {\n\n        @Override\n        public void onPlay() {\n            setSessionActive();\n        }\n\n        @Override\n        public void onSeekTo(long position) {\n\n        }\n\n        @Override\n        public void onPlayFromMediaId(final String mediaId, Bundle extras) {\n            long songId = Long.parseLong(mediaId);\n            setSessionActive();\n            MusicPlayer.playAll(mContext, new long[]{songId}, 0, -1, TimberUtils.IdType.NA, false);\n        }\n\n        @Override\n        public void onPause() {\n\n        }\n\n        @Override\n        public void onStop() {\n            setSessionInactive();\n        }\n\n        @Override\n        public void onSkipToNext() {\n\n        }\n\n        @Override\n        public void onSkipToPrevious() {\n\n        }\n\n        @Override\n        public void onFastForward() {\n\n        }\n\n        @Override\n        public void onRewind() {\n\n        }\n\n        @Override\n        public void onCustomAction(String action, Bundle extras) {\n\n        }\n    }\n\n    private void setSessionActive() {\n        if (!mServiceStarted) {\n            startService(new Intent(getApplicationContext(), WearBrowserService.class));\n            mServiceStarted = true;\n        }\n\n        if (!mSession.isActive()) {\n            mSession.setActive(true);\n        }\n    }\n\n    private void setSessionInactive() {\n        if (mServiceStarted) {\n            stopSelf();\n            mServiceStarted = false;\n        }\n\n        if (mSession.isActive()) {\n            mSession.setActive(false);\n        }\n    }\n\n    private void addMediaRoots(List<MediaBrowser.MediaItem> mMediaRoot) {\n        mMediaRoot.add(new MediaBrowser.MediaItem(\n                new MediaDescription.Builder()\n                        .setMediaId(Integer.toString(TYPE_ARTIST))\n                        .setTitle(getString(R.string.artists))\n                        .setIconUri(Uri.parse(\"android.resource://\" +\n                                \"naman14.timber/drawable/ic_empty_music2\"))\n                        .setSubtitle(getString(R.string.artists))\n                        .build(), MediaBrowser.MediaItem.FLAG_BROWSABLE\n        ));\n\n        mMediaRoot.add(new MediaBrowser.MediaItem(\n                new MediaDescription.Builder()\n                        .setMediaId(Integer.toString(TYPE_ALBUM))\n                        .setTitle(getString(R.string.albums))\n                        .setIconUri(Uri.parse(\"android.resource://\" +\n                                \"naman14.timber/drawable/ic_empty_music2\"))\n                        .setSubtitle(getString(R.string.albums))\n                        .build(), MediaBrowser.MediaItem.FLAG_BROWSABLE\n        ));\n\n        mMediaRoot.add(new MediaBrowser.MediaItem(\n                new MediaDescription.Builder()\n                        .setMediaId(Integer.toString(TYPE_SONG))\n                        .setTitle(getString(R.string.songs))\n                        .setIconUri(Uri.parse(\"android.resource://\" +\n                                \"naman14.timber/drawable/ic_empty_music2\"))\n                        .setSubtitle(getString(R.string.songs))\n                        .build(), MediaBrowser.MediaItem.FLAG_BROWSABLE\n        ));\n\n\n        mMediaRoot.add(new MediaBrowser.MediaItem(\n                new MediaDescription.Builder()\n                        .setMediaId(Integer.toString(TYPE_PLAYLIST))\n                        .setTitle(getString(R.string.playlists))\n                        .setIconUri(Uri.parse(\"android.resource://\" +\n                                \"naman14.timber/drawable/ic_empty_music2\"))\n                        .setSubtitle(getString(R.string.playlists))\n                        .build(), MediaBrowser.MediaItem.FLAG_BROWSABLE\n        ));\n\n    }\n\n\n    private void loadChildren(final String parentId, final Result<List<MediaBrowser.MediaItem>> result) {\n\n        final List<MediaBrowser.MediaItem> mediaItems = new ArrayList<>();\n\n        new AsyncTask<Void, Void, Void>() {\n            @Override\n            protected Void doInBackground(final Void... unused) {\n\n                if (parentId.equals(MEDIA_ID_ROOT)) {\n                    addMediaRoots(mediaItems);\n                } else {\n                    switch (Integer.parseInt(Character.toString(parentId.charAt(0)))) {\n                        case TYPE_ARTIST:\n                            List<Artist> artistList = ArtistLoader.getAllArtists(mContext);\n                            for (Artist artist : artistList) {\n                                String albumNmber = TimberUtils.makeLabel(mContext, R.plurals.Nalbums, artist.albumCount);\n                                String songCount = TimberUtils.makeLabel(mContext, R.plurals.Nsongs, artist.songCount);\n                                fillMediaItems(mediaItems, Integer.toString(TYPE_ARTIST_SONG_ALBUMS) + Long.toString(artist.id), artist.name, Uri.parse(\"android.resource://\" +\n                                        \"naman14.timber/drawable/ic_empty_music2\"), TimberUtils.makeCombinedString(mContext, albumNmber, songCount), MediaBrowser.MediaItem.FLAG_BROWSABLE);\n                            }\n                            break;\n                        case TYPE_ALBUM:\n                            List<Album> albumList = AlbumLoader.getAllAlbums(mContext);\n                            for (Album album : albumList) {\n                                fillMediaItems(mediaItems, Integer.toString(TYPE_ALBUM_SONGS) + Long.toString(album.id), album.title, TimberUtils.getAlbumArtUri(album.id), album.artistName, MediaBrowser.MediaItem.FLAG_BROWSABLE);\n                            }\n                            break;\n                        case TYPE_SONG:\n                            List<Song> songList = SongLoader.getAllSongs(mContext);\n                            for (Song song : songList) {\n                                fillMediaItems(mediaItems, String.valueOf(song.id), song.title, TimberUtils.getAlbumArtUri(song.albumId), song.artistName, MediaBrowser.MediaItem.FLAG_PLAYABLE);\n                            }\n                            break;\n                        case TYPE_ALBUM_SONGS:\n                            List<Song> albumSongList = AlbumSongLoader.getSongsForAlbum(mContext, Long.parseLong(parentId.substring(1)));\n                            for (Song song : albumSongList) {\n                                fillMediaItems(mediaItems, String.valueOf(song.id), song.title, TimberUtils.getAlbumArtUri(song.albumId), song.artistName, MediaBrowser.MediaItem.FLAG_PLAYABLE);\n                            }\n                            break;\n                        case TYPE_ARTIST_SONG_ALBUMS:\n                            fillMediaItems(mediaItems, Integer.toString(TYPE_ARTIST_ALL_SONGS) + Long.parseLong(parentId.substring(1)), \"All songs\", Uri.parse(\"android.resource://\" +\n                                    \"naman14.timber/drawable/ic_empty_music2\"), \"All songs by artist\", MediaBrowser.MediaItem.FLAG_BROWSABLE);\n                            List<Album> artistAlbums = ArtistAlbumLoader.getAlbumsForArtist(mContext, Long.parseLong(parentId.substring(1)));\n                            for (Album album : artistAlbums) {\n                                String songCount = TimberUtils.makeLabel(mContext, R.plurals.Nsongs, album.songCount);\n                                fillMediaItems(mediaItems, Integer.toString(TYPE_ALBUM_SONGS) + Long.toString(album.id), album.title, TimberUtils.getAlbumArtUri(album.id), songCount, MediaBrowser.MediaItem.FLAG_BROWSABLE);\n\n                            }\n                            break;\n                        case TYPE_ARTIST_ALL_SONGS:\n                            List<Song> artistSongs = ArtistSongLoader.getSongsForArtist(mContext, Long.parseLong(parentId.substring(1)));\n                            for (Song song : artistSongs) {\n                                fillMediaItems(mediaItems, String.valueOf(song.id), song.title, TimberUtils.getAlbumArtUri(song.albumId), song.albumName, MediaBrowser.MediaItem.FLAG_PLAYABLE);\n                            }\n                            break;\n                        case TYPE_PLAYLIST:\n                            List<Playlist> playlistList = PlaylistLoader.getPlaylists(mContext, false);\n                            for (Playlist playlist : playlistList) {\n                                String songCount = TimberUtils.makeLabel(mContext, R.plurals.Nsongs, playlist.songCount);\n                                fillMediaItems(mediaItems, Integer.toString(TYPE_PLAYLIST_ALL_SONGS) + Long.toString(playlist.id), playlist.name,\n                                        Uri.parse(\"android.resource://\" +\n                                                \"naman14.timber/drawable/ic_empty_music2\"), songCount, MediaBrowser.MediaItem.FLAG_BROWSABLE);\n                            }\n                            break;\n                        case TYPE_PLAYLIST_ALL_SONGS:\n                            List<Song> playlistSongs = PlaylistSongLoader.getSongsInPlaylist(mContext, Long.parseLong(parentId.substring(1)));\n                            for (Song song : playlistSongs) {\n                                fillMediaItems(mediaItems, String.valueOf(song.id), song.title, TimberUtils.getAlbumArtUri(song.albumId), song.albumName, MediaBrowser.MediaItem.FLAG_PLAYABLE);\n                            }\n                            break;\n\n                    }\n                }\n                return null;\n            }\n\n            @Override\n            protected void onPostExecute(Void aVoid) {\n                result.sendResult(mediaItems);\n            }\n        }.execute();\n\n    }\n\n    private void fillMediaItems(List<MediaBrowser.MediaItem> mediaItems, String mediaId, String title, Uri icon, String subTitle, int playableOrBrowsable) {\n        mediaItems.add(new MediaBrowser.MediaItem(\n                new MediaDescription.Builder()\n                        .setMediaId(mediaId)\n                        .setTitle(title)\n                        .setIconUri(icon)\n                        .setSubtitle(subTitle)\n                        .build(), playableOrBrowsable\n        ));\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/activities/BaseActivity.java",
    "content": "/*\n * Copyright (C) 2012 Andrew Neal\n * Copyright (C) 2014 The CyanogenMod Project\n * Copyright (C) 2015 Naman Dwivedi\n *\n * Licensed under the Apache License, Version 2.0\n * (the \"License\"); you may not use this file except in compliance with the\n * License. You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law\n * or agreed to in writing, software distributed under the License is\n * distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n * KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\npackage com.naman14.timber.activities;\n\nimport android.content.BroadcastReceiver;\nimport android.content.ComponentName;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.content.IntentFilter;\nimport android.content.ServiceConnection;\nimport android.media.AudioManager;\nimport android.os.AsyncTask;\nimport android.os.Bundle;\nimport android.os.Handler;\nimport android.os.IBinder;\nimport androidx.annotation.Nullable;\nimport androidx.fragment.app.FragmentManager;\nimport android.view.Menu;\nimport android.view.MenuItem;\nimport android.view.View;\nimport android.widget.Toast;\n\nimport com.afollestad.appthemeengine.ATE;\nimport com.afollestad.appthemeengine.ATEActivity;\nimport com.google.android.gms.cast.framework.CastButtonFactory;\nimport com.google.android.gms.cast.framework.CastContext;\nimport com.google.android.gms.cast.framework.CastSession;\nimport com.google.android.gms.cast.framework.Session;\nimport com.google.android.gms.cast.framework.SessionManager;\nimport com.google.android.gms.cast.framework.SessionManagerListener;\nimport com.google.android.gms.common.ConnectionResult;\nimport com.google.android.gms.common.GoogleApiAvailability;\nimport com.naman14.timber.ITimberService;\nimport com.naman14.timber.MusicPlayer;\nimport com.naman14.timber.MusicService;\nimport com.naman14.timber.R;\nimport com.naman14.timber.cast.SimpleSessionManagerListener;\nimport com.naman14.timber.cast.WebServer;\nimport com.naman14.timber.listeners.MusicStateListener;\nimport com.naman14.timber.slidinguppanel.SlidingUpPanelLayout;\nimport com.naman14.timber.subfragments.QuickControlsFragment;\nimport com.naman14.timber.utils.Helpers;\nimport com.naman14.timber.utils.NavigationUtils;\nimport com.naman14.timber.utils.TimberUtils;\n\nimport java.io.IOException;\nimport java.lang.ref.WeakReference;\nimport java.util.ArrayList;\n\nimport static com.naman14.timber.MusicPlayer.mService;\n\npublic class BaseActivity extends ATEActivity implements ServiceConnection, MusicStateListener {\n\n    private final ArrayList<MusicStateListener> mMusicStateListener = new ArrayList<>();\n    private MusicPlayer.ServiceToken mToken;\n    private PlaybackStatus mPlaybackStatus;\n\n    private CastSession mCastSession;\n    private SessionManager mSessionManager;\n    private final SessionManagerListener mSessionManagerListener =\n            new SessionManagerListenerImpl();\n    private WebServer castServer;\n\n    public boolean playServicesAvailable = false;\n\n    private class SessionManagerListenerImpl extends SimpleSessionManagerListener {\n        @Override\n        public void onSessionStarting(Session session) {\n            super.onSessionStarting(session);\n            startCastServer();\n        }\n\n        @Override\n        public void onSessionStarted(Session session, String sessionId) {\n            invalidateOptionsMenu();\n            mCastSession = mSessionManager.getCurrentCastSession();\n            showCastMiniController();\n        }\n        @Override\n        public void onSessionResumed(Session session, boolean wasSuspended) {\n            invalidateOptionsMenu();\n            mCastSession = mSessionManager.getCurrentCastSession();\n        }\n        @Override\n        public void onSessionEnded(Session session, int error) {\n            mCastSession = null;\n            hideCastMiniController();\n            stopCastServer();\n        }\n\n        @Override\n        public void onSessionResuming(Session session, String s) {\n            super.onSessionResuming(session, s);\n            startCastServer();\n        }\n\n        @Override\n        public void onSessionSuspended(Session session, int i) {\n            super.onSessionSuspended(session, i);\n            stopCastServer();\n        }\n    }\n\n    @Override\n    public void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n\n        mToken = MusicPlayer.bindToService(this, this);\n\n        mPlaybackStatus = new PlaybackStatus(this);\n        //make volume keys change multimedia volume even if music is not playing now\n        setVolumeControlStream(AudioManager.STREAM_MUSIC);\n\n        try {\n            playServicesAvailable = GoogleApiAvailability\n                    .getInstance().isGooglePlayServicesAvailable(this) == ConnectionResult.SUCCESS;\n        } catch (Exception ignored) {\n\n        }\n\n        if (playServicesAvailable)\n            initCast();\n    }\n\n    @Override\n    protected void onStart() {\n        super.onStart();\n\n        final IntentFilter filter = new IntentFilter();\n        // Play and pause changes\n        filter.addAction(MusicService.PLAYSTATE_CHANGED);\n        // Track changes\n        filter.addAction(MusicService.META_CHANGED);\n        // Update a list, probably the playlist fragment's\n        filter.addAction(MusicService.REFRESH);\n        // If a playlist has changed, notify us\n        filter.addAction(MusicService.PLAYLIST_CHANGED);\n        // If there is an error playing a track\n        filter.addAction(MusicService.TRACK_ERROR);\n\n        registerReceiver(mPlaybackStatus, filter);\n\n    }\n\n    @Override\n    protected void onStop() {\n        super.onStop();\n    }\n\n    @Override\n    public void onResume() {\n        if (playServicesAvailable) {\n            mCastSession = mSessionManager.getCurrentCastSession();\n            mSessionManager.addSessionManagerListener(mSessionManagerListener);\n        }\n        //For Android 8.0+: service may get destroyed if in background too long\n        if(mService == null){\n            mToken = MusicPlayer.bindToService(this, this);\n        }\n        onMetaChanged();\n        super.onResume();\n    }\n\n    @Override\n    protected void onPause() {\n        super.onPause();\n        if (playServicesAvailable) {\n            mSessionManager.removeSessionManagerListener(mSessionManagerListener);\n            mCastSession = null;\n        }\n    }\n\n    @Override\n    public void onServiceConnected(final ComponentName name, final IBinder service) {\n        mService = ITimberService.Stub.asInterface(service);\n        onMetaChanged();\n    }\n\n\n    private void initCast() {\n        CastContext castContext = CastContext.getSharedInstance(this);\n        mSessionManager = castContext.getSessionManager();\n    }\n\n    @Override\n    public void onServiceDisconnected(final ComponentName name) {\n        mService = null;\n    }\n\n    @Override\n    protected void onDestroy() {\n        super.onDestroy();\n        // Unbind from the service\n        if (mToken != null) {\n            MusicPlayer.unbindFromService(mToken);\n            mToken = null;\n        }\n\n        try {\n            unregisterReceiver(mPlaybackStatus);\n        } catch (final Throwable e) {\n        }\n        mMusicStateListener.clear();\n    }\n\n    @Override\n    public void onMetaChanged() {\n        // Let the listener know to the meta chnaged\n        for (final MusicStateListener listener : mMusicStateListener) {\n            if (listener != null) {\n                listener.onMetaChanged();\n            }\n        }\n    }\n\n    @Override\n    public void restartLoader() {\n        // Let the listener know to update a list\n        for (final MusicStateListener listener : mMusicStateListener) {\n            if (listener != null) {\n                listener.restartLoader();\n            }\n        }\n    }\n\n    @Override\n    public void onPlaylistChanged() {\n        // Let the listener know to update a list\n        for (final MusicStateListener listener : mMusicStateListener) {\n            if (listener != null) {\n                listener.onPlaylistChanged();\n            }\n        }\n    }\n\n    public void setMusicStateListenerListener(final MusicStateListener status) {\n        if (status == this) {\n            throw new UnsupportedOperationException(\"Override the method, don't add a listener\");\n        }\n\n        if (status != null) {\n            mMusicStateListener.add(status);\n        }\n    }\n\n    public void removeMusicStateListenerListener(final MusicStateListener status) {\n        if (status != null) {\n            mMusicStateListener.remove(status);\n        }\n    }\n\n    @Override\n    public boolean onCreateOptionsMenu(Menu menu) {\n        getMenuInflater().inflate(R.menu.menu_main, menu);\n\n        getMenuInflater().inflate(R.menu.menu_cast, menu);\n\n        if (playServicesAvailable) {\n            CastButtonFactory.setUpMediaRouteButton(getApplicationContext(),\n                    menu,\n                    R.id.media_route_menu_item);\n        }\n\n        if (!TimberUtils.hasEffectsPanel(BaseActivity.this)) {\n            menu.removeItem(R.id.action_equalizer);\n        }\n        ATE.applyMenu(this, getATEKey(), menu);\n        return true;\n    }\n\n    @Override\n    public boolean onOptionsItemSelected(MenuItem item) {\n        switch (item.getItemId()) {\n            case android.R.id.home:\n                super.onBackPressed();\n                return true;\n            case R.id.action_settings:\n                NavigationUtils.navigateToSettings(this);\n                return true;\n            case R.id.action_shuffle:\n                Handler handler = new Handler();\n                handler.postDelayed(new Runnable() {\n                    @Override\n                    public void run() {\n                        MusicPlayer.shuffleAll(BaseActivity.this);\n                    }\n                }, 80);\n\n                return true;\n            case R.id.action_search:\n                NavigationUtils.navigateToSearch(this);\n                return true;\n            case R.id.action_equalizer:\n                NavigationUtils.navigateToEqualizer(this);\n                return true;\n\n        }\n        return super.onOptionsItemSelected(item);\n    }\n\n    @Nullable\n    @Override\n    public String getATEKey() {\n        return Helpers.getATEKey(this);\n    }\n\n    public void setPanelSlideListeners(SlidingUpPanelLayout panelLayout) {\n        panelLayout.setPanelSlideListener(new SlidingUpPanelLayout.PanelSlideListener() {\n\n            @Override\n            public void onPanelSlide(View panel, float slideOffset) {\n                View nowPlayingCard = QuickControlsFragment.topContainer;\n                if (nowPlayingCard != null)\n                    nowPlayingCard.setAlpha(1 - slideOffset);\n            }\n\n            @Override\n            public void onPanelCollapsed(View panel) {\n                View nowPlayingCard = QuickControlsFragment.topContainer;\n                if (nowPlayingCard != null)\n                    nowPlayingCard.setAlpha(1);\n            }\n\n            @Override\n            public void onPanelExpanded(View panel) {\n                View nowPlayingCard = QuickControlsFragment.topContainer;\n                if (nowPlayingCard != null)\n                    nowPlayingCard.setAlpha(0);\n            }\n\n            @Override\n            public void onPanelAnchored(View panel) {\n\n            }\n\n            @Override\n            public void onPanelHidden(View panel) {\n\n            }\n        });\n    }\n\n    private final static class PlaybackStatus extends BroadcastReceiver {\n\n        private final WeakReference<BaseActivity> mReference;\n\n\n        public PlaybackStatus(final BaseActivity activity) {\n            mReference = new WeakReference<BaseActivity>(activity);\n        }\n\n        @Override\n        public void onReceive(final Context context, final Intent intent) {\n            final String action = intent.getAction();\n            BaseActivity baseActivity = mReference.get();\n            if (baseActivity != null) {\n                if (action.equals(MusicService.META_CHANGED)) {\n                    baseActivity.onMetaChanged();\n                } else if (action.equals(MusicService.PLAYSTATE_CHANGED)) {\n//                    baseActivity.mPlayPauseProgressButton.getPlayPauseButton().updateState();\n                } else if (action.equals(MusicService.REFRESH)) {\n                    baseActivity.restartLoader();\n                } else if (action.equals(MusicService.PLAYLIST_CHANGED)) {\n                    baseActivity.onPlaylistChanged();\n                } else if (action.equals(MusicService.TRACK_ERROR)) {\n                    final String errorMsg = context.getString(R.string.error_playing_track,\n                            intent.getStringExtra(MusicService.TrackErrorExtra.TRACK_NAME));\n                    Toast.makeText(baseActivity, errorMsg, Toast.LENGTH_SHORT).show();\n                }\n            }\n        }\n    }\n\n    public class initQuickControls extends AsyncTask<String, Void, String> {\n\n        @Override\n        protected String doInBackground(String... params) {\n            QuickControlsFragment fragment1 = new QuickControlsFragment();\n            FragmentManager fragmentManager1 = getSupportFragmentManager();\n            fragmentManager1.beginTransaction()\n                    .replace(R.id.quickcontrols_container, fragment1).commitAllowingStateLoss();\n            return \"Executed\";\n        }\n\n        @Override\n        protected void onPostExecute(String result) {\n        }\n\n        @Override\n        protected void onPreExecute() {\n        }\n    }\n\n    public void showCastMiniController() {\n        //implement by overriding in activities\n    }\n\n    public void hideCastMiniController() {\n        //implement by overriding in activities\n    }\n\n    public CastSession getCastSession() {\n        return mCastSession;\n    }\n\n    private void startCastServer() {\n        castServer = new WebServer(this);\n        try {\n            castServer.start();\n        } catch (IOException e) {\n            e.printStackTrace();\n        }\n    }\n\n    private void stopCastServer() {\n        if (castServer != null) {\n            castServer.stop();\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/activities/BaseThemedActivity.java",
    "content": "package com.naman14.timber.activities;\n\nimport android.media.AudioManager;\nimport android.os.Bundle;\nimport androidx.annotation.Nullable;\n\nimport com.afollestad.appthemeengine.ATEActivity;\nimport com.naman14.timber.utils.Helpers;\n\n/**\n * Created by naman on 31/12/15.\n */\npublic class BaseThemedActivity extends ATEActivity {\n\n    @Nullable\n    @Override\n    public String getATEKey() {\n        return Helpers.getATEKey(this);\n    }\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        //make volume keys change multimedia volume even if music is not playing now\n        setVolumeControlStream(AudioManager.STREAM_MUSIC);\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/activities/DonateActivity.java",
    "content": "package com.naman14.timber.activities;\n\nimport android.content.Intent;\nimport android.os.AsyncTask;\nimport android.os.Bundle;\nimport androidx.appcompat.widget.Toolbar;\nimport android.view.LayoutInflater;\nimport android.view.MenuItem;\nimport android.view.View;\nimport android.widget.LinearLayout;\nimport android.widget.ProgressBar;\nimport android.widget.TextView;\nimport android.widget.Toast;\n\nimport com.anjlab.android.iab.v3.BillingProcessor;\nimport com.anjlab.android.iab.v3.SkuDetails;\nimport com.anjlab.android.iab.v3.TransactionDetails;\nimport com.naman14.timber.R;\nimport com.naman14.timber.utils.PreferencesUtility;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.Comparator;\nimport java.util.List;\n\n/**\n * Created by naman on 29/10/16.\n */\npublic class DonateActivity extends BaseThemedActivity implements BillingProcessor.IBillingHandler {\n\n    private static final String DONATION_1 = \"naman14.timber.donate_1\";\n    private static final String DONATION_2 = \"naman14.timber.donate_2\";\n    private static final String DONATION_3 = \"naman14.timber.donate_3\";\n    private static final String DONATION_5 = \"naman14.timber.donate_5\";\n    private static final String DONATION_10 = \"naman14.timber.donate_10\";\n    private static final String DONATION_20 = \"naman14.timber.donate_20\";\n\n\n    private boolean readyToPurchase = false;\n    BillingProcessor bp;\n\n    private LinearLayout productListView;\n    private ProgressBar progressBar;\n    private TextView status;\n\n    private String action = \"support\";\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_donate);\n\n        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);\n        setSupportActionBar(toolbar);\n\n        getSupportActionBar().setDisplayHomeAsUpEnabled(true);\n        getSupportActionBar().setTitle(\"Support development\");\n        action = getIntent().getAction();\n\n        productListView = (LinearLayout) findViewById(R.id.product_list);\n        progressBar = (ProgressBar) findViewById(R.id.progressBar);\n        status = (TextView) findViewById(R.id.donation_status);\n\n        if (action != null && action.equals(\"restore\")) {\n            status.setText(\"Restoring purchases..\");\n        }\n\n        bp = new BillingProcessor(this, getString(R.string.play_billing_license_key), this);\n\n    }\n\n    @Override\n    public void onBillingInitialized() {\n        readyToPurchase = true;\n        checkStatus();\n        if (!(action != null && action.equals(\"restore\")))\n            getProducts();\n    }\n\n    @Override\n    public void onProductPurchased(String productId, TransactionDetails details) {\n        checkStatus();\n        runOnUiThread(new Runnable() {\n            @Override\n            public void run() {\n                Toast.makeText(DonateActivity.this, \"Thanks for your support!\", Toast.LENGTH_SHORT).show();\n            }\n        });\n    }\n\n    @Override\n    public void onBillingError(int errorCode, Throwable error) {\n        runOnUiThread(new Runnable() {\n            @Override\n            public void run() {\n                Toast.makeText(DonateActivity.this, \"Unable to process purchase\", Toast.LENGTH_SHORT).show();\n            }\n        });\n\n    }\n\n    @Override\n    public void onPurchaseHistoryRestored() {\n\n    }\n\n    @Override\n    public void onDestroy() {\n        if (bp != null)\n            bp.release();\n        super.onDestroy();\n    }\n\n    @Override\n    protected void onActivityResult(int requestCode, int resultCode, Intent data) {\n        if (!bp.handleActivityResult(requestCode, resultCode, data))\n            super.onActivityResult(requestCode, resultCode, data);\n    }\n\n    private void checkStatus() {\n        new AsyncTask<Void, Void, Boolean>() {\n            @Override\n            protected Boolean doInBackground(Void... voids) {\n                List<String> owned = bp.listOwnedProducts();\n                return owned != null && owned.size() != 0;\n            }\n\n            @Override\n            protected void onPostExecute(Boolean b) {\n                super.onPostExecute(b);\n                if (b) {\n                    PreferencesUtility.getInstance(DonateActivity.this).setFullUnlocked(true);\n                    status.setText(\"Thanks for your support!\");\n                    if (action!=null && action.equals(\"restore\")) {\n                        status.setText(\"Your purchases has been restored. Thanks for your support\");\n                        progressBar.setVisibility(View.GONE);\n                    }\n                    if (getSupportActionBar() != null)\n                        getSupportActionBar().setTitle(\"Support development\");\n                } else {\n                    if (action!=null && action.equals(\"restore\")) {\n                        status.setText(\"No previous purchase found\");\n                        getProducts();\n                    }\n                }\n            }\n        }.execute();\n    }\n\n    private void getProducts() {\n\n        new AsyncTask<Void, Void, List<SkuDetails>>() {\n            @Override\n            protected List<SkuDetails> doInBackground(Void... voids) {\n\n                ArrayList<String> products = new ArrayList<>();\n\n                products.add(DONATION_1);\n                products.add(DONATION_2);\n                products.add(DONATION_3);\n                products.add(DONATION_5);\n                products.add(DONATION_10);\n                products.add(DONATION_20);\n\n                return bp.getPurchaseListingDetails(products);\n            }\n\n            @Override\n            protected void onPostExecute(List<SkuDetails> productList) {\n                super.onPostExecute(productList);\n\n                if (productList == null)\n                    return;\n\n                Collections.sort(productList, new Comparator<SkuDetails>() {\n                    @Override\n                    public int compare(SkuDetails skuDetails, SkuDetails t1) {\n                        if (skuDetails.priceValue >= t1.priceValue)\n                            return 1;\n                        else if (skuDetails.priceValue <= t1.priceValue)\n                            return -1;\n                        else return 0;\n                    }\n                });\n                for (int i = 0; i < productList.size(); i++) {\n                    final SkuDetails product = productList.get(i);\n                    View rootView = LayoutInflater.from(DonateActivity.this).inflate(R.layout.item_donate_product, productListView, false);\n\n                    TextView detail = (TextView) rootView.findViewById(R.id.product_detail);\n                    detail.setText(product.priceText);\n\n                    rootView.findViewById(R.id.btn_donate).setOnClickListener(new View.OnClickListener() {\n                        @Override\n                        public void onClick(View view) {\n                            if (readyToPurchase)\n                                bp.purchase(DonateActivity.this, product.productId);\n                            else\n                                Toast.makeText(DonateActivity.this, \"Unable to initiate purchase\", Toast.LENGTH_SHORT).show();\n                        }\n                    });\n\n                    productListView.addView(rootView);\n\n                }\n                progressBar.setVisibility(View.GONE);\n            }\n        }.execute();\n    }\n\n    @Override\n    public boolean onOptionsItemSelected(final MenuItem item) {\n        switch (item.getItemId()) {\n            case android.R.id.home:\n                super.onBackPressed();\n                return true;\n            default:\n                break;\n        }\n        return super.onOptionsItemSelected(item);\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/activities/MainActivity.java",
    "content": "/*\n * Copyright (C) 2015 Naman Dwivedi\n *\n * Licensed under the GNU General Public License v3\n *\n * This is free software: you can redistribute it and/or modify it\n * under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;\n * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n * See the GNU General Public License for more details.\n */\n\npackage com.naman14.timber.activities;\n\nimport android.Manifest;\nimport android.content.Intent;\nimport android.os.Bundle;\nimport android.os.Handler;\nimport android.preference.PreferenceManager;\nimport com.google.android.material.navigation.NavigationView;\nimport com.google.android.material.snackbar.Snackbar;\nimport androidx.fragment.app.Fragment;\nimport androidx.fragment.app.FragmentManager;\nimport androidx.fragment.app.FragmentTransaction;\nimport androidx.core.view.GravityCompat;\nimport androidx.drawerlayout.widget.DrawerLayout;\nimport android.view.Gravity;\nimport android.view.LayoutInflater;\nimport android.view.Menu;\nimport android.view.MenuItem;\nimport android.view.View;\nimport android.widget.FrameLayout;\nimport android.widget.ImageView;\nimport android.widget.TextView;\n\nimport com.afollestad.appthemeengine.customizers.ATEActivityThemeCustomizer;\nimport com.anjlab.android.iab.v3.BillingProcessor;\nimport com.google.android.gms.cast.framework.media.widget.ExpandedControllerActivity;\nimport com.naman14.timber.MusicPlayer;\nimport com.naman14.timber.R;\nimport com.naman14.timber.cast.ExpandedControlsActivity;\nimport com.naman14.timber.fragments.AlbumDetailFragment;\nimport com.naman14.timber.fragments.ArtistDetailFragment;\nimport com.naman14.timber.fragments.FoldersFragment;\nimport com.naman14.timber.fragments.MainFragment;\nimport com.naman14.timber.fragments.PlaylistFragment;\nimport com.naman14.timber.fragments.QueueFragment;\nimport com.naman14.timber.permissions.Nammu;\nimport com.naman14.timber.permissions.PermissionCallback;\nimport com.naman14.timber.slidinguppanel.SlidingUpPanelLayout;\nimport com.naman14.timber.subfragments.LyricsFragment;\nimport com.naman14.timber.utils.Constants;\nimport com.naman14.timber.utils.Helpers;\nimport com.naman14.timber.utils.NavigationUtils;\nimport com.naman14.timber.utils.TimberUtils;\nimport com.nostra13.universalimageloader.core.DisplayImageOptions;\nimport com.nostra13.universalimageloader.core.ImageLoader;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\npublic class MainActivity extends BaseActivity implements ATEActivityThemeCustomizer {\n\n    private SlidingUpPanelLayout panelLayout;\n    private NavigationView navigationView;\n    private TextView songtitle, songartist;\n    private ImageView albumart;\n    private String action;\n    private Map<String, Runnable> navigationMap = new HashMap<String, Runnable>();\n    private Handler navDrawerRunnable = new Handler();\n    private Runnable runnable;\n    private DrawerLayout mDrawerLayout;\n    private boolean isDarkTheme;\n\n    private Runnable navigateLibrary = new Runnable() {\n        public void run() {\n            navigationView.getMenu().findItem(R.id.nav_library).setChecked(true);\n            Fragment fragment = new MainFragment();\n            FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();\n            transaction.replace(R.id.fragment_container, fragment).commitAllowingStateLoss();\n\n        }\n    };\n\n    private Runnable navigatePlaylist = new Runnable() {\n        public void run() {\n            navigationView.getMenu().findItem(R.id.nav_playlists).setChecked(true);\n            Fragment fragment = new PlaylistFragment();\n            FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();\n            transaction.hide(getSupportFragmentManager().findFragmentById(R.id.fragment_container));\n            transaction.replace(R.id.fragment_container, fragment).commit();\n\n        }\n    };\n\n    private Runnable navigateFolder = new Runnable() {\n        public void run() {\n            navigationView.getMenu().findItem(R.id.nav_folders).setChecked(true);\n            Fragment fragment = new FoldersFragment();\n            FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();\n            transaction.hide(getSupportFragmentManager().findFragmentById(R.id.fragment_container));\n            transaction.replace(R.id.fragment_container, fragment).commit();\n\n        }\n    };\n\n    private Runnable navigateQueue = new Runnable() {\n        public void run() {\n            navigationView.getMenu().findItem(R.id.nav_queue).setChecked(true);\n            Fragment fragment = new QueueFragment();\n            FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();\n            transaction.hide(getSupportFragmentManager().findFragmentById(R.id.fragment_container));\n            transaction.replace(R.id.fragment_container, fragment).commit();\n\n        }\n    };\n\n    private Runnable navigateAlbum = new Runnable() {\n        public void run() {\n            long albumID = getIntent().getExtras().getLong(Constants.ALBUM_ID);\n            Fragment fragment = AlbumDetailFragment.newInstance(albumID, false, null);\n            FragmentManager fragmentManager = getSupportFragmentManager();\n            fragmentManager.beginTransaction()\n                    .replace(R.id.fragment_container, fragment).commit();\n        }\n    };\n\n    private Runnable navigateArtist = new Runnable() {\n        public void run() {\n            long artistID = getIntent().getExtras().getLong(Constants.ARTIST_ID);\n            Fragment fragment = ArtistDetailFragment.newInstance(artistID, false, null);\n            FragmentManager fragmentManager = getSupportFragmentManager();\n            fragmentManager.beginTransaction()\n                    .replace(R.id.fragment_container, fragment).commit();\n        }\n    };\n\n    private Runnable navigateLyrics = new Runnable() {\n        public void run() {\n            Fragment fragment = new LyricsFragment();\n            FragmentManager fragmentManager = getSupportFragmentManager();\n            fragmentManager.beginTransaction()\n                    .replace(R.id.fragment_container, fragment).commit();\n        }\n    };\n\n    private Runnable navigateNowplaying = new Runnable() {\n        public void run() {\n            navigateLibrary.run();\n            startActivity(new Intent(MainActivity.this, NowPlayingActivity.class));\n        }\n    };\n\n    private final PermissionCallback permissionReadstorageCallback = new PermissionCallback() {\n        @Override\n        public void permissionGranted() {\n            loadEverything();\n        }\n\n        @Override\n        public void permissionRefused() {\n            finish();\n        }\n    };\n\n\n    @Override\n    public void onCreate(Bundle savedInstanceState) {\n\n        action = getIntent().getAction();\n\n        isDarkTheme = PreferenceManager.getDefaultSharedPreferences(this).getBoolean(\"dark_theme\", false);\n\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_main);\n\n        navigationMap.put(Constants.NAVIGATE_LIBRARY, navigateLibrary);\n        navigationMap.put(Constants.NAVIGATE_PLAYLIST, navigatePlaylist);\n        navigationMap.put(Constants.NAVIGATE_QUEUE, navigateQueue);\n        navigationMap.put(Constants.NAVIGATE_NOWPLAYING, navigateNowplaying);\n        navigationMap.put(Constants.NAVIGATE_ALBUM, navigateAlbum);\n        navigationMap.put(Constants.NAVIGATE_ARTIST, navigateArtist);\n        navigationMap.put(Constants.NAVIGATE_LYRICS, navigateLyrics);\n\n        mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);\n        panelLayout = (SlidingUpPanelLayout) findViewById(R.id.sliding_layout);\n\n        navigationView = (NavigationView) findViewById(R.id.nav_view);\n        View header = navigationView.inflateHeaderView(R.layout.nav_header);\n\n        albumart = (ImageView) header.findViewById(R.id.album_art);\n        songtitle = (TextView) header.findViewById(R.id.song_title);\n        songartist = (TextView) header.findViewById(R.id.song_artist);\n\n        setPanelSlideListeners(panelLayout);\n\n        navDrawerRunnable.postDelayed(new Runnable() {\n            @Override\n            public void run() {\n                setupDrawerContent(navigationView);\n                setupNavigationIcons(navigationView);\n            }\n        }, 700);\n\n\n        if (TimberUtils.isMarshmallow()) {\n            checkPermissionAndThenLoad();\n            //checkWritePermissions();\n        } else {\n            loadEverything();\n        }\n\n        addBackstackListener();\n\n        if(Intent.ACTION_VIEW.equals(action)) {\n            Handler handler = new Handler();\n            handler.postDelayed(new Runnable() {\n                @Override\n                public void run() {\n                    MusicPlayer.clearQueue();\n                    MusicPlayer.openFile(getIntent().getData().getPath());\n                    MusicPlayer.playOrPause();\n                    navigateNowplaying.run();\n                }\n            }, 350);\n        }\n\n        if (!panelLayout.isPanelHidden() && MusicPlayer.getTrackName() == null ) {\n            panelLayout.hidePanel();\n        }\n\n        if (playServicesAvailable) {\n\n            final FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(\n                    FrameLayout.LayoutParams.WRAP_CONTENT,\n                    FrameLayout.LayoutParams.WRAP_CONTENT);\n            params.gravity = Gravity.BOTTOM;\n\n            FrameLayout contentRoot = findViewById(R.id.content_root);\n            contentRoot.addView(LayoutInflater.from(this)\n                    .inflate(R.layout.fragment_cast_mini_controller, null), params);\n\n            findViewById(R.id.castMiniController).setOnClickListener(new View.OnClickListener() {\n                @Override\n                public void onClick(View view) {\n                    startActivity(new Intent(MainActivity.this, ExpandedControllerActivity.class));\n                }\n            });\n        }\n\n    }\n\n    private void loadEverything() {\n        Runnable navigation = navigationMap.get(action);\n        if (navigation != null) {\n            navigation.run();\n        } else {\n            navigateLibrary.run();\n        }\n\n        new initQuickControls().execute(\"\");\n    }\n\n    private void checkPermissionAndThenLoad() {\n        //check for permission\n        if (Nammu.checkPermission(Manifest.permission.READ_EXTERNAL_STORAGE) && Nammu.checkPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)) {\n            loadEverything();\n        } else {\n            if (Nammu.shouldShowRequestPermissionRationale(this, Manifest.permission.READ_EXTERNAL_STORAGE)) {\n                Snackbar.make(panelLayout, \"Timber will need to read external storage to display songs on your device.\",\n                        Snackbar.LENGTH_INDEFINITE)\n                        .setAction(\"OK\", new View.OnClickListener() {\n                            @Override\n                            public void onClick(View view) {\n                                Nammu.askForPermission(MainActivity.this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE}, permissionReadstorageCallback);\n                            }\n                        }).show();\n            } else {\n                Nammu.askForPermission(this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE}, permissionReadstorageCallback);\n            }\n        }\n    }\n\n\n    @Override\n    public boolean onCreateOptionsMenu(Menu menu) {\n        super.onCreateOptionsMenu(menu);\n\n        return true;\n    }\n\n    @Override\n    public boolean onOptionsItemSelected(MenuItem item) {\n        switch (item.getItemId()) {\n            case android.R.id.home: {\n                if (isNavigatingMain()) {\n                    mDrawerLayout.openDrawer(GravityCompat.START);\n                } else super.onBackPressed();\n                return true;\n            }\n        }\n        return super.onOptionsItemSelected(item);\n    }\n\n    @Override\n    public void onBackPressed() {\n        if (panelLayout.isPanelExpanded()) {\n            panelLayout.collapsePanel();\n        } else if (mDrawerLayout.isDrawerOpen(GravityCompat.START)) {\n            mDrawerLayout.closeDrawer(GravityCompat.START);\n        } else {\n            super.onBackPressed();\n        }\n    }\n\n    private void setupDrawerContent(NavigationView navigationView) {\n        navigationView.setNavigationItemSelectedListener(\n                new NavigationView.OnNavigationItemSelectedListener() {\n                    @Override\n                    public boolean onNavigationItemSelected(final MenuItem menuItem) {\n                        updatePosition(menuItem);\n                        return true;\n\n                    }\n                });\n    }\n\n    private void setupNavigationIcons(NavigationView navigationView) {\n\n        //material-icon-lib currently doesn't work with navigationview of design support library 22.2.0+\n        //set icons manually for now\n        //https://github.com/code-mc/material-icon-lib/issues/15\n\n        if (!isDarkTheme) {\n            navigationView.getMenu().findItem(R.id.nav_library).setIcon(R.drawable.library_music);\n            navigationView.getMenu().findItem(R.id.nav_playlists).setIcon(R.drawable.playlist_play);\n            navigationView.getMenu().findItem(R.id.nav_queue).setIcon(R.drawable.music_note);\n            navigationView.getMenu().findItem(R.id.nav_folders).setIcon(R.drawable.ic_folder_open_black_24dp);\n            navigationView.getMenu().findItem(R.id.nav_nowplaying).setIcon(R.drawable.bookmark_music);\n            navigationView.getMenu().findItem(R.id.nav_settings).setIcon(R.drawable.settings);\n            navigationView.getMenu().findItem(R.id.nav_about).setIcon(R.drawable.information);\n            navigationView.getMenu().findItem(R.id.nav_donate).setIcon(R.drawable.payment_black);\n        } else {\n            navigationView.getMenu().findItem(R.id.nav_library).setIcon(R.drawable.library_music_white);\n            navigationView.getMenu().findItem(R.id.nav_playlists).setIcon(R.drawable.playlist_play_white);\n            navigationView.getMenu().findItem(R.id.nav_queue).setIcon(R.drawable.music_note_white);\n            navigationView.getMenu().findItem(R.id.nav_folders).setIcon(R.drawable.ic_folder_open_white_24dp);\n            navigationView.getMenu().findItem(R.id.nav_nowplaying).setIcon(R.drawable.bookmark_music_white);\n            navigationView.getMenu().findItem(R.id.nav_settings).setIcon(R.drawable.settings_white);\n            navigationView.getMenu().findItem(R.id.nav_about).setIcon(R.drawable.information_white);\n            navigationView.getMenu().findItem(R.id.nav_donate).setIcon(R.drawable.payment_white);\n        }\n\n        try {\n            if (!BillingProcessor.isIabServiceAvailable(this)) {\n                navigationView.getMenu().removeItem(R.id.nav_donate);\n            }\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n\n    }\n\n    private void updatePosition(final MenuItem menuItem) {\n        runnable = null;\n\n        switch (menuItem.getItemId()) {\n            case R.id.nav_library:\n                runnable = navigateLibrary;\n\n                break;\n            case R.id.nav_playlists:\n                runnable = navigatePlaylist;\n\n                break;\n            case R.id.nav_folders:\n                runnable = navigateFolder;\n\n                break;\n            case R.id.nav_nowplaying:\n                if (getCastSession() != null) {\n                    startActivity(new Intent(MainActivity.this, ExpandedControlsActivity.class));\n                } else {\n                    NavigationUtils.navigateToNowplaying(MainActivity.this, false);\n                }\n                break;\n            case R.id.nav_queue:\n                runnable = navigateQueue;\n\n                break;\n            case R.id.nav_settings:\n                NavigationUtils.navigateToSettings(MainActivity.this);\n                break;\n            case R.id.nav_about:\n                mDrawerLayout.closeDrawers();\n                Handler handler = new Handler();\n                handler.postDelayed(new Runnable() {\n                    @Override\n                    public void run() {\n                        Helpers.showAbout(MainActivity.this);\n                    }\n                }, 350);\n\n                break;\n            case R.id.nav_donate:\n                startActivity(new Intent(MainActivity.this, DonateActivity.class));\n                break;\n        }\n\n        if (runnable != null) {\n            menuItem.setChecked(true);\n            mDrawerLayout.closeDrawers();\n            Handler handler = new Handler();\n            handler.postDelayed(new Runnable() {\n                @Override\n                public void run() {\n                    runnable.run();\n                }\n            }, 350);\n        }\n    }\n\n    public void setDetailsToHeader() {\n        String name = MusicPlayer.getTrackName();\n        String artist = MusicPlayer.getArtistName();\n\n        if (name != null && artist != null) {\n            songtitle.setText(name);\n            songartist.setText(artist);\n        }\n        ImageLoader.getInstance().displayImage(TimberUtils.getAlbumArtUri(MusicPlayer.getCurrentAlbumId()).toString(), albumart,\n                new DisplayImageOptions.Builder().cacheInMemory(true)\n                        .showImageOnFail(R.drawable.ic_empty_music2)\n                        .resetViewBeforeLoading(true)\n                        .build());\n    }\n\n    @Override\n    public void onMetaChanged() {\n        super.onMetaChanged();\n        setDetailsToHeader();\n\n        if (panelLayout.isPanelHidden() && MusicPlayer.getTrackName() != null) {\n            panelLayout.showPanel();\n        }\n    }\n\n    @Override\n    public void onRequestPermissionsResult(\n            int requestCode, String[] permissions, int[] grantResults) {\n        Nammu.onRequestPermissionsResult(requestCode, permissions, grantResults);\n    }\n\n    private boolean isNavigatingMain() {\n        Fragment currentFragment = getSupportFragmentManager().findFragmentById(R.id.fragment_container);\n        return (currentFragment instanceof MainFragment || currentFragment instanceof QueueFragment\n                || currentFragment instanceof PlaylistFragment || currentFragment instanceof FoldersFragment);\n    }\n\n    private void addBackstackListener() {\n        getSupportFragmentManager().addOnBackStackChangedListener(new FragmentManager.OnBackStackChangedListener() {\n            @Override\n            public void onBackStackChanged() {\n                getSupportFragmentManager().findFragmentById(R.id.fragment_container).onResume();\n            }\n        });\n    }\n\n\n    @Override\n    public int getActivityTheme() {\n        return isDarkTheme ? R.style.AppThemeNormalDark : R.style.AppThemeNormalLight;\n    }\n\n    @Override\n    protected void onActivityResult(int requestCode, int resultCode, Intent data) {\n        super.onActivityResult(requestCode, resultCode, data);\n        getSupportFragmentManager().findFragmentById(R.id.fragment_container).onActivityResult(requestCode, resultCode, data);\n    }\n\n\n    @Override\n    public void showCastMiniController() {\n        findViewById(R.id.castMiniController).setVisibility(View.VISIBLE);\n        findViewById(R.id.quickcontrols_container).setVisibility(View.GONE);\n        panelLayout.hidePanel();\n    }\n\n    @Override\n    public void hideCastMiniController() {\n\n        findViewById(R.id.castMiniController).setVisibility(View.GONE);\n        findViewById(R.id.quickcontrols_container).setVisibility(View.VISIBLE);\n\n        panelLayout.showPanel();\n    }\n}\n\n\n"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/activities/NowPlayingActivity.java",
    "content": "package com.naman14.timber.activities;\n\nimport android.content.Context;\nimport android.content.SharedPreferences;\nimport android.graphics.Color;\nimport android.os.Bundle;\nimport android.preference.PreferenceManager;\nimport androidx.annotation.StyleRes;\nimport androidx.fragment.app.Fragment;\nimport androidx.fragment.app.FragmentManager;\n\nimport com.afollestad.appthemeengine.Config;\nimport com.afollestad.appthemeengine.customizers.ATEActivityThemeCustomizer;\nimport com.afollestad.appthemeengine.customizers.ATEStatusBarCustomizer;\nimport com.afollestad.appthemeengine.customizers.ATEToolbarCustomizer;\nimport com.naman14.timber.R;\nimport com.naman14.timber.utils.Constants;\nimport com.naman14.timber.utils.NavigationUtils;\nimport com.naman14.timber.utils.PreferencesUtility;\n\n/**\n * Created by naman on 01/01/16.\n */\npublic class NowPlayingActivity extends BaseActivity implements ATEActivityThemeCustomizer, ATEToolbarCustomizer, ATEStatusBarCustomizer {\n\n    @Override\n    public void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_nowplaying);\n        SharedPreferences prefs = getSharedPreferences(Constants.FRAGMENT_ID, Context.MODE_PRIVATE);\n        String fragmentID = prefs.getString(Constants.NOWPLAYING_FRAGMENT_ID, Constants.TIMBER3);\n\n        Fragment fragment = NavigationUtils.getFragmentForNowplayingID(fragmentID);\n        FragmentManager fragmentManager = getSupportFragmentManager();\n\n        fragmentManager.beginTransaction()\n                .replace(R.id.container, fragment).commit();\n\n    }\n\n    @StyleRes\n    @Override\n    public int getActivityTheme() {\n        return PreferenceManager.getDefaultSharedPreferences(this).getBoolean(\"dark_theme\", false) ? R.style.AppTheme_FullScreen_Dark : R.style.AppTheme_FullScreen_Light;\n    }\n\n    @Override\n    public int getLightToolbarMode() {\n        return Config.LIGHT_TOOLBAR_AUTO;\n    }\n\n    @Override\n    public int getLightStatusBarMode() {\n        return Config.LIGHT_STATUS_BAR_OFF;\n    }\n\n    @Override\n    public int getToolbarColor() {\n        return Color.TRANSPARENT;\n    }\n\n    @Override\n    public int getStatusBarColor() {\n        return Color.TRANSPARENT;\n    }\n\n    @Override\n    public void onResume() {\n        super.onResume();\n        if (PreferencesUtility.getInstance(this).didNowplayingThemeChanged()) {\n            PreferencesUtility.getInstance(this).setNowPlayingThemeChanged(false);\n            recreate();\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/activities/PlaylistDetailActivity.java",
    "content": "/*\n * Copyright (C) 2015 Naman Dwivedi\n *\n * Licensed under the GNU General Public License v3\n *\n * This is free software: you can redistribute it and/or modify it\n * under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;\n * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n * See the GNU General Public License for more details.\n */\n\npackage com.naman14.timber.activities;\n\nimport android.annotation.TargetApi;\nimport android.app.Activity;\nimport android.content.Intent;\nimport android.graphics.Color;\nimport android.os.AsyncTask;\nimport android.os.Bundle;\nimport android.os.Handler;\nimport android.preference.PreferenceManager;\nimport android.provider.MediaStore;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.StyleRes;\nimport androidx.appcompat.app.AppCompatActivity;\nimport androidx.recyclerview.widget.LinearLayoutManager;\nimport androidx.recyclerview.widget.RecyclerView;\nimport androidx.appcompat.widget.Toolbar;\nimport android.transition.Transition;\nimport android.util.Log;\nimport android.view.Menu;\nimport android.view.MenuItem;\nimport android.view.View;\nimport android.widget.ImageView;\nimport android.widget.TextView;\n\nimport com.afollestad.appthemeengine.Config;\nimport com.afollestad.appthemeengine.customizers.ATEActivityThemeCustomizer;\nimport com.afollestad.appthemeengine.customizers.ATEToolbarCustomizer;\nimport com.afollestad.materialdialogs.DialogAction;\nimport com.afollestad.materialdialogs.MaterialDialog;\nimport com.naman14.timber.R;\nimport com.naman14.timber.adapters.SongsListAdapter;\nimport com.naman14.timber.dataloaders.LastAddedLoader;\nimport com.naman14.timber.dataloaders.PlaylistLoader;\nimport com.naman14.timber.dataloaders.PlaylistSongLoader;\nimport com.naman14.timber.dataloaders.SongLoader;\nimport com.naman14.timber.dataloaders.TopTracksLoader;\nimport com.naman14.timber.listeners.SimplelTransitionListener;\nimport com.naman14.timber.models.Song;\nimport com.naman14.timber.utils.Constants;\nimport com.naman14.timber.utils.TimberUtils;\nimport com.naman14.timber.widgets.DividerItemDecoration;\nimport com.naman14.timber.widgets.DragSortRecycler;\nimport com.nostra13.universalimageloader.core.DisplayImageOptions;\nimport com.nostra13.universalimageloader.core.ImageLoader;\n\nimport java.util.HashMap;\nimport java.util.List;\n\npublic class PlaylistDetailActivity extends BaseActivity implements ATEActivityThemeCustomizer, ATEToolbarCustomizer {\n\n    private String action;\n    private long playlistID;\n    private HashMap<String, Runnable> playlistsMap = new HashMap<>();\n\n    private AppCompatActivity mContext = PlaylistDetailActivity.this;\n    private SongsListAdapter mAdapter;\n    private RecyclerView recyclerView;\n    private ImageView blurFrame;\n    private TextView playlistname;\n    private View foreground;\n    private boolean animate;\n\n    private Runnable playlistLastAdded = new Runnable() {\n        public void run() {\n            new loadLastAdded().execute(\"\");\n        }\n    };\n    private Runnable playlistRecents = new Runnable() {\n        @Override\n        public void run() {\n            new loadRecentlyPlayed().execute(\"\");\n\n        }\n    };\n    private Runnable playlistToptracks = new Runnable() {\n        @Override\n        public void run() {\n            new loadTopTracks().execute(\"\");\n        }\n    };\n    private Runnable playlistUsercreated = new Runnable() {\n        @Override\n        public void run() {\n            new loadUserCreatedPlaylist().execute(\"\");\n\n        }\n    };\n\n    @TargetApi(21)\n    @Override\n    public void onCreate(Bundle savedInstanceState) {\n\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_playlist_detail);\n\n        action = getIntent().getAction();\n\n        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);\n        setSupportActionBar(toolbar);\n\n        getSupportActionBar().setDisplayHomeAsUpEnabled(true);\n        getSupportActionBar().setTitle(\"\");\n\n        playlistsMap.put(Constants.NAVIGATE_PLAYLIST_LASTADDED, playlistLastAdded);\n        playlistsMap.put(Constants.NAVIGATE_PLAYLIST_RECENT, playlistRecents);\n        playlistsMap.put(Constants.NAVIGATE_PLAYLIST_TOPTRACKS, playlistToptracks);\n        playlistsMap.put(Constants.NAVIGATE_PLAYLIST_USERCREATED, playlistUsercreated);\n\n        recyclerView = (RecyclerView) findViewById(R.id.recyclerview);\n        blurFrame = (ImageView) findViewById(R.id.blurFrame);\n        playlistname = (TextView) findViewById(R.id.name);\n        foreground = findViewById(R.id.foreground);\n\n        recyclerView.setLayoutManager(new LinearLayoutManager(this));\n\n        setAlbumart();\n\n        animate = getIntent().getBooleanExtra(Constants.ACTIVITY_TRANSITION, false);\n        if (animate && TimberUtils.isLollipop()) {\n            if(savedInstanceState != null && savedInstanceState.containsKey(\"ROTATION_RECREATION\")){\n                setUpSongs();\n            }\n            else{\n                getWindow().getEnterTransition().addListener(new EnterTransitionListener());\n            }\n        } else {\n            setUpSongs();\n        }\n    }\n\n    @Override\n    protected void onSaveInstanceState(Bundle outState) {\n        super.onSaveInstanceState(outState);\n        outState.putString(\"ROTATION_RECREATION\", \"Rotacion\");\n    }\n\n    private void setAlbumart() {\n        playlistname.setText(getIntent().getExtras().getString(Constants.PLAYLIST_NAME));\n        foreground.setBackgroundColor(getIntent().getExtras().getInt(Constants.PLAYLIST_FOREGROUND_COLOR));\n        loadBitmap(TimberUtils.getAlbumArtUri(getIntent().getExtras().getLong(Constants.ALBUM_ID)).toString());\n    }\n\n    private void setUpSongs() {\n        Runnable navigation = playlistsMap.get(action);\n        if (navigation != null) {\n            navigation.run();\n\n            DragSortRecycler dragSortRecycler = new DragSortRecycler();\n            dragSortRecycler.setViewHandleId(R.id.reorder);\n\n            dragSortRecycler.setOnItemMovedListener(new DragSortRecycler.OnItemMovedListener() {\n                @Override\n                public void onItemMoved(int from, int to) {\n                    Log.d(\"playlist\", \"onItemMoved \" + from + \" to \" + to);\n                    Song song = mAdapter.getSongAt(from);\n                    mAdapter.removeSongAt(from);\n                    mAdapter.addSongTo(to, song);\n                    mAdapter.notifyDataSetChanged();\n                    MediaStore.Audio.Playlists.Members.moveItem(getContentResolver(),\n                            playlistID, from, to);\n                }\n            });\n\n            recyclerView.addItemDecoration(dragSortRecycler);\n            recyclerView.addOnItemTouchListener(dragSortRecycler);\n            recyclerView.addOnScrollListener(dragSortRecycler.getScrollListener());\n\n        } else {\n            Log.d(\"PlaylistDetail\", \"mo action specified\");\n        }\n    }\n\n    private void loadBitmap(String uri) {\n        ImageLoader.getInstance().displayImage(uri, blurFrame,\n                new DisplayImageOptions.Builder().cacheInMemory(true)\n                        .showImageOnFail(R.drawable.ic_empty_music2)\n                        .resetViewBeforeLoading(true)\n                        .build());\n    }\n\n    private void setRecyclerViewAapter() {\n        recyclerView.setAdapter(mAdapter);\n        if (animate && TimberUtils.isLollipop()) {\n            Handler handler = new Handler();\n            handler.postDelayed(new Runnable() {\n                @Override\n                public void run() {\n                    recyclerView.addItemDecoration(new DividerItemDecoration(mContext, DividerItemDecoration.VERTICAL_LIST, R.drawable.item_divider_white));\n                }\n            }, 250);\n        } else\n            recyclerView.addItemDecoration(new DividerItemDecoration(mContext, DividerItemDecoration.VERTICAL_LIST, R.drawable.item_divider_white));\n    }\n\n    @StyleRes\n    @Override\n    public int getActivityTheme() {\n        return PreferenceManager.getDefaultSharedPreferences(this).getBoolean(\"dark_theme\", false) ? R.style.AppTheme_FullScreen_Dark : R.style.AppTheme_FullScreen_Light;\n\n    }\n\n    private class loadLastAdded extends AsyncTask<String, Void, String> {\n\n        @Override\n        protected String doInBackground(String... params) {\n            List<Song> lastadded = LastAddedLoader.getLastAddedSongs(mContext);\n            mAdapter = new SongsListAdapter(mContext, lastadded, true, animate);\n            mAdapter.setPlaylistId(playlistID);\n            return \"Executed\";\n        }\n\n        @Override\n        protected void onPostExecute(String result) {\n            setRecyclerViewAapter();\n        }\n\n        @Override\n        protected void onPreExecute() {\n        }\n    }\n\n    private class loadRecentlyPlayed extends AsyncTask<String, Void, String> {\n\n        @Override\n        protected String doInBackground(String... params) {\n            TopTracksLoader loader = new TopTracksLoader(mContext, TopTracksLoader.QueryType.RecentSongs);\n            List<Song> recentsongs = SongLoader.getSongsForCursor(TopTracksLoader.getCursor());\n            mAdapter = new SongsListAdapter(mContext, recentsongs, true, animate);\n            mAdapter.setPlaylistId(playlistID);\n            return \"Executed\";\n        }\n\n        @Override\n        protected void onPostExecute(String result) {\n            setRecyclerViewAapter();\n\n        }\n\n        @Override\n        protected void onPreExecute() {\n        }\n    }\n\n    private class loadTopTracks extends AsyncTask<String, Void, String> {\n\n        @Override\n        protected String doInBackground(String... params) {\n            TopTracksLoader loader = new TopTracksLoader(mContext, TopTracksLoader.QueryType.TopTracks);\n            List<Song> toptracks = SongLoader.getSongsForCursor(TopTracksLoader.getCursor());\n            mAdapter = new SongsListAdapter(mContext, toptracks, true, animate);\n            mAdapter.setPlaylistId(playlistID);\n            return \"Executed\";\n        }\n\n        @Override\n        protected void onPostExecute(String result) {\n            setRecyclerViewAapter();\n        }\n\n        @Override\n        protected void onPreExecute() {\n        }\n    }\n\n    private class loadUserCreatedPlaylist extends AsyncTask<String, Void, String> {\n\n        @Override\n        protected String doInBackground(String... params) {\n            playlistID = getIntent().getExtras().getLong(Constants.PLAYLIST_ID);\n            List<Song> playlistsongs = PlaylistSongLoader.getSongsInPlaylist(mContext, playlistID);\n            mAdapter = new SongsListAdapter(mContext, playlistsongs, true, animate);\n            mAdapter.setPlaylistId(playlistID);\n            return \"Executed\";\n        }\n\n        @Override\n        protected void onPostExecute(String result) {\n            setRecyclerViewAapter();\n        }\n\n        @Override\n        protected void onPreExecute() {\n        }\n    }\n\n    private class EnterTransitionListener extends SimplelTransitionListener {\n\n        @TargetApi(21)\n        public void onTransitionEnd(Transition paramTransition) {\n            setUpSongs();\n        }\n\n        public void onTransitionStart(Transition paramTransition) {\n        }\n\n    }\n\n    @Override\n    public boolean onCreateOptionsMenu(final Menu menu) {\n\n        getMenuInflater().inflate(R.menu.menu_playlist_detail, menu);\n        return super.onCreateOptionsMenu(menu);\n    }\n\n    @Override\n    public boolean onPrepareOptionsMenu(Menu menu) {\n        if (action.equals(Constants.NAVIGATE_PLAYLIST_USERCREATED)) {\n            menu.findItem(R.id.action_delete_playlist).setVisible(true);\n            menu.findItem(R.id.action_clear_auto_playlist).setVisible(false);\n        } else {\n            menu.findItem(R.id.action_delete_playlist).setVisible(false);\n            menu.findItem(R.id.action_clear_auto_playlist).setTitle(\"Clear \" + playlistname.getText().toString());\n        }\n\n        return super.onPrepareOptionsMenu(menu);\n    }\n\n    @Override\n    public boolean onOptionsItemSelected(final MenuItem item) {\n        switch (item.getItemId()) {\n            case android.R.id.home:\n                super.onBackPressed();\n                return true;\n            case R.id.action_delete_playlist:\n                showDeletePlaylistDialog();\n                break;\n            case R.id.action_clear_auto_playlist:\n                clearAutoPlaylists();\n                break;\n            default:\n                break;\n        }\n        return super.onOptionsItemSelected(item);\n    }\n\n    private void showDeletePlaylistDialog() {\n        new MaterialDialog.Builder(this)\n                .title(\"Delete playlist?\")\n                .content(\"Are you sure you want to delete playlist \" + playlistname.getText().toString() + \" ?\")\n                .positiveText(\"Delete\")\n                .negativeText(\"Cancel\")\n                .onPositive(new MaterialDialog.SingleButtonCallback() {\n                    @Override\n                    public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) {\n                        PlaylistLoader.deletePlaylists(PlaylistDetailActivity.this, playlistID);\n                        Intent returnIntent = new Intent();\n                        setResult(Activity.RESULT_OK, returnIntent);\n                        finish();\n                    }\n                })\n                .onNegative(new MaterialDialog.SingleButtonCallback() {\n                    @Override\n                    public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) {\n                        dialog.dismiss();\n                    }\n                })\n                .show();\n    }\n\n    private void clearAutoPlaylists() {\n        switch (action) {\n            case Constants.NAVIGATE_PLAYLIST_LASTADDED:\n                TimberUtils.clearLastAdded(this);\n                break;\n            case Constants.NAVIGATE_PLAYLIST_RECENT:\n                TimberUtils.clearRecent(this);\n                break;\n            case Constants.NAVIGATE_PLAYLIST_TOPTRACKS:\n                TimberUtils.clearTopTracks(this);\n                break;\n        }\n        Intent returnIntent = new Intent();\n        setResult(Activity.RESULT_OK, returnIntent);\n        finish();\n    }\n\n    @Override\n    public void onMetaChanged() {\n        super.onMetaChanged();\n        if (mAdapter != null)\n            mAdapter.notifyDataSetChanged();\n    }\n\n    @Override\n    public int getToolbarColor() {\n        return Color.TRANSPARENT;\n    }\n\n    @Override\n    public int getLightToolbarMode() {\n        return Config.LIGHT_TOOLBAR_AUTO;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/activities/SearchActivity.java",
    "content": "/*\n * Copyright (C) 2015 Naman Dwivedi\n *\n * Licensed under the GNU General Public License v3\n *\n * This is free software: you can redistribute it and/or modify it\n * under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;\n * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n * See the GNU General Public License for more details.\n */\n\npackage com.naman14.timber.activities;\n\nimport android.content.Context;\nimport android.os.AsyncTask;\nimport android.os.Bundle;\nimport androidx.annotation.Nullable;\nimport androidx.core.view.MenuItemCompat;\nimport androidx.recyclerview.widget.LinearLayoutManager;\nimport androidx.recyclerview.widget.RecyclerView;\nimport androidx.appcompat.widget.SearchView;\nimport androidx.appcompat.widget.Toolbar;\nimport android.util.Log;\nimport android.view.Menu;\nimport android.view.MenuItem;\nimport android.view.MotionEvent;\nimport android.view.View;\nimport android.view.inputmethod.InputMethodManager;\n\nimport com.naman14.timber.R;\nimport com.naman14.timber.adapters.SearchAdapter;\nimport com.naman14.timber.dataloaders.AlbumLoader;\nimport com.naman14.timber.dataloaders.ArtistLoader;\nimport com.naman14.timber.dataloaders.SongLoader;\nimport com.naman14.timber.models.Album;\nimport com.naman14.timber.models.Artist;\nimport com.naman14.timber.models.Song;\nimport com.naman14.timber.provider.SearchHistory;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.concurrent.Executor;\nimport java.util.concurrent.Executors;\n\npublic class SearchActivity extends BaseActivity implements SearchView.OnQueryTextListener, View.OnTouchListener {\n\n    private final Executor mSearchExecutor = Executors.newSingleThreadExecutor();\n    @Nullable\n    private AsyncTask mSearchTask = null;\n\n    private SearchView mSearchView;\n    private InputMethodManager mImm;\n    private String queryString;\n\n    private SearchAdapter adapter;\n    private RecyclerView recyclerView;\n\n    private List<Object> searchResults = Collections.emptyList();\n\n    Bundle bundle;\n\n    @Override\n    public void onCreate(Bundle savedInstanceState) {\n\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_search);\n\n        mImm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);\n\n        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);\n        setSupportActionBar(toolbar);\n        getSupportActionBar().setDisplayHomeAsUpEnabled(true);\n\n        recyclerView = (RecyclerView) findViewById(R.id.recyclerview);\n        recyclerView.setLayoutManager(new LinearLayoutManager(this));\n        adapter = new SearchAdapter(this);\n        recyclerView.setAdapter(adapter);\n\n        if(savedInstanceState != null && savedInstanceState.containsKey(\"QUERY_STRING\")){\n            bundle = savedInstanceState;\n        }\n    }\n\n    @Override\n    protected void onSaveInstanceState(Bundle outState) {\n        super.onSaveInstanceState(outState);\n        if (queryString != null){\n            outState.putString(\"QUERY_STRING\", queryString);\n        }\n    }\n\n\n    @Override\n    public boolean onCreateOptionsMenu(final Menu menu) {\n\n        getMenuInflater().inflate(R.menu.menu_search, menu);\n\n        mSearchView = (SearchView) MenuItemCompat.getActionView(menu.findItem(R.id.menu_search));\n\n        mSearchView.setOnQueryTextListener(this);\n        mSearchView.setQueryHint(getString(R.string.search_library));\n\n        mSearchView.setIconifiedByDefault(false);\n        mSearchView.setIconified(false);\n\n        MenuItemCompat.setOnActionExpandListener(menu.findItem(R.id.menu_search), new MenuItemCompat.OnActionExpandListener() {\n            @Override\n            public boolean onMenuItemActionExpand(MenuItem item) {\n                return true;\n            }\n\n            @Override\n            public boolean onMenuItemActionCollapse(MenuItem item) {\n                finish();\n                return false;\n            }\n        });\n\n        menu.findItem(R.id.menu_search).expandActionView();\n\n        if(bundle != null && bundle.containsKey(\"QUERY_STRING\")){\n            mSearchView.setQuery(bundle.getString(\"QUERY_STRING\"), true);\n        }\n\n        return super.onCreateOptionsMenu(menu);\n    }\n\n\n\n    @Override\n    public boolean onPrepareOptionsMenu(Menu menu) {\n        MenuItem item = menu.findItem(R.id.action_search);\n        item.setVisible(false);\n        return true;\n    }\n\n    @Override\n    public boolean onOptionsItemSelected(final MenuItem item) {\n        switch (item.getItemId()) {\n            case android.R.id.home:\n                finish();\n                return true;\n            default:\n                break;\n        }\n        return super.onOptionsItemSelected(item);\n    }\n\n    @Override\n    public boolean onQueryTextSubmit(final String query) {\n        onQueryTextChange(query);\n        hideInputManager();\n\n        return true;\n    }\n\n    @Override\n    public boolean onQueryTextChange(final String newText) {\n\n        if (newText.equals(queryString)) {\n            return true;\n        }\n        if (mSearchTask != null) {\n            mSearchTask.cancel(false);\n            mSearchTask = null;\n        }\n        queryString = newText;\n        if (queryString.trim().equals(\"\")) {\n            searchResults.clear();\n            adapter.updateSearchResults(searchResults);\n            adapter.notifyDataSetChanged();\n        } else {\n            mSearchTask = new SearchTask().executeOnExecutor(mSearchExecutor, queryString);\n            Log.d(\"AAAABBBBBB\", \"TaskCanelled? \" + (mSearchTask.isCancelled()));\n        }\n\n        return true;\n    }\n\n    @Override\n    public boolean onTouch(View v, MotionEvent event) {\n        hideInputManager();\n        return false;\n    }\n\n    @Override\n    protected void onDestroy() {\n        if (mSearchTask != null && mSearchTask.getStatus() != AsyncTask.Status.FINISHED) {\n            mSearchTask.cancel(false);\n        }\n        super.onDestroy();\n    }\n\n    public void hideInputManager() {\n        if (mSearchView != null) {\n            if (mImm != null) {\n                mImm.hideSoftInputFromWindow(mSearchView.getWindowToken(), 0);\n            }\n            mSearchView.clearFocus();\n\n            SearchHistory.getInstance(this).addSearchString(queryString);\n        }\n    }\n\n    private class SearchTask extends AsyncTask<String,Void,ArrayList<Object>> {\n\n        @Override\n        protected ArrayList<Object> doInBackground(String... params) {\n            ArrayList<Object> results = new ArrayList<>(27);\n            List<Song> songList = SongLoader.searchSongs(SearchActivity.this, params[0], 10);\n            if (!songList.isEmpty()) {\n                results.add(getString(R.string.songs));\n                results.addAll(songList);\n            }\n            boolean canceled = isCancelled();\n            if (canceled) {\n                return null;\n            }\n            List<Album> albumList = AlbumLoader.getAlbums(SearchActivity.this, params[0], 7);\n            if (!albumList.isEmpty()) {\n                results.add(getString(R.string.albums));\n                results.addAll(albumList);\n            }\n\n            canceled = isCancelled();\n            if (canceled) {\n                return null;\n            }\n            List<Artist> artistList = ArtistLoader.getArtists(SearchActivity.this, params[0], 7);\n            if (!artistList.isEmpty()) {\n                results.add(getString(R.string.artists));\n                results.addAll(artistList);\n            }\n            if (results.size() == 0) {\n                results.add(getString(R.string.nothing_found));\n            }\n            return results;\n        }\n\n        @Override\n        protected void onPostExecute(ArrayList<Object> objects) {\n            super.onPostExecute(objects);\n            mSearchTask = null;\n            if (objects != null) {\n                adapter.updateSearchResults(objects);\n                adapter.notifyDataSetChanged();\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/activities/SettingsActivity.java",
    "content": "/*\n * Copyright (C) 2015 Naman Dwivedi\n *\n * Licensed under the GNU General Public License v3\n *\n * This is free software: you can redistribute it and/or modify it\n * under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;\n * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n * See the GNU General Public License for more details.\n */\n\npackage com.naman14.timber.activities;\n\nimport android.os.Bundle;\nimport android.preference.PreferenceFragment;\nimport android.preference.PreferenceManager;\nimport androidx.annotation.ColorInt;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.StyleRes;\nimport androidx.fragment.app.Fragment;\nimport androidx.fragment.app.FragmentManager;\nimport androidx.appcompat.widget.Toolbar;\nimport android.view.MenuItem;\n\nimport com.afollestad.appthemeengine.ATE;\nimport com.afollestad.appthemeengine.Config;\nimport com.afollestad.appthemeengine.customizers.ATEActivityThemeCustomizer;\nimport com.afollestad.materialdialogs.color.ColorChooserDialog;\nimport com.naman14.timber.R;\nimport com.naman14.timber.fragments.SettingsFragment;\nimport com.naman14.timber.subfragments.StyleSelectorFragment;\nimport com.naman14.timber.utils.Constants;\nimport com.naman14.timber.utils.PreferencesUtility;\n\npublic class SettingsActivity extends BaseThemedActivity implements ColorChooserDialog.ColorCallback, ATEActivityThemeCustomizer {\n\n    private String action;\n\n    @Override\n    public void onCreate(Bundle savedInstanceState) {\n\n        if (PreferencesUtility.getInstance(this).getTheme().equals(\"dark\"))\n            setTheme(R.style.AppThemeNormalDark);\n        else if (PreferencesUtility.getInstance(this).getTheme().equals(\"black\"))\n            setTheme(R.style.AppThemeNormalBlack);\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_settings);\n\n        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);\n        setSupportActionBar(toolbar);\n\n        getSupportActionBar().setDisplayHomeAsUpEnabled(true);\n\n        action = getIntent().getAction();\n\n        if (action.equals(Constants.SETTINGS_STYLE_SELECTOR)) {\n            getSupportActionBar().setTitle(R.string.now_playing);\n            String what = getIntent().getExtras().getString(Constants.SETTINGS_STYLE_SELECTOR_WHAT);\n            Fragment fragment = StyleSelectorFragment.newInstance(what);\n            FragmentManager fragmentManager = getSupportFragmentManager();\n            fragmentManager.beginTransaction()\n                    .add(R.id.fragment_container, fragment).commit();\n        } else {\n            getSupportActionBar().setTitle(R.string.settings);\n            PreferenceFragment fragment = new SettingsFragment();\n            android.app.FragmentManager fragmentManager = getFragmentManager();\n            fragmentManager.beginTransaction()\n                    .replace(R.id.fragment_container, fragment).commit();\n        }\n\n    }\n\n    @Override\n    public boolean onOptionsItemSelected(final MenuItem item) {\n        switch (item.getItemId()) {\n            case android.R.id.home:\n                finish();\n                return true;\n            default:\n                break;\n        }\n        return super.onOptionsItemSelected(item);\n    }\n\n    @StyleRes\n    @Override\n    public int getActivityTheme() {\n        return PreferenceManager.getDefaultSharedPreferences(this).getBoolean(\"dark_theme\", false) ?\n                R.style.AppThemeDark : R.style.AppThemeLight;\n    }\n\n    @Override\n    public void onColorSelection(@NonNull ColorChooserDialog dialog, @ColorInt int selectedColor) {\n        final Config config = ATE.config(this, getATEKey());\n        switch (dialog.getTitle()) {\n            case R.string.primary_color:\n                config.primaryColor(selectedColor);\n                break;\n            case R.string.accent_color:\n                config.accentColor(selectedColor);\n                break;\n        }\n        config.commit();\n        recreate(); // recreation needed to reach the checkboxes in the preferences layout\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/adapters/AlbumAdapter.java",
    "content": "/*\n * Copyright (C) 2015 Naman Dwivedi\n *\n * Licensed under the GNU General Public License v3\n *\n * This is free software: you can redistribute it and/or modify it\n * under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;\n * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n * See the GNU General Public License for more details.\n */\n\npackage com.naman14.timber.adapters;\n\nimport android.app.Activity;\nimport android.graphics.Bitmap;\nimport androidx.palette.graphics.Palette;\nimport androidx.recyclerview.widget.RecyclerView;\nimport android.util.Pair;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.ImageView;\nimport android.widget.TextView;\n\nimport com.afollestad.appthemeengine.Config;\nimport com.naman14.timber.R;\nimport com.naman14.timber.models.Album;\nimport com.naman14.timber.utils.Helpers;\nimport com.naman14.timber.utils.NavigationUtils;\nimport com.naman14.timber.utils.PreferencesUtility;\nimport com.naman14.timber.utils.TimberUtils;\nimport com.nostra13.universalimageloader.core.DisplayImageOptions;\nimport com.nostra13.universalimageloader.core.ImageLoader;\nimport com.nostra13.universalimageloader.core.assist.FailReason;\nimport com.nostra13.universalimageloader.core.display.FadeInBitmapDisplayer;\nimport com.nostra13.universalimageloader.core.listener.SimpleImageLoadingListener;\n\nimport java.util.List;\n\npublic class AlbumAdapter extends RecyclerView.Adapter<AlbumAdapter.ItemHolder> {\n\n    private List<Album> arraylist;\n    private Activity mContext;\n    private boolean isGrid;\n\n    public AlbumAdapter(Activity context, List<Album> arraylist) {\n        this.arraylist = arraylist;\n        this.mContext = context;\n        this.isGrid = PreferencesUtility.getInstance(mContext).isAlbumsInGrid();\n\n    }\n\n    @Override\n    public ItemHolder onCreateViewHolder(ViewGroup viewGroup, int i) {\n        if (isGrid) {\n            View v = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.item_album_grid, null);\n            ItemHolder ml = new ItemHolder(v);\n            return ml;\n        } else {\n            View v = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.item_album_list, null);\n            ItemHolder ml = new ItemHolder(v);\n            return ml;\n        }\n    }\n\n    @Override\n    public void onBindViewHolder(final ItemHolder itemHolder, int i) {\n        Album localItem = arraylist.get(i);\n\n        itemHolder.title.setText(localItem.title);\n        itemHolder.artist.setText(localItem.artistName);\n\n        ImageLoader.getInstance().displayImage(TimberUtils.getAlbumArtUri(localItem.id).toString(), itemHolder.albumArt,\n                new DisplayImageOptions.Builder().cacheInMemory(true)\n                        .showImageOnLoading(R.drawable.ic_empty_music2)\n                        .resetViewBeforeLoading(true)\n                        .displayer(new FadeInBitmapDisplayer(400))\n                        .build(), new SimpleImageLoadingListener() {\n                    @Override\n                    public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {\n                        if (isGrid) {\n                            new Palette.Builder(loadedImage).generate(new Palette.PaletteAsyncListener() {\n                                @Override\n                                public void onGenerated(Palette palette) {\n                                    Palette.Swatch swatch = palette.getVibrantSwatch();\n                                    if (swatch != null) {\n                                        int color = swatch.getRgb();\n                                        itemHolder.footer.setBackgroundColor(color);\n                                        int textColor = TimberUtils.getBlackWhiteColor(swatch.getTitleTextColor());\n                                        itemHolder.title.setTextColor(textColor);\n                                        itemHolder.artist.setTextColor(textColor);\n                                    } else {\n                                        Palette.Swatch mutedSwatch = palette.getMutedSwatch();\n                                        if (mutedSwatch != null) {\n                                            int color = mutedSwatch.getRgb();\n                                            itemHolder.footer.setBackgroundColor(color);\n                                            int textColor = TimberUtils.getBlackWhiteColor(mutedSwatch.getTitleTextColor());\n                                            itemHolder.title.setTextColor(textColor);\n                                            itemHolder.artist.setTextColor(textColor);\n                                        }\n                                    }\n\n\n                                }\n                            });\n                        }\n\n                    }\n\n                    @Override\n                    public void onLoadingFailed(String imageUri, View view, FailReason failReason) {\n                        if (isGrid) {\n                            itemHolder.footer.setBackgroundColor(0);\n                            if (mContext != null) {\n                                int textColorPrimary = Config.textColorPrimary(mContext, Helpers.getATEKey(mContext));\n                                itemHolder.title.setTextColor(textColorPrimary);\n                                itemHolder.artist.setTextColor(textColorPrimary);\n                            }\n                        }\n                    }\n                });\n\n        if (TimberUtils.isLollipop())\n            itemHolder.albumArt.setTransitionName(\"transition_album_art\" + i);\n\n    }\n\n    @Override\n    public int getItemCount() {\n        return (null != arraylist ? arraylist.size() : 0);\n    }\n\n    public void updateDataSet(List<Album> arraylist) {\n        this.arraylist = arraylist;\n    }\n\n    public class ItemHolder extends RecyclerView.ViewHolder implements View.OnClickListener {\n        protected TextView title, artist;\n        protected ImageView albumArt;\n        protected View footer;\n\n        public ItemHolder(View view) {\n            super(view);\n            this.title = (TextView) view.findViewById(R.id.album_title);\n            this.artist = (TextView) view.findViewById(R.id.album_artist);\n            this.albumArt = (ImageView) view.findViewById(R.id.album_art);\n            this.footer = view.findViewById(R.id.footer);\n            view.setOnClickListener(this);\n        }\n\n        @Override\n        public void onClick(View v) {\n            NavigationUtils.navigateToAlbum(mContext, arraylist.get(getAdapterPosition()).id,\n                    new Pair<View, String>(albumArt, \"transition_album_art\" + getAdapterPosition()));\n        }\n\n    }\n\n\n}\n\n\n\n"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/adapters/AlbumSongsAdapter.java",
    "content": "/*\n * Copyright (C) 2015 Naman Dwivedi\n *\n * Licensed under the GNU General Public License v3\n *\n * This is free software: you can redistribute it and/or modify it\n * under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;\n * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n * See the GNU General Public License for more details.\n */\n\npackage com.naman14.timber.adapters;\n\nimport android.app.Activity;\nimport android.os.Handler;\nimport androidx.appcompat.app.AppCompatActivity;\nimport androidx.recyclerview.widget.RecyclerView;\nimport android.view.LayoutInflater;\nimport android.view.MenuItem;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.ImageView;\nimport android.widget.PopupMenu;\nimport android.widget.TextView;\n\nimport com.naman14.timber.MusicPlayer;\nimport com.naman14.timber.R;\nimport com.naman14.timber.dialogs.AddPlaylistDialog;\nimport com.naman14.timber.models.Song;\nimport com.naman14.timber.utils.NavigationUtils;\nimport com.naman14.timber.utils.TimberUtils;\n\nimport java.util.List;\n\npublic class AlbumSongsAdapter extends BaseSongAdapter<AlbumSongsAdapter.ItemHolder> {\n\n    private List<Song> arraylist;\n    private Activity mContext;\n    private long albumID;\n    private long[] songIDs;\n\n    public AlbumSongsAdapter(Activity context, List<Song> arraylist, long albumID) {\n        this.arraylist = arraylist;\n        this.mContext = context;\n        this.songIDs = getSongIds();\n        this.albumID = albumID;\n    }\n\n    @Override\n    public ItemHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {\n\n        View v = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.item_album_song, null);\n        ItemHolder ml = new ItemHolder(v);\n        return ml;\n\n\n    }\n\n    @Override\n    public void onBindViewHolder(ItemHolder itemHolder, int i) {\n\n        Song localItem = arraylist.get(i);\n\n        itemHolder.title.setText(localItem.title);\n        itemHolder.duration.setText(TimberUtils.makeShortTimeString(mContext, (localItem.duration) / 1000));\n        int tracknumber = localItem.trackNumber;\n        if (tracknumber == 0) {\n            itemHolder.trackNumber.setText(\"-\");\n        } else itemHolder.trackNumber.setText(String.valueOf(tracknumber));\n\n        setOnPopupMenuListener(itemHolder, i);\n\n\n    }\n\n    private void setOnPopupMenuListener(ItemHolder itemHolder, final int position) {\n\n        itemHolder.menu.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n\n                final PopupMenu menu = new PopupMenu(mContext, v);\n                menu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {\n                    @Override\n                    public boolean onMenuItemClick(MenuItem item) {\n                        switch (item.getItemId()) {\n                            case R.id.popup_song_play:\n                                MusicPlayer.playAll(mContext, songIDs, position, -1, TimberUtils.IdType.NA, false);\n                                break;\n                            case R.id.popup_song_play_next:\n                                long[] ids = new long[1];\n                                ids[0] = arraylist.get(position).id;\n                                MusicPlayer.playNext(mContext, ids, -1, TimberUtils.IdType.NA);\n                                break;\n                            case R.id.popup_song_goto_album:\n                                NavigationUtils.goToAlbum(mContext, arraylist.get(position).albumId);\n                                break;\n                            case R.id.popup_song_goto_artist:\n                                NavigationUtils.goToArtist(mContext, arraylist.get(position).artistId);\n                                break;\n                            case R.id.popup_song_addto_queue:\n                                long[] id = new long[1];\n                                id[0] = arraylist.get(position).id;\n                                MusicPlayer.addToQueue(mContext, id, -1, TimberUtils.IdType.NA);\n                                break;\n                            case R.id.popup_song_addto_playlist:\n                                AddPlaylistDialog.newInstance(arraylist.get(position)).show(((AppCompatActivity) mContext).getSupportFragmentManager(), \"ADD_PLAYLIST\");\n                                break;\n                            case R.id.popup_song_share:\n                                TimberUtils.shareTrack(mContext, arraylist.get(position).id);\n                                break;\n                            case R.id.popup_song_delete:\n                                long[] deleteIds = {arraylist.get(position).id};\n                                TimberUtils.showDeleteDialog(mContext,arraylist.get(position).title, deleteIds, AlbumSongsAdapter.this, position);\n                                break;\n                        }\n                        return false;\n                    }\n                });\n                menu.inflate(R.menu.popup_song);\n                menu.show();\n            }\n        });\n    }\n\n    @Override\n    public int getItemCount() {\n        return (null != arraylist ? arraylist.size() : 0);\n    }\n\n    public long[] getSongIds() {\n        long[] ret = new long[getItemCount()];\n        for (int i = 0; i < getItemCount(); i++) {\n            ret[i] = arraylist.get(i).id;\n        }\n\n        return ret;\n    }\n\n    @Override\n    public void updateDataSet(List<Song> arraylist) {\n        this.arraylist = arraylist;\n        this.songIDs = getSongIds();\n    }\n\n    @Override\n    public void removeSongAt(int i){\n        arraylist.remove(i);\n    }\n\n    public class ItemHolder extends RecyclerView.ViewHolder implements View.OnClickListener {\n        protected TextView title, duration, trackNumber;\n        protected ImageView menu;\n\n        public ItemHolder(View view) {\n            super(view);\n            this.title = (TextView) view.findViewById(R.id.song_title);\n            this.duration = (TextView) view.findViewById(R.id.song_duration);\n            this.trackNumber = (TextView) view.findViewById(R.id.trackNumber);\n            this.menu = (ImageView) view.findViewById(R.id.popup_menu);\n            view.setOnClickListener(this);\n        }\n\n        @Override\n        public void onClick(View v) {\n            Handler handler = new Handler();\n            handler.postDelayed(new Runnable() {\n                @Override\n                public void run() {\n                    playAll(mContext, songIDs, getAdapterPosition(), albumID,\n                            TimberUtils.IdType.Album, false,\n                            arraylist.get(getAdapterPosition()), true);\n                }\n            }, 100);\n\n        }\n\n    }\n\n}\n\n\n\n"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/adapters/ArtistAdapter.java",
    "content": "/*\n * Copyright (C) 2015 Naman Dwivedi\n *\n * Licensed under the GNU General Public License v3\n *\n * This is free software: you can redistribute it and/or modify it\n * under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;\n * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n * See the GNU General Public License for more details.\n */\n\npackage com.naman14.timber.adapters;\n\nimport android.app.Activity;\nimport android.graphics.Bitmap;\nimport android.graphics.Color;\nimport androidx.annotation.ColorInt;\nimport androidx.palette.graphics.Palette;\nimport androidx.recyclerview.widget.RecyclerView;\nimport android.util.Pair;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.ImageView;\nimport android.widget.TextView;\n\nimport com.afollestad.appthemeengine.Config;\nimport com.naman14.timber.R;\nimport com.naman14.timber.lastfmapi.LastFmClient;\nimport com.naman14.timber.lastfmapi.callbacks.ArtistInfoListener;\nimport com.naman14.timber.lastfmapi.models.ArtistQuery;\nimport com.naman14.timber.lastfmapi.models.LastfmArtist;\nimport com.naman14.timber.models.Artist;\nimport com.naman14.timber.utils.Helpers;\nimport com.naman14.timber.utils.NavigationUtils;\nimport com.naman14.timber.utils.PreferencesUtility;\nimport com.naman14.timber.utils.TimberUtils;\nimport com.naman14.timber.widgets.BubbleTextGetter;\nimport com.nostra13.universalimageloader.core.DisplayImageOptions;\nimport com.nostra13.universalimageloader.core.ImageLoader;\nimport com.nostra13.universalimageloader.core.assist.FailReason;\nimport com.nostra13.universalimageloader.core.display.FadeInBitmapDisplayer;\nimport com.nostra13.universalimageloader.core.listener.SimpleImageLoadingListener;\n\nimport java.util.List;\n\npublic class ArtistAdapter extends RecyclerView.Adapter<ArtistAdapter.ItemHolder> implements BubbleTextGetter {\n\n    private List<Artist> arraylist;\n    private Activity mContext;\n    private boolean isGrid;\n\n    public ArtistAdapter(Activity context, List<Artist> arraylist) {\n        this.arraylist = arraylist;\n        this.mContext = context;\n        this.isGrid = PreferencesUtility.getInstance(mContext).isArtistsInGrid();\n    }\n\n    public static int getOpaqueColor(@ColorInt int paramInt) {\n        return 0xFF000000 | paramInt;\n    }\n\n    @Override\n    public ItemHolder onCreateViewHolder(ViewGroup viewGroup, int i) {\n        if (isGrid) {\n            View v = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.item_artist_grid, null);\n            ItemHolder ml = new ItemHolder(v);\n            return ml;\n        } else {\n            View v = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.item_artist, null);\n            ItemHolder ml = new ItemHolder(v);\n            return ml;\n        }\n    }\n\n    @Override\n    public void onBindViewHolder(final ItemHolder itemHolder, int i) {\n        final Artist localItem = arraylist.get(i);\n\n        itemHolder.name.setText(localItem.name);\n        String albumNmber = TimberUtils.makeLabel(mContext, R.plurals.Nalbums, localItem.albumCount);\n        String songCount = TimberUtils.makeLabel(mContext, R.plurals.Nsongs, localItem.songCount);\n        itemHolder.albums.setText(TimberUtils.makeCombinedString(mContext, albumNmber, songCount));\n\n\n        LastFmClient.getInstance(mContext).getArtistInfo(new ArtistQuery(localItem.name), new ArtistInfoListener() {\n            @Override\n            public void artistInfoSucess(LastfmArtist artist) {\n                if (artist != null && artist.mArtwork != null) {\n                    if (isGrid) {\n                        ImageLoader.getInstance().displayImage(artist.mArtwork.get(2).mUrl, itemHolder.artistImage,\n                                new DisplayImageOptions.Builder().cacheInMemory(true)\n                                        .cacheOnDisk(true)\n                                        .showImageOnLoading(R.drawable.ic_empty_music2)\n                                        .resetViewBeforeLoading(true)\n                                        .displayer(new FadeInBitmapDisplayer(400))\n                                        .build(), new SimpleImageLoadingListener() {\n                                    @Override\n                                    public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {\n                                        if (isGrid && loadedImage != null) {\n                                            new Palette.Builder(loadedImage).generate(new Palette.PaletteAsyncListener() {\n                                                @Override\n                                                public void onGenerated(Palette palette) {\n                                                    int color = palette.getVibrantColor(Color.parseColor(\"#66000000\"));\n                                                    itemHolder.footer.setBackgroundColor(color);\n                                                    Palette.Swatch swatch = palette.getVibrantSwatch();\n                                                    int textColor;\n                                                    if (swatch != null) {\n                                                        textColor = getOpaqueColor(swatch.getTitleTextColor());\n                                                    } else textColor = Color.parseColor(\"#ffffff\");\n\n                                                    itemHolder.name.setTextColor(textColor);\n                                                    itemHolder.albums.setTextColor(textColor);\n                                                }\n                                            });\n                                        }\n\n                                    }\n\n                                    @Override\n                                    public void onLoadingFailed(String imageUri, View view, FailReason failReason) {\n                                        if (isGrid) {\n                                            itemHolder.footer.setBackgroundColor(0);\n                                            if (mContext != null) {\n                                                int textColorPrimary = Config.textColorPrimary(mContext, Helpers.getATEKey(mContext));\n                                                itemHolder.name.setTextColor(textColorPrimary);\n                                                itemHolder.albums.setTextColor(textColorPrimary);\n                                            }\n                                        }\n                                    }\n                                });\n                    } else {\n                        ImageLoader.getInstance().displayImage(artist.mArtwork.get(1).mUrl, itemHolder.artistImage,\n                                new DisplayImageOptions.Builder().cacheInMemory(true)\n                                        .cacheOnDisk(true)\n                                        .showImageOnLoading(R.drawable.ic_empty_music2)\n                                        .resetViewBeforeLoading(true)\n                                        .displayer(new FadeInBitmapDisplayer(400))\n                                        .build());\n                    }\n                }\n            }\n\n            @Override\n            public void artistInfoFailed() {\n\n            }\n        });\n\n        if (TimberUtils.isLollipop())\n            itemHolder.artistImage.setTransitionName(\"transition_artist_art\" + i);\n\n    }\n\n    @Override\n    public long getItemId(int position) {\n        return arraylist.get(position).id;\n    }\n\n    @Override\n    public int getItemCount() {\n        return (null != arraylist ? arraylist.size() : 0);\n    }\n\n    @Override\n    public String getTextToShowInBubble(final int pos) {\n        if (arraylist == null || arraylist.size() == 0)\n            return \"\";\n        return Character.toString(arraylist.get(pos).name.charAt(0));\n    }\n\n    public void updateDataSet(List<Artist> arrayList) {\n        this.arraylist = arrayList;\n    }\n\n    public class ItemHolder extends RecyclerView.ViewHolder implements View.OnClickListener {\n        protected TextView name, albums;\n        protected ImageView artistImage;\n        protected View footer;\n\n        public ItemHolder(View view) {\n            super(view);\n            this.name = (TextView) view.findViewById(R.id.artist_name);\n            this.albums = (TextView) view.findViewById(R.id.album_song_count);\n            this.artistImage = (ImageView) view.findViewById(R.id.artistImage);\n            this.footer = view.findViewById(R.id.footer);\n            view.setOnClickListener(this);\n        }\n\n        @Override\n        public void onClick(View v) {\n            NavigationUtils.navigateToArtist(mContext, arraylist.get(getAdapterPosition()).id,\n                    new Pair<View, String>(artistImage, \"transition_artist_art\" + getAdapterPosition()));\n        }\n\n    }\n}\n\n\n\n\n"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/adapters/ArtistAlbumAdapter.java",
    "content": "/*\n * Copyright (C) 2015 Naman Dwivedi\n *\n * Licensed under the GNU General Public License v3\n *\n * This is free software: you can redistribute it and/or modify it\n * under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;\n * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n * See the GNU General Public License for more details.\n */\n\npackage com.naman14.timber.adapters;\n\nimport android.app.Activity;\nimport androidx.cardview.widget.CardView;\nimport androidx.recyclerview.widget.RecyclerView;\nimport android.util.Pair;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.ImageView;\nimport android.widget.TextView;\n\nimport com.naman14.timber.R;\nimport com.naman14.timber.models.Album;\nimport com.naman14.timber.utils.ImageUtils;\nimport com.naman14.timber.utils.NavigationUtils;\nimport com.naman14.timber.utils.TimberUtils;\n\nimport java.util.List;\n\npublic class ArtistAlbumAdapter extends RecyclerView.Adapter<ArtistAlbumAdapter.ItemHolder> {\n\n    private List<Album> arraylist;\n    private Activity mContext;\n\n    public ArtistAlbumAdapter(Activity context, List<Album> arraylist) {\n        this.arraylist = arraylist;\n        this.mContext = context;\n\n    }\n\n    @Override\n    public ItemHolder onCreateViewHolder(ViewGroup viewGroup, int i) {\n        View v = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.item_artist_album, null);\n        ItemHolder ml = new ItemHolder(v);\n        return ml;\n    }\n\n    @Override\n    public void onBindViewHolder(ItemHolder itemHolder, int i) {\n\n        Album localItem = arraylist.get(i);\n\n        itemHolder.title.setText(localItem.title);\n        String songCount = TimberUtils.makeLabel(mContext, R.plurals.Nsongs, localItem.songCount);\n        itemHolder.details.setText(songCount);\n\n        ImageUtils.loadAlbumArtIntoView(localItem.id, itemHolder.albumArt);\n\n        if (TimberUtils.isLollipop())\n            itemHolder.albumArt.setTransitionName(\"transition_album_art\" + i);\n\n    }\n\n    @Override\n    public int getItemCount() {\n        return (null != arraylist ? arraylist.size() : 0);\n    }\n\n\n    public class ItemHolder extends RecyclerView.ViewHolder implements View.OnClickListener {\n        protected TextView title, details;\n        protected ImageView albumArt;\n        protected CardView rootView;\n\n        public ItemHolder(View view) {\n            super(view);\n            this.rootView = (CardView) view.findViewById(R.id.root_view);\n            this.title = (TextView) view.findViewById(R.id.album_title);\n            this.details = (TextView) view.findViewById(R.id.album_details);\n            this.albumArt = (ImageView) view.findViewById(R.id.album_art);\n            view.setOnClickListener(this);\n        }\n\n        @Override\n        public void onClick(View v) {\n            NavigationUtils.navigateToAlbum(mContext, arraylist.get(getAdapterPosition()).id,\n                    new Pair<View, String>(albumArt, \"transition_album_art\" + getAdapterPosition()));\n        }\n\n    }\n}\n\n\n\n\n"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/adapters/ArtistSongAdapter.java",
    "content": "/*\n * Copyright (C) 2015 Naman Dwivedi\n *\n * Licensed under the GNU General Public License v3\n *\n * This is free software: you can redistribute it and/or modify it\n * under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;\n * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n * See the GNU General Public License for more details.\n */\n\npackage com.naman14.timber.adapters;\n\nimport android.app.Activity;\nimport android.graphics.Rect;\nimport android.os.Handler;\nimport androidx.appcompat.app.AppCompatActivity;\nimport androidx.recyclerview.widget.LinearLayoutManager;\nimport androidx.recyclerview.widget.RecyclerView;\nimport android.view.LayoutInflater;\nimport android.view.MenuItem;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.ImageView;\nimport android.widget.PopupMenu;\nimport android.widget.TextView;\n\nimport com.naman14.timber.MusicPlayer;\nimport com.naman14.timber.R;\nimport com.naman14.timber.dataloaders.ArtistAlbumLoader;\nimport com.naman14.timber.dialogs.AddPlaylistDialog;\nimport com.naman14.timber.models.Song;\nimport com.naman14.timber.utils.NavigationUtils;\nimport com.naman14.timber.utils.TimberUtils;\nimport com.nostra13.universalimageloader.core.DisplayImageOptions;\nimport com.nostra13.universalimageloader.core.ImageLoader;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\npublic class ArtistSongAdapter extends BaseSongAdapter<ArtistSongAdapter.ItemHolder> {\n\n    private List<Song> arraylist;\n    private Activity mContext;\n    private long artistID;\n    private long[] songIDs;\n\n    public ArtistSongAdapter(Activity context, List<Song> arraylist, long artistID) {\n        this.arraylist = arraylist;\n        this.mContext = context;\n        this.artistID = artistID;\n        this.songIDs = getSongIds();\n    }\n\n    @Override\n    public ItemHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {\n        if (viewType == 0) {\n            View v0 = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.artist_detail_albums_header, null);\n            ItemHolder ml = new ItemHolder(v0);\n            return ml;\n        } else {\n            View v2 = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.item_artist_song, null);\n            ItemHolder ml = new ItemHolder(v2);\n            return ml;\n        }\n    }\n\n    @Override\n    public void onBindViewHolder(ItemHolder itemHolder, int i) {\n\n        if (getItemViewType(i) == 0) {\n            //nothing\n            setUpAlbums(itemHolder.albumsRecyclerView);\n        } else {\n            Song localItem = arraylist.get(i);\n            itemHolder.title.setText(localItem.title);\n            itemHolder.album.setText(localItem.albumName);\n\n            ImageLoader.getInstance().displayImage(TimberUtils.getAlbumArtUri(localItem.albumId).toString(),\n                    itemHolder.albumArt, new DisplayImageOptions.Builder()\n                            .cacheInMemory(true).showImageOnLoading(R.drawable.ic_empty_music2).resetViewBeforeLoading(true).build());\n            setOnPopupMenuListener(itemHolder, i - 1);\n        }\n\n    }\n\n    @Override\n    public void onViewRecycled(ItemHolder itemHolder) {\n\n        if (itemHolder.getItemViewType() == 0)\n            clearExtraSpacingBetweenCards(itemHolder.albumsRecyclerView);\n\n    }\n\n    @Override\n    public int getItemCount() {\n        return (null != arraylist ? arraylist.size() : 0);\n    }\n\n    private void setOnPopupMenuListener(ItemHolder itemHolder, final int position) {\n\n        itemHolder.menu.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n\n                final PopupMenu menu = new PopupMenu(mContext, v);\n                menu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {\n                    @Override\n                    public boolean onMenuItemClick(MenuItem item) {\n                        switch (item.getItemId()) {\n                            case R.id.popup_song_play:\n                                MusicPlayer.playAll(mContext, songIDs, position + 1, -1, TimberUtils.IdType.NA, false);\n                                break;\n                            case R.id.popup_song_play_next:\n                                long[] ids = new long[1];\n                                ids[0] = arraylist.get(position + 1).id;\n                                MusicPlayer.playNext(mContext, ids, -1, TimberUtils.IdType.NA);\n                                break;\n                            case R.id.popup_song_goto_album:\n                                NavigationUtils.goToAlbum(mContext, arraylist.get(position + 1).albumId);\n                                break;\n                            case R.id.popup_song_goto_artist:\n                                NavigationUtils.goToArtist(mContext, arraylist.get(position + 1).artistId);\n                                break;\n                            case R.id.popup_song_addto_queue:\n                                long[] id = new long[1];\n                                id[0] = arraylist.get(position + 1).id;\n                                MusicPlayer.addToQueue(mContext, id, -1, TimberUtils.IdType.NA);\n                                break;\n                            case R.id.popup_song_addto_playlist:\n                                AddPlaylistDialog.newInstance(arraylist.get(position + 1)).show(((AppCompatActivity) mContext).getSupportFragmentManager(), \"ADD_PLAYLIST\");\n                                break;\n                            case R.id.popup_song_share:\n                                TimberUtils.shareTrack(mContext, arraylist.get(position + 1).id);\n                                break;\n                            case R.id.popup_song_delete:\n                                long[] deleteIds = {arraylist.get(position + 1).id};\n                                TimberUtils.showDeleteDialog(mContext,arraylist.get(position + 1).title, deleteIds, ArtistSongAdapter.this, position + 1);\n                                break;\n                        }\n                        return false;\n                    }\n                });\n                menu.inflate(R.menu.popup_song);\n                menu.show();\n            }\n        });\n    }\n\n    private void setUpAlbums(RecyclerView albumsRecyclerview) {\n\n        albumsRecyclerview.setLayoutManager(new LinearLayoutManager(mContext, LinearLayoutManager.HORIZONTAL, false));\n        albumsRecyclerview.setHasFixedSize(true);\n\n        //to add spacing between cards\n        int spacingInPixels = mContext.getResources().getDimensionPixelSize(R.dimen.spacing_card);\n        albumsRecyclerview.addItemDecoration(new SpacesItemDecoration(spacingInPixels));\n        albumsRecyclerview.setNestedScrollingEnabled(false);\n\n\n        ArtistAlbumAdapter mAlbumAdapter = new ArtistAlbumAdapter(mContext, ArtistAlbumLoader.getAlbumsForArtist(mContext, artistID));\n        albumsRecyclerview.setAdapter(mAlbumAdapter);\n    }\n\n    private void clearExtraSpacingBetweenCards(RecyclerView albumsRecyclerview) {\n        //to clear any extra spacing between cards\n        int spacingInPixelstoClear = -(mContext.getResources().getDimensionPixelSize(R.dimen.spacing_card));\n        albumsRecyclerview.addItemDecoration(new SpacesItemDecoration(spacingInPixelstoClear));\n\n    }\n\n    public long[] getSongIds() {\n        List<Song> actualArraylist = new ArrayList<Song>(arraylist);\n        //actualArraylist.remove(0);\n        long[] ret = new long[actualArraylist.size()];\n        for (int i = 0; i < actualArraylist.size(); i++) {\n            ret[i] = actualArraylist.get(i).id;\n        }\n        return ret;\n    }\n\n    @Override\n    public void removeSongAt(int i){\n        arraylist.remove(i);\n        updateDataSet(arraylist);\n    }\n\n    @Override\n    public void updateDataSet(List<Song> arraylist) {\n        this.arraylist = arraylist;\n        this.songIDs = getSongIds();\n    }\n\n    @Override\n    public int getItemViewType(int position) {\n        int viewType;\n        if (position == 0) {\n            viewType = 0;\n        } else viewType = 1;\n        return viewType;\n    }\n\n    public class ItemHolder extends RecyclerView.ViewHolder implements View.OnClickListener {\n        protected TextView title, album;\n        protected ImageView albumArt, menu;\n        protected RecyclerView albumsRecyclerView;\n\n        public ItemHolder(View view) {\n            super(view);\n\n            this.albumsRecyclerView = (RecyclerView) view.findViewById(R.id.recycler_view_album);\n\n            this.title = (TextView) view.findViewById(R.id.song_title);\n            this.album = (TextView) view.findViewById(R.id.song_album);\n            this.albumArt = (ImageView) view.findViewById(R.id.albumArt);\n            this.menu = (ImageView) view.findViewById(R.id.popup_menu);\n\n\n            view.setOnClickListener(this);\n        }\n\n        @Override\n        public void onClick(View v) {\n            Handler handler = new Handler();\n            handler.postDelayed(new Runnable() {\n                @Override\n                public void run() {\n                    playAll(mContext, songIDs, getAdapterPosition(), artistID,\n                            TimberUtils.IdType.Artist, false,\n                            arraylist.get(getAdapterPosition()), true);\n                }\n            }, 100);\n\n        }\n\n    }\n\n    public class SpacesItemDecoration extends RecyclerView.ItemDecoration {\n        private int space;\n\n        public SpacesItemDecoration(int space) {\n            this.space = space;\n        }\n\n        @Override\n        public void getItemOffsets(Rect outRect, View view,\n                                   RecyclerView parent, RecyclerView.State state) {\n\n            //the padding from left\n            outRect.left = space;\n\n\n        }\n    }\n}\n\n\n\n\n"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/adapters/BaseQueueAdapter.java",
    "content": "/*\n * Copyright (C) 2015 Naman Dwivedi\n *\n * Licensed under the GNU General Public License v3\n *\n * This is free software: you can redistribute it and/or modify it\n * under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;\n * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n * See the GNU General Public License for more details.\n */\n\npackage com.naman14.timber.adapters;\n\nimport android.os.Handler;\nimport androidx.appcompat.app.AppCompatActivity;\nimport androidx.recyclerview.widget.RecyclerView;\nimport android.view.LayoutInflater;\nimport android.view.MenuItem;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.ImageView;\nimport android.widget.PopupMenu;\nimport android.widget.TextView;\n\nimport com.afollestad.appthemeengine.Config;\nimport com.naman14.timber.MusicPlayer;\nimport com.naman14.timber.R;\nimport com.naman14.timber.dialogs.AddPlaylistDialog;\nimport com.naman14.timber.models.Song;\nimport com.naman14.timber.utils.Helpers;\nimport com.naman14.timber.utils.NavigationUtils;\nimport com.naman14.timber.utils.TimberUtils;\nimport com.naman14.timber.widgets.MusicVisualizer;\nimport com.nostra13.universalimageloader.core.DisplayImageOptions;\nimport com.nostra13.universalimageloader.core.ImageLoader;\n\nimport java.util.List;\n\npublic class BaseQueueAdapter extends RecyclerView.Adapter<BaseQueueAdapter.ItemHolder> {\n\n    public static int currentlyPlayingPosition;\n    private List<Song> arraylist;\n    private AppCompatActivity mContext;\n    private String ateKey;\n\n    public BaseQueueAdapter(AppCompatActivity context, List<Song> arraylist) {\n        this.arraylist = arraylist;\n        this.mContext = context;\n        currentlyPlayingPosition = MusicPlayer.getQueuePosition();\n        this.ateKey = Helpers.getATEKey(context);\n    }\n\n    @Override\n    public ItemHolder onCreateViewHolder(ViewGroup viewGroup, int i) {\n        View v = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.item_song_timber1, null);\n        ItemHolder ml = new ItemHolder(v);\n        return ml;\n    }\n\n    @Override\n    public void onBindViewHolder(ItemHolder itemHolder, int i) {\n        Song localItem = arraylist.get(i);\n\n        itemHolder.title.setText(localItem.title);\n        itemHolder.artist.setText(localItem.artistName);\n\n        if (MusicPlayer.getCurrentAudioId() == localItem.id) {\n            itemHolder.title.setTextColor(Config.accentColor(mContext, ateKey));\n            if (MusicPlayer.isPlaying()) {\n                itemHolder.visualizer.setColor(Config.accentColor(mContext, ateKey));\n                itemHolder.visualizer.setVisibility(View.VISIBLE);\n            } else {\n                itemHolder.visualizer.setVisibility(View.GONE);\n            }\n        } else {\n            itemHolder.title.setTextColor(Config.textColorPrimary(mContext, ateKey));\n            itemHolder.visualizer.setVisibility(View.GONE);\n        }\n        ImageLoader.getInstance().displayImage(TimberUtils.getAlbumArtUri(localItem.albumId).toString(),\n                itemHolder.albumArt, new DisplayImageOptions.Builder().cacheInMemory(true)\n                        .showImageOnLoading(R.drawable.ic_empty_music2).resetViewBeforeLoading(true).build());\n        setOnPopupMenuListener(itemHolder, i);\n    }\n\n    @Override\n    public int getItemCount() {\n        return (null != arraylist ? arraylist.size() : 0);\n    }\n\n    private void setOnPopupMenuListener(ItemHolder itemHolder, final int position) {\n\n        itemHolder.popupMenu.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n\n                final PopupMenu menu = new PopupMenu(mContext, v);\n                menu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {\n                    @Override\n                    public boolean onMenuItemClick(MenuItem item) {\n                        switch (item.getItemId()) {\n                            case R.id.popup_song_play:\n                                MusicPlayer.playAll(mContext, getSongIds(), position, -1, TimberUtils.IdType.NA, false);\n                                break;\n                            case R.id.popup_song_play_next:\n                                long[] ids = new long[1];\n                                ids[0] = arraylist.get(position).id;\n                                MusicPlayer.playNext(mContext, ids, -1, TimberUtils.IdType.NA);\n                                break;\n                            case R.id.popup_song_goto_album:\n                                NavigationUtils.goToAlbum(mContext, arraylist.get(position).albumId);\n                                break;\n                            case R.id.popup_song_goto_artist:\n                                NavigationUtils.goToArtist(mContext, arraylist.get(position).artistId);\n                                break;\n                            case R.id.popup_song_addto_queue:\n                                long[] id = new long[1];\n                                id[0] = arraylist.get(position).id;\n                                MusicPlayer.addToQueue(mContext, id, -1, TimberUtils.IdType.NA);\n                                break;\n                            case R.id.popup_song_addto_playlist:\n                                AddPlaylistDialog.newInstance(arraylist.get(position)).show(mContext.getSupportFragmentManager(), \"ADD_PLAYLIST\");\n                                break;\n                            case R.id.popup_song_share:\n                                TimberUtils.shareTrack(mContext, arraylist.get(position).id);\n                                break;\n                            case R.id.popup_song_delete:\n                                long[] deleteIds = {arraylist.get(position).id};\n                                TimberUtils.showDeleteDialog(mContext,arraylist.get(position).title, deleteIds, BaseQueueAdapter.this, position);\n                                break;\n                        }\n                        return false;\n                    }\n                });\n                menu.inflate(R.menu.popup_song);\n                menu.show();\n            }\n        });\n    }\n\n    public long[] getSongIds() {\n        long[] ret = new long[getItemCount()];\n        for (int i = 0; i < getItemCount(); i++) {\n            ret[i] = arraylist.get(i).id;\n        }\n\n        return ret;\n    }\n\n    public void removeSongAt(int i){\n        arraylist.remove(i);\n    }\n\n    public class ItemHolder extends RecyclerView.ViewHolder implements View.OnClickListener {\n        protected TextView title, artist;\n        protected ImageView albumArt, popupMenu;\n        private MusicVisualizer visualizer;\n\n        public ItemHolder(View view) {\n            super(view);\n            this.title = (TextView) view.findViewById(R.id.song_title);\n            this.artist = (TextView) view.findViewById(R.id.song_artist);\n            this.albumArt = (ImageView) view.findViewById(R.id.albumArt);\n            this.popupMenu = (ImageView) view.findViewById(R.id.popup_menu);\n            visualizer = (MusicVisualizer) view.findViewById(R.id.visualizer);\n            view.setOnClickListener(this);\n        }\n\n        @Override\n        public void onClick(View v) {\n            final Handler handler = new Handler();\n            handler.postDelayed(new Runnable() {\n                @Override\n                public void run() {\n                    MusicPlayer.setQueuePosition(getAdapterPosition());\n                    Handler handler1 = new Handler();\n                    handler1.postDelayed(new Runnable() {\n                        @Override\n                        public void run() {\n                            notifyItemChanged(currentlyPlayingPosition);\n                            notifyItemChanged(getAdapterPosition());\n                        }\n                    }, 50);\n                }\n            }, 100);\n\n        }\n\n    }\n\n}\n\n\n\n"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/adapters/BaseSongAdapter.java",
    "content": "package com.naman14.timber.adapters;\n\nimport android.app.Activity;\n\nimport androidx.recyclerview.widget.RecyclerView;\n\nimport android.view.View;\nimport android.view.ViewGroup;\n\nimport com.google.android.gms.cast.framework.CastSession;\nimport com.naman14.timber.MusicPlayer;\nimport com.naman14.timber.activities.BaseActivity;\nimport com.naman14.timber.cast.TimberCastHelper;\nimport com.naman14.timber.models.Song;\nimport com.naman14.timber.utils.NavigationUtils;\nimport com.naman14.timber.utils.TimberUtils;\n\nimport java.util.List;\n\n/**\n * Created by naman on 7/12/17.\n */\n\npublic class BaseSongAdapter<V extends RecyclerView.ViewHolder> extends RecyclerView.Adapter<V> {\n\n    @Override\n    public V onCreateViewHolder(ViewGroup parent, int viewType) {\n        return null;\n    }\n\n    @Override\n    public void onBindViewHolder(V holder, int position) {\n\n    }\n\n    @Override\n    public int getItemCount() {\n        return 0;\n    }\n\n    @Override\n    public int getItemViewType(int position) {\n        return super.getItemViewType(position);\n    }\n\n    public class ItemHolder extends RecyclerView.ViewHolder {\n\n        public ItemHolder(View view) {\n            super(view);\n        }\n\n    }\n\n    public void playAll(final Activity context, final long[] list, int position,\n                        final long sourceId, final TimberUtils.IdType sourceType,\n                        final boolean forceShuffle, final Song currentSong, boolean navigateNowPlaying) {\n\n        if (context instanceof BaseActivity) {\n            CastSession castSession = ((BaseActivity) context).getCastSession();\n            if (castSession != null) {\n                navigateNowPlaying = false;\n                TimberCastHelper.startCasting(castSession, currentSong);\n            } else {\n                MusicPlayer.playAll(context, list, position, -1, TimberUtils.IdType.NA, false);\n            }\n        } else {\n            MusicPlayer.playAll(context, list, position, -1, TimberUtils.IdType.NA, false);\n        }\n\n        if (navigateNowPlaying) {\n            NavigationUtils.navigateToNowplaying(context, true);\n        }\n\n\n    }\n    public void removeSongAt(int i){}\n    public void updateDataSet(List<Song> arraylist) {}\n\n}\n"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/adapters/FolderAdapter.java",
    "content": "package com.naman14.timber.adapters;\n\nimport android.app.Activity;\nimport android.graphics.Color;\nimport android.graphics.ColorFilter;\nimport android.graphics.PorterDuff;\nimport android.graphics.PorterDuffColorFilter;\nimport android.graphics.drawable.Drawable;\nimport android.os.AsyncTask;\nimport android.os.Handler;\nimport androidx.annotation.NonNull;\nimport androidx.core.content.ContextCompat;\nimport androidx.recyclerview.widget.RecyclerView;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.ImageView;\nimport android.widget.TextView;\n\nimport com.naman14.timber.R;\nimport com.naman14.timber.dataloaders.FolderLoader;\nimport com.naman14.timber.dataloaders.SongLoader;\nimport com.naman14.timber.models.Song;\nimport com.naman14.timber.utils.PreferencesUtility;\nimport com.naman14.timber.utils.TimberUtils;\nimport com.naman14.timber.widgets.BubbleTextGetter;\nimport com.nostra13.universalimageloader.core.DisplayImageOptions;\nimport com.nostra13.universalimageloader.core.ImageLoader;\n\nimport java.io.File;\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * Created by nv95 on 10.11.16.\n */\n\npublic class FolderAdapter extends BaseSongAdapter<FolderAdapter.ItemHolder> implements BubbleTextGetter {\n\n    @NonNull\n    private List<File> mFileSet;\n    private List<Song> mSongs;\n    private File mRoot;\n    private Activity mContext;\n    private final Drawable[] mIcons;\n    private boolean mBusy = false;\n\n\n    public FolderAdapter(Activity context, File root) {\n        mContext = context;\n        mIcons = new Drawable[]{\n                ContextCompat.getDrawable(context, R.drawable.ic_folder_open_black_24dp),\n                ContextCompat.getDrawable(context, R.drawable.ic_folder_parent_dark),\n                ContextCompat.getDrawable(context, R.drawable.ic_file_music_dark),\n                ContextCompat.getDrawable(context, R.drawable.ic_timer_wait)\n        };\n        mSongs = new ArrayList<>();\n        updateDataSet(root);\n    }\n\n    public void applyTheme(boolean dark) {\n        ColorFilter cf = new PorterDuffColorFilter(Color.WHITE, PorterDuff.Mode.SRC_ATOP);\n        for (Drawable d : mIcons) {\n            if (dark) {\n                d.setColorFilter(cf);\n            } else {\n                d.clearColorFilter();\n            }\n        }\n    }\n\n    @Override\n    public FolderAdapter.ItemHolder onCreateViewHolder(ViewGroup viewGroup, int i) {\n        View v = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.item_folder_list, viewGroup, false);\n        return new ItemHolder(v);\n    }\n\n    @Override\n    public void onBindViewHolder(final FolderAdapter.ItemHolder itemHolder, int i) {\n        File localItem = mFileSet.get(i);\n        Song song = mSongs.get(i);\n        itemHolder.title.setText(localItem.getName());\n        if (localItem.isDirectory()) {\n            itemHolder.albumArt.setImageDrawable(\"..\".equals(localItem.getName()) ? mIcons[1] : mIcons[0]);\n        } else {\n            ImageLoader.getInstance().displayImage(TimberUtils.getAlbumArtUri(song.albumId).toString(),\n                    itemHolder.albumArt,\n                    new DisplayImageOptions.Builder().\n                            cacheInMemory(true).showImageOnFail(mIcons[2])\n                            .resetViewBeforeLoading(true).build());\n        }\n    }\n\n    @Override\n    public int getItemCount() {\n        return mFileSet.size();\n    }\n\n    @Deprecated\n    public void updateDataSet(File newRoot) {\n        if (mBusy) {\n            return;\n        }\n        if (\"..\".equals(newRoot.getName())) {\n            goUp();\n            return;\n        }\n        mRoot = newRoot;\n        mFileSet = FolderLoader.getMediaFiles(newRoot, true);\n        getSongsForFiles(mFileSet);\n    }\n\n    @Deprecated\n    public boolean goUp() {\n        if (mRoot == null || mBusy) {\n            return false;\n        }\n        File parent = mRoot.getParentFile();\n        if (parent != null && parent.canRead()) {\n            updateDataSet(parent);\n            return true;\n        } else {\n            return false;\n        }\n    }\n\n    public boolean goUpAsync() {\n        if (mRoot == null || mBusy) {\n            return false;\n        }\n        File parent = mRoot.getParentFile();\n        if (parent != null && parent.canRead()) {\n            return updateDataSetAsync(parent);\n        } else {\n            return false;\n        }\n    }\n\n    public boolean updateDataSetAsync(File newRoot) {\n        if (mBusy) {\n            return false;\n        }\n        if (\"..\".equals(newRoot.getName())) {\n            goUpAsync();\n            return false;\n        }\n        mRoot = newRoot;\n        new NavigateTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, mRoot);\n        return true;\n    }\n\n    @Override\n    public String getTextToShowInBubble(int pos) {\n        if (mBusy || mFileSet.size() == 0)\n            return \"\";\n        try {\n            File f = mFileSet.get(pos);\n            if (f.isDirectory()) {\n                return String.valueOf(f.getName().charAt(0));\n            } else {\n                return Character.toString(f.getName().charAt(0));\n            }\n        } catch (Exception e) {\n            return \"\";\n        }\n    }\n\n    private void getSongsForFiles(List<File> files) {\n        mSongs.clear();\n        for (File file : files) {\n            mSongs.add(SongLoader.getSongFromPath(file.getAbsolutePath(), mContext));\n        }\n    }\n\n\n    private class NavigateTask extends AsyncTask<File, Void, List<File>> {\n\n        @Override\n        protected void onPreExecute() {\n            super.onPreExecute();\n            mBusy = true;\n        }\n\n        @Override\n        protected List<File> doInBackground(File... params) {\n            List<File> files = FolderLoader.getMediaFiles(params[0], true);\n            getSongsForFiles(files);\n            return files;\n        }\n\n        @Override\n        protected void onPostExecute(List<File> files) {\n            super.onPostExecute(files);\n            mFileSet = files;\n            notifyDataSetChanged();\n            mBusy = false;\n            PreferencesUtility.getInstance(mContext).storeLastFolder(mRoot.getPath());\n        }\n    }\n\n    public class ItemHolder extends RecyclerView.ViewHolder implements View.OnClickListener {\n\n        protected TextView title;\n        protected ImageView albumArt;\n\n        public ItemHolder(View view) {\n            super(view);\n            this.title = (TextView) view.findViewById(R.id.folder_title);\n            this.albumArt = (ImageView) view.findViewById(R.id.album_art);\n            view.setOnClickListener(this);\n        }\n\n        @Override\n        public void onClick(View v) {\n            if (mBusy) {\n                return;\n            }\n            final File f = mFileSet.get(getAdapterPosition());\n\n            if (f.isDirectory() && updateDataSetAsync(f)) {\n                albumArt.setImageDrawable(mIcons[3]);\n            } else if (f.isFile()) {\n\n                final Handler handler = new Handler();\n                handler.postDelayed(new Runnable() {\n                    @Override\n                    public void run() {\n                        int current = -1;\n                        long songId = SongLoader.getSongFromPath(mFileSet.get(getAdapterPosition()).getAbsolutePath(),mContext).id;\n                        int count = 0;\n                        for (Song song : mSongs) {\n                            if (song.id != -1) {\n                                count++;\n                            }\n                        }\n                        long[] ret = new long[count];\n                        int j = 0;\n                        for (int i = 0; i < getItemCount(); i++) {\n                            if (mSongs.get(i).id != -1) {\n                                ret[j] = mSongs.get(i).id;\n                                if (mSongs.get(i).id == songId) {\n                                    current = j;\n                                }\n                                j++;\n                            }\n                        }\n                        playAll(mContext, ret, current, -1, TimberUtils.IdType.NA,\n                                false, mSongs.get(getAdapterPosition()), false);\n                    }\n                }, 100);\n\n\n            }\n        }\n\n    }\n\n\n}"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/adapters/PlayingQueueAdapter.java",
    "content": "/*\n * Copyright (C) 2015 Naman Dwivedi\n *\n * Licensed under the GNU General Public License v3\n *\n * This is free software: you can redistribute it and/or modify it\n * under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;\n * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n * See the GNU General Public License for more details.\n */\n\npackage com.naman14.timber.adapters;\n\nimport android.app.Activity;\nimport android.os.Handler;\nimport androidx.appcompat.app.AppCompatActivity;\nimport androidx.recyclerview.widget.RecyclerView;\nimport android.util.Log;\nimport android.view.LayoutInflater;\nimport android.view.MenuItem;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.ImageView;\nimport android.widget.PopupMenu;\nimport android.widget.TextView;\n\nimport com.afollestad.appthemeengine.Config;\nimport com.naman14.timber.MusicPlayer;\nimport com.naman14.timber.R;\nimport com.naman14.timber.dialogs.AddPlaylistDialog;\nimport com.naman14.timber.models.Song;\nimport com.naman14.timber.utils.Helpers;\nimport com.naman14.timber.utils.NavigationUtils;\nimport com.naman14.timber.utils.TimberUtils;\nimport com.naman14.timber.widgets.MusicVisualizer;\nimport com.nostra13.universalimageloader.core.DisplayImageOptions;\nimport com.nostra13.universalimageloader.core.ImageLoader;\n\nimport java.util.List;\n\npublic class PlayingQueueAdapter extends RecyclerView.Adapter<PlayingQueueAdapter.ItemHolder> {\n    private static final String TAG = \"PlayingQueueAdapter\";\n\n    public int currentlyPlayingPosition;\n    private List<Song> arraylist;\n    private Activity mContext;\n    private String ateKey;\n\n    public PlayingQueueAdapter(Activity context, List<Song> arraylist) {\n        this.arraylist = arraylist;\n        this.mContext = context;\n        this.currentlyPlayingPosition = MusicPlayer.getQueuePosition();\n        this.ateKey = Helpers.getATEKey(context);\n    }\n\n    @Override\n    public ItemHolder onCreateViewHolder(ViewGroup viewGroup, int i) {\n        View v = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.item_playing_queue, null);\n        ItemHolder ml = new ItemHolder(v);\n        return ml;\n    }\n\n    @Override\n    public void onBindViewHolder(ItemHolder itemHolder, int i) {\n        Song localItem = arraylist.get(i);\n\n        itemHolder.title.setText(localItem.title);\n        itemHolder.artist.setText(localItem.artistName);\n\n        if (MusicPlayer.getCurrentAudioId() == localItem.id) {\n            itemHolder.title.setTextColor(Config.accentColor(mContext, ateKey));\n            if (MusicPlayer.isPlaying()) {\n                itemHolder.visualizer.setColor(Config.accentColor(mContext, ateKey));\n                itemHolder.visualizer.setVisibility(View.VISIBLE);\n            } else {\n                itemHolder.visualizer.setVisibility(View.GONE);\n            }\n        } else {\n            itemHolder.title.setTextColor(Config.textColorPrimary(mContext, ateKey));\n            itemHolder.visualizer.setVisibility(View.GONE);\n        }\n        ImageLoader.getInstance().displayImage(TimberUtils.getAlbumArtUri(localItem.albumId).toString(),\n                itemHolder.albumArt, new DisplayImageOptions.Builder().cacheInMemory(true)\n                        .showImageOnLoading(R.drawable.ic_empty_music2).resetViewBeforeLoading(true).build());\n        setOnPopupMenuListener(itemHolder, i);\n    }\n\n    private void setOnPopupMenuListener(ItemHolder itemHolder, final int position) {\n\n        itemHolder.menu.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n\n                final PopupMenu menu = new PopupMenu(mContext, v);\n                menu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {\n                    @Override\n                    public boolean onMenuItemClick(MenuItem item) {\n                        switch (item.getItemId()) {\n                            case R.id.popup_song_remove_queue:\n                                Log.v(TAG,\"Removing \" + position);\n                                MusicPlayer.removeTrackAtPosition(getSongAt(position).id, position);\n                                removeSongAt(position);\n                                notifyItemRemoved(position);\n                                break;\n                            case R.id.popup_song_play:\n                                MusicPlayer.playAll(mContext, getSongIds(), position, -1, TimberUtils.IdType.NA, false);\n                                break;\n                            case R.id.popup_song_goto_album:\n                                NavigationUtils.goToAlbum(mContext, arraylist.get(position).albumId);\n                                break;\n                            case R.id.popup_song_goto_artist:\n                                NavigationUtils.goToArtist(mContext, arraylist.get(position).artistId);\n                                break;\n                            case R.id.popup_song_addto_playlist:\n                                AddPlaylistDialog.newInstance(arraylist.get(position)).show(((AppCompatActivity) mContext).getSupportFragmentManager(), \"ADD_PLAYLIST\");\n                                break;\n                        }\n                        return false;\n                    }\n                });\n                menu.inflate(R.menu.popup_playing_queue);\n                menu.show();\n            }\n        });\n    }\n\n    @Override\n    public int getItemCount() {\n        return (null != arraylist ? arraylist.size() : 0);\n    }\n\n    public long[] getSongIds() {\n        long[] ret = new long[getItemCount()];\n        for (int i = 0; i < getItemCount(); i++) {\n            ret[i] = arraylist.get(i).id;\n        }\n\n        return ret;\n    }\n\n    public Song getSongAt(int i) {\n        return arraylist.get(i);\n    }\n\n    public void addSongTo(int i, Song song) {\n        arraylist.add(i, song);\n    }\n\n    public void removeSongAt(int i) {\n        arraylist.remove(i);\n    }\n\n    public class ItemHolder extends RecyclerView.ViewHolder implements View.OnClickListener {\n        protected TextView title, artist;\n        protected ImageView albumArt, reorder, menu;\n        private MusicVisualizer visualizer;\n\n        public ItemHolder(View view) {\n            super(view);\n            this.title = (TextView) view.findViewById(R.id.song_title);\n            this.artist = (TextView) view.findViewById(R.id.song_artist);\n            this.albumArt = (ImageView) view.findViewById(R.id.albumArt);\n            this.menu = (ImageView) view.findViewById(R.id.popup_menu);\n            this.reorder = (ImageView) view.findViewById(R.id.reorder);\n            visualizer = (MusicVisualizer) view.findViewById(R.id.visualizer);\n            view.setOnClickListener(this);\n        }\n\n        @Override\n        public void onClick(View v) {\n            final Handler handler = new Handler();\n            handler.postDelayed(new Runnable() {\n                @Override\n                public void run() {\n                    MusicPlayer.setQueuePosition(getAdapterPosition());\n                    Handler handler1 = new Handler();\n                    handler1.postDelayed(new Runnable() {\n                        @Override\n                        public void run() {\n                            notifyItemChanged(currentlyPlayingPosition);\n                            notifyItemChanged(getAdapterPosition());\n                        }\n                    }, 50);\n                }\n            }, 100);\n\n        }\n\n    }\n\n}\n\n\n\n\n"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/adapters/PlaylistAdapter.java",
    "content": "package com.naman14.timber.adapters;\n\nimport android.app.Activity;\nimport android.graphics.Bitmap;\nimport androidx.palette.graphics.Palette;\nimport androidx.recyclerview.widget.RecyclerView;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.ImageView;\nimport android.widget.TextView;\n\nimport com.afollestad.appthemeengine.Config;\nimport com.naman14.timber.R;\nimport com.naman14.timber.dataloaders.LastAddedLoader;\nimport com.naman14.timber.dataloaders.PlaylistSongLoader;\nimport com.naman14.timber.dataloaders.SongLoader;\nimport com.naman14.timber.dataloaders.TopTracksLoader;\nimport com.naman14.timber.models.Playlist;\nimport com.naman14.timber.models.Song;\nimport com.naman14.timber.utils.Constants;\nimport com.naman14.timber.utils.Helpers;\nimport com.naman14.timber.utils.NavigationUtils;\nimport com.naman14.timber.utils.PreferencesUtility;\nimport com.naman14.timber.utils.TimberUtils;\nimport com.nostra13.universalimageloader.core.DisplayImageOptions;\nimport com.nostra13.universalimageloader.core.ImageLoader;\nimport com.nostra13.universalimageloader.core.assist.FailReason;\nimport com.nostra13.universalimageloader.core.listener.SimpleImageLoadingListener;\n\nimport java.util.List;\nimport java.util.Random;\n\n/**\n * Created by naman on 31/10/16.\n */\npublic class PlaylistAdapter extends RecyclerView.Adapter<PlaylistAdapter.ItemHolder> {\n\n    private List<Playlist> arraylist;\n    private Activity mContext;\n    private boolean isGrid;\n    private boolean showAuto;\n    private int songCountInt;\n    private long totalRuntime;\n    private long firstAlbumID = -1;\n    private int foregroundColor;\n    int[] foregroundColors = {R.color.pink_transparent, R.color.green_transparent, R.color.blue_transparent, R.color.red_transparent, R.color.purple_transparent};\n\n    public PlaylistAdapter(Activity context, List<Playlist> arraylist) {\n        this.arraylist = arraylist;\n        this.mContext = context;\n        this.isGrid = PreferencesUtility.getInstance(mContext).getPlaylistView() == Constants.PLAYLIST_VIEW_GRID;\n        this.showAuto = PreferencesUtility.getInstance(mContext).showAutoPlaylist();\n        Random random = new Random();\n        int rndInt = random.nextInt(foregroundColors.length);\n        foregroundColor = foregroundColors[rndInt];\n\n    }\n\n    @Override\n    public ItemHolder onCreateViewHolder(ViewGroup viewGroup, int i) {\n        if (isGrid) {\n            View v = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.item_album_grid, null);\n            ItemHolder ml = new ItemHolder(v);\n            return ml;\n        } else {\n            View v = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.item_album_list, null);\n            ItemHolder ml = new ItemHolder(v);\n            return ml;\n        }\n    }\n\n    @Override\n    public void onBindViewHolder(final ItemHolder itemHolder, int i) {\n        final Playlist localItem = arraylist.get(i);\n\n        itemHolder.title.setText(localItem.name);\n\n        String s = getAlbumArtUri(i, localItem.id);\n        itemHolder.albumArt.setTag(firstAlbumID);\n        ImageLoader.getInstance().displayImage(s, itemHolder.albumArt,\n                new DisplayImageOptions.Builder().cacheInMemory(true)\n                        .showImageOnFail(R.drawable.ic_empty_music2)\n                        .resetViewBeforeLoading(true)\n                        .build(), new SimpleImageLoadingListener() {\n                    @Override\n                    public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {\n                        if (isGrid) {\n                            new Palette.Builder(loadedImage).generate(new Palette.PaletteAsyncListener() {\n                                @Override\n                                public void onGenerated(Palette palette) {\n                                    Palette.Swatch swatch = palette.getVibrantSwatch();\n                                    if (swatch != null) {\n                                        int color = swatch.getRgb();\n                                        itemHolder.footer.setBackgroundColor(color);\n                                        int textColor = TimberUtils.getBlackWhiteColor(swatch.getTitleTextColor());\n                                        itemHolder.title.setTextColor(textColor);\n                                        itemHolder.artist.setTextColor(textColor);\n                                    } else {\n                                        Palette.Swatch mutedSwatch = palette.getMutedSwatch();\n                                        if (mutedSwatch != null) {\n                                            int color = mutedSwatch.getRgb();\n                                            itemHolder.footer.setBackgroundColor(color);\n                                            int textColor = TimberUtils.getBlackWhiteColor(mutedSwatch.getTitleTextColor());\n                                            itemHolder.title.setTextColor(textColor);\n                                            itemHolder.artist.setTextColor(textColor);\n                                        }\n                                    }\n\n\n                                }\n                            });\n                        }\n\n                    }\n\n                    @Override\n                    public void onLoadingFailed(String imageUri, View view, FailReason failReason) {\n                        if (isGrid) {\n                            itemHolder.footer.setBackgroundColor(0);\n                            if (mContext != null) {\n                                int textColorPrimary = Config.textColorPrimary(mContext, Helpers.getATEKey(mContext));\n                                itemHolder.title.setTextColor(textColorPrimary);\n                                itemHolder.artist.setTextColor(textColorPrimary);\n                            }\n                        }\n                    }\n                });\n        itemHolder.artist.setText(\" \" + String.valueOf(songCountInt) + \" \" + mContext.getString(R.string.songs) + \" - \" + TimberUtils.makeShortTimeString(mContext,totalRuntime));\n\n        if (TimberUtils.isLollipop())\n            itemHolder.albumArt.setTransitionName(\"transition_album_art\" + i);\n\n    }\n\n    private String getAlbumArtUri(int position, long id) {\n        if (mContext != null) {\n            firstAlbumID = -1;\n            if (showAuto) {\n                switch (position) {\n                    case 0:\n                        List<Song> lastAddedSongs = LastAddedLoader.getLastAddedSongs(mContext);\n                        songCountInt = lastAddedSongs.size();\n                        totalRuntime = 0;\n                        for(Song song : lastAddedSongs){\n                                totalRuntime += song.duration / 1000; //for some reason default playlists have songs with durations 1000x larger than they should be\n                        }\n\n                        if (songCountInt != 0) {\n                            firstAlbumID = lastAddedSongs.get(0).albumId;\n                            return TimberUtils.getAlbumArtUri(firstAlbumID).toString();\n                        } else return \"nosongs\";\n                    case 1:\n                        TopTracksLoader recentloader = new TopTracksLoader(mContext, TopTracksLoader.QueryType.RecentSongs);\n                        List<Song> recentsongs = SongLoader.getSongsForCursor(TopTracksLoader.getCursor());\n                        songCountInt = recentsongs.size();\n                        totalRuntime = 0;\n                        for(Song song : recentsongs){\n                            totalRuntime += song.duration / 1000; //for some reason default playlists have songs with durations 1000x larger than they should be\n                        }\n\n                        if (songCountInt != 0) {\n                            firstAlbumID = recentsongs.get(0).albumId;\n                            return TimberUtils.getAlbumArtUri(firstAlbumID).toString();\n                        } else return \"nosongs\";\n                    case 2:\n                        TopTracksLoader topTracksLoader = new TopTracksLoader(mContext, TopTracksLoader.QueryType.TopTracks);\n                        List<Song> topsongs = SongLoader.getSongsForCursor(TopTracksLoader.getCursor());\n                        songCountInt = topsongs.size();\n                        totalRuntime = 0;\n                        for(Song song : topsongs){\n                            totalRuntime += song.duration / 1000; //for some reason default playlists have songs with durations 1000x larger than they should be\n                        }\n\n                        if (songCountInt != 0) {\n                            firstAlbumID = topsongs.get(0).albumId;\n                            return TimberUtils.getAlbumArtUri(firstAlbumID).toString();\n                        } else return \"nosongs\";\n                    default:\n                        List<Song> playlistsongs = PlaylistSongLoader.getSongsInPlaylist(mContext, id);\n                        songCountInt = playlistsongs.size();\n                        totalRuntime = 0;\n                        for(Song song : playlistsongs){\n                            totalRuntime += song.duration;\n                        }\n\n                        if (songCountInt != 0) {\n                            firstAlbumID = playlistsongs.get(0).albumId;\n                            return TimberUtils.getAlbumArtUri(firstAlbumID).toString();\n                        } else return \"nosongs\";\n\n                }\n            } else {\n                List<Song> playlistsongs = PlaylistSongLoader.getSongsInPlaylist(mContext, id);\n                songCountInt = playlistsongs.size();\n                totalRuntime = 0;\n                for(Song song : playlistsongs){\n                    totalRuntime += song.duration;\n                }\n\n                if (songCountInt != 0) {\n                    firstAlbumID = playlistsongs.get(0).albumId;\n                    return TimberUtils.getAlbumArtUri(firstAlbumID).toString();\n                } else return \"nosongs\";\n            }\n        }\n        return null;\n    }\n\n    @Override\n    public int getItemCount() {\n        return (null != arraylist ? arraylist.size() : 0);\n    }\n\n    public void updateDataSet(List<Playlist> arraylist) {\n        this.arraylist.clear();\n        this.arraylist.addAll(arraylist);\n        notifyDataSetChanged();\n    }\n\n    public class ItemHolder extends RecyclerView.ViewHolder implements View.OnClickListener {\n        protected TextView title, artist;\n        protected ImageView albumArt;\n        protected View footer;\n\n        public ItemHolder(View view) {\n            super(view);\n            this.title = (TextView) view.findViewById(R.id.album_title);\n            this.artist = (TextView) view.findViewById(R.id.album_artist);\n            this.albumArt = (ImageView) view.findViewById(R.id.album_art);\n            this.footer = view.findViewById(R.id.footer);\n            view.setOnClickListener(this);\n        }\n\n        @Override\n        public void onClick(View v) {\n            NavigationUtils.navigateToPlaylistDetail(mContext, getPlaylistType(getAdapterPosition()), (long) albumArt.getTag(), String.valueOf(title.getText()), foregroundColor, arraylist.get(getAdapterPosition()).id, null);\n\n        }\n\n    }\n\n    private String getPlaylistType(int position) {\n        if (showAuto) {\n            switch (position) {\n                case 0:\n                    return Constants.NAVIGATE_PLAYLIST_LASTADDED;\n                case 1:\n                    return Constants.NAVIGATE_PLAYLIST_RECENT;\n                case 2:\n                    return Constants.NAVIGATE_PLAYLIST_TOPTRACKS;\n                default:\n                    return Constants.NAVIGATE_PLAYLIST_USERCREATED;\n            }\n        } else return Constants.NAVIGATE_PLAYLIST_USERCREATED;\n    }\n\n\n}\n\n\n\n\n"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/adapters/SearchAdapter.java",
    "content": "/*\n * Copyright (C) 2015 Naman Dwivedi\n *\n * Licensed under the GNU General Public License v3\n *\n * This is free software: you can redistribute it and/or modify it\n * under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;\n * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n * See the GNU General Public License for more details.\n */\n\npackage com.naman14.timber.adapters;\n\nimport android.app.Activity;\nimport android.os.Handler;\nimport androidx.appcompat.app.AppCompatActivity;\nimport androidx.recyclerview.widget.RecyclerView;\nimport android.view.LayoutInflater;\nimport android.view.MenuItem;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.ImageView;\nimport android.widget.PopupMenu;\nimport android.widget.TextView;\n\nimport com.naman14.timber.MusicPlayer;\nimport com.naman14.timber.R;\nimport com.naman14.timber.dialogs.AddPlaylistDialog;\nimport com.naman14.timber.lastfmapi.LastFmClient;\nimport com.naman14.timber.lastfmapi.callbacks.ArtistInfoListener;\nimport com.naman14.timber.lastfmapi.models.ArtistQuery;\nimport com.naman14.timber.lastfmapi.models.LastfmArtist;\nimport com.naman14.timber.models.Album;\nimport com.naman14.timber.models.Artist;\nimport com.naman14.timber.models.Song;\nimport com.naman14.timber.utils.NavigationUtils;\nimport com.naman14.timber.utils.TimberUtils;\nimport com.nostra13.universalimageloader.core.DisplayImageOptions;\nimport com.nostra13.universalimageloader.core.ImageLoader;\nimport com.nostra13.universalimageloader.core.display.FadeInBitmapDisplayer;\n\nimport java.util.Collections;\nimport java.util.List;\n\npublic class SearchAdapter extends BaseSongAdapter<SearchAdapter.ItemHolder> {\n\n    private Activity mContext;\n    private List searchResults = Collections.emptyList();\n\n    public SearchAdapter(Activity context) {\n        this.mContext = context;\n\n    }\n\n    @Override\n    public ItemHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {\n        switch (viewType) {\n            case 0:\n                View v0 = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.item_song, null);\n                ItemHolder ml0 = new ItemHolder(v0);\n                return ml0;\n            case 1:\n                View v1 = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.item_album_search, null);\n                ItemHolder ml1 = new ItemHolder(v1);\n                return ml1;\n            case 2:\n                View v2 = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.item_artist, null);\n                ItemHolder ml2 = new ItemHolder(v2);\n                return ml2;\n            case 10:\n                View v10 = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.search_section_header, null);\n                ItemHolder ml10 = new ItemHolder(v10);\n                return ml10;\n            default:\n                View v3 = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.item_song, null);\n                ItemHolder ml3 = new ItemHolder(v3);\n                return ml3;\n        }\n    }\n\n    @Override\n    public void onBindViewHolder(final ItemHolder itemHolder, int i) {\n        switch (getItemViewType(i)) {\n            case 0:\n                Song song = (Song) searchResults.get(i);\n                itemHolder.title.setText(song.title);\n                itemHolder.songartist.setText(song.albumName);\n                ImageLoader.getInstance().displayImage(TimberUtils.getAlbumArtUri(song.albumId).toString(), itemHolder.albumArt,\n                        new DisplayImageOptions.Builder().cacheInMemory(true)\n                                .cacheOnDisk(true)\n                                .showImageOnFail(R.drawable.ic_empty_music2)\n                                .resetViewBeforeLoading(true)\n                                .displayer(new FadeInBitmapDisplayer(400))\n                                .build());\n                setOnPopupMenuListener(itemHolder, i);\n                break;\n            case 1:\n                Album album = (Album) searchResults.get(i);\n                itemHolder.albumtitle.setText(album.title);\n                itemHolder.albumartist.setText(album.artistName);\n                ImageLoader.getInstance().displayImage(TimberUtils.getAlbumArtUri(album.id).toString(), itemHolder.albumArt,\n                        new DisplayImageOptions.Builder().cacheInMemory(true)\n                                .cacheOnDisk(true)\n                                .showImageOnFail(R.drawable.ic_empty_music2)\n                                .resetViewBeforeLoading(true)\n                                .displayer(new FadeInBitmapDisplayer(400))\n                                .build());\n                break;\n            case 2:\n                Artist artist = (Artist) searchResults.get(i);\n                itemHolder.artisttitle.setText(artist.name);\n                String albumNmber = TimberUtils.makeLabel(mContext, R.plurals.Nalbums, artist.albumCount);\n                String songCount = TimberUtils.makeLabel(mContext, R.plurals.Nsongs, artist.songCount);\n                itemHolder.albumsongcount.setText(TimberUtils.makeCombinedString(mContext, albumNmber, songCount));\n                LastFmClient.getInstance(mContext).getArtistInfo(new ArtistQuery(artist.name), new ArtistInfoListener() {\n                    @Override\n                    public void artistInfoSucess(LastfmArtist artist) {\n                        if (artist != null && itemHolder.artistImage != null) {\n                            ImageLoader.getInstance().displayImage(artist.mArtwork.get(1).mUrl, itemHolder.artistImage,\n                                    new DisplayImageOptions.Builder().cacheInMemory(true)\n                                            .cacheOnDisk(true)\n                                            .showImageOnFail(R.drawable.ic_empty_music2)\n                                            .resetViewBeforeLoading(true)\n                                            .displayer(new FadeInBitmapDisplayer(400))\n                                            .build());\n                        }\n                    }\n\n                    @Override\n                    public void artistInfoFailed() {\n\n                    }\n                });\n                break;\n            case 10:\n                itemHolder.sectionHeader.setText((String) searchResults.get(i));\n            case 3:\n                break;\n        }\n    }\n\n    @Override\n    public void onViewRecycled(ItemHolder itemHolder) {\n\n    }\n\n    @Override\n    public int getItemCount() {\n        return searchResults.size();\n    }\n\n    private void setOnPopupMenuListener(ItemHolder itemHolder, final int position) {\n\n        itemHolder.menu.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n\n                final PopupMenu menu = new PopupMenu(mContext, v);\n                menu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {\n                    @Override\n                    public boolean onMenuItemClick(MenuItem item) {\n                        long[] song = new long[1];\n                        song[0] = ((Song) searchResults.get(position)).id;\n                        switch (item.getItemId()) {\n                            case R.id.popup_song_play:\n                                MusicPlayer.playAll(mContext, song, 0, -1, TimberUtils.IdType.NA, false);\n                                break;\n                            case R.id.popup_song_play_next:\n                                MusicPlayer.playNext(mContext, song, -1, TimberUtils.IdType.NA);\n                                break;\n                            case R.id.popup_song_goto_album:\n                                NavigationUtils.navigateToAlbum(mContext, ((Song) searchResults.get(position)).albumId, null);\n                                break;\n                            case R.id.popup_song_goto_artist:\n                                NavigationUtils.navigateToArtist(mContext, ((Song) searchResults.get(position)).artistId, null);\n                                break;\n                            case R.id.popup_song_addto_queue:\n                                MusicPlayer.addToQueue(mContext, song, -1, TimberUtils.IdType.NA);\n                                break;\n                            case R.id.popup_song_addto_playlist:\n                                AddPlaylistDialog.newInstance(((Song) searchResults.get(position))).show(((AppCompatActivity) mContext).getSupportFragmentManager(), \"ADD_PLAYLIST\");\n                                break;\n                        }\n                        return false;\n                    }\n                });\n                menu.inflate(R.menu.popup_song);\n                //Hide these because they aren't implemented\n                menu.getMenu().findItem(R.id.popup_song_delete).setVisible(false);\n                menu.getMenu().findItem(R.id.popup_song_share).setVisible(false);\n                menu.show();\n            }\n        });\n    }\n\n    @Override\n    public int getItemViewType(int position) {\n        if (searchResults.get(position) instanceof Song)\n            return 0;\n        if (searchResults.get(position) instanceof Album)\n            return 1;\n        if (searchResults.get(position) instanceof Artist)\n            return 2;\n        if (searchResults.get(position) instanceof String)\n            return 10;\n        return 3;\n    }\n\n    public void updateSearchResults(List searchResults) {\n        this.searchResults = searchResults;\n    }\n\n    public class ItemHolder extends RecyclerView.ViewHolder implements View.OnClickListener {\n        protected TextView title, songartist, albumtitle, artisttitle, albumartist, albumsongcount, sectionHeader;\n        protected ImageView albumArt, artistImage, menu;\n\n        public ItemHolder(View view) {\n            super(view);\n\n            this.title = (TextView) view.findViewById(R.id.song_title);\n            this.songartist = (TextView) view.findViewById(R.id.song_artist);\n            this.albumsongcount = (TextView) view.findViewById(R.id.album_song_count);\n            this.artisttitle = (TextView) view.findViewById(R.id.artist_name);\n            this.albumtitle = (TextView) view.findViewById(R.id.album_title);\n            this.albumartist = (TextView) view.findViewById(R.id.album_artist);\n            this.albumArt = (ImageView) view.findViewById(R.id.albumArt);\n            this.artistImage = (ImageView) view.findViewById(R.id.artistImage);\n            this.menu = (ImageView) view.findViewById(R.id.popup_menu);\n\n            this.sectionHeader = (TextView) view.findViewById(R.id.section_header);\n\n\n            view.setOnClickListener(this);\n        }\n\n        @Override\n        public void onClick(View v) {\n            switch (getItemViewType()) {\n                case 0:\n                    final Handler handler = new Handler();\n                    handler.postDelayed(new Runnable() {\n                        @Override\n                        public void run() {\n                            long[] ret = new long[1];\n                            ret[0] = ((Song) searchResults.get(getAdapterPosition())).id;\n                            playAll(mContext, ret, 0, -1, TimberUtils.IdType.NA,\n                                    false, (Song) searchResults.get(getAdapterPosition()), false);\n                        }\n                    }, 100);\n\n                    break;\n                case 1:\n                    NavigationUtils.goToAlbum(mContext, ((Album) searchResults.get(getAdapterPosition())).id);\n                    break;\n                case 2:\n                    NavigationUtils.goToArtist(mContext, ((Artist) searchResults.get(getAdapterPosition())).id);\n                    break;\n                case 3:\n                    break;\n                case 10:\n                    break;\n            }\n        }\n\n    }\n}\n\n\n\n\n\n"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/adapters/SlidingQueueAdapter.java",
    "content": "/*\n * Copyright (C) 2015 Naman Dwivedi\n *\n * Licensed under the GNU General Public License v3\n *\n * This is free software: you can redistribute it and/or modify it\n * under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;\n * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n * See the GNU General Public License for more details.\n */\n\npackage com.naman14.timber.adapters;\n\nimport android.app.Activity;\nimport android.os.Handler;\nimport androidx.recyclerview.widget.RecyclerView;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.view.animation.Animation;\nimport android.view.animation.AnimationUtils;\nimport android.widget.ImageView;\n\nimport com.naman14.timber.MusicPlayer;\nimport com.naman14.timber.R;\nimport com.naman14.timber.models.Song;\nimport com.naman14.timber.utils.TimberUtils;\nimport com.nostra13.universalimageloader.core.DisplayImageOptions;\nimport com.nostra13.universalimageloader.core.ImageLoader;\n\nimport java.util.List;\n\npublic class SlidingQueueAdapter extends RecyclerView.Adapter<SlidingQueueAdapter.ItemHolder> {\n\n    public static int currentlyPlayingPosition;\n    private List<Song> arraylist;\n    private Activity mContext;\n    private int lastPosition = -1;\n\n    public SlidingQueueAdapter(Activity context, List<Song> arraylist) {\n        this.arraylist = arraylist;\n        this.mContext = context;\n        currentlyPlayingPosition = MusicPlayer.getQueuePosition();\n    }\n\n    @Override\n    public ItemHolder onCreateViewHolder(ViewGroup viewGroup, int i) {\n        View v = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.item_song_sliding_queue, null);\n        ItemHolder ml = new ItemHolder(v);\n        return ml;\n    }\n\n    @Override\n    public void onBindViewHolder(ItemHolder itemHolder, int i) {\n\n//        setAnimation(itemHolder.itemView, i);\n        Song localItem = arraylist.get(i);\n\n        ImageLoader.getInstance().displayImage(TimberUtils.getAlbumArtUri(localItem.albumId).toString(),\n                itemHolder.albumArt, new DisplayImageOptions.Builder().cacheInMemory(true)\n                        .showImageOnLoading(R.drawable.ic_empty_music2).resetViewBeforeLoading(true).build());\n\n    }\n\n    @Override\n    public int getItemCount() {\n        return (null != arraylist ? arraylist.size() : 0);\n    }\n\n    public long[] getSongIds() {\n        long[] ret = new long[getItemCount()];\n        for (int i = 0; i < getItemCount(); i++) {\n            ret[i] = arraylist.get(i).id;\n        }\n\n        return ret;\n    }\n\n    private void setAnimation(View viewToAnimate, int position) {\n        // If the bound view wasn't previously displayed on screen, it's animated\n        if (position > lastPosition) {\n            Animation animation = AnimationUtils.loadAnimation(mContext, R.anim.scale);\n            viewToAnimate.startAnimation(animation);\n            lastPosition = position;\n        }\n    }\n\n    public class ItemHolder extends RecyclerView.ViewHolder implements View.OnClickListener {\n        protected ImageView albumArt;\n\n        public ItemHolder(View view) {\n            super(view);\n            this.albumArt = (ImageView) view.findViewById(R.id.album_art);\n            view.setOnClickListener(this);\n        }\n\n        @Override\n        public void onClick(View v) {\n            final Handler handler = new Handler();\n            handler.postDelayed(new Runnable() {\n                @Override\n                public void run() {\n                    MusicPlayer.setQueuePosition(getAdapterPosition());\n                    Handler handler1 = new Handler();\n                    handler1.postDelayed(new Runnable() {\n                        @Override\n                        public void run() {\n                            notifyItemChanged(currentlyPlayingPosition);\n                            notifyItemChanged(getAdapterPosition());\n                            Handler handler2 = new Handler();\n                            handler2.postDelayed(new Runnable() {\n                                @Override\n                                public void run() {\n                                }\n                            }, 50);\n                        }\n                    }, 50);\n                }\n            }, 100);\n\n        }\n\n    }\n\n\n}\n\n\n\n\n"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/adapters/SongsListAdapter.java",
    "content": "/*\n * Copyright (C) 2015 Naman Dwivedi\n *\n * Licensed under the GNU General Public License v3\n *\n * This is free software: you can redistribute it and/or modify it\n * under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;\n * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n * See the GNU General Public License for more details.\n */\n\npackage com.naman14.timber.adapters;\n\nimport android.graphics.Color;\nimport android.os.Handler;\nimport androidx.appcompat.app.AppCompatActivity;\nimport androidx.recyclerview.widget.RecyclerView;\n\nimport android.view.LayoutInflater;\nimport android.view.MenuItem;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.view.animation.Animation;\nimport android.view.animation.AnimationUtils;\nimport android.widget.ImageView;\nimport android.widget.PopupMenu;\nimport android.widget.TextView;\n\nimport com.afollestad.appthemeengine.Config;\nimport com.naman14.timber.MusicPlayer;\nimport com.naman14.timber.R;\nimport com.naman14.timber.dialogs.AddPlaylistDialog;\nimport com.naman14.timber.models.Song;\nimport com.naman14.timber.utils.Helpers;\nimport com.naman14.timber.utils.NavigationUtils;\nimport com.naman14.timber.utils.TimberUtils;\nimport com.naman14.timber.widgets.BubbleTextGetter;\nimport com.naman14.timber.widgets.MusicVisualizer;\nimport com.nostra13.universalimageloader.core.DisplayImageOptions;\nimport com.nostra13.universalimageloader.core.ImageLoader;\n\nimport java.util.List;\n\npublic class SongsListAdapter extends BaseSongAdapter<SongsListAdapter.ItemHolder> implements BubbleTextGetter {\n\n    public int currentlyPlayingPosition;\n    private List<Song> arraylist;\n    private AppCompatActivity mContext;\n    private long[] songIDs;\n    private boolean isPlaylist;\n    private boolean animate;\n    private int lastPosition = -1;\n    private String ateKey;\n    private long playlistId;\n\n    public SongsListAdapter(AppCompatActivity context, List<Song> arraylist, boolean isPlaylistSong, boolean animate) {\n        this.arraylist = arraylist;\n        this.mContext = context;\n        this.isPlaylist = isPlaylistSong;\n        this.songIDs = getSongIds();\n        this.ateKey = Helpers.getATEKey(context);\n        this.animate = animate;\n    }\n\n    @Override\n    public ItemHolder onCreateViewHolder(ViewGroup viewGroup, int i) {\n        if (isPlaylist) {\n            View v = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.item_song_playlist, null);\n            ItemHolder ml = new ItemHolder(v);\n            return ml;\n        } else {\n            View v = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.item_song, null);\n            ItemHolder ml = new ItemHolder(v);\n            return ml;\n        }\n    }\n\n    @Override\n    public void onBindViewHolder(ItemHolder itemHolder, int i) {\n        Song localItem = arraylist.get(i);\n\n        itemHolder.title.setText(localItem.title);\n        itemHolder.artist.setText(localItem.artistName);\n\n        ImageLoader.getInstance().displayImage(TimberUtils.getAlbumArtUri(localItem.albumId).toString(),\n                itemHolder.albumArt, new DisplayImageOptions.Builder().cacheInMemory(true)\n                        .showImageOnLoading(R.drawable.ic_empty_music2)\n                        .resetViewBeforeLoading(true).build());\n\n        if (MusicPlayer.getCurrentAudioId() == localItem.id) {\n            itemHolder.title.setTextColor(Config.accentColor(mContext, ateKey));\n            if (MusicPlayer.isPlaying()) {\n                itemHolder.visualizer.setColor(Config.accentColor(mContext, ateKey));\n                itemHolder.visualizer.setVisibility(View.VISIBLE);\n            } else {\n                itemHolder.visualizer.setVisibility(View.GONE);\n            }\n        } else {\n            itemHolder.visualizer.setVisibility(View.GONE);\n            if (isPlaylist) {\n                itemHolder.title.setTextColor(Color.WHITE);\n            } else {\n                itemHolder.title.setTextColor(Config.textColorPrimary(mContext, ateKey));\n            }\n        }\n\n\n        if (animate && isPlaylist) {\n            if (TimberUtils.isLollipop())\n                setAnimation(itemHolder.itemView, i);\n            else {\n                if (i > 10)\n                    setAnimation(itemHolder.itemView, i);\n            }\n        }\n\n\n        setOnPopupMenuListener(itemHolder, i);\n\n    }\n\n    public void setPlaylistId(long playlistId) {\n        this.playlistId = playlistId;\n    }\n\n    @Override\n    public int getItemCount() {\n        return (null != arraylist ? arraylist.size() : 0);\n    }\n\n    private void setOnPopupMenuListener(ItemHolder itemHolder, final int position) {\n\n        itemHolder.popupMenu.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n\n                final PopupMenu menu = new PopupMenu(mContext, v);\n\n                menu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {\n                    @Override\n                    public boolean onMenuItemClick(MenuItem item) {\n                        switch (item.getItemId()) {\n                            case R.id.popup_song_remove_playlist:\n                                TimberUtils.removeFromPlaylist(mContext, arraylist.get(position).id, playlistId);\n                                removeSongAt(position);\n                                notifyItemRemoved(position);\n                                break;\n                            case R.id.popup_song_play:\n                                MusicPlayer.playAll(mContext, songIDs, position, -1, TimberUtils.IdType.NA, false);\n                                break;\n                            case R.id.popup_song_play_next:\n                                long[] ids = new long[1];\n                                ids[0] = arraylist.get(position).id;\n                                MusicPlayer.playNext(mContext, ids, -1, TimberUtils.IdType.NA);\n                                break;\n                            case R.id.popup_song_goto_album:\n                                NavigationUtils.goToAlbum(mContext, arraylist.get(position).albumId);\n                                break;\n                            case R.id.popup_song_goto_artist:\n                                NavigationUtils.goToArtist(mContext, arraylist.get(position).artistId);\n                                break;\n                            case R.id.popup_song_addto_queue:\n                                long[] id = new long[1];\n                                id[0] = arraylist.get(position).id;\n                                MusicPlayer.addToQueue(mContext, id, -1, TimberUtils.IdType.NA);\n                                break;\n                            case R.id.popup_song_addto_playlist:\n                                AddPlaylistDialog.newInstance(arraylist.get(position)).show(mContext.getSupportFragmentManager(), \"ADD_PLAYLIST\");\n                                break;\n                            case R.id.popup_song_share:\n                               TimberUtils.shareTrack(mContext, arraylist.get(position).id);\n                                break;\n                            case R.id.popup_song_delete:\n                                long[] deleteIds = {arraylist.get(position).id};\n                                TimberUtils.showDeleteDialog(mContext,arraylist.get(position).title, deleteIds, SongsListAdapter.this, position);\n                                break;\n                        }\n                        return false;\n                    }\n                });\n                menu.inflate(R.menu.popup_song);\n                menu.show();\n                if (isPlaylist)\n                    menu.getMenu().findItem(R.id.popup_song_remove_playlist).setVisible(true);\n            }\n        });\n    }\n\n    public long[] getSongIds() {\n        long[] ret = new long[getItemCount()];\n        for (int i = 0; i < getItemCount(); i++) {\n            ret[i] = arraylist.get(i).id;\n        }\n\n        return ret;\n    }\n\n    @Override\n    public String getTextToShowInBubble(final int pos) {\n        if (arraylist == null || arraylist.size() == 0)\n            return \"\";\n        Character ch = arraylist.get(pos).title.charAt(0);\n        if (Character.isDigit(ch)) {\n            return \"#\";\n        } else\n            return Character.toString(ch);\n    }\n\n    private void setAnimation(View viewToAnimate, int position) {\n        // If the bound view wasn't previously displayed on screen, it's animated\n        if (position > lastPosition) {\n            Animation animation = AnimationUtils.loadAnimation(mContext, R.anim.abc_slide_in_bottom);\n            viewToAnimate.startAnimation(animation);\n            lastPosition = position;\n        }\n    }\n\n    @Override\n    public void updateDataSet(List<Song> arraylist) {\n        this.arraylist = arraylist;\n        this.songIDs = getSongIds();\n    }\n\n    public class ItemHolder extends RecyclerView.ViewHolder implements View.OnClickListener {\n        protected TextView title, artist;\n        protected ImageView albumArt, popupMenu;\n        private MusicVisualizer visualizer;\n\n        public ItemHolder(View view) {\n            super(view);\n            this.title = (TextView) view.findViewById(R.id.song_title);\n            this.artist = (TextView) view.findViewById(R.id.song_artist);\n            this.albumArt = (ImageView) view.findViewById(R.id.albumArt);\n            this.popupMenu = (ImageView) view.findViewById(R.id.popup_menu);\n            visualizer = (MusicVisualizer) view.findViewById(R.id.visualizer);\n            view.setOnClickListener(this);\n        }\n\n        @Override\n        public void onClick(View v) {\n            final Handler handler = new Handler();\n            handler.postDelayed(new Runnable() {\n                @Override\n                public void run() {\n                    playAll(mContext, songIDs, getAdapterPosition(), -1,\n                            TimberUtils.IdType.NA, false,\n                            arraylist.get(getAdapterPosition()), false);\n                    Handler handler1 = new Handler();\n                    handler1.postDelayed(new Runnable() {\n                        @Override\n                        public void run() {\n                            notifyItemChanged(currentlyPlayingPosition);\n                            notifyItemChanged(getAdapterPosition());\n                        }\n                    }, 50);\n                }\n            }, 100);\n\n\n        }\n\n    }\n\n    public Song getSongAt(int i) {\n        return arraylist.get(i);\n    }\n\n    public void addSongTo(int i, Song song) {\n        arraylist.add(i, song);\n    }\n\n    @Override\n    public void removeSongAt(int i) {\n        arraylist.remove(i);\n        updateDataSet(arraylist);\n    }\n}\n\n\n"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/cast/CastOptionsProvider.java",
    "content": "package com.naman14.timber.cast;\n\nimport android.content.Context;\n\nimport com.google.android.gms.cast.framework.CastOptions;\nimport com.google.android.gms.cast.framework.OptionsProvider;\nimport com.google.android.gms.cast.framework.SessionProvider;\nimport com.google.android.gms.cast.framework.media.CastMediaOptions;\nimport com.google.android.gms.cast.framework.media.MediaIntentReceiver;\nimport com.google.android.gms.cast.framework.media.NotificationOptions;\nimport com.naman14.timber.R;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\npublic class CastOptionsProvider implements OptionsProvider {\n\n    @Override\n    public CastOptions getCastOptions(Context appContext) {\n\n        List<String> buttonActions = new ArrayList<>();\n        buttonActions.add(MediaIntentReceiver.ACTION_TOGGLE_PLAYBACK);\n        buttonActions.add(MediaIntentReceiver.ACTION_STOP_CASTING);\n        int[] compatButtonActionsIndicies = new int[]{ 0, 1 };\n\n        NotificationOptions notificationOptions = new NotificationOptions.Builder()\n                .setActions(buttonActions, compatButtonActionsIndicies)\n                .setTargetActivityClassName(ExpandedControlsActivity.class.getName())\n                .build();\n\n        CastMediaOptions mediaOptions = new CastMediaOptions.Builder()\n                .setNotificationOptions(notificationOptions)\n                .setExpandedControllerActivityClassName(ExpandedControlsActivity.class.getName())\n                .build();\n\n        CastOptions castOptions = new CastOptions.Builder()\n            .setReceiverApplicationId(appContext.getString(R.string.cast_app_id))\n                .setCastMediaOptions(mediaOptions)\n                .build();\n\n        return castOptions;\n    }\n    @Override\n    public List<SessionProvider> getAdditionalSessionProviders(Context context) {\n        return null;\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/cast/ExpandedControlsActivity.java",
    "content": "package com.naman14.timber.cast;\n\nimport android.os.Bundle;\nimport android.view.Menu;\nimport android.view.View;\nimport android.view.WindowManager;\n\nimport com.google.android.gms.cast.framework.CastButtonFactory;\nimport com.google.android.gms.cast.framework.media.widget.ExpandedControllerActivity;\nimport com.naman14.timber.R;\n\npublic class ExpandedControlsActivity extends ExpandedControllerActivity {\n\n    @Override\n    public boolean onCreateOptionsMenu(Menu menu) {\n        super.onCreateOptionsMenu(menu);\n        getMenuInflater().inflate(R.menu.menu_expanded_controller, menu);\n        CastButtonFactory.setUpMediaRouteButton(this, menu, R.id.media_route_menu_item);\n        return true;\n    }\n\n    @Override\n    protected void onCreate(Bundle bundle) {\n        super.onCreate(bundle);\n        getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE);\n\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/cast/SimpleSessionManagerListener.java",
    "content": "package com.naman14.timber.cast;\n\nimport com.google.android.gms.cast.framework.Session;\nimport com.google.android.gms.cast.framework.SessionManagerListener;\n\n/**\n * Created by naman on 7/12/17.\n */\n\npublic class SimpleSessionManagerListener implements SessionManagerListener {\n\n    public void onSessionStarted(Session session, String sessionId) {\n    }\n\n    public void onSessionResumed(Session session, boolean wasSuspended) {\n    }\n\n    public void onSessionEnded(Session session, int error) {\n    }\n\n    public void onSessionSuspended(Session session, int i) {\n\n    }\n\n    public void onSessionStarting(Session session) {\n\n    }\n\n    public void onSessionEnding(Session session) {\n\n    }\n\n    public void onSessionResuming(Session session, String s) {\n\n    }\n\n    public void onSessionResumeFailed(Session session, int i) {\n\n    }\n\n    public void onSessionStartFailed(Session session, int i) {\n\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/cast/TimberCastHelper.java",
    "content": "package com.naman14.timber.cast;\n\nimport android.net.Uri;\nimport android.util.Log;\n\nimport com.google.android.gms.cast.MediaInfo;\nimport com.google.android.gms.cast.MediaMetadata;\nimport com.google.android.gms.cast.TextTrackStyle;\nimport com.google.android.gms.cast.framework.CastSession;\nimport com.google.android.gms.cast.framework.media.RemoteMediaClient;\nimport com.google.android.gms.common.images.WebImage;\nimport com.naman14.timber.models.Song;\nimport com.naman14.timber.utils.Constants;\nimport com.naman14.timber.utils.TimberUtils;\n\nimport java.net.MalformedURLException;\nimport java.net.URL;\n\n/**\n * Created by naman on 2/12/17.\n */\n\npublic class TimberCastHelper  {\n\n    public static void startCasting(CastSession castSession, Song song) {\n\n        String ipAddress = TimberUtils.getIPAddress(true);\n        URL baseUrl;\n        try {\n            baseUrl = new URL(\"http\", ipAddress, Constants.CAST_SERVER_PORT, \"\" );\n        } catch (MalformedURLException e) {\n            e.printStackTrace();\n            return;\n        }\n\n        String songUrl = baseUrl.toString() + \"/song?id=\" + song.id;\n        String albumArtUrl = baseUrl.toString() + \"/albumart?id=\" + song.albumId;\n\n        MediaMetadata musicMetadata = new MediaMetadata(MediaMetadata.MEDIA_TYPE_MUSIC_TRACK);\n\n        musicMetadata.putString(MediaMetadata.KEY_TITLE, song.title);\n        musicMetadata.putString(MediaMetadata.KEY_ARTIST, song.artistName);\n        musicMetadata.putString(MediaMetadata.KEY_ALBUM_TITLE, song.albumName);\n        musicMetadata.putInt(MediaMetadata.KEY_TRACK_NUMBER, song.trackNumber);\n        musicMetadata.addImage(new WebImage(Uri.parse(albumArtUrl)));\n\n        try {\n            MediaInfo mediaInfo = new MediaInfo.Builder(songUrl)\n                    .setStreamType(MediaInfo.STREAM_TYPE_BUFFERED)\n                    .setContentType(\"audio/mpeg\")\n                    .setMetadata(musicMetadata)\n                    .setStreamDuration(song.duration)\n                    .build();\n            RemoteMediaClient remoteMediaClient = castSession.getRemoteMediaClient();\n            remoteMediaClient.load(mediaInfo, true, 0);\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/cast/WebServer.java",
    "content": "package com.naman14.timber.cast;\n\nimport android.content.Context;\nimport android.net.Uri;\n\nimport com.naman14.timber.utils.Constants;\nimport com.naman14.timber.utils.TimberUtils;\n\nimport java.io.File;\nimport java.io.FileInputStream;\nimport java.io.FileNotFoundException;\nimport java.io.InputStream;\nimport java.util.Map;\n\nimport fi.iki.elonen.NanoHTTPD;\n\npublic class WebServer extends NanoHTTPD {\n\n    private Context context;\n    private Uri songUri, albumArtUri;\n\n    public WebServer(Context context) {\n        super(Constants.CAST_SERVER_PORT);\n        this.context = context;\n    }\n\n    @Override\n    public Response serve(String uri, Method method,\n                          Map<String, String> header,\n                          Map<String, String> parameters,\n                          Map<String, String> files) {\n        if (uri.contains(\"albumart\")) {\n            //serve the picture\n\n            String albumId = parameters.get(\"id\");\n            this.albumArtUri = TimberUtils.getAlbumArtUri(Long.parseLong(albumId));\n\n            if (albumArtUri != null) {\n                String mediasend = \"image/jpg\";\n                InputStream fisAlbumArt = null;\n                try {\n                    fisAlbumArt = context.getContentResolver().openInputStream(albumArtUri);\n                } catch (FileNotFoundException e) {\n                    e.printStackTrace();\n                }\n                Response.Status st = Response.Status.OK;\n\n                //serve the song\n                return newChunkedResponse(st, mediasend, fisAlbumArt);\n            }\n\n        } else if (uri.contains(\"song\")) {\n\n            String songId = parameters.get(\"id\");\n            this.songUri = TimberUtils.getSongUri(context, Long.parseLong(songId));\n\n            if (songUri != null) {\n                String mediasend = \"audio/mp3\";\n                FileInputStream fisSong = null;\n                File song = new File(songUri.getPath());\n                try {\n                    fisSong = new FileInputStream(song);\n                } catch (FileNotFoundException e) {\n                    e.printStackTrace();\n                }\n                Response.Status st = Response.Status.OK;\n\n                //serve the song\n                return newFixedLengthResponse(st, mediasend, fisSong, song.length());\n            }\n\n        }\n        return newFixedLengthResponse(\"Error\");\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/dataloaders/AlbumLoader.java",
    "content": "/*\n * Copyright (C) 2015 Naman Dwivedi\n *\n * Licensed under the GNU General Public License v3\n *\n * This is free software: you can redistribute it and/or modify it\n * under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;\n * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n * See the GNU General Public License for more details.\n */\n\npackage com.naman14.timber.dataloaders;\n\nimport android.content.Context;\nimport android.database.Cursor;\nimport android.provider.MediaStore;\n\nimport com.naman14.timber.models.Album;\nimport com.naman14.timber.utils.PreferencesUtility;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\npublic class AlbumLoader {\n\n\n    public static Album getAlbum(Cursor cursor) {\n        Album album = new Album();\n        if (cursor != null) {\n            if (cursor.moveToFirst())\n                album = new Album(cursor.getLong(0), cursor.getString(1), cursor.getString(2), cursor.getLong(3), cursor.getInt(4), cursor.getInt(5));\n        }\n        if (cursor != null)\n            cursor.close();\n        return album;\n    }\n\n\n    public static List<Album> getAlbumsForCursor(Cursor cursor) {\n        ArrayList arrayList = new ArrayList();\n        if ((cursor != null) && (cursor.moveToFirst()))\n            do {\n                arrayList.add(new Album(cursor.getLong(0), cursor.getString(1), cursor.getString(2), cursor.getLong(3), cursor.getInt(4), cursor.getInt(5)));\n            }\n            while (cursor.moveToNext());\n        if (cursor != null)\n            cursor.close();\n        return arrayList;\n    }\n\n    public static List<Album> getAllAlbums(Context context) {\n        return getAlbumsForCursor(makeAlbumCursor(context, null, null));\n    }\n\n    public static Album getAlbum(Context context, long id) {\n        return getAlbum(makeAlbumCursor(context, \"_id=?\", new String[]{String.valueOf(id)}));\n    }\n\n    public static List<Album> getAlbums(Context context, String paramString, int limit) {\n        List<Album> result = getAlbumsForCursor(makeAlbumCursor(context, \"album LIKE ?\", new String[]{paramString + \"%\"}));\n        if (result.size() < limit) {\n            result.addAll(getAlbumsForCursor(makeAlbumCursor(context, \"album LIKE ?\", new String[]{\"%_\" + paramString + \"%\"})));\n        }\n        return result.size() < limit ? result : result.subList(0, limit);\n    }\n\n\n    public static Cursor makeAlbumCursor(Context context, String selection, String[] paramArrayOfString) {\n        final String albumSortOrder = PreferencesUtility.getInstance(context).getAlbumSortOrder();\n        Cursor cursor = context.getContentResolver().query(MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI, new String[]{\"_id\", \"album\", \"artist\", \"artist_id\", \"numsongs\", \"minyear\"}, selection, paramArrayOfString, albumSortOrder);\n\n        return cursor;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/dataloaders/AlbumSongLoader.java",
    "content": "/*\n * Copyright (C) 2015 Naman Dwivedi\n *\n * Licensed under the GNU General Public License v3\n *\n * This is free software: you can redistribute it and/or modify it\n * under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;\n * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n * See the GNU General Public License for more details.\n */\n\npackage com.naman14.timber.dataloaders;\n\nimport android.content.ContentResolver;\nimport android.content.Context;\nimport android.database.Cursor;\nimport android.net.Uri;\nimport android.provider.MediaStore;\n\nimport com.naman14.timber.models.Song;\nimport com.naman14.timber.utils.PreferencesUtility;\n\nimport java.util.ArrayList;\n\npublic class AlbumSongLoader {\n\n    private static final long[] sEmptyList = new long[0];\n\n    public static ArrayList<Song> getSongsForAlbum(Context context, long albumID) {\n\n        Cursor cursor = makeAlbumSongCursor(context, albumID);\n        ArrayList arrayList = new ArrayList();\n        if ((cursor != null) && (cursor.moveToFirst()))\n            do {\n                long id = cursor.getLong(0);\n                String title = cursor.getString(1);\n                String artist = cursor.getString(2);\n                String album = cursor.getString(3);\n                int duration = cursor.getInt(4);\n                int trackNumber = cursor.getInt(5);\n                /*This fixes bug where some track numbers displayed as 100 or 200*/\n                while (trackNumber >= 1000) {\n                    trackNumber -= 1000; //When error occurs the track numbers have an extra 1000 or 2000 added, so decrease till normal.\n                }\n                long artistId = cursor.getInt(6);\n                long albumId = albumID;\n\n                arrayList.add(new Song(id, albumId, artistId, title, artist, album, duration, trackNumber));\n            }\n            while (cursor.moveToNext());\n        if (cursor != null)\n            cursor.close();\n        return arrayList;\n    }\n\n    public static Cursor makeAlbumSongCursor(Context context, long albumID) {\n        ContentResolver contentResolver = context.getContentResolver();\n        final String albumSongSortOrder = PreferencesUtility.getInstance(context).getAlbumSongSortOrder();\n        Uri uri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;\n        String string = \"is_music=1 AND title != '' AND album_id=\" + albumID;\n        Cursor cursor = contentResolver.query(uri, new String[]{\"_id\", \"title\", \"artist\", \"album\", \"duration\", \"track\", \"artist_id\"}, string, null, albumSongSortOrder);\n        return cursor;\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/dataloaders/ArtistAlbumLoader.java",
    "content": "/*\n * Copyright (C) 2015 Naman Dwivedi\n *\n * Licensed under the GNU General Public License v3\n *\n * This is free software: you can redistribute it and/or modify it\n * under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;\n * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n * See the GNU General Public License for more details.\n */\n\npackage com.naman14.timber.dataloaders;\n\nimport android.content.Context;\nimport android.database.Cursor;\nimport android.provider.MediaStore;\n\nimport com.naman14.timber.models.Album;\nimport com.naman14.timber.models.Song;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\npublic class ArtistAlbumLoader {\n\n    public static ArrayList<Album> getAlbumsForArtist(Context context, long artistID) {\n\n        if (artistID == -1)\n            return null;\n\n        List<Album> allAlbums = AlbumLoader.getAllAlbums(context);\n        ArrayList<Album> artistAlbums = new ArrayList<>();\n        for (Album album: allAlbums) {\n            if (album.artistId == artistID) {\n                artistAlbums.add(album);\n            }\n        }\n        return artistAlbums;\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/dataloaders/ArtistLoader.java",
    "content": "/*\n * Copyright (C) 2015 Naman Dwivedi\n *\n * Licensed under the GNU General Public License v3\n *\n * This is free software: you can redistribute it and/or modify it\n * under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;\n * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n * See the GNU General Public License for more details.\n */\n\npackage com.naman14.timber.dataloaders;\n\nimport android.content.Context;\nimport android.database.Cursor;\nimport android.provider.MediaStore;\n\nimport com.naman14.timber.models.Artist;\nimport com.naman14.timber.utils.PreferencesUtility;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\npublic class ArtistLoader {\n\n    public static Artist getArtist(Cursor cursor) {\n        Artist artist = new Artist();\n        if (cursor != null) {\n            if (cursor.moveToFirst())\n                artist = new Artist(cursor.getLong(0), cursor.getString(1), cursor.getInt(2), cursor.getInt(3));\n        }\n        if (cursor != null)\n            cursor.close();\n        return artist;\n    }\n\n    public static List<Artist> getArtistsForCursor(Cursor cursor) {\n        ArrayList arrayList = new ArrayList();\n        if ((cursor != null) && (cursor.moveToFirst()))\n            do {\n                arrayList.add(new Artist(cursor.getLong(0), cursor.getString(1), cursor.getInt(2), cursor.getInt(3)));\n            }\n            while (cursor.moveToNext());\n        if (cursor != null)\n            cursor.close();\n        return arrayList;\n    }\n\n    public static List<Artist> getAllArtists(Context context) {\n        return getArtistsForCursor(makeArtistCursor(context, null, null));\n    }\n\n    public static Artist getArtist(Context context, long id) {\n        return getArtist(makeArtistCursor(context, \"_id=?\", new String[]{String.valueOf(id)}));\n    }\n\n    public static List<Artist> getArtists(Context context, String paramString, int limit) {\n        List<Artist> result = getArtistsForCursor(makeArtistCursor(context, \"artist LIKE ?\", new String[]{paramString + \"%\"}));\n        if (result.size() < limit) {\n            result.addAll(getArtistsForCursor(makeArtistCursor(context, \"artist LIKE ?\", new String[]{\"%_\" + paramString + \"%\"})));\n        }\n        return result.size() < limit ? result : result.subList(0, limit);\n    }\n\n\n    public static Cursor makeArtistCursor(Context context, String selection, String[] paramArrayOfString) {\n        final String artistSortOrder = PreferencesUtility.getInstance(context).getArtistSortOrder();\n        Cursor cursor = context.getContentResolver().query(MediaStore.Audio.Artists.EXTERNAL_CONTENT_URI, new String[]{\"_id\", \"artist\", \"number_of_albums\", \"number_of_tracks\"}, selection, paramArrayOfString, artistSortOrder);\n        return cursor;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/dataloaders/ArtistSongLoader.java",
    "content": "/*\n * Copyright (C) 2015 Naman Dwivedi\n *\n * Licensed under the GNU General Public License v3\n *\n * This is free software: you can redistribute it and/or modify it\n * under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;\n * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n * See the GNU General Public License for more details.\n */\n\npackage com.naman14.timber.dataloaders;\n\nimport android.content.ContentResolver;\nimport android.content.Context;\nimport android.database.Cursor;\nimport android.net.Uri;\nimport android.provider.MediaStore;\n\nimport com.naman14.timber.models.Song;\nimport com.naman14.timber.utils.PreferencesUtility;\n\nimport java.util.ArrayList;\n\npublic class ArtistSongLoader {\n\n    public static ArrayList<Song> getSongsForArtist(Context context, long artistID) {\n        Cursor cursor = makeArtistSongCursor(context, artistID);\n        ArrayList songsList = new ArrayList();\n        if ((cursor != null) && (cursor.moveToFirst()))\n            do {\n                long id = cursor.getLong(0);\n                String title = cursor.getString(1);\n                String artist = cursor.getString(2);\n                String album = cursor.getString(3);\n                int duration = cursor.getInt(4);\n                int trackNumber = cursor.getInt(5);\n                long albumId = cursor.getInt(6);\n                long artistId = artistID;\n\n                songsList.add(new Song(id, albumId, artistID, title, artist, album, duration, trackNumber));\n            }\n            while (cursor.moveToNext());\n        if (cursor != null)\n            cursor.close();\n        return songsList;\n    }\n\n\n    public static Cursor makeArtistSongCursor(Context context, long artistID) {\n        ContentResolver contentResolver = context.getContentResolver();\n        final String artistSongSortOrder = PreferencesUtility.getInstance(context).getArtistSongSortOrder();\n        Uri uri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;\n        String string = \"is_music=1 AND title != '' AND artist_id=\" + artistID;\n        return contentResolver.query(uri, new String[]{\"_id\", \"title\", \"artist\", \"album\", \"duration\", \"track\", \"album_id\"}, string, null, artistSongSortOrder);\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/dataloaders/FolderLoader.java",
    "content": "package com.naman14.timber.dataloaders;\n\nimport android.text.TextUtils;\n\nimport java.io.File;\nimport java.io.FileFilter;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.Comparator;\nimport java.util.List;\n\n/**\n * Created by nv95 on 10.11.16.\n */\n\npublic class FolderLoader {\n\n    private static final String[] SUPPORTED_EXT = new String[] {\n            \"mp3\",\n            \"mp4\",\n            \"m4a\",\n            \"aac\",\n            \"ogg\",\n            \"wav\"\n    };\n\n    public static List<File> getMediaFiles(File dir, final boolean acceptDirs) {\n        ArrayList<File> list = new ArrayList<>();\n        list.add(new File(dir, \"..\"));\n        if (dir.isDirectory()) {\n            List<File> files = Arrays.asList(dir.listFiles(new FileFilter() {\n\n                @Override\n                public boolean accept(File file) {\n                    if (file.isFile()) {\n                        String name = file.getName();\n                        return !\".nomedia\".equals(name) && checkFileExt(name);\n                    } else if (file.isDirectory()) {\n                        return acceptDirs && checkDir(file);\n                    } else\n                        return false;\n                }\n            }));\n            Collections.sort(files, new FilenameComparator());\n            Collections.sort(files, new DirFirstComparator());\n            list.addAll(files);\n        }\n\n        return list;\n    }\n\n    public static boolean isMediaFile(File file) {\n        return file.exists() && file.canRead() && checkFileExt(file.getName());\n    }\n\n    private static boolean checkDir(File dir) {\n        return dir.exists() && dir.canRead() && !\".\".equals(dir.getName()) && dir.listFiles(new FileFilter() {\n            @Override\n            public boolean accept(File pathname) {\n                String name = pathname.getName();\n                return !\".\".equals(name) && !\"..\".equals(name) && pathname.canRead() && (pathname.isDirectory()  || (pathname.isFile() && checkFileExt(name)));\n            }\n\n        }).length != 0;\n    }\n\n    private static boolean checkFileExt(String name) {\n        if (TextUtils.isEmpty(name)) {\n            return false;\n        }\n        int p = name.lastIndexOf(\".\") + 1;\n        if (p < 1) {\n            return false;\n        }\n        String ext = name.substring(p).toLowerCase();\n        for (String o : SUPPORTED_EXT) {\n            if (o.equals(ext)) {\n                return true;\n            }\n        }\n        return false;\n    }\n\n    private static class FilenameComparator implements Comparator<File> {\n        @Override\n        public int compare(File f1, File f2) {\n            return f1.getName().compareTo(f2.getName());\n        }\n    }\n\n    private static class DirFirstComparator implements Comparator<File> {\n        @Override\n        public int compare(File f1, File f2) {\n            if (f1.isDirectory() == f2.isDirectory())\n                return 0;\n            else if (f1.isDirectory() && !f2.isDirectory())\n                return -1;\n            else\n                return 1;\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/dataloaders/LastAddedLoader.java",
    "content": "/*\n * Copyright (C) 2012 Andrew Neal\n * Copyright (C) 2014 The CyanogenMod Project\n * Copyright (C) 2015 Naman Dwivedi\n * Licensed under the Apache License, Version 2.0\n * (the \"License\"); you may not use this file except in compliance with the\n * License. You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law\n * or agreed to in writing, software distributed under the License is\n * distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n * KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\npackage com.naman14.timber.dataloaders;\n\nimport android.content.Context;\nimport android.database.Cursor;\nimport android.provider.MediaStore;\nimport android.provider.MediaStore.Audio.AudioColumns;\n\nimport com.naman14.timber.models.Song;\nimport com.naman14.timber.utils.PreferencesUtility;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\npublic class LastAddedLoader {\n\n    private static Cursor mCursor;\n\n    public static List<Song> getLastAddedSongs(Context context) {\n\n        ArrayList<Song> mSongList = new ArrayList<>();\n        mCursor = makeLastAddedCursor(context);\n\n        if (mCursor != null && mCursor.moveToFirst()) {\n            do {\n                long id = mCursor.getLong(0);\n                String title = mCursor.getString(1);\n                String artist = mCursor.getString(2);\n                String album = mCursor.getString(3);\n                int duration = mCursor.getInt(4);\n                int trackNumber = mCursor.getInt(5);\n                long artistId = mCursor.getInt(6);\n                long albumId = mCursor.getLong(7);\n\n                final Song song = new Song(id, albumId, artistId, title, artist, album, duration, trackNumber);\n\n                mSongList.add(song);\n            } while (mCursor.moveToNext());\n        }\n        if (mCursor != null) {\n            mCursor.close();\n            mCursor = null;\n        }\n        return mSongList;\n    }\n\n    public static final Cursor makeLastAddedCursor(final Context context) {\n        //four weeks ago\n        long fourWeeksAgo = (System.currentTimeMillis() / 1000) - (4 * 3600 * 24 * 7);\n        long cutoff = PreferencesUtility.getInstance(context).getLastAddedCutoff();\n        // use the most recent of the two timestamps\n        if (cutoff < fourWeeksAgo) {\n            cutoff = fourWeeksAgo;\n        }\n\n        final StringBuilder selection = new StringBuilder();\n        selection.append(AudioColumns.IS_MUSIC + \"=1\");\n        selection.append(\" AND \" + AudioColumns.TITLE + \" != ''\");\n        selection.append(\" AND \" + MediaStore.Audio.Media.DATE_ADDED + \">\");\n        selection.append(cutoff);\n\n        return context.getContentResolver().query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,\n                new String[]{\"_id\", \"title\", \"artist\", \"album\", \"duration\", \"track\", \"artist_id\", \"album_id\"}, selection.toString(), null, MediaStore.Audio.Media.DATE_ADDED + \" DESC\");\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/dataloaders/NowPlayingCursor.java",
    "content": "/*\n * Copyright (C) 2012 Andrew Neal\n * Copyright (C) 2014 The CyanogenMod Project\n * Licensed under the Apache License, Version 2.0\n * (the \"License\"); you may not use this file except in compliance with the\n * License. You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law\n * or agreed to in writing, software distributed under the License is\n * distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n * KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\npackage com.naman14.timber.dataloaders;\n\nimport android.content.Context;\nimport android.database.AbstractCursor;\nimport android.database.Cursor;\nimport android.os.RemoteException;\nimport android.provider.BaseColumns;\nimport android.provider.MediaStore;\nimport android.provider.MediaStore.Audio.AudioColumns;\nimport android.util.Log;\n\nimport com.naman14.timber.MusicPlayer;\n\nimport java.util.Arrays;\n\nimport static com.naman14.timber.MusicPlayer.mService;\n\n\npublic class NowPlayingCursor extends AbstractCursor {\n\n    private static final String[] PROJECTION = new String[]{\n\n            BaseColumns._ID,\n\n            AudioColumns.TITLE,\n\n            AudioColumns.ARTIST,\n\n            AudioColumns.ALBUM_ID,\n\n            AudioColumns.ALBUM,\n\n            AudioColumns.DURATION,\n\n            AudioColumns.TRACK,\n\n            AudioColumns.ARTIST_ID,\n\n            AudioColumns.TRACK,\n    };\n\n    private final Context mContext;\n\n    private long[] mNowPlaying;\n\n    private long[] mCursorIndexes;\n\n    private int mSize;\n\n    private int mCurPos;\n\n    private Cursor mQueueCursor;\n\n\n    public NowPlayingCursor(final Context context) {\n        mContext = context;\n        makeNowPlayingCursor();\n    }\n\n\n    @Override\n    public int getCount() {\n        return mSize;\n    }\n\n\n    @Override\n    public boolean onMove(final int oldPosition, final int newPosition) {\n        if (oldPosition == newPosition) {\n            return true;\n        }\n\n        if (mNowPlaying == null || mCursorIndexes == null || newPosition >= mNowPlaying.length) {\n            return false;\n        }\n\n        final long id = mNowPlaying[newPosition];\n        final int cursorIndex = Arrays.binarySearch(mCursorIndexes, id);\n        mQueueCursor.moveToPosition(cursorIndex);\n        mCurPos = newPosition;\n        return true;\n    }\n\n    @Override\n    public String getString(final int column) {\n        try {\n            return mQueueCursor.getString(column);\n        } catch (final Exception ignored) {\n            onChange(true);\n            return \"\";\n        }\n    }\n\n\n    @Override\n    public short getShort(final int column) {\n        return mQueueCursor.getShort(column);\n    }\n\n\n    @Override\n    public int getInt(final int column) {\n        try {\n            return mQueueCursor.getInt(column);\n        } catch (final Exception ignored) {\n            onChange(true);\n            return 0;\n        }\n    }\n\n\n    @Override\n    public long getLong(final int column) {\n        try {\n            return mQueueCursor.getLong(column);\n        } catch (final Exception ignored) {\n            onChange(true);\n            return 0;\n        }\n    }\n\n\n    @Override\n    public float getFloat(final int column) {\n        return mQueueCursor.getFloat(column);\n    }\n\n\n    @Override\n    public double getDouble(final int column) {\n        return mQueueCursor.getDouble(column);\n    }\n\n\n    @Override\n    public int getType(final int column) {\n        return mQueueCursor.getType(column);\n    }\n\n    @Override\n    public boolean isNull(final int column) {\n        return mQueueCursor.isNull(column);\n    }\n\n\n    @Override\n    public String[] getColumnNames() {\n        return PROJECTION;\n    }\n\n\n    @SuppressWarnings(\"deprecation\")\n    @Override\n    public void deactivate() {\n        if (mQueueCursor != null) {\n            mQueueCursor.deactivate();\n        }\n    }\n\n    @Override\n    public boolean requery() {\n        makeNowPlayingCursor();\n        return true;\n    }\n\n\n    @Override\n    public void close() {\n        try {\n            if (mQueueCursor != null) {\n                mQueueCursor.close();\n                mQueueCursor = null;\n            }\n        } catch (final Exception close) {\n        }\n        super.close();\n    }\n\n\n    private void makeNowPlayingCursor() {\n        mQueueCursor = null;\n        mNowPlaying = MusicPlayer.getQueue();\n        Log.d(\"lol1\", mNowPlaying.toString() + \"   \" + mNowPlaying.length);\n        mSize = mNowPlaying.length;\n        if (mSize == 0) {\n            return;\n        }\n\n        final StringBuilder selection = new StringBuilder();\n        selection.append(MediaStore.Audio.Media._ID + \" IN (\");\n        for (int i = 0; i < mSize; i++) {\n            selection.append(mNowPlaying[i]);\n            if (i < mSize - 1) {\n                selection.append(\",\");\n            }\n        }\n        selection.append(\")\");\n\n        mQueueCursor = mContext.getContentResolver().query(\n                MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, PROJECTION, selection.toString(),\n                null, MediaStore.Audio.Media._ID);\n\n        if (mQueueCursor == null) {\n            mSize = 0;\n            return;\n        }\n\n        final int playlistSize = mQueueCursor.getCount();\n        mCursorIndexes = new long[playlistSize];\n        mQueueCursor.moveToFirst();\n        final int columnIndex = mQueueCursor.getColumnIndexOrThrow(MediaStore.Audio.Media._ID);\n        for (int i = 0; i < playlistSize; i++) {\n            mCursorIndexes[i] = mQueueCursor.getLong(columnIndex);\n            mQueueCursor.moveToNext();\n        }\n        mQueueCursor.moveToFirst();\n        mCurPos = -1;\n\n        int removed = 0;\n        for (int i = mNowPlaying.length - 1; i >= 0; i--) {\n            final long trackId = mNowPlaying[i];\n            final int cursorIndex = Arrays.binarySearch(mCursorIndexes, trackId);\n            if (cursorIndex < 0) {\n                removed += MusicPlayer.removeTrack(trackId);\n            }\n        }\n        if (removed > 0) {\n            mNowPlaying = MusicPlayer.getQueue();\n            mSize = mNowPlaying.length;\n            if (mSize == 0) {\n                mCursorIndexes = null;\n                return;\n            }\n        }\n    }\n\n\n    public boolean removeItem(final int which) {\n        try {\n            if (mService.removeTracks(which, which) == 0) {\n                return false;\n            }\n            int i = which;\n            mSize--;\n            while (i < mSize) {\n                mNowPlaying[i] = mNowPlaying[i + 1];\n                i++;\n            }\n            onMove(-1, mCurPos);\n        } catch (final RemoteException ignored) {\n        }\n        return true;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/dataloaders/PlaylistLoader.java",
    "content": "/*\n * Copyright (C) 2015 Naman Dwivedi\n *\n * Licensed under the GNU General Public License v3\n *\n * This is free software: you can redistribute it and/or modify it\n * under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;\n * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n * See the GNU General Public License for more details.\n */\n\npackage com.naman14.timber.dataloaders;\n\nimport android.content.Context;\nimport android.content.res.Resources;\nimport android.database.Cursor;\nimport android.net.Uri;\nimport android.provider.BaseColumns;\nimport android.provider.MediaStore;\nimport android.provider.MediaStore.Audio.PlaylistsColumns;\n\nimport com.naman14.timber.models.Playlist;\nimport com.naman14.timber.utils.TimberUtils;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\npublic class PlaylistLoader {\n\n    static ArrayList<Playlist> mPlaylistList;\n    private static Cursor mCursor;\n\n    public static List<Playlist> getPlaylists(Context context, boolean defaultIncluded) {\n\n        mPlaylistList = new ArrayList<>();\n\n        if (defaultIncluded)\n            makeDefaultPlaylists(context);\n\n        mCursor = makePlaylistCursor(context);\n\n        if (mCursor != null && mCursor.moveToFirst()) {\n            do {\n\n                final long id = mCursor.getLong(0);\n\n                final String name = mCursor.getString(1);\n\n                final int songCount = TimberUtils.getSongCountForPlaylist(context, id);\n\n                final Playlist playlist = new Playlist(id, name, songCount);\n\n                mPlaylistList.add(playlist);\n            } while (mCursor.moveToNext());\n        }\n        if (mCursor != null) {\n            mCursor.close();\n            mCursor = null;\n        }\n        return mPlaylistList;\n    }\n\n    private static void makeDefaultPlaylists(Context context) {\n        final Resources resources = context.getResources();\n\n        /* Last added list */\n        final Playlist lastAdded = new Playlist(TimberUtils.PlaylistType.LastAdded.mId,\n                resources.getString(TimberUtils.PlaylistType.LastAdded.mTitleId), -1);\n        mPlaylistList.add(lastAdded);\n\n        /* Recently Played */\n        final Playlist recentlyPlayed = new Playlist(TimberUtils.PlaylistType.RecentlyPlayed.mId,\n                resources.getString(TimberUtils.PlaylistType.RecentlyPlayed.mTitleId), -1);\n        mPlaylistList.add(recentlyPlayed);\n\n        /* Top Tracks */\n        final Playlist topTracks = new Playlist(TimberUtils.PlaylistType.TopTracks.mId,\n                resources.getString(TimberUtils.PlaylistType.TopTracks.mTitleId), -1);\n        mPlaylistList.add(topTracks);\n    }\n\n\n    public static final Cursor makePlaylistCursor(final Context context) {\n        return context.getContentResolver().query(MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI,\n                new String[]{\n                        BaseColumns._ID,\n                        PlaylistsColumns.NAME\n                }, null, null, MediaStore.Audio.Playlists.DEFAULT_SORT_ORDER);\n    }\n\n    public static void deletePlaylists(Context context, long playlistId) {\n        Uri localUri = MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI;\n        StringBuilder localStringBuilder = new StringBuilder();\n        localStringBuilder.append(\"_id IN (\");\n        localStringBuilder.append((playlistId));\n        localStringBuilder.append(\")\");\n        context.getContentResolver().delete(localUri, localStringBuilder.toString(), null);\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/dataloaders/PlaylistSongLoader.java",
    "content": "/*\n * Copyright (C) 2015 Naman Dwivedi\n *\n * Licensed under the GNU General Public License v3\n *\n * This is free software: you can redistribute it and/or modify it\n * under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;\n * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n * See the GNU General Public License for more details.\n */\n\npackage com.naman14.timber.dataloaders;\n\nimport android.content.ContentProviderOperation;\nimport android.content.Context;\nimport android.content.OperationApplicationException;\nimport android.database.Cursor;\nimport android.net.Uri;\nimport android.os.RemoteException;\nimport android.provider.MediaStore;\nimport android.provider.MediaStore.Audio.AudioColumns;\nimport android.provider.MediaStore.Audio.Playlists;\n\nimport com.naman14.timber.models.Song;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\npublic class PlaylistSongLoader {\n\n    private static Cursor mCursor;\n\n    private static long mPlaylistID;\n    private static Context context;\n\n\n    public static List<Song> getSongsInPlaylist(Context mContext, long playlistID) {\n        ArrayList<Song> mSongList = new ArrayList<>();\n\n        context = mContext;\n        mPlaylistID = playlistID;\n\n        final int playlistCount = countPlaylist(context, mPlaylistID);\n\n        mCursor = makePlaylistSongCursor(context, mPlaylistID);\n\n        if (mCursor != null) {\n            boolean runCleanup = false;\n            if (mCursor.getCount() != playlistCount) {\n                runCleanup = true;\n            }\n\n            if (!runCleanup && mCursor.moveToFirst()) {\n                final int playOrderCol = mCursor.getColumnIndexOrThrow(Playlists.Members.PLAY_ORDER);\n\n                int lastPlayOrder = -1;\n                do {\n                    int playOrder = mCursor.getInt(playOrderCol);\n                    if (playOrder == lastPlayOrder) {\n                        runCleanup = true;\n                        break;\n                    }\n                    lastPlayOrder = playOrder;\n                } while (mCursor.moveToNext());\n            }\n\n            if (runCleanup) {\n\n                cleanupPlaylist(context, mPlaylistID, mCursor);\n\n                mCursor.close();\n                mCursor = makePlaylistSongCursor(context, mPlaylistID);\n                if (mCursor != null) {\n                }\n            }\n        }\n\n        if (mCursor != null && mCursor.moveToFirst()) {\n            do {\n\n                final long id = mCursor.getLong(mCursor\n                        .getColumnIndexOrThrow(MediaStore.Audio.Playlists.Members.AUDIO_ID));\n\n                final String songName = mCursor.getString(mCursor\n                        .getColumnIndexOrThrow(AudioColumns.TITLE));\n\n                final String artist = mCursor.getString(mCursor\n                        .getColumnIndexOrThrow(AudioColumns.ARTIST));\n\n                final long albumId = mCursor.getLong(mCursor\n                        .getColumnIndexOrThrow(AudioColumns.ALBUM_ID));\n\n                final long artistId = mCursor.getLong(mCursor\n                        .getColumnIndexOrThrow(AudioColumns.ARTIST_ID));\n\n                final String album = mCursor.getString(mCursor\n                        .getColumnIndexOrThrow(AudioColumns.ALBUM));\n\n                final long duration = mCursor.getLong(mCursor\n                        .getColumnIndexOrThrow(AudioColumns.DURATION));\n\n                final int durationInSecs = (int) duration / 1000;\n\n                final int tracknumber = mCursor.getInt(mCursor\n                        .getColumnIndexOrThrow(AudioColumns.TRACK));\n\n                final Song song = new Song(id, albumId, artistId, songName, artist, album, durationInSecs, tracknumber);\n\n                mSongList.add(song);\n            } while (mCursor.moveToNext());\n        }\n        // Close the cursor\n        if (mCursor != null) {\n            mCursor.close();\n            mCursor = null;\n        }\n        return mSongList;\n    }\n\n    private static void cleanupPlaylist(final Context context, final long playlistId,\n                                        final Cursor cursor) {\n        final int idCol = cursor.getColumnIndexOrThrow(MediaStore.Audio.Playlists.Members.AUDIO_ID);\n        final Uri uri = MediaStore.Audio.Playlists.Members.getContentUri(\"external\", playlistId);\n\n        ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();\n\n        ops.add(ContentProviderOperation.newDelete(uri).build());\n\n        final int YIELD_FREQUENCY = 100;\n\n        if (cursor.moveToFirst() && cursor.getCount() > 0) {\n            do {\n                final ContentProviderOperation.Builder builder =\n                        ContentProviderOperation.newInsert(uri)\n                                .withValue(Playlists.Members.PLAY_ORDER, cursor.getPosition())\n                                .withValue(Playlists.Members.AUDIO_ID, cursor.getLong(idCol));\n\n                if ((cursor.getPosition() + 1) % YIELD_FREQUENCY == 0) {\n                    builder.withYieldAllowed(true);\n                }\n                ops.add(builder.build());\n            } while (cursor.moveToNext());\n        }\n\n        try {\n            context.getContentResolver().applyBatch(MediaStore.AUTHORITY, ops);\n        } catch (RemoteException e) {\n        } catch (OperationApplicationException e) {\n        }\n    }\n\n\n    private static int countPlaylist(final Context context, final long playlistId) {\n        Cursor c = null;\n        try {\n            c = context.getContentResolver().query(\n                    MediaStore.Audio.Playlists.Members.getContentUri(\"external\", playlistId),\n                    new String[]{\n                            MediaStore.Audio.Playlists.Members.AUDIO_ID,\n                    }, null, null,\n                    MediaStore.Audio.Playlists.Members.DEFAULT_SORT_ORDER);\n\n            if (c != null) {\n                return c.getCount();\n            }\n        } finally {\n            if (c != null) {\n                c.close();\n                c = null;\n            }\n        }\n\n        return 0;\n    }\n\n\n    public static final Cursor makePlaylistSongCursor(final Context context, final Long playlistID) {\n        final StringBuilder mSelection = new StringBuilder();\n        mSelection.append(AudioColumns.IS_MUSIC + \"=1\");\n        mSelection.append(\" AND \" + AudioColumns.TITLE + \" != ''\");\n        return context.getContentResolver().query(\n                MediaStore.Audio.Playlists.Members.getContentUri(\"external\", playlistID),\n                new String[]{\n                        MediaStore.Audio.Playlists.Members._ID,\n                        MediaStore.Audio.Playlists.Members.AUDIO_ID,\n                        AudioColumns.TITLE,\n                        AudioColumns.ARTIST,\n                        AudioColumns.ALBUM_ID,\n                        AudioColumns.ARTIST_ID,\n                        AudioColumns.ALBUM,\n                        AudioColumns.DURATION,\n                        AudioColumns.TRACK,\n                        Playlists.Members.PLAY_ORDER,\n                }, mSelection.toString(), null,\n                MediaStore.Audio.Playlists.Members.DEFAULT_SORT_ORDER);\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/dataloaders/QueueLoader.java",
    "content": "/*\n * Copyright (C) 2015 Naman Dwivedi\n *\n * Licensed under the GNU General Public License v3\n *\n * This is free software: you can redistribute it and/or modify it\n * under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;\n * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n * See the GNU General Public License for more details.\n */\n\npackage com.naman14.timber.dataloaders;\n\nimport android.content.Context;\n\nimport com.naman14.timber.models.Song;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n\npublic class QueueLoader {\n\n\n    private static NowPlayingCursor mCursor;\n\n    public static List<Song> getQueueSongs(Context context) {\n\n        final ArrayList<Song> mSongList = new ArrayList<>();\n        mCursor = new NowPlayingCursor(context);\n\n        if (mCursor != null && mCursor.moveToFirst()) {\n            do {\n\n                final long id = mCursor.getLong(0);\n\n                final String songName = mCursor.getString(1);\n\n                final String artist = mCursor.getString(2);\n\n                final long albumId = mCursor.getLong(3);\n\n                final String album = mCursor.getString(4);\n\n                final int duration = mCursor.getInt(5);\n\n                final long artistid = mCursor.getInt(7);\n\n                final int tracknumber = mCursor.getInt(6);\n\n                final Song song = new Song(id, albumId, artistid, songName, artist, album, duration, tracknumber);\n\n                mSongList.add(song);\n            } while (mCursor.moveToNext());\n        }\n        if (mCursor != null) {\n            mCursor.close();\n            mCursor = null;\n        }\n        return mSongList;\n    }\n\n\n\n}\n"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/dataloaders/SongLoader.java",
    "content": "/*\n * Copyright (C) 2015 Naman Dwivedi\n *\n * Licensed under the GNU General Public License v3\n *\n * This is free software: you can redistribute it and/or modify it\n * under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;\n * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n * See the GNU General Public License for more details.\n */\n\npackage com.naman14.timber.dataloaders;\n\nimport android.content.ContentResolver;\nimport android.content.Context;\nimport android.database.Cursor;\nimport android.media.MediaMetadataRetriever;\nimport android.net.Uri;\nimport android.provider.BaseColumns;\nimport android.provider.MediaStore;\nimport android.text.TextUtils;\n\nimport com.naman14.timber.models.Song;\nimport com.naman14.timber.utils.PreferencesUtility;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\npublic class SongLoader {\n\n    private static final long[] sEmptyList = new long[0];\n\n    public static ArrayList<Song> getSongsForCursor(Cursor cursor) {\n        ArrayList arrayList = new ArrayList();\n        if ((cursor != null) && (cursor.moveToFirst()))\n            do {\n                long id = cursor.getLong(0);\n                String title = cursor.getString(1);\n                String artist = cursor.getString(2);\n                String album = cursor.getString(3);\n                int duration = cursor.getInt(4);\n                int trackNumber = cursor.getInt(5);\n                long artistId = cursor.getInt(6);\n                long albumId = cursor.getLong(7);\n\n                arrayList.add(new Song(id, albumId, artistId, title, artist, album, duration, trackNumber));\n            }\n            while (cursor.moveToNext());\n        if (cursor != null)\n            cursor.close();\n        return arrayList;\n    }\n\n    public static Song getSongForCursor(Cursor cursor) {\n        Song song = new Song();\n        if ((cursor != null) && (cursor.moveToFirst())) {\n            long id = cursor.getLong(0);\n            String title = cursor.getString(1);\n            String artist = cursor.getString(2);\n            String album = cursor.getString(3);\n            int duration = cursor.getInt(4);\n            int trackNumber = cursor.getInt(5);\n            long artistId = cursor.getInt(6);\n            long albumId = cursor.getLong(7);\n\n            song = new Song(id, albumId, artistId, title, artist, album, duration, trackNumber);\n        }\n\n        if (cursor != null)\n            cursor.close();\n        return song;\n    }\n\n    public static final long[] getSongListForCursor(Cursor cursor) {\n        if (cursor == null) {\n            return sEmptyList;\n        }\n        final int len = cursor.getCount();\n        final long[] list = new long[len];\n        cursor.moveToFirst();\n        int columnIndex = -1;\n        try {\n            columnIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.Playlists.Members.AUDIO_ID);\n        } catch (final IllegalArgumentException notaplaylist) {\n            columnIndex = cursor.getColumnIndexOrThrow(BaseColumns._ID);\n        }\n        for (int i = 0; i < len; i++) {\n            list[i] = cursor.getLong(columnIndex);\n            cursor.moveToNext();\n        }\n        cursor.close();\n        cursor = null;\n        return list;\n    }\n\n    public static Song getSongFromPath(String songPath, Context context) {\n        ContentResolver cr = context.getContentResolver();\n\n        Uri uri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;\n        String selection = MediaStore.Audio.Media.DATA;\n        String[] selectionArgs = {songPath};\n        String[] projection = new String[]{\"_id\", \"title\", \"artist\", \"album\", \"duration\", \"track\", \"artist_id\", \"album_id\"};\n        String sortOrder = MediaStore.Audio.Media.TITLE + \" ASC\";\n\n        Cursor cursor = cr.query(uri, projection, selection + \"=?\", selectionArgs, sortOrder);\n\n        if (cursor != null && cursor.getCount() > 0) {\n            Song song = getSongForCursor(cursor);\n            cursor.close();\n            return song;\n        }\n        else return new Song();\n    }\n\n    public static ArrayList<Song> getAllSongs(Context context) {\n        return getSongsForCursor(makeSongCursor(context, null, null));\n    }\n\n    public static long[] getSongListInFolder(Context context, String path) {\n        String[] whereArgs = new String[]{path + \"%\"};\n        return getSongListForCursor(makeSongCursor(context, MediaStore.Audio.Media.DATA + \" LIKE ?\", whereArgs, null));\n    }\n\n    public static Song getSongForID(Context context, long id) {\n        return getSongForCursor(makeSongCursor(context, \"_id=\" + String.valueOf(id), null));\n    }\n\n    public static List<Song> searchSongs(Context context, String searchString, int limit) {\n        ArrayList<Song> result = getSongsForCursor(makeSongCursor(context, \"title LIKE ?\", new String[]{searchString + \"%\"}));\n        if (result.size() < limit) {\n            result.addAll(getSongsForCursor(makeSongCursor(context, \"title LIKE ?\", new String[]{\"%_\" + searchString + \"%\"})));\n        }\n        return result.size() < limit ? result : result.subList(0, limit);\n    }\n\n\n    public static Cursor makeSongCursor(Context context, String selection, String[] paramArrayOfString) {\n        final String songSortOrder = PreferencesUtility.getInstance(context).getSongSortOrder();\n        return makeSongCursor(context, selection, paramArrayOfString, songSortOrder);\n    }\n\n    private static Cursor makeSongCursor(Context context, String selection, String[] paramArrayOfString, String sortOrder) {\n        String selectionStatement = \"is_music=1 AND title != ''\";\n\n        if (!TextUtils.isEmpty(selection)) {\n            selectionStatement = selectionStatement + \" AND \" + selection;\n        }\n        return context.getContentResolver().query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, new String[]{\"_id\", \"title\", \"artist\", \"album\", \"duration\", \"track\", \"artist_id\", \"album_id\"}, selectionStatement, paramArrayOfString, sortOrder);\n\n    }\n\n    public static Song songFromFile(String filePath) {\n        MediaMetadataRetriever mmr = new MediaMetadataRetriever();\n        mmr.setDataSource(filePath);\n        return new Song(\n                -1,\n                -1,\n                -1,\n                mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_TITLE),\n                mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ARTIST),\n                mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ALBUM),\n                Integer.parseInt(mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)),\n                0\n        );\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/dataloaders/SortedCursor.java",
    "content": "/*\n* Copyright (C) 2014 The CyanogenMod Project\n*\n* Licensed under the Apache License, Version 2.0 (the \"License\");\n* you may not use this file except in compliance with the License.\n* You may obtain a copy of the License at\n*\n* http://www.apache.org/licenses/LICENSE-2.0\n*\n* Unless required by applicable law or agreed to in writing, software\n* distributed under the License is distributed on an \"AS IS\" BASIS,\n* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n* See the License for the specific language governing permissions and\n* limitations under the License.\n*/\npackage com.naman14.timber.dataloaders;\n\nimport android.database.AbstractCursor;\nimport android.database.Cursor;\n\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.HashMap;\nimport java.util.List;\n\n/**\n * This cursor basically wraps a song cursor and is given a list of the order of the ids of the\n * contents of the cursor. It wraps the Cursor and simulates the internal cursor being sorted\n * by moving the point to the appropriate spot\n */\npublic class SortedCursor extends AbstractCursor {\n    // cursor to wrap\n    private final Cursor mCursor;\n    // the map of external indices to internal indices\n    private ArrayList<Integer> mOrderedPositions;\n    // this contains the ids that weren't found in the underlying cursor\n    private ArrayList<Long> mMissingIds;\n    // this contains the mapped cursor positions and afterwards the extra ids that weren't found\n    private HashMap<Long, Integer> mMapCursorPositions;\n    // extra we want to store with the cursor\n    private ArrayList<Object> mExtraData;\n\n    /**\n     * @param cursor     to wrap\n     * @param order      the list of unique ids in sorted order to display\n     * @param columnName the column name of the id to look up in the internal cursor\n     */\n    public SortedCursor(final Cursor cursor, final long[] order, final String columnName,\n                        final List<? extends Object> extraData) {\n        if (cursor == null) {\n            throw new IllegalArgumentException(\"Non-null cursor is needed\");\n        }\n\n        mCursor = cursor;\n        mMissingIds = buildCursorPositionMapping(order, columnName, extraData);\n    }\n\n    /**\n     * This function populates mOrderedPositions with the cursor positions in the order based\n     * on the order passed in\n     *\n     * @param order     the target order of the internal cursor\n     * @param extraData Extra data we want to add to the cursor\n     * @return returns the ids that aren't found in the underlying cursor\n     */\n    private ArrayList<Long> buildCursorPositionMapping(final long[] order,\n                                                       final String columnName, final List<? extends Object> extraData) {\n        ArrayList<Long> missingIds = new ArrayList<Long>();\n\n        mOrderedPositions = new ArrayList<Integer>(mCursor.getCount());\n        mExtraData = new ArrayList<Object>();\n\n        mMapCursorPositions = new HashMap<Long, Integer>(mCursor.getCount());\n        final int idPosition = mCursor.getColumnIndex(columnName);\n\n        if (mCursor.moveToFirst()) {\n            // first figure out where each of the ids are in the cursor\n            do {\n                mMapCursorPositions.put(mCursor.getLong(idPosition), mCursor.getPosition());\n            } while (mCursor.moveToNext());\n\n            // now create the ordered positions to map to the internal cursor given the\n            // external sort order\n            for (int i = 0; order != null && i < order.length; i++) {\n                final long id = order[i];\n                if (mMapCursorPositions.containsKey(id)) {\n                    mOrderedPositions.add(mMapCursorPositions.get(id));\n                    mMapCursorPositions.remove(id);\n                    if (extraData != null) {\n                        mExtraData.add(extraData.get(i));\n                    }\n                } else {\n                    missingIds.add(id);\n                }\n            }\n\n            mCursor.moveToFirst();\n        }\n\n        return missingIds;\n    }\n\n    /**\n     * @return the list of ids that weren't found in the underlying cursor\n     */\n    public ArrayList<Long> getMissingIds() {\n        return mMissingIds;\n    }\n\n    /**\n     * @return the list of ids that were in the underlying cursor but not part of the ordered list\n     */\n    public Collection<Long> getExtraIds() {\n        return mMapCursorPositions.keySet();\n    }\n\n    /**\n     * @return the extra object data that was passed in to be attached to the current row\n     */\n    public Object getExtraData() {\n        int position = getPosition();\n        return position < mExtraData.size() ? mExtraData.get(position) : null;\n    }\n\n    @Override\n    public void close() {\n        mCursor.close();\n\n        super.close();\n    }\n\n    @Override\n    public int getCount() {\n        return mOrderedPositions.size();\n    }\n\n    @Override\n    public String[] getColumnNames() {\n        return mCursor.getColumnNames();\n    }\n\n    @Override\n    public String getString(int column) {\n        return mCursor.getString(column);\n    }\n\n    @Override\n    public short getShort(int column) {\n        return mCursor.getShort(column);\n    }\n\n    @Override\n    public int getInt(int column) {\n        return mCursor.getInt(column);\n    }\n\n    @Override\n    public long getLong(int column) {\n        return mCursor.getLong(column);\n    }\n\n    @Override\n    public float getFloat(int column) {\n        return mCursor.getFloat(column);\n    }\n\n    @Override\n    public double getDouble(int column) {\n        return mCursor.getDouble(column);\n    }\n\n    @Override\n    public boolean isNull(int column) {\n        return mCursor.isNull(column);\n    }\n\n    @Override\n    public boolean onMove(int oldPosition, int newPosition) {\n        if (newPosition >= 0 && newPosition < getCount()) {\n            mCursor.moveToPosition(mOrderedPositions.get(newPosition));\n            return true;\n        }\n\n        return false;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/dataloaders/TopTracksLoader.java",
    "content": "/*\n* Copyright (C) 2014 The CyanogenMod Project\n*\n* Licensed under the Apache License, Version 2.0 (the \"License\");\n* you may not use this file except in compliance with the License.\n* You may obtain a copy of the License at\n*\n* http://www.apache.org/licenses/LICENSE-2.0\n*\n* Unless required by applicable law or agreed to in writing, software\n* distributed under the License is distributed on an \"AS IS\" BASIS,\n* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n* See the License for the specific language governing permissions and\n* limitations under the License.\n*/\n\npackage com.naman14.timber.dataloaders;\n\nimport android.content.Context;\nimport android.database.Cursor;\nimport android.provider.BaseColumns;\n\nimport com.naman14.timber.provider.RecentStore;\nimport com.naman14.timber.provider.SongPlayCount;\n\nimport java.util.ArrayList;\n\npublic class TopTracksLoader extends SongLoader {\n\n    public static final int NUMBER_OF_SONGS = 99;\n    protected static QueryType mQueryType;\n    private static Context mContext;\n\n    public TopTracksLoader(final Context context, QueryType type) {\n        mContext = context;\n        mQueryType = type;\n    }\n\n    public static Cursor getCursor() {\n        SortedCursor retCursor = null;\n        if (mQueryType == QueryType.TopTracks) {\n            retCursor = makeTopTracksCursor(mContext);\n        } else if (mQueryType == QueryType.RecentSongs) {\n            retCursor = makeRecentTracksCursor(mContext);\n        }\n\n        if (retCursor != null) {\n            ArrayList<Long> missingIds = retCursor.getMissingIds();\n            if (missingIds != null && missingIds.size() > 0) {\n                for (long id : missingIds) {\n                    if (mQueryType == QueryType.TopTracks) {\n                        SongPlayCount.getInstance(mContext).removeItem(id);\n                    } else if (mQueryType == QueryType.RecentSongs) {\n                        RecentStore.getInstance(mContext).removeItem(id);\n                    }\n                }\n            }\n        }\n\n        return retCursor;\n    }\n\n    public static final SortedCursor makeTopTracksCursor(final Context context) {\n\n        Cursor songs = SongPlayCount.getInstance(context).getTopPlayedResults(NUMBER_OF_SONGS);\n\n        try {\n            return makeSortedCursor(context, songs,\n                    songs.getColumnIndex(SongPlayCount.SongPlayCountColumns.ID));\n        } finally {\n            if (songs != null) {\n                songs.close();\n                songs = null;\n            }\n        }\n    }\n\n    public static final SortedCursor makeRecentTracksCursor(final Context context) {\n\n        Cursor songs = RecentStore.getInstance(context).queryRecentIds(null);\n\n        try {\n            return makeSortedCursor(context, songs,\n                    songs.getColumnIndex(SongPlayCount.SongPlayCountColumns.ID));\n        } finally {\n            if (songs != null) {\n                songs.close();\n                songs = null;\n            }\n        }\n    }\n\n    public static final SortedCursor makeSortedCursor(final Context context, final Cursor cursor,\n                                                      final int idColumn) {\n        if (cursor != null && cursor.moveToFirst()) {\n\n            StringBuilder selection = new StringBuilder();\n            selection.append(BaseColumns._ID);\n            selection.append(\" IN (\");\n\n            long[] order = new long[cursor.getCount()];\n\n            long id = cursor.getLong(idColumn);\n            selection.append(id);\n            order[cursor.getPosition()] = id;\n\n            while (cursor.moveToNext()) {\n                selection.append(\",\");\n\n                id = cursor.getLong(idColumn);\n                order[cursor.getPosition()] = id;\n                selection.append(String.valueOf(id));\n            }\n\n            selection.append(\")\");\n\n            Cursor songCursor = makeSongCursor(context, selection.toString(), null);\n            if (songCursor != null) {\n                return new SortedCursor(songCursor, order, BaseColumns._ID, null);\n            }\n        }\n\n        return null;\n    }\n\n\n    public enum QueryType {\n        TopTracks,\n        RecentSongs,\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/dialogs/AddPlaylistDialog.java",
    "content": "package com.naman14.timber.dialogs;\n\nimport android.app.Dialog;\nimport android.os.Bundle;\nimport androidx.annotation.NonNull;\nimport androidx.fragment.app.DialogFragment;\nimport android.view.View;\n\nimport com.afollestad.materialdialogs.MaterialDialog;\nimport com.naman14.timber.MusicPlayer;\nimport com.naman14.timber.dataloaders.PlaylistLoader;\nimport com.naman14.timber.models.Playlist;\nimport com.naman14.timber.models.Song;\n\nimport java.util.List;\n\n/**\n * Created by naman on 20/12/15.\n */\npublic class AddPlaylistDialog extends DialogFragment {\n\n    public static AddPlaylistDialog newInstance(Song song) {\n        long[] songs = new long[1];\n        songs[0] = song.id;\n        return newInstance(songs);\n    }\n\n    public static AddPlaylistDialog newInstance(long[] songList) {\n        AddPlaylistDialog dialog = new AddPlaylistDialog();\n        Bundle bundle = new Bundle();\n        bundle.putLongArray(\"songs\", songList);\n        dialog.setArguments(bundle);\n        return dialog;\n    }\n\n    @NonNull\n    @Override\n    public Dialog onCreateDialog(Bundle savedInstanceState) {\n\n        final List<Playlist> playlists = PlaylistLoader.getPlaylists(getActivity(), false);\n        CharSequence[] chars = new CharSequence[playlists.size() + 1];\n        chars[0] = \"Create new playlist\";\n\n        for (int i = 0; i < playlists.size(); i++) {\n            chars[i + 1] = playlists.get(i).name;\n        }\n        return new MaterialDialog.Builder(getActivity()).title(\"Add to playlist\").items(chars).itemsCallback(new MaterialDialog.ListCallback() {\n            @Override\n            public void onSelection(MaterialDialog dialog, View itemView, int which, CharSequence text) {\n                long[] songs = getArguments().getLongArray(\"songs\");\n                if (which == 0) {\n                    CreatePlaylistDialog.newInstance(songs).show(getActivity().getSupportFragmentManager(), \"CREATE_PLAYLIST\");\n                    return;\n                }\n\n                MusicPlayer.addToPlaylist(getActivity(), songs, playlists.get(which - 1).id);\n                dialog.dismiss();\n\n            }\n        }).build();\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/dialogs/CreatePlaylistDialog.java",
    "content": "package com.naman14.timber.dialogs;\n\nimport android.app.Dialog;\nimport android.os.Bundle;\nimport androidx.annotation.NonNull;\nimport androidx.fragment.app.DialogFragment;\nimport android.widget.Toast;\n\nimport com.afollestad.materialdialogs.MaterialDialog;\nimport com.naman14.timber.MusicPlayer;\nimport com.naman14.timber.fragments.PlaylistFragment;\nimport com.naman14.timber.models.Song;\n\n/**\n * Created by naman on 20/12/15.\n */\npublic class CreatePlaylistDialog extends DialogFragment {\n\n    public static CreatePlaylistDialog newInstance() {\n        return newInstance((Song) null);\n    }\n\n    public static CreatePlaylistDialog newInstance(Song song) {\n        long[] songs;\n        if (song == null) {\n            songs = new long[0];\n        } else {\n            songs = new long[1];\n            songs[0] = song.id;\n        }\n        return newInstance(songs);\n    }\n\n    public static CreatePlaylistDialog newInstance(long[] songList) {\n        CreatePlaylistDialog dialog = new CreatePlaylistDialog();\n        Bundle bundle = new Bundle();\n        bundle.putLongArray(\"songs\", songList);\n        dialog.setArguments(bundle);\n        return dialog;\n    }\n\n    @NonNull\n    @Override\n    public Dialog onCreateDialog(Bundle savedInstanceState) {\n        return new MaterialDialog.Builder(getActivity()).positiveText(\"Create\").negativeText(\"Cancel\").input(\"Enter playlist name\", \"\", false, new MaterialDialog.InputCallback() {\n            @Override\n            public void onInput(@NonNull MaterialDialog dialog, CharSequence input) {\n\n                long[] songs = getArguments().getLongArray(\"songs\");\n                long playistId = MusicPlayer.createPlaylist(getActivity(), input.toString());\n\n                if (playistId != -1) {\n                    if (songs != null && songs.length != 0)\n                        MusicPlayer.addToPlaylist(getActivity(), songs, playistId);\n                    else\n                        Toast.makeText(getActivity(), \"Created playlist\", Toast.LENGTH_SHORT).show();\n                    if (getParentFragment() instanceof PlaylistFragment) {\n                        ((PlaylistFragment) getParentFragment()).updatePlaylists(playistId);\n                    }\n                } else {\n                    Toast.makeText(getActivity(), \"Unable to create playlist\", Toast.LENGTH_SHORT).show();\n                }\n\n            }\n        }).build();\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/dialogs/LastFmLoginDialog.java",
    "content": "package com.naman14.timber.dialogs;\n\nimport android.app.Dialog;\nimport android.app.DialogFragment;\nimport android.app.ProgressDialog;\nimport android.os.Bundle;\nimport androidx.annotation.NonNull;\nimport android.widget.EditText;\nimport android.widget.Toast;\n\nimport com.afollestad.materialdialogs.DialogAction;\nimport com.afollestad.materialdialogs.MaterialDialog;\nimport com.naman14.timber.R;\nimport com.naman14.timber.fragments.SettingsFragment;\nimport com.naman14.timber.lastfmapi.LastFmClient;\nimport com.naman14.timber.lastfmapi.callbacks.UserListener;\nimport com.naman14.timber.lastfmapi.models.UserLoginQuery;\n\n/**\n * Created by christoph on 17.07.16.\n */\npublic class LastFmLoginDialog extends DialogFragment {\n    public static final String FRAGMENT_NAME = \"LastFMLogin\";\n\n    @Override\n    public Dialog onCreateDialog(Bundle savedInstanceState) {\n        return new MaterialDialog.Builder(getActivity()).\n                positiveText(\"Login\").\n                negativeText(getString(R.string.cancel)).\n                title(getString(R.string.lastfm_login)).\n                customView(R.layout.dialog_lastfm_login, false).\n                onPositive(new MaterialDialog.SingleButtonCallback() {\n                    @Override\n                    public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) {\n                        String username = ((EditText) dialog.findViewById(R.id.lastfm_username)).getText().toString();\n                        String password = ((EditText) dialog.findViewById(R.id.lastfm_password)).getText().toString();\n                        if (username.length() == 0 || password.length() == 0) return;\n                        final ProgressDialog progressDialog = new ProgressDialog(getActivity());\n                        progressDialog.setMessage(\"Logging in..\");\n                        progressDialog.show();\n                        LastFmClient.getInstance(getActivity()).getUserLoginInfo(new UserLoginQuery(username, password), new UserListener() {\n\n                            @Override\n                            public void userSuccess() {\n                                progressDialog.dismiss();\n                                if (getTargetFragment() instanceof SettingsFragment) {\n                                    ((SettingsFragment) getTargetFragment()).updateLastFM();\n                                }\n                            }\n\n                            @Override\n                            public void userInfoFailed() {\n                                progressDialog.dismiss();\n                                Toast.makeText(getTargetFragment().getActivity(), getString(R.string.lastfm_login_failture), Toast.LENGTH_SHORT).show();\n                            }\n                        });\n                    }\n                }).build();\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/dialogs/StorageSelectDialog.java",
    "content": "package com.naman14.timber.dialogs;\n\nimport android.content.Context;\nimport android.content.DialogInterface;\nimport android.os.Environment;\nimport androidx.appcompat.app.AlertDialog;\n\nimport com.naman14.timber.R;\n\nimport java.io.File;\nimport java.io.FileFilter;\n\n/**\n * Created by nv95 on 06.12.16.\n */\n\npublic class StorageSelectDialog implements DialogInterface.OnClickListener {\n\n    private final AlertDialog mDialog;\n    private final File[] mStorages;\n    private OnDirSelectListener mDirSelectListener;\n\n    public StorageSelectDialog(final Context context) {\n        mStorages = getAvailableStorages(context);\n        String[] names = new String[mStorages.length];\n        for (int i=0;i<mStorages.length;i++) {\n            names[i] = mStorages[i].getName();\n        }\n        mDialog = new AlertDialog.Builder(context)\n                .setItems(names, this)\n                .setNegativeButton(android.R.string.cancel, null)\n                .setNeutralButton(R.string.menu_show_as_entry_default, new DialogInterface.OnClickListener() {\n                    @Override\n                    public void onClick(DialogInterface dialog, int which) {\n                        mDirSelectListener.onDirSelected(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC));\n                    }\n                })\n                .setCancelable(true)\n                .setTitle(R.string.select_storage)\n                .create();\n    }\n\n    public StorageSelectDialog setDirSelectListener(OnDirSelectListener dirSelectListener) {\n        this.mDirSelectListener = dirSelectListener;\n        return this;\n    }\n\n    public void show() {\n        mDialog.show();\n    }\n\n    @Override\n    public void onClick(DialogInterface dialogInterface, int position) {\n        File dir = mStorages[position];\n        mDirSelectListener.onDirSelected(dir);\n    }\n\n\n    private static File[] getAvailableStorages(Context context) {\n        File storageRoot = new File(\"/storage\");\n        return storageRoot.listFiles(new FileFilter() {\n            @Override\n            public boolean accept(File file) {\n                return file.canRead();\n            }\n        });\n    }\n\n    public interface OnDirSelectListener {\n        void onDirSelected(File dir);\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/fragments/AlbumDetailFragment.java",
    "content": "/*\n * Copyright (C) 2015 Naman Dwivedi\n *\n * Licensed under the GNU General Public License v3\n *\n * This is free software: you can redistribute it and/or modify it\n * under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;\n * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n * See the GNU General Public License for more details.\n */\npackage com.naman14.timber.fragments;\n\nimport android.annotation.TargetApi;\nimport android.content.Context;\nimport android.graphics.Bitmap;\nimport android.graphics.Color;\nimport android.os.AsyncTask;\nimport android.os.Bundle;\nimport android.os.Handler;\nimport com.google.android.material.appbar.AppBarLayout;\nimport com.google.android.material.appbar.CollapsingToolbarLayout;\nimport com.google.android.material.floatingactionbutton.FloatingActionButton;\nimport androidx.fragment.app.Fragment;\nimport androidx.appcompat.app.ActionBar;\nimport androidx.appcompat.app.AppCompatActivity;\nimport androidx.palette.graphics.Palette;\nimport androidx.recyclerview.widget.LinearLayoutManager;\nimport androidx.recyclerview.widget.RecyclerView;\nimport androidx.appcompat.widget.Toolbar;\nimport android.transition.Transition;\nimport android.view.LayoutInflater;\nimport android.view.Menu;\nimport android.view.MenuInflater;\nimport android.view.MenuItem;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.ImageView;\nimport android.widget.TextView;\n\nimport com.afollestad.appthemeengine.ATE;\nimport com.afollestad.appthemeengine.Config;\nimport com.naman14.timber.MusicPlayer;\nimport com.naman14.timber.R;\nimport com.naman14.timber.adapters.AlbumSongsAdapter;\nimport com.naman14.timber.dataloaders.AlbumLoader;\nimport com.naman14.timber.dataloaders.AlbumSongLoader;\nimport com.naman14.timber.dialogs.AddPlaylistDialog;\nimport com.naman14.timber.listeners.SimplelTransitionListener;\nimport com.naman14.timber.models.Album;\nimport com.naman14.timber.models.Song;\nimport com.naman14.timber.utils.ATEUtils;\nimport com.naman14.timber.utils.Constants;\nimport com.naman14.timber.utils.FabAnimationUtils;\nimport com.naman14.timber.utils.Helpers;\nimport com.naman14.timber.utils.ImageUtils;\nimport com.naman14.timber.utils.NavigationUtils;\nimport com.naman14.timber.utils.PreferencesUtility;\nimport com.naman14.timber.utils.SortOrder;\nimport com.naman14.timber.utils.TimberUtils;\nimport com.naman14.timber.widgets.DividerItemDecoration;\nimport com.nostra13.universalimageloader.core.assist.FailReason;\nimport com.nostra13.universalimageloader.core.listener.ImageLoadingListener;\n\nimport net.steamcrafted.materialiconlib.MaterialDrawableBuilder;\n\nimport java.util.List;\n\npublic class AlbumDetailFragment extends Fragment {\n\n    private long albumID = -1;\n\n    private ImageView albumArt, artistArt;\n    private TextView albumTitle, albumDetails;\n    private AppCompatActivity mContext;\n\n    private RecyclerView recyclerView;\n    private AlbumSongsAdapter mAdapter;\n\n    private Toolbar toolbar;\n\n    private Album album;\n\n    private CollapsingToolbarLayout collapsingToolbarLayout;\n    private AppBarLayout appBarLayout;\n    private FloatingActionButton fab;\n\n    private boolean loadFailed = false;\n\n    private PreferencesUtility mPreferences;\n    private Context context;\n    private int primaryColor = -1;\n\n    public static AlbumDetailFragment newInstance(long id, boolean useTransition, String transitionName) {\n        AlbumDetailFragment fragment = new AlbumDetailFragment();\n        Bundle args = new Bundle();\n        args.putLong(Constants.ALBUM_ID, id);\n        args.putBoolean(\"transition\", useTransition);\n        if (useTransition)\n            args.putString(\"transition_name\", transitionName);\n        fragment.setArguments(args);\n        return fragment;\n    }\n\n    @Override\n    public void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        if (getArguments() != null) {\n            albumID = getArguments().getLong(Constants.ALBUM_ID);\n        }\n        context = getActivity();\n        mContext = (AppCompatActivity) context;\n        mPreferences = PreferencesUtility.getInstance(context);\n    }\n\n    @TargetApi(21)\n    @Override\n    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {\n        final View rootView = inflater.inflate(\n                R.layout.fragment_album_detail, container, false);\n\n        albumArt = (ImageView) rootView.findViewById(R.id.album_art);\n        artistArt = (ImageView) rootView.findViewById(R.id.artist_art);\n        albumTitle = (TextView) rootView.findViewById(R.id.album_title);\n        albumDetails = (TextView) rootView.findViewById(R.id.album_details);\n\n        toolbar = (Toolbar) rootView.findViewById(R.id.toolbar);\n\n        fab = (FloatingActionButton) rootView.findViewById(R.id.fab);\n\n        if (getArguments().getBoolean(\"transition\")) {\n            albumArt.setTransitionName(getArguments().getString(\"transition_name\"));\n        }\n        recyclerView = (RecyclerView) rootView.findViewById(R.id.recyclerview);\n        collapsingToolbarLayout = (CollapsingToolbarLayout) rootView.findViewById(R.id.collapsing_toolbar);\n        appBarLayout = (AppBarLayout) rootView.findViewById(R.id.app_bar);\n        recyclerView.setEnabled(false);\n        recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));\n\n        album = AlbumLoader.getAlbum(getActivity(), albumID);\n\n        setAlbumart();\n\n        setUpEverything();\n\n\n        fab.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                Handler handler = new Handler();\n                handler.postDelayed(new Runnable() {\n                    @Override\n                    public void run() {\n                        AlbumSongsAdapter adapter = (AlbumSongsAdapter) recyclerView.getAdapter();\n                        MusicPlayer.playAll(getActivity(), adapter.getSongIds(), 0, albumID, TimberUtils.IdType.Album, true);\n                        NavigationUtils.navigateToNowplaying(getActivity(), false);\n                    }\n                }, 150);\n            }\n        });\n\n        return rootView;\n    }\n\n    private void setupToolbar() {\n\n        ((AppCompatActivity) getActivity()).setSupportActionBar(toolbar);\n        final ActionBar ab = ((AppCompatActivity) getActivity()).getSupportActionBar();\n        ab.setDisplayHomeAsUpEnabled(true);\n        collapsingToolbarLayout.setTitle(album.title);\n\n    }\n\n    private void setAlbumart() {\n        ImageUtils.loadAlbumArtIntoView(album.id, albumArt, new ImageLoadingListener() {\n                    @Override\n                    public void onLoadingStarted(String imageUri, View view) {\n                    }\n\n                    @Override\n                    public void onLoadingFailed(String imageUri, View view, FailReason failReason) {\n                        loadFailed = true;\n                        MaterialDrawableBuilder builder = MaterialDrawableBuilder.with(context)\n                                .setIcon(MaterialDrawableBuilder.IconValue.SHUFFLE)\n                                .setColor(TimberUtils.getBlackWhiteColor(Config.accentColor(context, Helpers.getATEKey(context))));\n                        ATEUtils.setFabBackgroundTint(fab, Config.accentColor(context, Helpers.getATEKey(context)));\n                        fab.setImageDrawable(builder.build());\n                    }\n\n                    @Override\n                    public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {\n                        try {\n                            new Palette.Builder(loadedImage).generate(new Palette.PaletteAsyncListener() {\n                                                                          @Override\n                                                                          public void onGenerated(Palette palette) {\n                                                                              Palette.Swatch swatch = palette.getVibrantSwatch();\n                                                                              if (swatch != null) {\n                                                                                  primaryColor = swatch.getRgb();\n                                                                                  collapsingToolbarLayout.setContentScrimColor(primaryColor);\n                                                                                  if (getActivity() != null)\n                                                                                      ATEUtils.setStatusBarColor(getActivity(), Helpers.getATEKey(getActivity()), primaryColor);\n                                                                              } else {\n                                                                                  Palette.Swatch swatchMuted = palette.getMutedSwatch();\n                                                                                  if (swatchMuted != null) {\n                                                                                      primaryColor = swatchMuted.getRgb();\n                                                                                      collapsingToolbarLayout.setContentScrimColor(primaryColor);\n                                                                                      if (getActivity() != null)\n                                                                                          ATEUtils.setStatusBarColor(getActivity(), Helpers.getATEKey(getActivity()), primaryColor);\n                                                                                  }\n                                                                              }\n\n                                                                              if (getActivity() != null) {\n                                                                                  MaterialDrawableBuilder builder = MaterialDrawableBuilder.with(getActivity())\n                                                                                          .setIcon(MaterialDrawableBuilder.IconValue.SHUFFLE)\n                                                                                          .setSizeDp(30);\n                                                                                  if (primaryColor != -1) {\n                                                                                      builder.setColor(TimberUtils.getBlackWhiteColor(primaryColor));\n                                                                                      ATEUtils.setFabBackgroundTint(fab, primaryColor);\n                                                                                      fab.setImageDrawable(builder.build());\n                                                                                  } else {\n                                                                                      if (context != null) {\n                                                                                          ATEUtils.setFabBackgroundTint(fab, Config.accentColor(context, Helpers.getATEKey(context)));\n                                                                                          builder.setColor(TimberUtils.getBlackWhiteColor(Config.accentColor(context, Helpers.getATEKey(context))));\n                                                                                          fab.setImageDrawable(builder.build());\n                                                                                      }\n                                                                                  }\n                                                                              }\n                                                                          }\n                                                                      }\n\n                            );\n                        } catch (\n                                Exception ignored\n                                )\n\n                        {\n\n                        }\n                    }\n\n                    @Override\n                    public void onLoadingCancelled(String imageUri, View view) {\n                    }\n\n                }\n\n        );\n    }\n\n    private void setAlbumDetails() {\n\n        String songCount = TimberUtils.makeLabel(getActivity(), R.plurals.Nsongs, album.songCount);\n\n        String year = (album.year != 0) ? (\" - \" + String.valueOf(album.year)) : \"\";\n\n        albumTitle.setText(album.title);\n        albumDetails.setText(album.artistName + \" - \" + songCount + year);\n\n\n    }\n\n    private void setUpAlbumSongs() {\n\n        List<Song> songList = AlbumSongLoader.getSongsForAlbum(getActivity(), albumID);\n        mAdapter = new AlbumSongsAdapter(getActivity(), songList, albumID);\n        recyclerView.addItemDecoration(new DividerItemDecoration(getActivity(), DividerItemDecoration.VERTICAL_LIST));\n        recyclerView.setAdapter(mAdapter);\n\n    }\n\n    private void setUpEverything() {\n        setupToolbar();\n        setAlbumDetails();\n        setUpAlbumSongs();\n    }\n\n    private void reloadAdapter() {\n        new AsyncTask<Void, Void, Void>() {\n            @Override\n            protected Void doInBackground(final Void... unused) {\n                List<Song> songList = AlbumSongLoader.getSongsForAlbum(getActivity(), albumID);\n                mAdapter.updateDataSet(songList);\n                return null;\n            }\n\n            @Override\n            protected void onPostExecute(Void aVoid) {\n                mAdapter.notifyDataSetChanged();\n            }\n        }.execute();\n    }\n\n    @Override\n    public void onActivityCreated(final Bundle savedInstanceState) {\n        super.onActivityCreated(savedInstanceState);\n        setHasOptionsMenu(true);\n    }\n\n    @Override\n    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {\n        super.onCreateOptionsMenu(menu, inflater);\n        inflater.inflate(R.menu.album_detail, menu);\n        if (getActivity() != null)\n            ATE.applyMenu(getActivity(), \"dark_theme\", menu);\n    }\n\n    @Override\n    public boolean onOptionsItemSelected(MenuItem item) {\n        switch (item.getItemId()) {\n            case R.id.menu_go_to_artist:\n                NavigationUtils.goToArtist(getContext(), album.artistId);\n                break;\n            case R.id.popup_song_addto_queue:\n                MusicPlayer.addToQueue(context, mAdapter.getSongIds(), -1, TimberUtils.IdType.NA);\n                break;\n            case R.id.popup_song_addto_playlist:\n                AddPlaylistDialog.newInstance(mAdapter.getSongIds()).show(mContext.getSupportFragmentManager(), \"ADD_PLAYLIST\");\n                break;\n            case R.id.menu_sort_by_az:\n                mPreferences.setAlbumSongSortOrder(SortOrder.AlbumSongSortOrder.SONG_A_Z);\n                reloadAdapter();\n                return true;\n            case R.id.menu_sort_by_za:\n                mPreferences.setAlbumSongSortOrder(SortOrder.AlbumSongSortOrder.SONG_Z_A);\n                reloadAdapter();\n                return true;\n            case R.id.menu_sort_by_year:\n                mPreferences.setAlbumSongSortOrder(SortOrder.AlbumSongSortOrder.SONG_YEAR);\n                reloadAdapter();\n                return true;\n            case R.id.menu_sort_by_duration:\n                mPreferences.setAlbumSongSortOrder(SortOrder.AlbumSongSortOrder.SONG_DURATION);\n                reloadAdapter();\n                return true;\n            case R.id.menu_sort_by_track_number:\n                mPreferences.setAlbumSongSortOrder(SortOrder.AlbumSongSortOrder.SONG_TRACK_LIST);\n                reloadAdapter();\n                return true;\n        }\n        return super.onOptionsItemSelected(item);\n    }\n\n    @Override\n    public void onResume() {\n        super.onResume();\n        String ateKey = Helpers.getATEKey(getActivity());\n        toolbar.setBackgroundColor(Color.TRANSPARENT);\n        if (primaryColor != -1 && getActivity() != null) {\n            collapsingToolbarLayout.setContentScrimColor(primaryColor);\n            ATEUtils.setFabBackgroundTint(fab, primaryColor);\n            ATEUtils.setStatusBarColor(getActivity(), ateKey, primaryColor);\n        }\n\n    }\n\n    private class EnterTransitionListener extends SimplelTransitionListener {\n\n        @TargetApi(21)\n        public void onTransitionEnd(Transition paramTransition) {\n            FabAnimationUtils.scaleIn(fab);\n        }\n\n        public void onTransitionStart(Transition paramTransition) {\n            FabAnimationUtils.scaleOut(fab, 0, null);\n        }\n\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/fragments/AlbumFragment.java",
    "content": "/*\n * Copyright (C) 2015 Naman Dwivedi\n *\n * Licensed under the GNU General Public License v3\n *\n * This is free software: you can redistribute it and/or modify it\n * under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;\n * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n * See the GNU General Public License for more details.\n */\n\npackage com.naman14.timber.fragments;\n\nimport android.graphics.Rect;\nimport android.os.AsyncTask;\nimport android.os.Bundle;\nimport androidx.fragment.app.Fragment;\nimport androidx.recyclerview.widget.GridLayoutManager;\nimport androidx.recyclerview.widget.RecyclerView;\nimport android.view.LayoutInflater;\nimport android.view.Menu;\nimport android.view.MenuInflater;\nimport android.view.MenuItem;\nimport android.view.View;\nimport android.view.ViewGroup;\n\nimport com.naman14.timber.R;\nimport com.naman14.timber.adapters.AlbumAdapter;\nimport com.naman14.timber.dataloaders.AlbumLoader;\nimport com.naman14.timber.models.Album;\nimport com.naman14.timber.utils.PreferencesUtility;\nimport com.naman14.timber.utils.SortOrder;\nimport com.naman14.timber.widgets.BaseRecyclerView;\nimport com.naman14.timber.widgets.DividerItemDecoration;\nimport com.naman14.timber.widgets.FastScroller;\n\nimport java.util.List;\n\npublic class AlbumFragment extends Fragment {\n\n    private AlbumAdapter mAdapter;\n    private BaseRecyclerView recyclerView;\n    private FastScroller fastScroller;\n    private GridLayoutManager layoutManager;\n    private RecyclerView.ItemDecoration itemDecoration;\n    private PreferencesUtility mPreferences;\n    private boolean isGrid;\n\n    @Override\n    public void onCreate(final Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        mPreferences = PreferencesUtility.getInstance(getActivity());\n        isGrid = mPreferences.isAlbumsInGrid();\n    }\n\n    @Override\n    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {\n        View rootView = inflater.inflate(\n                R.layout.fragment_recyclerview, container, false);\n\n        recyclerView = rootView.findViewById(R.id.recyclerview);\n        fastScroller = rootView.findViewById(R.id.fastscroller);\n\n        recyclerView.setEmptyView(getActivity(), rootView.findViewById(R.id.list_empty), \"No media found\");\n\n        setLayoutManager();\n\n        if (getActivity() != null)\n            new loadAlbums().execute(\"\");\n        return rootView;\n    }\n\n    private void setLayoutManager() {\n        if (isGrid) {\n            layoutManager = new GridLayoutManager(getActivity(), 2);\n            fastScroller.setVisibility(View.GONE);\n        } else {\n            layoutManager = new GridLayoutManager(getActivity(), 1);\n            fastScroller.setVisibility(View.VISIBLE);\n            fastScroller.setRecyclerView(recyclerView);\n        }\n        recyclerView.setLayoutManager(layoutManager);\n    }\n\n    private void setItemDecoration() {\n        if (isGrid) {\n            int spacingInPixels = getActivity().getResources().getDimensionPixelSize(R.dimen.spacing_card_album_grid);\n            itemDecoration = new SpacesItemDecoration(spacingInPixels);\n        } else {\n            itemDecoration = new DividerItemDecoration(getActivity(), DividerItemDecoration.VERTICAL_LIST);\n        }\n        recyclerView.addItemDecoration(itemDecoration);\n    }\n\n    private void updateLayoutManager(int column) {\n        recyclerView.removeItemDecoration(itemDecoration);\n        recyclerView.setAdapter(new AlbumAdapter(getActivity(), AlbumLoader.getAllAlbums(getActivity())));\n        layoutManager.setSpanCount(column);\n        layoutManager.requestLayout();\n        setItemDecoration();\n    }\n\n    private void reloadAdapter() {\n        new AsyncTask<Void, Void, Void>() {\n            @Override\n            protected Void doInBackground(final Void... unused) {\n                List<Album> albumList = AlbumLoader.getAllAlbums(getActivity());\n                mAdapter.updateDataSet(albumList);\n                return null;\n            }\n\n            @Override\n            protected void onPostExecute(Void aVoid) {\n                mAdapter.notifyDataSetChanged();\n            }\n        }.execute();\n    }\n\n    @Override\n    public void onActivityCreated(final Bundle savedInstanceState) {\n        super.onActivityCreated(savedInstanceState);\n        setHasOptionsMenu(true);\n    }\n\n    @Override\n    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {\n        super.onCreateOptionsMenu(menu, inflater);\n        inflater.inflate(R.menu.album_sort_by, menu);\n        inflater.inflate(R.menu.menu_show_as, menu);\n\n    }\n\n    @Override\n    public boolean onOptionsItemSelected(MenuItem item) {\n        switch (item.getItemId()) {\n            case R.id.menu_sort_by_az:\n                mPreferences.setAlbumSortOrder(SortOrder.AlbumSortOrder.ALBUM_A_Z);\n                reloadAdapter();\n                return true;\n            case R.id.menu_sort_by_za:\n                mPreferences.setAlbumSortOrder(SortOrder.AlbumSortOrder.ALBUM_Z_A);\n                reloadAdapter();\n                return true;\n            case R.id.menu_sort_by_year:\n                mPreferences.setAlbumSortOrder(SortOrder.AlbumSortOrder.ALBUM_YEAR);\n                reloadAdapter();\n                return true;\n            case R.id.menu_sort_by_artist:\n                mPreferences.setAlbumSortOrder(SortOrder.AlbumSortOrder.ALBUM_ARTIST);\n                reloadAdapter();\n                return true;\n            case R.id.menu_sort_by_number_of_songs:\n                mPreferences.setAlbumSortOrder(SortOrder.AlbumSortOrder.ALBUM_NUMBER_OF_SONGS);\n                reloadAdapter();\n                return true;\n            case R.id.menu_show_as_list:\n                mPreferences.setAlbumsInGrid(false);\n                isGrid = false;\n                updateLayoutManager(1);\n                return true;\n            case R.id.menu_show_as_grid:\n                mPreferences.setAlbumsInGrid(true);\n                isGrid = true;\n                updateLayoutManager(2);\n                return true;\n        }\n        return super.onOptionsItemSelected(item);\n    }\n\n    public class SpacesItemDecoration extends RecyclerView.ItemDecoration {\n        private int space;\n\n        public SpacesItemDecoration(int space) {\n            this.space = space;\n        }\n\n        @Override\n        public void getItemOffsets(Rect outRect, View view,\n                                   RecyclerView parent, RecyclerView.State state) {\n\n\n            outRect.left = space;\n            outRect.top = space;\n            outRect.right = space;\n            outRect.bottom = space;\n\n        }\n    }\n\n    private class loadAlbums extends AsyncTask<String, Void, String> {\n\n        @Override\n        protected String doInBackground(String... params) {\n            if (getActivity() != null)\n                mAdapter = new AlbumAdapter(getActivity(), AlbumLoader.getAllAlbums(getActivity()));\n            return \"Executed\";\n        }\n\n        @Override\n        protected void onPostExecute(String result) {\n            recyclerView.setAdapter(mAdapter);\n            //to add spacing between cards\n            if (getActivity() != null) {\n                setItemDecoration();\n            }\n\n        }\n\n        @Override\n        protected void onPreExecute() {\n        }\n    }\n}\n\n"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/fragments/ArtistBioFragment.java",
    "content": "/*\n * Copyright (C) 2015 Naman Dwivedi\n *\n * Licensed under the GNU General Public License v3\n *\n * This is free software: you can redistribute it and/or modify it\n * under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;\n * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n * See the GNU General Public License for more details.\n */\n\npackage com.naman14.timber.fragments;\n\nimport android.os.Bundle;\nimport androidx.fragment.app.Fragment;\nimport androidx.fragment.app.FragmentStatePagerAdapter;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\n\nimport com.naman14.timber.R;\nimport com.naman14.timber.dataloaders.ArtistLoader;\nimport com.naman14.timber.lastfmapi.LastFmClient;\nimport com.naman14.timber.lastfmapi.callbacks.ArtistInfoListener;\nimport com.naman14.timber.lastfmapi.models.ArtistQuery;\nimport com.naman14.timber.lastfmapi.models.LastfmArtist;\nimport com.naman14.timber.models.Artist;\nimport com.naman14.timber.subfragments.ArtistTagFragment;\nimport com.naman14.timber.utils.Constants;\nimport com.naman14.timber.widgets.MultiViewPager;\n\npublic class ArtistBioFragment extends Fragment {\n\n    long artistID = -1;\n\n    public static ArtistBioFragment newInstance(long id) {\n        ArtistBioFragment fragment = new ArtistBioFragment();\n        Bundle args = new Bundle();\n        args.putLong(Constants.ARTIST_ID, id);\n        fragment.setArguments(args);\n        return fragment;\n    }\n\n    @Override\n    public void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        if (getArguments() != null) {\n            artistID = getArguments().getLong(Constants.ARTIST_ID);\n        }\n    }\n\n    @Override\n    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {\n        View rootView = inflater.inflate(\n                R.layout.fragment_artist_bio, container, false);\n\n        Artist artist = ArtistLoader.getArtist(getActivity(), artistID);\n\n        LastFmClient.getInstance(getActivity()).getArtistInfo(new ArtistQuery(artist.name), new ArtistInfoListener() {\n            @Override\n            public void artistInfoSucess(LastfmArtist artist) {\n\n            }\n\n            @Override\n            public void artistInfoFailed() {\n            }\n        });\n\n        final MultiViewPager pager = (MultiViewPager) rootView.findViewById(R.id.tagspager);\n\n        final FragmentStatePagerAdapter adapter = new FragmentStatePagerAdapter(getActivity().getSupportFragmentManager()) {\n\n            @Override\n            public int getCount() {\n                return 20;\n            }\n\n            @Override\n            public Fragment getItem(int position) {\n                return ArtistTagFragment.newInstance(position);\n            }\n\n        };\n        pager.setAdapter(adapter);\n\n        return rootView;\n\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/fragments/ArtistDetailFragment.java",
    "content": "/*\n * Copyright (C) 2015 Naman Dwivedi\n *\n * Licensed under the GNU General Public License v3\n *\n * This is free software: you can redistribute it and/or modify it\n * under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;\n * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n * See the GNU General Public License for more details.\n */\n\npackage com.naman14.timber.fragments;\n\nimport android.graphics.Bitmap;\nimport android.graphics.Color;\nimport android.graphics.drawable.Drawable;\nimport android.os.AsyncTask;\nimport android.os.Bundle;\nimport android.os.Handler;\nimport com.google.android.material.appbar.AppBarLayout;\nimport com.google.android.material.appbar.CollapsingToolbarLayout;\nimport androidx.fragment.app.Fragment;\nimport androidx.appcompat.app.ActionBar;\nimport androidx.appcompat.app.AppCompatActivity;\nimport androidx.palette.graphics.Palette;\nimport androidx.appcompat.widget.Toolbar;\nimport android.view.LayoutInflater;\nimport android.view.Menu;\nimport android.view.MenuInflater;\nimport android.view.MenuItem;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.ImageView;\n\nimport com.afollestad.appthemeengine.ATE;\nimport com.naman14.timber.MusicPlayer;\nimport com.naman14.timber.R;\nimport com.naman14.timber.adapters.ArtistSongAdapter;\nimport com.naman14.timber.dataloaders.ArtistLoader;\nimport com.naman14.timber.dataloaders.ArtistSongLoader;\nimport com.naman14.timber.dialogs.AddPlaylistDialog;\nimport com.naman14.timber.lastfmapi.LastFmClient;\nimport com.naman14.timber.lastfmapi.callbacks.ArtistInfoListener;\nimport com.naman14.timber.lastfmapi.models.ArtistQuery;\nimport com.naman14.timber.lastfmapi.models.LastfmArtist;\nimport com.naman14.timber.models.Artist;\nimport com.naman14.timber.models.Song;\nimport com.naman14.timber.utils.ATEUtils;\nimport com.naman14.timber.utils.Constants;\nimport com.naman14.timber.utils.Helpers;\nimport com.naman14.timber.utils.ImageUtils;\nimport com.naman14.timber.utils.TimberUtils;\nimport com.nostra13.universalimageloader.core.DisplayImageOptions;\nimport com.nostra13.universalimageloader.core.ImageLoader;\nimport com.nostra13.universalimageloader.core.listener.SimpleImageLoadingListener;\n\nimport java.util.List;\n\npublic class ArtistDetailFragment extends Fragment {\n\n    private long artistID = -1;\n    private ImageView artistArt;\n    private Toolbar toolbar;\n    private CollapsingToolbarLayout collapsingToolbarLayout;\n    private AppBarLayout appBarLayout;\n    private boolean largeImageLoaded = false;\n    private int primaryColor = -1;\n    private ArtistSongAdapter mAdapter;\n\n    public static ArtistDetailFragment newInstance(long id, boolean useTransition, String transitionName) {\n        ArtistDetailFragment fragment = new ArtistDetailFragment();\n        Bundle args = new Bundle();\n        args.putLong(Constants.ARTIST_ID, id);\n        args.putBoolean(\"transition\", useTransition);\n        if (useTransition)\n            args.putString(\"transition_name\", transitionName);\n        fragment.setArguments(args);\n        return fragment;\n    }\n\n    @Override\n    public void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        if (getArguments() != null) {\n            artistID = getArguments().getLong(Constants.ARTIST_ID);\n        }\n    }\n\n\n    @Override\n    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {\n        View rootView = inflater.inflate(\n                R.layout.fragment_artist_detail, container, false);\n\n        artistArt = (ImageView) rootView.findViewById(R.id.artist_art);\n\n        collapsingToolbarLayout = (CollapsingToolbarLayout) rootView.findViewById(R.id.collapsing_toolbar);\n        appBarLayout = (AppBarLayout) rootView.findViewById(R.id.app_bar);\n\n        if (getArguments().getBoolean(\"transition\")) {\n            artistArt.setTransitionName(getArguments().getString(\"transition_name\"));\n        }\n\n        toolbar = (Toolbar) rootView.findViewById(R.id.toolbar);\n        setupToolbar();\n        setUpArtistDetails();\n\n        getChildFragmentManager().beginTransaction().replace(R.id.container, ArtistMusicFragment.newInstance(artistID)).commit();\n\n\n        return rootView;\n    }\n\n    private void setupToolbar() {\n\n        ((AppCompatActivity) getActivity()).setSupportActionBar(toolbar);\n\n        final ActionBar ab = ((AppCompatActivity) getActivity()).getSupportActionBar();\n        ab.setDisplayShowTitleEnabled(false);\n        ab.setDisplayHomeAsUpEnabled(true);\n    }\n\n    private void setUpArtistDetails() {\n\n        final Artist artist = ArtistLoader.getArtist(getActivity(), artistID);\n        List<Song> songList = ArtistSongLoader.getSongsForArtist(getActivity(), artistID);\n        mAdapter = new ArtistSongAdapter(getActivity(), songList, artistID);\n\n        collapsingToolbarLayout.setTitle(artist.name);\n\n        LastFmClient.getInstance(getActivity()).getArtistInfo(new ArtistQuery(artist.name), new ArtistInfoListener() {\n            @Override\n            public void artistInfoSucess(final LastfmArtist artist) {\n                if (artist != null) {\n\n                    ImageLoader.getInstance().displayImage(artist.mArtwork.get(4).mUrl, artistArt,\n                            new DisplayImageOptions.Builder().cacheInMemory(true)\n                                    .cacheOnDisk(true)\n                                    .showImageOnFail(R.drawable.ic_empty_music2)\n                                    .build(), new SimpleImageLoadingListener() {\n                                @Override\n                                public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {\n                                    largeImageLoaded = true;\n                                    try {\n                                        new Palette.Builder(loadedImage).generate(new Palette.PaletteAsyncListener() {\n                                            @Override\n                                            public void onGenerated(Palette palette) {\n                                                Palette.Swatch swatch = palette.getVibrantSwatch();\n                                                if (swatch != null) {\n                                                    primaryColor = swatch.getRgb();\n                                                    collapsingToolbarLayout.setContentScrimColor(primaryColor);\n                                                    if (getActivity() != null)\n                                                        ATEUtils.setStatusBarColor(getActivity(), Helpers.getATEKey(getActivity()), primaryColor);\n                                                } else {\n                                                    Palette.Swatch swatchMuted = palette.getMutedSwatch();\n                                                    if (swatchMuted != null) {\n                                                        primaryColor = swatchMuted.getRgb();\n                                                        collapsingToolbarLayout.setContentScrimColor(primaryColor);\n                                                        if (getActivity() != null)\n                                                            ATEUtils.setStatusBarColor(getActivity(), Helpers.getATEKey(getActivity()), primaryColor);\n                                                    }\n                                                }\n\n                                            }\n                                        });\n                                    } catch (Exception ignored) {\n\n                                    }\n                                }\n                            });\n                    Handler handler = new Handler();\n                    handler.postDelayed(new Runnable() {\n                        @Override\n                        public void run() {\n                            setBlurredPlaceholder(artist);\n                        }\n                    }, 100);\n\n                }\n            }\n\n            @Override\n            public void artistInfoFailed() {\n\n            }\n        });\n\n    }\n\n    private void setBlurredPlaceholder(LastfmArtist artist) {\n        ImageLoader.getInstance().loadImage(artist.mArtwork.get(1).mUrl, new SimpleImageLoadingListener() {\n            @Override\n            public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {\n                if (getActivity() != null && !largeImageLoaded)\n                    new setBlurredAlbumArt().execute(loadedImage);\n\n            }\n        });\n    }\n\n    @Override\n    public void onActivityCreated(final Bundle savedInstanceState) {\n        super.onActivityCreated(savedInstanceState);\n        setHasOptionsMenu(true);\n    }\n\n    @Override\n    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {\n        super.onCreateOptionsMenu(menu, inflater);\n        inflater.inflate(R.menu.artist_detail, menu);\n        if (getActivity() != null)\n            ATE.applyMenu(getActivity(), \"dark_theme\", menu);\n    }\n    @Override\n    public boolean onOptionsItemSelected(MenuItem item) {\n        switch (item.getItemId()) {\n            case R.id.popup_song_addto_queue:\n                MusicPlayer.addToQueue(getContext(), mAdapter.getSongIds(), -1, TimberUtils.IdType.NA);\n                break;\n            case R.id.popup_song_addto_playlist:\n                AddPlaylistDialog.newInstance(mAdapter.getSongIds()).show(getActivity().getSupportFragmentManager(), \"ADD_PLAYLIST\");\n                break;\n        }\n        return super.onOptionsItemSelected(item);\n    }\n\n    @Override\n    public void onResume() {\n        super.onResume();\n        toolbar.setBackgroundColor(Color.TRANSPARENT);\n        if (primaryColor != -1 && getActivity() != null) {\n            collapsingToolbarLayout.setContentScrimColor(primaryColor);\n            String ateKey = Helpers.getATEKey(getActivity());\n            ATEUtils.setStatusBarColor(getActivity(), ateKey, primaryColor);\n        }\n\n    }\n\n    private class setBlurredAlbumArt extends AsyncTask<Bitmap, Void, Drawable> {\n\n        @Override\n        protected Drawable doInBackground(Bitmap... loadedImage) {\n            Drawable drawable = null;\n            try {\n                drawable = ImageUtils.createBlurredImageFromBitmap(loadedImage[0], getActivity(), 3);\n            } catch (Exception e) {\n                e.printStackTrace();\n            }\n            return drawable;\n        }\n\n        @Override\n        protected void onPostExecute(Drawable result) {\n            if (result != null && !largeImageLoaded) {\n                artistArt.setImageDrawable(result);\n            }\n        }\n\n        @Override\n        protected void onPreExecute() {\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/fragments/ArtistFragment.java",
    "content": "/*\n * Copyright (C) 2015 Naman Dwivedi\n *\n * Licensed under the GNU General Public License v3\n *\n * This is free software: you can redistribute it and/or modify it\n * under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;\n * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n * See the GNU General Public License for more details.\n */\n\npackage com.naman14.timber.fragments;\n\nimport android.graphics.Rect;\nimport android.os.AsyncTask;\nimport android.os.Bundle;\nimport androidx.fragment.app.Fragment;\nimport androidx.recyclerview.widget.GridLayoutManager;\nimport androidx.recyclerview.widget.RecyclerView;\nimport android.view.LayoutInflater;\nimport android.view.Menu;\nimport android.view.MenuInflater;\nimport android.view.MenuItem;\nimport android.view.View;\nimport android.view.ViewGroup;\n\nimport com.naman14.timber.R;\nimport com.naman14.timber.adapters.ArtistAdapter;\nimport com.naman14.timber.dataloaders.ArtistLoader;\nimport com.naman14.timber.models.Artist;\nimport com.naman14.timber.utils.PreferencesUtility;\nimport com.naman14.timber.utils.SortOrder;\nimport com.naman14.timber.widgets.BaseRecyclerView;\nimport com.naman14.timber.widgets.DividerItemDecoration;\nimport com.naman14.timber.widgets.FastScroller;\n\nimport java.util.List;\n\npublic class ArtistFragment extends Fragment {\n\n    private ArtistAdapter mAdapter;\n    private BaseRecyclerView recyclerView;\n    private GridLayoutManager layoutManager;\n    private RecyclerView.ItemDecoration itemDecoration;\n    private PreferencesUtility mPreferences;\n    private boolean isGrid;\n\n    @Override\n    public void onCreate(final Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        mPreferences = PreferencesUtility.getInstance(getActivity());\n        isGrid = mPreferences.isArtistsInGrid();\n    }\n\n    @Override\n    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {\n        View rootView = inflater.inflate(\n                R.layout.fragment_recyclerview, container, false);\n\n        recyclerView =  rootView.findViewById(R.id.recyclerview);\n        FastScroller fastScroller = rootView.findViewById(R.id.fastscroller);\n        fastScroller.setRecyclerView(recyclerView);\n        recyclerView.setEmptyView(getActivity(), rootView.findViewById(R.id.list_empty), \"No media found\");\n\n        setLayoutManager();\n\n        if (getActivity() != null)\n            new loadArtists().execute(\"\");\n        return rootView;\n    }\n\n    private void setLayoutManager() {\n        if (isGrid) {\n            layoutManager = new GridLayoutManager(getActivity(), 2);\n        } else {\n            layoutManager = new GridLayoutManager(getActivity(), 1);\n        }\n        recyclerView.setLayoutManager(layoutManager);\n    }\n\n    private void setItemDecoration() {\n        if (isGrid) {\n            int spacingInPixels = getActivity().getResources().getDimensionPixelSize(R.dimen.spacing_card_album_grid);\n            itemDecoration = new SpacesItemDecoration(spacingInPixels);\n        } else {\n            itemDecoration = new DividerItemDecoration(getActivity(), DividerItemDecoration.VERTICAL_LIST);\n        }\n        recyclerView.addItemDecoration(itemDecoration);\n    }\n\n    private void updateLayoutManager(int column) {\n        recyclerView.removeItemDecoration(itemDecoration);\n        recyclerView.setAdapter(new ArtistAdapter(getActivity(), ArtistLoader.getAllArtists(getActivity())));\n        layoutManager.setSpanCount(column);\n        layoutManager.requestLayout();\n        setItemDecoration();\n    }\n\n    private void reloadAdapter() {\n        new AsyncTask<Void, Void, Void>() {\n            @Override\n            protected Void doInBackground(final Void... unused) {\n                List<Artist> artistList = ArtistLoader.getAllArtists(getActivity());\n                mAdapter.updateDataSet(artistList);\n                return null;\n            }\n\n            @Override\n            protected void onPostExecute(Void aVoid) {\n                mAdapter.notifyDataSetChanged();\n            }\n        }.execute();\n    }\n\n    @Override\n    public void onActivityCreated(final Bundle savedInstanceState) {\n        super.onActivityCreated(savedInstanceState);\n        setHasOptionsMenu(true);\n    }\n\n    @Override\n    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {\n        super.onCreateOptionsMenu(menu, inflater);\n        inflater.inflate(R.menu.artist_sort_by, menu);\n        inflater.inflate(R.menu.menu_show_as, menu);\n\n    }\n\n    @Override\n    public boolean onOptionsItemSelected(MenuItem item) {\n        switch (item.getItemId()) {\n            case R.id.menu_sort_by_az:\n                mPreferences.setArtistSortOrder(SortOrder.ArtistSortOrder.ARTIST_A_Z);\n                reloadAdapter();\n                return true;\n            case R.id.menu_sort_by_za:\n                mPreferences.setArtistSortOrder(SortOrder.ArtistSortOrder.ARTIST_Z_A);\n                reloadAdapter();\n                return true;\n            case R.id.menu_sort_by_number_of_songs:\n                mPreferences.setArtistSortOrder(SortOrder.ArtistSortOrder.ARTIST_NUMBER_OF_SONGS);\n                reloadAdapter();\n                return true;\n            case R.id.menu_sort_by_number_of_albums:\n                mPreferences.setArtistSortOrder(SortOrder.ArtistSortOrder.ARTIST_NUMBER_OF_ALBUMS);\n                reloadAdapter();\n                return true;\n            case R.id.menu_show_as_list:\n                mPreferences.setArtistsInGrid(false);\n                isGrid = false;\n                updateLayoutManager(1);\n                return true;\n            case R.id.menu_show_as_grid:\n                mPreferences.setArtistsInGrid(true);\n                isGrid = true;\n                updateLayoutManager(2);\n                return true;\n        }\n        return super.onOptionsItemSelected(item);\n    }\n\n    private class loadArtists extends AsyncTask<String, Void, String> {\n\n        @Override\n        protected String doInBackground(String... params) {\n            if (getActivity() != null)\n                mAdapter = new ArtistAdapter(getActivity(), ArtistLoader.getAllArtists(getActivity()));\n            return \"Executed\";\n        }\n\n        @Override\n        protected void onPostExecute(String result) {\n            if (mAdapter != null) {\n                mAdapter.setHasStableIds(true);\n                recyclerView.setAdapter(mAdapter);\n            }\n            if (getActivity() != null) {\n                setItemDecoration();\n            }\n        }\n\n        @Override\n        protected void onPreExecute() {\n        }\n    }\n\n    public class SpacesItemDecoration extends RecyclerView.ItemDecoration {\n        private int space;\n\n        public SpacesItemDecoration(int space) {\n            this.space = space;\n        }\n\n        @Override\n        public void getItemOffsets(Rect outRect, View view,\n                                   RecyclerView parent, RecyclerView.State state) {\n            outRect.left = space;\n            outRect.top = space;\n            outRect.right = space;\n            outRect.bottom = space;\n\n        }\n    }\n\n\n}\n"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/fragments/ArtistMusicFragment.java",
    "content": "/*\n * Copyright (C) 2015 Naman Dwivedi\n *\n * Licensed under the GNU General Public License v3\n *\n * This is free software: you can redistribute it and/or modify it\n * under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;\n * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n * See the GNU General Public License for more details.\n */\n\npackage com.naman14.timber.fragments;\n\nimport android.os.Bundle;\nimport androidx.fragment.app.Fragment;\nimport androidx.recyclerview.widget.LinearLayoutManager;\nimport androidx.recyclerview.widget.RecyclerView;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\n\nimport com.naman14.timber.R;\nimport com.naman14.timber.adapters.ArtistSongAdapter;\nimport com.naman14.timber.dataloaders.ArtistSongLoader;\nimport com.naman14.timber.models.Song;\nimport com.naman14.timber.utils.Constants;\nimport com.naman14.timber.widgets.DividerItemDecoration;\n\nimport java.util.ArrayList;\n\npublic class ArtistMusicFragment extends Fragment {\n\n    public static RecyclerView songsRecyclerview;\n    private long artistID = -1;\n    private ArtistSongAdapter mSongAdapter;\n\n    public static ArtistMusicFragment newInstance(long id) {\n        ArtistMusicFragment fragment = new ArtistMusicFragment();\n        Bundle args = new Bundle();\n        args.putLong(Constants.ARTIST_ID, id);\n        fragment.setArguments(args);\n        return fragment;\n    }\n\n    @Override\n    public void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        if (getArguments() != null) {\n            artistID = getArguments().getLong(Constants.ARTIST_ID);\n        }\n    }\n\n    @Override\n    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {\n        View rootView = inflater.inflate(\n                R.layout.fragment_artist_music, container, false);\n\n        songsRecyclerview = (RecyclerView) rootView.findViewById(R.id.recycler_view_songs);\n\n        setUpSongs();\n\n\n        return rootView;\n    }\n\n\n    private void setUpSongs() {\n        songsRecyclerview.setLayoutManager(new LinearLayoutManager(getActivity()));\n\n        ArrayList<Song> songList;\n        songList = ArtistSongLoader.getSongsForArtist(getActivity(), artistID);\n\n        // adding one dummy song to top of arraylist\n        //there will be albums header at this position in recyclerview\n        songList.add(0, new Song(-1, -1, -1, \"dummy\", \"dummy\", \"dummy\", -1, -1));\n\n        mSongAdapter = new ArtistSongAdapter(getActivity(), songList, artistID);\n        songsRecyclerview.addItemDecoration(new DividerItemDecoration(getActivity(), DividerItemDecoration.VERTICAL_LIST));\n        songsRecyclerview.setAdapter(mSongAdapter);\n    }\n\n\n}\n"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/fragments/FoldersFragment.java",
    "content": "package com.naman14.timber.fragments;\n\nimport android.app.Activity;\nimport android.content.Context;\nimport android.os.AsyncTask;\nimport android.os.Bundle;\nimport android.preference.PreferenceManager;\nimport androidx.fragment.app.Fragment;\nimport androidx.appcompat.app.ActionBar;\nimport androidx.appcompat.app.AppCompatActivity;\nimport androidx.recyclerview.widget.LinearLayoutManager;\nimport androidx.recyclerview.widget.RecyclerView;\nimport androidx.appcompat.widget.Toolbar;\nimport android.view.LayoutInflater;\nimport android.view.Menu;\nimport android.view.MenuInflater;\nimport android.view.MenuItem;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.ProgressBar;\n\nimport com.afollestad.appthemeengine.ATE;\nimport com.naman14.timber.R;\nimport com.naman14.timber.adapters.FolderAdapter;\nimport com.naman14.timber.dialogs.StorageSelectDialog;\nimport com.naman14.timber.utils.PreferencesUtility;\nimport com.naman14.timber.widgets.DividerItemDecoration;\nimport com.naman14.timber.widgets.FastScroller;\n\nimport java.io.File;\n\n/**\n * Created by nv95 on 10.11.16.\n */\n\npublic class FoldersFragment extends Fragment implements StorageSelectDialog.OnDirSelectListener {\n\n    private FolderAdapter mAdapter;\n    private RecyclerView recyclerView;\n    private FastScroller fastScroller;\n    private ProgressBar mProgressBar;\n\n    @Override\n    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {\n        View rootView = inflater.inflate(\n                R.layout.fragment_folders, container, false);\n\n        Toolbar toolbar = (Toolbar) rootView.findViewById(R.id.toolbar);\n        ((AppCompatActivity) getActivity()).setSupportActionBar(toolbar);\n\n        ActionBar ab = ((AppCompatActivity) getActivity()).getSupportActionBar();\n        ab.setHomeAsUpIndicator(R.drawable.ic_menu);\n        ab.setDisplayHomeAsUpEnabled(true);\n        ab.setTitle(R.string.folders);\n\n        recyclerView = (RecyclerView) rootView.findViewById(R.id.recyclerview);\n        fastScroller = (FastScroller) rootView.findViewById(R.id.fastscroller);\n        mProgressBar = (ProgressBar) rootView.findViewById(R.id.progressBar);\n\n        recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));\n\n        if (getActivity() != null)\n            new loadFolders().execute(\"\");\n        return rootView;\n    }\n\n    @Override\n    public void onViewCreated(View view, Bundle savedInstanceState) {\n        super.onViewCreated(view, savedInstanceState);\n        boolean dark = PreferenceManager.getDefaultSharedPreferences(getActivity()).getBoolean(\"dark_theme\", false);\n        if (dark) {\n            ATE.apply(this, \"dark_theme\");\n        } else {\n            ATE.apply(this, \"light_theme\");\n        }\n        if (mAdapter != null) {\n            mAdapter.applyTheme(dark);\n            mAdapter.notifyDataSetChanged();\n        }\n    }\n\n    private void setItemDecoration() {\n        recyclerView.addItemDecoration(new DividerItemDecoration(getActivity(), DividerItemDecoration.VERTICAL_LIST));\n    }\n\n    @Override\n    public void onActivityCreated(final Bundle savedInstanceState) {\n        super.onActivityCreated(savedInstanceState);\n        setHasOptionsMenu(true);\n    }\n\n    @Override\n    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {\n        super.onCreateOptionsMenu(menu, inflater);\n        inflater.inflate(R.menu.menu_folders, menu);\n    }\n\n    @Override\n    public boolean onOptionsItemSelected(MenuItem item) {\n        if (item.getItemId() == R.id.action_storages) {\n            new StorageSelectDialog(getActivity())\n                    .setDirSelectListener(this)\n                    .show();\n        }\n        return super.onOptionsItemSelected(item);\n    }\n\n    public void updateTheme() {\n        Context context = getActivity();\n        if (context != null) {\n            boolean dark = PreferenceManager.getDefaultSharedPreferences(context).getBoolean(\"dark_theme\", false);\n            mAdapter.applyTheme(dark);\n        }\n    }\n\n    @Override\n    public void onDirSelected(File dir) {\n        mAdapter.updateDataSetAsync(dir);\n    }\n\n    private class loadFolders extends AsyncTask<String, Void, String> {\n\n        @Override\n        protected String doInBackground(String... params) {\n            Activity activity = getActivity();\n            if (activity != null) {\n                mAdapter = new FolderAdapter(activity, new File(PreferencesUtility.getInstance(activity).getLastFolder()));\n                updateTheme();\n            }\n            return \"Executed\";\n        }\n\n        @Override\n        protected void onPostExecute(String result) {\n            recyclerView.setAdapter(mAdapter);\n            //to add spacing between cards\n            if (getActivity() != null) {\n                setItemDecoration();\n            }\n            mAdapter.notifyDataSetChanged();\n            mProgressBar.setVisibility(View.GONE);\n            fastScroller.setVisibility(View.VISIBLE);\n            fastScroller.setRecyclerView(recyclerView);\n        }\n\n        @Override\n        protected void onPreExecute() {\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/fragments/MainFragment.java",
    "content": "/*\n * Copyright (C) 2015 Naman Dwivedi\n *\n * Licensed under the GNU General Public License v3\n *\n * This is free software: you can redistribute it and/or modify it\n * under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;\n * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n * See the GNU General Public License for more details.\n */\n\npackage com.naman14.timber.fragments;\n\nimport android.os.Bundle;\nimport android.preference.PreferenceManager;\nimport com.google.android.material.tabs.TabLayout;\nimport androidx.fragment.app.Fragment;\nimport androidx.fragment.app.FragmentManager;\nimport androidx.fragment.app.FragmentPagerAdapter;\nimport androidx.viewpager.widget.ViewPager;\nimport androidx.appcompat.app.ActionBar;\nimport androidx.appcompat.app.AppCompatActivity;\nimport androidx.appcompat.widget.Toolbar;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\n\nimport com.afollestad.appthemeengine.ATE;\nimport com.afollestad.appthemeengine.Config;\nimport com.naman14.timber.R;\nimport com.naman14.timber.utils.ATEUtils;\nimport com.naman14.timber.utils.Helpers;\nimport com.naman14.timber.utils.PreferencesUtility;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\npublic class MainFragment extends Fragment {\n\n    private PreferencesUtility mPreferences;\n    private ViewPager viewPager;\n\n    @Override\n    public void onCreate(final Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        mPreferences = PreferencesUtility.getInstance(getActivity());\n    }\n\n    @Override\n    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {\n        View rootView = inflater.inflate(\n                R.layout.fragment_main, container, false);\n\n        Toolbar toolbar = (Toolbar) rootView.findViewById(R.id.toolbar);\n        ((AppCompatActivity) getActivity()).setSupportActionBar(toolbar);\n\n        final ActionBar ab = ((AppCompatActivity) getActivity()).getSupportActionBar();\n        ab.setHomeAsUpIndicator(R.drawable.ic_menu);\n        ab.setDisplayHomeAsUpEnabled(true);\n\n\n        viewPager = (ViewPager) rootView.findViewById(R.id.viewpager);\n        if (viewPager != null) {\n            setupViewPager(viewPager);\n            viewPager.setOffscreenPageLimit(2);\n        }\n\n        TabLayout tabLayout = (TabLayout) rootView.findViewById(R.id.tabs);\n        tabLayout.setupWithViewPager(viewPager);\n\n        return rootView;\n\n    }\n\n    @Override\n    public void onViewCreated(View view, Bundle savedInstanceState) {\n        super.onViewCreated(view, savedInstanceState);\n        if (PreferenceManager.getDefaultSharedPreferences(getActivity()).getBoolean(\"dark_theme\", false)) {\n            ATE.apply(this, \"dark_theme\");\n        } else {\n            ATE.apply(this, \"light_theme\");\n        }\n        viewPager.setCurrentItem(mPreferences.getStartPageIndex());\n    }\n\n    private void setupViewPager(ViewPager viewPager) {\n        Adapter adapter = new Adapter(getChildFragmentManager());\n        adapter.addFragment(new SongsFragment(), this.getString(R.string.songs));\n        adapter.addFragment(new AlbumFragment(), this.getString(R.string.albums));\n        adapter.addFragment(new ArtistFragment(), this.getString(R.string.artists));\n        viewPager.setAdapter(adapter);\n    }\n\n    @Override\n    public void onPause() {\n        super.onPause();\n        if (mPreferences.lastOpenedIsStartPagePreference()) {\n            mPreferences.setStartPageIndex(viewPager.getCurrentItem());\n        }\n    }\n\n    @Override\n    public void onResume() {\n        super.onResume();\n        String ateKey = Helpers.getATEKey(getActivity());\n        ATEUtils.setStatusBarColor(getActivity(), ateKey, Config.primaryColor(getActivity(), ateKey));\n\n    }\n\n    @Override\n    public void onStart() {\n        super.onStart();\n    }\n\n    static class Adapter extends FragmentPagerAdapter {\n        private final List<Fragment> mFragments = new ArrayList<>();\n        private final List<String> mFragmentTitles = new ArrayList<>();\n\n        public Adapter(FragmentManager fm) {\n            super(fm);\n        }\n\n        public void addFragment(Fragment fragment, String title) {\n            mFragments.add(fragment);\n            mFragmentTitles.add(title);\n        }\n\n        @Override\n        public Fragment getItem(int position) {\n            return mFragments.get(position);\n        }\n\n        @Override\n        public int getCount() {\n            return mFragments.size();\n        }\n\n        @Override\n        public CharSequence getPageTitle(int position) {\n            return mFragmentTitles.get(position);\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/fragments/PlaylistFragment.java",
    "content": "/*\n * Copyright (C) 2015 Naman Dwivedi\n *\n * Licensed under the GNU General Public License v3\n *\n * This is free software: you can redistribute it and/or modify it\n * under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;\n * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n * See the GNU General Public License for more details.\n */\n\npackage com.naman14.timber.fragments;\n\nimport android.app.Activity;\nimport android.content.Intent;\nimport android.graphics.Rect;\nimport android.os.Bundle;\nimport android.os.Handler;\nimport android.preference.PreferenceManager;\nimport androidx.annotation.Nullable;\nimport androidx.fragment.app.Fragment;\nimport androidx.fragment.app.FragmentStatePagerAdapter;\nimport androidx.appcompat.app.ActionBar;\nimport androidx.appcompat.app.AppCompatActivity;\nimport androidx.recyclerview.widget.GridLayoutManager;\nimport androidx.recyclerview.widget.RecyclerView;\nimport androidx.appcompat.widget.Toolbar;\nimport android.view.LayoutInflater;\nimport android.view.Menu;\nimport android.view.MenuInflater;\nimport android.view.MenuItem;\nimport android.view.View;\nimport android.view.ViewGroup;\n\nimport com.afollestad.appthemeengine.ATE;\nimport com.naman14.timber.R;\nimport com.naman14.timber.adapters.PlaylistAdapter;\nimport com.naman14.timber.dataloaders.PlaylistLoader;\nimport com.naman14.timber.dialogs.CreatePlaylistDialog;\nimport com.naman14.timber.models.Playlist;\nimport com.naman14.timber.subfragments.PlaylistPagerFragment;\nimport com.naman14.timber.utils.Constants;\nimport com.naman14.timber.utils.PreferencesUtility;\nimport com.naman14.timber.widgets.DividerItemDecoration;\nimport com.naman14.timber.widgets.MultiViewPager;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\npublic class PlaylistFragment extends Fragment {\n\n    private int playlistcount;\n    private FragmentStatePagerAdapter adapter;\n    private MultiViewPager pager;\n    private RecyclerView recyclerView;\n    private GridLayoutManager layoutManager;\n    private RecyclerView.ItemDecoration itemDecoration;\n\n    private PreferencesUtility mPreferences;\n    private boolean isGrid;\n    private boolean isDefault;\n    private boolean showAuto;\n    private PlaylistAdapter mAdapter;\n\n    private List<Playlist> playlists = new ArrayList<>();\n\n    @Override\n    public void onCreate(@Nullable Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        mPreferences = PreferencesUtility.getInstance(getActivity());\n        isGrid = mPreferences.getPlaylistView() == Constants.PLAYLIST_VIEW_GRID;\n        isDefault = mPreferences.getPlaylistView() == Constants.PLAYLIST_VIEW_DEFAULT;\n        showAuto = mPreferences.showAutoPlaylist();\n\n    }\n\n    @Override\n    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {\n        View rootView = inflater.inflate(\n                R.layout.fragment_playlist, container, false);\n\n        Toolbar toolbar = (Toolbar) rootView.findViewById(R.id.toolbar);\n        pager = (MultiViewPager) rootView.findViewById(R.id.playlistpager);\n        recyclerView = (RecyclerView) rootView.findViewById(R.id.recyclerview);\n\n\n        ((AppCompatActivity) getActivity()).setSupportActionBar(toolbar);\n\n        final ActionBar ab = ((AppCompatActivity) getActivity()).getSupportActionBar();\n        ab.setHomeAsUpIndicator(R.drawable.ic_menu);\n        ab.setDisplayHomeAsUpEnabled(true);\n        ab.setTitle(R.string.playlists);\n\n        playlists = PlaylistLoader.getPlaylists(getActivity(), showAuto);\n        playlistcount = playlists.size();\n\n        if (isDefault) {\n            initPager();\n        } else {\n            initRecyclerView();\n        }\n\n        return rootView;\n\n    }\n\n\n    private void initPager() {\n        pager.setVisibility(View.VISIBLE);\n        recyclerView.setVisibility(View.GONE);\n        recyclerView.setAdapter(null);\n        adapter = new FragmentStatePagerAdapter(getChildFragmentManager()) {\n\n            @Override\n            public int getCount() {\n                return playlistcount;\n            }\n\n            @Override\n            public Fragment getItem(int position) {\n                return PlaylistPagerFragment.newInstance(position);\n            }\n\n        };\n        pager.setAdapter(adapter);\n        pager.setOffscreenPageLimit(3);\n    }\n\n    private void initRecyclerView() {\n        recyclerView.setVisibility(View.VISIBLE);\n        pager.setVisibility(View.GONE);\n        setLayoutManager();\n        mAdapter = new PlaylistAdapter(getActivity(), playlists);\n\n        recyclerView.setAdapter(mAdapter);\n        //to add spacing between cards\n        if (getActivity() != null) {\n            setItemDecoration();\n        }\n    }\n\n\n    private void setLayoutManager() {\n        if (isGrid) {\n            layoutManager = new GridLayoutManager(getActivity(), 2);\n        } else {\n            layoutManager = new GridLayoutManager(getActivity(), 1);\n        }\n        recyclerView.setLayoutManager(layoutManager);\n    }\n\n    private void setItemDecoration() {\n        if (isGrid) {\n            int spacingInPixels = getActivity().getResources().getDimensionPixelSize(R.dimen.spacing_card_album_grid);\n            itemDecoration = new SpacesItemDecoration(spacingInPixels);\n        } else {\n            itemDecoration = new DividerItemDecoration(getActivity(), DividerItemDecoration.VERTICAL_LIST);\n        }\n        recyclerView.addItemDecoration(itemDecoration);\n    }\n\n    private void updateLayoutManager(int column) {\n        recyclerView.removeItemDecoration(itemDecoration);\n        recyclerView.setAdapter(new PlaylistAdapter(getActivity(), PlaylistLoader.getPlaylists(getActivity(), showAuto)));\n        layoutManager.setSpanCount(column);\n        layoutManager.requestLayout();\n        setItemDecoration();\n    }\n\n\n    public class SpacesItemDecoration extends RecyclerView.ItemDecoration {\n        private int space;\n\n        public SpacesItemDecoration(int space) {\n            this.space = space;\n        }\n\n        @Override\n        public void getItemOffsets(Rect outRect, View view,\n                                   RecyclerView parent, RecyclerView.State state) {\n\n\n            outRect.left = space;\n            outRect.top = space;\n            outRect.right = space;\n            outRect.bottom = space;\n\n        }\n    }\n\n\n    @Override\n    public void onViewCreated(View view, Bundle savedInstanceState) {\n        super.onViewCreated(view, savedInstanceState);\n        if (PreferenceManager.getDefaultSharedPreferences(getActivity()).getBoolean(\"dark_theme\", false)) {\n            ATE.apply(this, \"dark_theme\");\n        } else {\n            ATE.apply(this, \"light_theme\");\n        }\n    }\n\n    @Override\n    public void onActivityCreated(final Bundle savedInstanceState) {\n        super.onActivityCreated(savedInstanceState);\n        setHasOptionsMenu(true);\n    }\n\n    @Override\n    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {\n        super.onCreateOptionsMenu(menu, inflater);\n        inflater.inflate(R.menu.menu_playlist, menu);\n\n    }\n\n    @Override\n    public void onPrepareOptionsMenu(Menu menu) {\n        super.onPrepareOptionsMenu(menu);\n        if (showAuto) {\n            menu.findItem(R.id.action_view_auto_playlists).setTitle(\"Hide auto playlists\");\n        } else menu.findItem(R.id.action_view_auto_playlists).setTitle(\"Show auto playlists\");\n    }\n\n    @Override\n    public boolean onOptionsItemSelected(MenuItem item) {\n        switch (item.getItemId()) {\n            case R.id.action_new_playlist:\n                CreatePlaylistDialog.newInstance().show(getChildFragmentManager(), \"CREATE_PLAYLIST\");\n                return true;\n            case R.id.menu_show_as_list:\n                mPreferences.setPlaylistView(Constants.PLAYLIST_VIEW_LIST);\n                isGrid = false;\n                isDefault = false;\n                initRecyclerView();\n                updateLayoutManager(1);\n                return true;\n            case R.id.menu_show_as_grid:\n                mPreferences.setPlaylistView(Constants.PLAYLIST_VIEW_GRID);\n                isGrid = true;\n                isDefault = false;\n                initRecyclerView();\n                updateLayoutManager(2);\n                return true;\n            case R.id.menu_show_as_default:\n                mPreferences.setPlaylistView(Constants.PLAYLIST_VIEW_DEFAULT);\n                isDefault = true;\n                initPager();\n                return true;\n            case R.id.action_view_auto_playlists:\n                if (showAuto) {\n                    showAuto = false;\n                    mPreferences.setToggleShowAutoPlaylist(false);\n                } else {\n                    showAuto = true;\n                    mPreferences.setToggleShowAutoPlaylist(true);\n                }\n                reloadPlaylists();\n                getActivity().invalidateOptionsMenu();\n                break;\n\n        }\n        return super.onOptionsItemSelected(item);\n    }\n\n    public void updatePlaylists(final long id) {\n        playlists = PlaylistLoader.getPlaylists(getActivity(), showAuto);\n        playlistcount = playlists.size();\n\n        if (isDefault) {\n            adapter.notifyDataSetChanged();\n            if (id != -1) {\n                Handler handler = new Handler();\n                handler.postDelayed(new Runnable() {\n                    @Override\n                    public void run() {\n                        for (int i = 0; i < playlists.size(); i++) {\n                            long playlistid = playlists.get(i).id;\n                            if (playlistid == id) {\n                                pager.setCurrentItem(i);\n                                break;\n                            }\n                        }\n                    }\n                }, 200);\n            }\n\n        } else {\n            mAdapter.updateDataSet(playlists);\n        }\n    }\n\n    public void reloadPlaylists() {\n        playlists = PlaylistLoader.getPlaylists(getActivity(), showAuto);\n        playlistcount = playlists.size();\n\n        if (isDefault) {\n            initPager();\n        } else {\n            initRecyclerView();\n        }\n    }\n\n    @Override\n    public void onActivityResult(int requestCode, int resultCode, Intent data) {\n        super.onActivityResult(requestCode, resultCode, data);\n\n        if (requestCode == Constants.ACTION_DELETE_PLAYLIST) {\n            if (resultCode == Activity.RESULT_OK) {\n                reloadPlaylists();\n            }\n\n        }\n    }\n}\n\n"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/fragments/QueueFragment.java",
    "content": "/*\n * Copyright (C) 2015 Naman Dwivedi\n *\n * Licensed under the GNU General Public License v3\n *\n * This is free software: you can redistribute it and/or modify it\n * under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;\n * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n * See the GNU General Public License for more details.\n */\n\npackage com.naman14.timber.fragments;\n\nimport android.os.AsyncTask;\nimport android.os.Bundle;\nimport android.preference.PreferenceManager;\nimport androidx.fragment.app.Fragment;\nimport androidx.appcompat.app.ActionBar;\nimport androidx.appcompat.app.AppCompatActivity;\nimport androidx.recyclerview.widget.LinearLayoutManager;\nimport androidx.appcompat.widget.Toolbar;\nimport android.util.Log;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\n\nimport com.afollestad.appthemeengine.ATE;\nimport com.naman14.timber.MusicPlayer;\nimport com.naman14.timber.R;\nimport com.naman14.timber.activities.BaseActivity;\nimport com.naman14.timber.adapters.PlayingQueueAdapter;\nimport com.naman14.timber.dataloaders.QueueLoader;\nimport com.naman14.timber.listeners.MusicStateListener;\nimport com.naman14.timber.models.Song;\nimport com.naman14.timber.widgets.BaseRecyclerView;\nimport com.naman14.timber.widgets.DragSortRecycler;\n\npublic class QueueFragment extends Fragment implements MusicStateListener {\n\n    private PlayingQueueAdapter mAdapter;\n    private BaseRecyclerView recyclerView;\n\n    @Override\n    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {\n        View rootView = inflater.inflate(\n                R.layout.fragment_queue, container, false);\n\n        Toolbar toolbar = rootView.findViewById(R.id.toolbar);\n        ((AppCompatActivity) getActivity()).setSupportActionBar(toolbar);\n\n        final ActionBar ab = ((AppCompatActivity) getActivity()).getSupportActionBar();\n        ab.setHomeAsUpIndicator(R.drawable.ic_menu);\n        ab.setDisplayHomeAsUpEnabled(true);\n        ab.setTitle(R.string.playing_queue);\n\n        recyclerView = rootView.findViewById(R.id.recyclerview);\n        recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));\n        recyclerView.setItemAnimator(null);\n        recyclerView.setEmptyView(getActivity(), rootView.findViewById(R.id.list_empty), \"No songs in queue\");\n\n        new loadQueueSongs().execute(\"\");\n        ((BaseActivity) getActivity()).setMusicStateListenerListener(this);\n\n        return rootView;\n    }\n\n    @Override\n    public void onViewCreated(View view, Bundle savedInstanceState) {\n        super.onViewCreated(view, savedInstanceState);\n        if (PreferenceManager.getDefaultSharedPreferences(getActivity()).getBoolean(\"dark_theme\", false)) {\n            ATE.apply(this, \"dark_theme\");\n        } else {\n            ATE.apply(this, \"light_theme\");\n        }\n    }\n\n    public void restartLoader() {\n\n    }\n\n    public void onPlaylistChanged() {\n\n    }\n\n    public void onMetaChanged() {\n        if (mAdapter != null)\n            mAdapter.notifyDataSetChanged();\n    }\n\n    private class loadQueueSongs extends AsyncTask<String, Void, String> {\n\n        @Override\n        protected String doInBackground(String... params) {\n            mAdapter = new PlayingQueueAdapter(getActivity(), QueueLoader.getQueueSongs(getActivity()));\n            return \"Executed\";\n        }\n\n        @Override\n        protected void onPostExecute(String result) {\n            recyclerView.setAdapter(mAdapter);\n            DragSortRecycler dragSortRecycler = new DragSortRecycler();\n            dragSortRecycler.setViewHandleId(R.id.reorder);\n\n            dragSortRecycler.setOnItemMovedListener(new DragSortRecycler.OnItemMovedListener() {\n                @Override\n                public void onItemMoved(int from, int to) {\n                    Log.d(\"queue\", \"onItemMoved \" + from + \" to \" + to);\n                    Song song = mAdapter.getSongAt(from);\n                    mAdapter.removeSongAt(from);\n                    mAdapter.addSongTo(to, song);\n                    mAdapter.notifyDataSetChanged();\n                    MusicPlayer.moveQueueItem(from, to);\n                }\n            });\n\n            recyclerView.addItemDecoration(dragSortRecycler);\n            recyclerView.addOnItemTouchListener(dragSortRecycler);\n            recyclerView.addOnScrollListener(dragSortRecycler.getScrollListener());\n\n            recyclerView.getLayoutManager().scrollToPosition(mAdapter.currentlyPlayingPosition);\n\n        }\n\n        @Override\n        protected void onPreExecute() {\n        }\n    }\n\n}\n\n"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/fragments/SettingsFragment.java",
    "content": "/*\n * Copyright (C) 2015 Naman Dwivedi\n *\n * Licensed under the GNU General Public License v3\n *\n * This is free software: you can redistribute it and/or modify it\n * under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;\n * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n * See the GNU General Public License for more details.\n */\n\npackage com.naman14.timber.fragments;\n\nimport android.content.Intent;\nimport android.content.SharedPreferences;\nimport android.graphics.Color;\nimport android.os.Bundle;\nimport android.preference.ListPreference;\nimport android.preference.Preference;\nimport android.preference.PreferenceFragment;\nimport android.preference.SwitchPreference;\nimport android.view.View;\n\nimport com.afollestad.appthemeengine.ATE;\nimport com.afollestad.appthemeengine.Config;\nimport com.afollestad.appthemeengine.prefs.ATECheckBoxPreference;\nimport com.afollestad.appthemeengine.prefs.ATEColorPreference;\nimport com.afollestad.materialdialogs.color.ColorChooserDialog;\nimport com.naman14.timber.R;\nimport com.naman14.timber.activities.DonateActivity;\nimport com.naman14.timber.activities.SettingsActivity;\nimport com.naman14.timber.dialogs.LastFmLoginDialog;\nimport com.naman14.timber.lastfmapi.LastFmClient;\nimport com.naman14.timber.utils.Constants;\nimport com.naman14.timber.utils.NavigationUtils;\nimport com.naman14.timber.utils.PreferencesUtility;\n\npublic class SettingsFragment extends PreferenceFragment implements SharedPreferences.OnSharedPreferenceChangeListener {\n\n    private static final String NOW_PLAYING_SELECTOR = \"now_playing_selector\";\n    private static final String LASTFM_LOGIN = \"lastfm_login\";\n\n    private static final String LOCKSCREEN = \"show_albumart_lockscreen\";\n    private static final String XPOSED = \"toggle_xposed_trackselector\";\n\n    private static final String KEY_ABOUT = \"preference_about\";\n    private static final String KEY_SOURCE = \"preference_source\";\n    private static final String KEY_THEME = \"theme_preference\";\n    private static final String TOGGLE_ANIMATIONS = \"toggle_animations\";\n    private static final String TOGGLE_SYSTEM_ANIMATIONS = \"toggle_system_animations\";\n    private static final String KEY_START_PAGE = \"start_page_preference\";\n    private boolean lastFMlogedin;\n\n    private Preference nowPlayingSelector,  lastFMlogin, lockscreen, xposed;\n\n    private SwitchPreference toggleAnimations;\n    private ListPreference themePreference, startPagePreference;\n    private PreferencesUtility mPreferences;\n    private String mAteKey;\n\n    @Override\n    public void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n\n        addPreferencesFromResource(R.xml.preferences);\n\n        mPreferences = PreferencesUtility.getInstance(getActivity());\n\n        lockscreen = findPreference(LOCKSCREEN);\n        nowPlayingSelector = findPreference(NOW_PLAYING_SELECTOR);\n\n        xposed = findPreference(XPOSED);\n\n        lastFMlogin = findPreference(LASTFM_LOGIN);\n        updateLastFM();\n//        themePreference = (ListPreference) findPreference(KEY_THEME);\n        startPagePreference = (ListPreference) findPreference(KEY_START_PAGE);\n\n        nowPlayingSelector.setIntent(NavigationUtils.getNavigateToStyleSelectorIntent(getActivity(), Constants.SETTINGS_STYLE_SELECTOR_NOWPLAYING));\n\n        setPreferenceClickListeners();\n\n    }\n\n    @Override\n    public void onSharedPreferenceChanged(SharedPreferences sharedPreferences,\n                                          String key) {\n    }\n\n    private void setPreferenceClickListeners() {\n\n//        themePreference.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {\n//            @Override\n//            public boolean onPreferenceChange(Preference preference, Object newValue) {\n//                Intent i = getActivity().getBaseContext().getPackageManager().getLaunchIntentForPackage(getActivity().getBaseContext().getPackageName());\n//                i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);\n//                startActivity(i);\n//                return true;\n//            }\n//        });\n\n        startPagePreference.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {\n            @Override\n            public boolean onPreferenceChange(Preference preference, Object newValue) {\n                switch ((String) newValue) {\n                    case \"last_opened\":\n                        mPreferences.setLastOpenedAsStartPagePreference(true);\n                        break;\n                    case \"songs\":\n                        mPreferences.setLastOpenedAsStartPagePreference(false);\n                        mPreferences.setStartPageIndex(0);\n                        break;\n                    case \"albums\":\n                        mPreferences.setLastOpenedAsStartPagePreference(false);\n                        mPreferences.setStartPageIndex(1);\n                        break;\n                    case \"artists\":\n                        mPreferences.setLastOpenedAsStartPagePreference(false);\n                        mPreferences.setStartPageIndex(2);\n                        break;\n                }\n                return true;\n            }\n        });\n\n        Intent restoreIntent = new Intent(getActivity(), DonateActivity.class);\n        restoreIntent.putExtra(\"title\", \"Restoring purchases..\");\n        restoreIntent.setAction(\"restore\");\n\n        findPreference(\"support_development\").setIntent(new Intent(getActivity(), DonateActivity.class));\n        findPreference(\"restore_purchases\").setIntent(restoreIntent);\n\n        lockscreen.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {\n            @Override\n            public boolean onPreferenceChange(Preference preference, Object newValue) {\n                Bundle extras = new Bundle();\n                extras.putBoolean(\"lockscreen\",(boolean)newValue);\n                mPreferences.updateService(extras);\n                return true;\n            }\n        });\n\n        xposed.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {\n            @Override\n            public boolean onPreferenceChange(Preference preference, Object newValue) {\n                Bundle extras = new Bundle();\n                extras.putBoolean(\"xtrack\",(boolean)newValue);\n                mPreferences.updateService(extras);\n                return true;\n            }\n        });\n\n        lastFMlogin.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {\n            @Override\n            public boolean onPreferenceClick(Preference preference) {\n                if (lastFMlogedin) {\n                    LastFmClient.getInstance(getActivity()).logout();\n                    Bundle extras = new Bundle();\n                    extras.putString(\"lf_token\",\"logout\");\n                    extras.putString(\"lf_user\",null);\n                    mPreferences.updateService(extras);\n                    updateLastFM();\n                } else {\n                    LastFmLoginDialog lastFmLoginDialog = new LastFmLoginDialog();\n                    lastFmLoginDialog.show(getChildFragmentManager(), LastFmLoginDialog.FRAGMENT_NAME);\n\n                }\n                return true;\n            }\n        });\n\n    }\n\n    @Override\n    public void onViewCreated(View view, Bundle savedInstanceState) {\n        super.onViewCreated(view, savedInstanceState);\n        invalidateSettings();\n        ATE.apply(view, mAteKey);\n    }\n\n    public void invalidateSettings() {\n        mAteKey = ((SettingsActivity) getActivity()).getATEKey();\n\n        ATEColorPreference primaryColorPref = (ATEColorPreference) findPreference(\"primary_color\");\n        primaryColorPref.setColor(Config.primaryColor(getActivity(), mAteKey), Color.BLACK);\n        primaryColorPref.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {\n            @Override\n            public boolean onPreferenceClick(Preference preference) {\n                new ColorChooserDialog.Builder((SettingsActivity) getActivity(), R.string.primary_color)\n                        .preselect(Config.primaryColor(getActivity(), mAteKey))\n                        .show();\n                return true;\n            }\n        });\n\n        ATEColorPreference accentColorPref = (ATEColorPreference) findPreference(\"accent_color\");\n        accentColorPref.setColor(Config.accentColor(getActivity(), mAteKey), Color.BLACK);\n        accentColorPref.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {\n            @Override\n            public boolean onPreferenceClick(Preference preference) {\n                new ColorChooserDialog.Builder((SettingsActivity) getActivity(), R.string.accent_color)\n                        .preselect(Config.accentColor(getActivity(), mAteKey))\n                        .show();\n                return true;\n            }\n        });\n\n\n        findPreference(\"dark_theme\").setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {\n            @Override\n            public boolean onPreferenceChange(Preference preference, Object newValue) {\n                // Marks both theme configs as changed so MainActivity restarts itself on return\n                Config.markChanged(getActivity(), \"light_theme\");\n                Config.markChanged(getActivity(), \"dark_theme\");\n                // The dark_theme preference value gets saved by Android in the default PreferenceManager.\n                // It's used in getATEKey() of both the Activities.\n                getActivity().recreate();\n                return true;\n            }\n        });\n\n        final ATECheckBoxPreference statusBarPref = (ATECheckBoxPreference) findPreference(\"colored_status_bar\");\n        final ATECheckBoxPreference navBarPref = (ATECheckBoxPreference) findPreference(\"colored_nav_bar\");\n\n        statusBarPref.setChecked(Config.coloredStatusBar(getActivity(), mAteKey));\n        statusBarPref.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {\n            @Override\n            public boolean onPreferenceChange(Preference preference, Object newValue) {\n                ATE.config(getActivity(), mAteKey)\n                        .coloredStatusBar((Boolean) newValue)\n                        .apply(getActivity());\n                return true;\n            }\n        });\n\n\n        navBarPref.setChecked(Config.coloredNavigationBar(getActivity(), mAteKey));\n        navBarPref.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {\n            @Override\n            public boolean onPreferenceChange(Preference preference, Object newValue) {\n                ATE.config(getActivity(), mAteKey)\n                        .coloredNavigationBar((Boolean) newValue)\n                        .apply(getActivity());\n                return true;\n            }\n        });\n\n    }\n\n\n    public void updateLastFM() {\n        String username = LastFmClient.getInstance(getActivity()).getUsername();\n        if (username != null) {\n            lastFMlogedin = true;\n            lastFMlogin.setTitle(\"Logout\");\n            lastFMlogin.setSummary(String.format(getString(R.string.lastfm_loged_in),username));\n        } else {\n            lastFMlogedin = false;\n            lastFMlogin.setTitle(\"Login\");\n            lastFMlogin.setSummary(getString(R.string.lastfm_pref));\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/fragments/SimilarArtistFragment.java",
    "content": "/*\n * Copyright (C) 2015 Naman Dwivedi\n *\n * Licensed under the GNU General Public License v3\n *\n * This is free software: you can redistribute it and/or modify it\n * under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;\n * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n * See the GNU General Public License for more details.\n */\n\npackage com.naman14.timber.fragments;\n\nimport android.os.Bundle;\nimport androidx.fragment.app.Fragment;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\n\nimport com.naman14.timber.R;\nimport com.naman14.timber.dataloaders.ArtistLoader;\nimport com.naman14.timber.lastfmapi.LastFmClient;\nimport com.naman14.timber.lastfmapi.callbacks.ArtistInfoListener;\nimport com.naman14.timber.lastfmapi.models.ArtistQuery;\nimport com.naman14.timber.lastfmapi.models.LastfmArtist;\nimport com.naman14.timber.models.Artist;\nimport com.naman14.timber.utils.Constants;\n\npublic class SimilarArtistFragment extends Fragment {\n\n    private long artistID = -1;\n\n    public static SimilarArtistFragment newInstance(long id) {\n        SimilarArtistFragment fragment = new SimilarArtistFragment();\n        Bundle args = new Bundle();\n        args.putLong(Constants.ARTIST_ID, id);\n        fragment.setArguments(args);\n        return fragment;\n    }\n\n    @Override\n    public void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        if (getArguments() != null) {\n            artistID = getArguments().getLong(Constants.ARTIST_ID);\n        }\n    }\n\n    @Override\n    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {\n        View rootView = inflater.inflate(\n                R.layout.fragment_similar_artists, container, false);\n\n        Artist artist = ArtistLoader.getArtist(getActivity(), artistID);\n\n        LastFmClient.getInstance(getActivity()).getArtistInfo(new ArtistQuery(artist.name), new ArtistInfoListener() {\n            @Override\n            public void artistInfoSucess(LastfmArtist artist) {\n\n            }\n\n            @Override\n            public void artistInfoFailed() {\n            }\n        });\n\n        return rootView;\n\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/fragments/SongsFragment.java",
    "content": "/*\n * Copyright (C) 2015 Naman Dwivedi\n *\n * Licensed under the GNU General Public License v3\n *\n * This is free software: you can redistribute it and/or modify it\n * under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;\n * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n * See the GNU General Public License for more details.\n */\n\npackage com.naman14.timber.fragments;\n\nimport android.os.AsyncTask;\nimport android.os.Bundle;\nimport androidx.fragment.app.Fragment;\nimport androidx.appcompat.app.AppCompatActivity;\nimport androidx.recyclerview.widget.LinearLayoutManager;\n\nimport android.view.LayoutInflater;\nimport android.view.Menu;\nimport android.view.MenuInflater;\nimport android.view.MenuItem;\nimport android.view.View;\nimport android.view.ViewGroup;\n\nimport com.naman14.timber.R;\nimport com.naman14.timber.activities.BaseActivity;\nimport com.naman14.timber.adapters.SongsListAdapter;\nimport com.naman14.timber.dataloaders.SongLoader;\nimport com.naman14.timber.listeners.MusicStateListener;\nimport com.naman14.timber.models.Song;\nimport com.naman14.timber.utils.PreferencesUtility;\nimport com.naman14.timber.utils.SortOrder;\nimport com.naman14.timber.widgets.BaseRecyclerView;\nimport com.naman14.timber.widgets.DividerItemDecoration;\nimport com.naman14.timber.widgets.FastScroller;\n\nimport java.util.List;\n\npublic class SongsFragment extends Fragment implements MusicStateListener {\n\n    private SongsListAdapter mAdapter;\n    private BaseRecyclerView recyclerView;\n    private PreferencesUtility mPreferences;\n\n    @Override\n    public void onCreate(final Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        mPreferences = PreferencesUtility.getInstance(getActivity());\n    }\n\n    @Override\n    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {\n        View rootView = inflater.inflate(\n                R.layout.fragment_recyclerview, container, false);\n\n        recyclerView = rootView.findViewById(R.id.recyclerview);\n        recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));\n        recyclerView.setEmptyView(getActivity(), rootView.findViewById(R.id.list_empty), \"No media found\");\n        FastScroller fastScroller =  rootView.findViewById(R.id.fastscroller);\n        fastScroller.setRecyclerView(recyclerView);\n\n        new loadSongs().execute(\"\");\n        ((BaseActivity) getActivity()).setMusicStateListenerListener(this);\n\n        return rootView;\n    }\n\n    public void restartLoader() {\n\n    }\n\n    public void onPlaylistChanged() {\n\n    }\n\n    public void onMetaChanged() {\n        if (mAdapter != null)\n            mAdapter.notifyDataSetChanged();\n    }\n\n    private void reloadAdapter() {\n        new AsyncTask<Void, Void, Void>() {\n            @Override\n            protected Void doInBackground(final Void... unused) {\n                List<Song> songList = SongLoader.getAllSongs(getActivity());\n                mAdapter.updateDataSet(songList);\n                return null;\n            }\n\n            @Override\n            protected void onPostExecute(Void aVoid) {\n                mAdapter.notifyDataSetChanged();\n            }\n        }.execute();\n    }\n\n    @Override\n    public void onActivityCreated(final Bundle savedInstanceState) {\n        super.onActivityCreated(savedInstanceState);\n        setHasOptionsMenu(true);\n    }\n\n    @Override\n    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {\n        super.onCreateOptionsMenu(menu, inflater);\n        inflater.inflate(R.menu.song_sort_by, menu);\n    }\n\n    @Override\n    public boolean onOptionsItemSelected(MenuItem item) {\n        switch (item.getItemId()) {\n            case R.id.menu_sort_by_az:\n                mPreferences.setSongSortOrder(SortOrder.SongSortOrder.SONG_A_Z);\n                reloadAdapter();\n                return true;\n            case R.id.menu_sort_by_za:\n                mPreferences.setSongSortOrder(SortOrder.SongSortOrder.SONG_Z_A);\n                reloadAdapter();\n                return true;\n            case R.id.menu_sort_by_artist:\n                mPreferences.setSongSortOrder(SortOrder.SongSortOrder.SONG_ARTIST);\n                reloadAdapter();\n                return true;\n            case R.id.menu_sort_by_album:\n                mPreferences.setSongSortOrder(SortOrder.SongSortOrder.SONG_ALBUM);\n                reloadAdapter();\n                return true;\n            case R.id.menu_sort_by_year:\n                mPreferences.setSongSortOrder(SortOrder.SongSortOrder.SONG_YEAR);\n                reloadAdapter();\n                return true;\n            case R.id.menu_sort_by_duration:\n                mPreferences.setSongSortOrder(SortOrder.SongSortOrder.SONG_DURATION);\n                reloadAdapter();\n                return true;\n        }\n        return super.onOptionsItemSelected(item);\n    }\n\n    private class loadSongs extends AsyncTask<String, Void, String> {\n\n        @Override\n        protected String doInBackground(String... params) {\n            if (getActivity() != null)\n                mAdapter = new SongsListAdapter((AppCompatActivity) getActivity(), SongLoader.getAllSongs(getActivity()), false, false);\n            return \"Executed\";\n        }\n\n        @Override\n        protected void onPostExecute(String result) {\n            recyclerView.setAdapter(mAdapter);\n            if (getActivity() != null)\n                recyclerView.addItemDecoration(new DividerItemDecoration(getActivity(), DividerItemDecoration.VERTICAL_LIST));\n\n        }\n\n        @Override\n        protected void onPreExecute() {\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/helpers/MediaButtonIntentReceiver.java",
    "content": "/*\n * Copyright (C) 2007 The Android Open Source Project Licensed under the Apache\n * License, Version 2.0 (the \"License\"); you may not use this file except in\n * compliance with the License. You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law\n * or agreed to in writing, software distributed under the License is\n * distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n * KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\npackage com.naman14.timber.helpers;\n\nimport android.content.Context;\nimport android.content.Intent;\nimport android.media.AudioManager;\nimport android.os.Handler;\nimport android.os.Message;\nimport android.os.PowerManager;\nimport android.os.PowerManager.WakeLock;\nimport androidx.legacy.content.WakefulBroadcastReceiver;\nimport android.util.Log;\nimport android.view.KeyEvent;\n\nimport com.naman14.timber.MusicService;\nimport com.naman14.timber.activities.MainActivity;\nimport com.naman14.timber.utils.PreferencesUtility;\n\n/**\n * Used to control headset playback.\n * Single press: pause/resume\n * Double press: next track\n * Triple press: previous track\n * Long press: voice search\n */\npublic class MediaButtonIntentReceiver extends WakefulBroadcastReceiver {\n    private static final boolean DEBUG = false;\n    private static final String TAG = \"ButtonIntentReceiver\";\n\n    private static final int MSG_LONGPRESS_TIMEOUT = 1;\n    private static final int MSG_HEADSET_DOUBLE_CLICK_TIMEOUT = 2;\n\n    private static final int LONG_PRESS_DELAY = 1000;\n    private static final int DOUBLE_CLICK = 800;\n\n    private static WakeLock mWakeLock = null;\n    private static int mClickCounter = 0;\n    private static long mLastClickTime = 0;\n    private static boolean mDown = false;\n    private static boolean mLaunched = false;\n\n    private static Handler mHandler = new Handler() {\n\n        /**\n         * {@inheritDoc}\n         */\n        @Override\n        public void handleMessage(final Message msg) {\n            switch (msg.what) {\n                case MSG_LONGPRESS_TIMEOUT:\n                    if (DEBUG) Log.v(TAG, \"Handling longpress timeout, launched \" + mLaunched);\n                    if (!mLaunched) {\n                        final Context context = (Context) msg.obj;\n                        final Intent i = new Intent();\n                        i.setClass(context, MainActivity.class);\n                        i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);\n                        context.startActivity(i);\n                        mLaunched = true;\n                    }\n                    break;\n\n                case MSG_HEADSET_DOUBLE_CLICK_TIMEOUT:\n                    final int clickCount = msg.arg1;\n                    final String command;\n\n                    if (DEBUG) Log.v(TAG, \"Handling headset click, count = \" + clickCount);\n                    switch (clickCount) {\n                        case 1:\n                            command = MusicService.CMDTOGGLEPAUSE;\n                            break;\n                        case 2:\n                            command = MusicService.CMDNEXT;\n                            break;\n                        case 3:\n                            command = MusicService.CMDPREVIOUS;\n                            break;\n                        default:\n                            command = null;\n                            break;\n                    }\n\n                    if (command != null) {\n                        final Context context = (Context) msg.obj;\n                        startService(context, command);\n                    }\n                    break;\n            }\n            releaseWakeLockIfHandlerIdle();\n        }\n    };\n\n    private static void startService(Context context, String command) {\n        final Intent i = new Intent(context, MusicService.class);\n        i.setAction(MusicService.SERVICECMD);\n        i.putExtra(MusicService.CMDNAME, command);\n        i.putExtra(MusicService.FROM_MEDIA_BUTTON, true);\n        startWakefulService(context, i);\n    }\n\n    private static void acquireWakeLockAndSendMessage(Context context, Message msg, long delay) {\n        if (mWakeLock == null) {\n            Context appContext = context.getApplicationContext();\n            PowerManager pm = (PowerManager) appContext.getSystemService(Context.POWER_SERVICE);\n            mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, \"Timber headset button\");\n            mWakeLock.setReferenceCounted(false);\n        }\n        if (DEBUG) Log.v(TAG, \"Acquiring wake lock and sending \" + msg.what);\n        // Make sure we don't indefinitely hold the wake lock under any circumstances\n        mWakeLock.acquire(10000);\n\n        mHandler.sendMessageDelayed(msg, delay);\n    }\n\n    private static void releaseWakeLockIfHandlerIdle() {\n        if (mHandler.hasMessages(MSG_LONGPRESS_TIMEOUT)\n                || mHandler.hasMessages(MSG_HEADSET_DOUBLE_CLICK_TIMEOUT)) {\n            if (DEBUG) Log.v(TAG, \"Handler still has messages pending, not releasing wake lock\");\n            return;\n        }\n\n        if (mWakeLock != null) {\n            if (DEBUG) Log.v(TAG, \"Releasing wake lock\");\n            mWakeLock.release();\n            mWakeLock = null;\n        }\n    }\n\n    @Override\n    public void onReceive(final Context context, final Intent intent) {\n        final String intentAction = intent.getAction();\n        if (AudioManager.ACTION_AUDIO_BECOMING_NOISY.equals(intentAction)) {\n            if (PreferencesUtility.getInstance(context).pauseEnabledOnDetach())\n                startService(context, MusicService.CMDPAUSE);\n        } else if (Intent.ACTION_MEDIA_BUTTON.equals(intentAction)) {\n            final KeyEvent event = intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);\n            if (event == null) {\n                return;\n            }\n\n            final int keycode = event.getKeyCode();\n            final int action = event.getAction();\n            final long eventtime = event.getEventTime();\n\n            String command = null;\n            switch (keycode) {\n                case KeyEvent.KEYCODE_MEDIA_STOP:\n                    command = MusicService.CMDSTOP;\n                    break;\n                case KeyEvent.KEYCODE_HEADSETHOOK:\n                case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:\n                    command = MusicService.CMDTOGGLEPAUSE;\n                    break;\n                case KeyEvent.KEYCODE_MEDIA_NEXT:\n                    command = MusicService.CMDNEXT;\n                    break;\n                case KeyEvent.KEYCODE_MEDIA_PREVIOUS:\n                    command = MusicService.CMDPREVIOUS;\n                    break;\n                case KeyEvent.KEYCODE_MEDIA_PAUSE:\n                    command = MusicService.CMDPAUSE;\n                    break;\n                case KeyEvent.KEYCODE_MEDIA_PLAY:\n                    command = MusicService.CMDPLAY;\n                    break;\n            }\n            if (command != null) {\n                if (action == KeyEvent.ACTION_DOWN) {\n                    if (mDown) {\n                        if (MusicService.CMDTOGGLEPAUSE.equals(command)\n                                || MusicService.CMDPLAY.equals(command)) {\n                            if (mLastClickTime != 0\n                                    && eventtime - mLastClickTime > LONG_PRESS_DELAY) {\n                                acquireWakeLockAndSendMessage(context,\n                                        mHandler.obtainMessage(MSG_LONGPRESS_TIMEOUT, context), 0);\n                            }\n                        }\n                    } else if (event.getRepeatCount() == 0) {\n\n                        if (keycode == KeyEvent.KEYCODE_HEADSETHOOK) {\n                            if (eventtime - mLastClickTime >= DOUBLE_CLICK) {\n                                mClickCounter = 0;\n                            }\n\n                            mClickCounter++;\n                            if (DEBUG) Log.v(TAG, \"Got headset click, count = \" + mClickCounter);\n                            mHandler.removeMessages(MSG_HEADSET_DOUBLE_CLICK_TIMEOUT);\n\n                            Message msg = mHandler.obtainMessage(\n                                    MSG_HEADSET_DOUBLE_CLICK_TIMEOUT, mClickCounter, 0, context);\n\n                            long delay = mClickCounter < 3 ? DOUBLE_CLICK : 0;\n                            if (mClickCounter >= 3) {\n                                mClickCounter = 0;\n                            }\n                            mLastClickTime = eventtime;\n                            acquireWakeLockAndSendMessage(context, msg, delay);\n                        } else {\n                            startService(context, command);\n                        }\n                        mLaunched = false;\n                        mDown = true;\n                    }\n                } else {\n                    mHandler.removeMessages(MSG_LONGPRESS_TIMEOUT);\n                    mDown = false;\n                }\n                if (isOrderedBroadcast()) {\n                    abortBroadcast();\n                }\n                releaseWakeLockIfHandlerIdle();\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/helpers/MusicPlaybackTrack.java",
    "content": "/*\n* Copyright (C) 2014 The CyanogenMod Project\n*\n* Licensed under the Apache License, Version 2.0 (the \"License\");\n* you may not use this file except in compliance with the License.\n* You may obtain a copy of the License at\n*\n* http://www.apache.org/licenses/LICENSE-2.0\n*\n* Unless required by applicable law or agreed to in writing, software\n* distributed under the License is distributed on an \"AS IS\" BASIS,\n* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n* See the License for the specific language governing permissions and\n* limitations under the License.\n*/\n\npackage com.naman14.timber.helpers;\n\nimport android.os.Parcel;\nimport android.os.Parcelable;\n\nimport com.naman14.timber.utils.TimberUtils;\n\n/**\n * This is used by the music playback service to track the music tracks it is playing\n * It has extra meta data to determine where the track came from so that we can show the appropriate\n * song playing indicator\n */\npublic class MusicPlaybackTrack implements Parcelable {\n\n    public static final Creator<MusicPlaybackTrack> CREATOR = new Creator<MusicPlaybackTrack>() {\n        @Override\n        public MusicPlaybackTrack createFromParcel(Parcel source) {\n            return new MusicPlaybackTrack(source);\n        }\n\n        @Override\n        public MusicPlaybackTrack[] newArray(int size) {\n            return new MusicPlaybackTrack[size];\n        }\n    };\n    public long mId;\n    public long mSourceId;\n    public TimberUtils.IdType mSourceType;\n    public int mSourcePosition;\n\n    public MusicPlaybackTrack(long id, long sourceId, TimberUtils.IdType type, int sourcePosition) {\n        mId = id;\n        mSourceId = sourceId;\n        mSourceType = type;\n        mSourcePosition = sourcePosition;\n    }\n\n    public MusicPlaybackTrack(Parcel in) {\n        mId = in.readLong();\n        mSourceId = in.readLong();\n        mSourceType = TimberUtils.IdType.getTypeById(in.readInt());\n        mSourcePosition = in.readInt();\n    }\n\n    @Override\n    public int describeContents() {\n        return 0;\n    }\n\n    @Override\n    public void writeToParcel(Parcel dest, int flags) {\n        dest.writeLong(mId);\n        dest.writeLong(mSourceId);\n        dest.writeInt(mSourceType.mId);\n        dest.writeInt(mSourcePosition);\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (o instanceof MusicPlaybackTrack) {\n            MusicPlaybackTrack other = (MusicPlaybackTrack) o;\n            if (other != null) {\n                return mId == other.mId\n                        && mSourceId == other.mSourceId\n                        && mSourceType == other.mSourceType\n                        && mSourcePosition == other.mSourcePosition;\n\n            }\n        }\n\n        return super.equals(o);\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/lastfmapi/LastFmClient.java",
    "content": "/*\r\n * Copyright (C) 2015 Naman Dwivedi\r\n *\r\n * Licensed under the GNU General Public License v3\r\n *\r\n * This is free software: you can redistribute it and/or modify it\r\n * under the terms of the GNU General Public License as published by\r\n * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.\r\n *\r\n * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;\r\n * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\r\n * See the GNU General Public License for more details.\r\n */\r\n\r\npackage com.naman14.timber.lastfmapi;\r\n\r\nimport android.content.Context;\r\nimport android.content.SharedPreferences;\r\nimport android.os.Bundle;\r\nimport android.util.Log;\r\n\r\nimport com.naman14.timber.lastfmapi.callbacks.AlbumInfoListener;\r\nimport com.naman14.timber.lastfmapi.callbacks.ArtistInfoListener;\r\nimport com.naman14.timber.lastfmapi.callbacks.UserListener;\r\nimport com.naman14.timber.lastfmapi.models.AlbumInfo;\r\nimport com.naman14.timber.lastfmapi.models.AlbumQuery;\r\nimport com.naman14.timber.lastfmapi.models.ArtistInfo;\r\nimport com.naman14.timber.lastfmapi.models.ArtistQuery;\r\nimport com.naman14.timber.lastfmapi.models.LastfmUserSession;\r\nimport com.naman14.timber.lastfmapi.models.ScrobbleInfo;\r\nimport com.naman14.timber.lastfmapi.models.ScrobbleQuery;\r\nimport com.naman14.timber.lastfmapi.models.UserLoginInfo;\r\nimport com.naman14.timber.lastfmapi.models.UserLoginQuery;\r\nimport com.naman14.timber.utils.PreferencesUtility;\r\n\r\nimport java.io.UnsupportedEncodingException;\r\nimport java.security.MessageDigest;\r\nimport java.security.NoSuchAlgorithmException;\r\nimport java.util.HashSet;\r\nimport java.util.Map;\r\nimport java.util.TreeMap;\r\n\r\nimport retrofit.Callback;\r\nimport retrofit.RetrofitError;\r\nimport retrofit.client.Response;\r\n\r\npublic class LastFmClient {\r\n\r\n    //TODO update the api keys\r\n    public static final String API_KEY = \"62ac1851456e4558bef1c41747b1aec2\";\r\n    public static final String API_SECRET = \"b4ae8965723d67fb18e35d207014d6f3\";\r\n\r\n    public static final String JSON = \"json\";\r\n\r\n    public static final String BASE_API_URL = \"http://ws.audioscrobbler.com/2.0\";\r\n    public static final String BASE_SECURE_API_URL = \"https://ws.audioscrobbler.com/2.0\";\r\n\r\n    public static final String PREFERENCES_NAME = \"Lastfm\";\r\n    static final String PREFERENCE_CACHE_NAME = \"Cache\";\r\n\r\n    private static LastFmClient sInstance;\r\n    private LastFmRestService mRestService;\r\n    private LastFmUserRestService mUserRestService;\r\n\r\n    private HashSet<String> queries;\r\n    private boolean isUploading = false;\r\n\r\n    private Context context;\r\n\r\n    private LastfmUserSession mUserSession;\r\n    private static final Object sLock = new Object();\r\n\r\n    public static LastFmClient getInstance(Context context) {\r\n        synchronized (sLock) {\r\n            if (sInstance == null) {\r\n                sInstance = new LastFmClient();\r\n                sInstance.context = context;\r\n                sInstance.mRestService = RestServiceFactory.createStatic(context, BASE_API_URL, LastFmRestService.class);\r\n                sInstance.mUserRestService = RestServiceFactory.create(context, BASE_SECURE_API_URL, LastFmUserRestService.class);\r\n                sInstance.mUserSession = LastfmUserSession.getSession(context);\r\n\r\n            }\r\n            return sInstance;\r\n        }\r\n    }\r\n\r\n    private static String generateMD5(String in) {\r\n        try {\r\n            byte[] bytesOfMessage = in.getBytes(\"UTF-8\");\r\n            MessageDigest md = MessageDigest.getInstance(\"MD5\");\r\n            byte[] digest = md.digest(bytesOfMessage);\r\n            String out = \"\";\r\n            for (byte symbol : digest) {\r\n                out += String.format(\"%02X\", symbol);\r\n            }\r\n            return out;\r\n        } catch (UnsupportedEncodingException | NoSuchAlgorithmException ignored) {\r\n            return null;\r\n        }\r\n\r\n\r\n    }\r\n\r\n    public void getAlbumInfo(AlbumQuery albumQuery, final AlbumInfoListener listener) {\r\n        mRestService.getAlbumInfo(albumQuery.mArtist, albumQuery.mALbum, new Callback<AlbumInfo>() {\r\n            @Override\r\n            public void success(AlbumInfo albumInfo, Response response) {\r\n                listener.albumInfoSuccess(albumInfo.mAlbum);\r\n            }\r\n\r\n            @Override\r\n            public void failure(RetrofitError error) {\r\n                listener.albumInfoFailed();\r\n                error.printStackTrace();\r\n            }\r\n        });\r\n    }\r\n\r\n    public void getArtistInfo(ArtistQuery artistQuery, final ArtistInfoListener listener) {\r\n        mRestService.getArtistInfo(artistQuery.mArtist, new Callback<ArtistInfo>() {\r\n            @Override\r\n            public void success(ArtistInfo artistInfo, Response response) {\r\n                listener.artistInfoSucess(artistInfo.mArtist);\r\n            }\r\n\r\n            @Override\r\n            public void failure(RetrofitError error) {\r\n                listener.artistInfoFailed();\r\n                error.printStackTrace();\r\n            }\r\n        });\r\n    }\r\n\r\n    public void getUserLoginInfo(UserLoginQuery userLoginQuery, final UserListener listener) {\r\n        mUserRestService.getUserLoginInfo(UserLoginQuery.Method, JSON, API_KEY, generateMD5(userLoginQuery.getSignature()), userLoginQuery.mUsername, userLoginQuery.mPassword, new Callback<UserLoginInfo>() {\r\n            @Override\r\n            public void success(UserLoginInfo userLoginInfo, Response response) {\r\n                Log.d(\"Logedin\", userLoginInfo.mSession.mToken + \" \" + userLoginInfo.mSession.mUsername);\r\n                Bundle extras = new Bundle();\r\n                extras.putString(\"lf_token\",userLoginInfo.mSession.mToken);\r\n                extras.putString(\"lf_user\",userLoginInfo.mSession.mUsername);\r\n                PreferencesUtility.getInstance(context).updateService(extras);\r\n                mUserSession = userLoginInfo.mSession;\r\n                mUserSession.update(context);\r\n                listener.userSuccess();\r\n            }\r\n\r\n            @Override\r\n            public void failure(RetrofitError error) {\r\n                listener.userInfoFailed();\r\n            }\r\n        });\r\n    }\r\n\r\n    public void Scrobble(final ScrobbleQuery scrobbleQuery) {\r\n        if (mUserSession.isLogedin())\r\n            new ScrobbleUploader(scrobbleQuery);\r\n    }\r\n\r\n    private class ScrobbleUploader {\r\n        boolean cachedirty = false;\r\n        ScrobbleQuery newquery;\r\n        SharedPreferences preferences = context.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE);\r\n\r\n        ScrobbleUploader(ScrobbleQuery query) {\r\n            if (queries == null) {\r\n                queries = new HashSet<>();\r\n                queries.addAll(preferences.getStringSet(PREFERENCE_CACHE_NAME, new HashSet<String>()));\r\n            }\r\n            if (query != null) {\r\n                synchronized (sLock) {\r\n                    if (isUploading) {\r\n                        cachedirty = true;\r\n                        queries.add(query.toString());\r\n                        save();\r\n                        return;\r\n                    }\r\n                }\r\n                newquery = query;\r\n            }\r\n            upload();\r\n        }\r\n\r\n        void upload() {\r\n            synchronized (sLock) {\r\n                isUploading = true;\r\n            }\r\n            int size = queries.size();\r\n            if (size == 0 && newquery == null) return;\r\n            //Max 50 Scrobbles per Request (restriction by LastFM)\r\n            if (size > 50) size = 50;\r\n            if (newquery != null && size > 49) size = 49;\r\n            final String currentqueries[] = new String[size];\r\n            int n = 0;\r\n            for (String t : queries) {\r\n                currentqueries[n++] = t;\r\n                if (n >= size) break;\r\n            }\r\n\r\n            TreeMap<String, String> fields = new TreeMap<>();\r\n            fields.put(\"method\", ScrobbleQuery.Method);\r\n            fields.put(\"api_key\", API_KEY);\r\n            fields.put(\"sk\", mUserSession.mToken);\r\n\r\n            int i = 0;\r\n            for (String squery : currentqueries) {\r\n                ScrobbleQuery query = new ScrobbleQuery(squery);\r\n                fields.put(\"artist[\" + i + ']', query.mArtist);\r\n                fields.put(\"track[\" + i + ']', query.mTrack);\r\n                fields.put(\"timestamp[\" + i + ']', Long.toString(query.mTimestamp));\r\n                i++;\r\n            }\r\n            if (newquery != null) {\r\n                fields.put(\"artist[\" + i + ']', newquery.mArtist);\r\n                fields.put(\"track[\" + i + ']', newquery.mTrack);\r\n                fields.put(\"timestamp[\" + i + ']', Long.toString(newquery.mTimestamp));\r\n            }\r\n            String sig = \"\";\r\n            for (Map.Entry<String, String> ent : fields.entrySet()) {\r\n                sig += ent.getKey() + ent.getValue();\r\n            }\r\n            sig += API_SECRET;\r\n            mUserRestService.getScrobbleInfo(generateMD5(sig), JSON, fields, new Callback<ScrobbleInfo>() {\r\n                @Override\r\n                public void success(ScrobbleInfo scrobbleInfo, Response response) {\r\n                    synchronized (sLock) {\r\n                        isUploading = false;\r\n                        cachedirty = true;\r\n                        if (newquery != null) newquery = null;\r\n\r\n                        for (String squery : currentqueries) {\r\n                            queries.remove(squery);\r\n                        }\r\n                        if (queries.size() > 0)\r\n                            upload();\r\n                        else\r\n                            save();\r\n\r\n                    }\r\n                }\r\n\r\n                @Override\r\n                public void failure(RetrofitError error) {\r\n                    synchronized (sLock) {\r\n                        isUploading = false;\r\n                        //Max 500 scrobbles in Cache\r\n                        if (newquery != null && queries.size() <= 500)\r\n                            queries.add(newquery.toString());\r\n\r\n                        if (cachedirty)\r\n                            save();\r\n                    }\r\n                }\r\n            });\r\n\r\n\r\n        }\r\n\r\n        void save() {\r\n            if (!cachedirty) return;\r\n            SharedPreferences.Editor editor = preferences.edit();\r\n            editor.putStringSet(PREFERENCE_CACHE_NAME, queries);\r\n            editor.apply();\r\n        }\r\n\r\n    }\r\n\r\n    public void logout() {\r\n        this.mUserSession.mToken = null;\r\n        this.mUserSession.mUsername = null;\r\n        SharedPreferences preferences = context.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE);\r\n        SharedPreferences.Editor editor = preferences.edit();\r\n        editor.clear();\r\n        editor.apply();\r\n    }\r\n\r\n    public String getUsername() {\r\n        if (mUserSession != null) return mUserSession.mUsername;\r\n        return null;\r\n    }\r\n}\r\n"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/lastfmapi/LastFmRestService.java",
    "content": "/*\n * Copyright (C) 2015 Naman Dwivedi\n *\n * Licensed under the GNU General Public License v3\n *\n * This is free software: you can redistribute it and/or modify it\n * under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;\n * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n * See the GNU General Public License for more details.\n */\n\npackage com.naman14.timber.lastfmapi;\n\nimport com.naman14.timber.lastfmapi.models.AlbumInfo;\nimport com.naman14.timber.lastfmapi.models.ArtistInfo;\n\nimport retrofit.Callback;\nimport retrofit.http.GET;\nimport retrofit.http.Headers;\nimport retrofit.http.Query;\n\npublic interface LastFmRestService {\n\n    String BASE_PARAMETERS_ALBUM = \"/?method=album.getinfo&api_key=fdb3a51437d4281d4d64964d333531d4&format=json\";\n    String BASE_PARAMETERS_ARTIST = \"/?method=artist.getinfo&api_key=fdb3a51437d4281d4d64964d333531d4&format=json\";\n\n    @Headers(\"Cache-Control: public\")\n    @GET(BASE_PARAMETERS_ALBUM)\n    void getAlbumInfo(@Query(\"artist\") String artist, @Query(\"album\") String album, Callback<AlbumInfo> callback);\n\n    @Headers(\"Cache-Control: public\")\n    @GET(BASE_PARAMETERS_ARTIST)\n    void getArtistInfo(@Query(\"artist\") String artist, Callback<ArtistInfo> callback);\n\n}\n"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/lastfmapi/LastFmUserRestService.java",
    "content": "package com.naman14.timber.lastfmapi;\n\nimport com.naman14.timber.lastfmapi.models.ScrobbleInfo;\nimport com.naman14.timber.lastfmapi.models.UserLoginInfo;\n\nimport java.util.Map;\n\nimport retrofit.Callback;\nimport retrofit.http.Field;\nimport retrofit.http.FieldMap;\nimport retrofit.http.FormUrlEncoded;\nimport retrofit.http.POST;\n\n/**\n * Created by christoph on 17.07.16.\n */\npublic interface LastFmUserRestService {\n\n    String BASE = \"/\";\n\n    @POST(BASE)\n    @FormUrlEncoded\n    void getUserLoginInfo(@Field(\"method\") String method, @Field(\"format\") String format, @Field(\"api_key\") String apikey, @Field(\"api_sig\") String apisig, @Field(\"username\") String username, @Field(\"password\") String password, Callback<UserLoginInfo> callback);\n\n    @POST(BASE)\n    @FormUrlEncoded\n    void getScrobbleInfo(@Field(\"api_sig\") String apisig, @Field(\"format\") String format, @FieldMap Map<String, String> fields, Callback<ScrobbleInfo> callback);\n\n}\n"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/lastfmapi/RestServiceFactory.java",
    "content": "/*\n * Copyright (C) 2015 Naman Dwivedi\n *\n * Licensed under the GNU General Public License v3\n *\n * This is free software: you can redistribute it and/or modify it\n * under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;\n * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n * See the GNU General Public License for more details.\n */\n\npackage com.naman14.timber.lastfmapi;\n\nimport android.content.Context;\n\nimport com.naman14.timber.utils.PreferencesUtility;\nimport com.squareup.okhttp.Cache;\nimport com.squareup.okhttp.OkHttpClient;\n\nimport java.util.concurrent.TimeUnit;\n\nimport retrofit.RequestInterceptor;\nimport retrofit.RestAdapter;\nimport retrofit.client.OkClient;\n\npublic class RestServiceFactory {\n    private static final String TAG_OK_HTTP = \"OkHttp\";\n    private static final long CACHE_SIZE = 1024 * 1024;\n\n    public static <T> T createStatic(final Context context, String baseUrl, Class<T> clazz) {\n        final OkHttpClient okHttpClient = new OkHttpClient();\n\n        okHttpClient.setCache(new Cache(context.getApplicationContext().getCacheDir(),\n                CACHE_SIZE));\n        okHttpClient.setConnectTimeout(40, TimeUnit.SECONDS);\n\n        RequestInterceptor interceptor = new RequestInterceptor() {\n            PreferencesUtility prefs = PreferencesUtility.getInstance(context);\n\n            @Override\n            public void intercept(RequestFacade request) {\n                //7-days cache\n                request.addHeader(\"Cache-Control\",\n                        String.format(\"max-age=%d,%smax-stale=%d\",\n                                Integer.valueOf(60 * 60 * 24 * 7),\n                                prefs.loadArtistAndAlbumImages() ? \"\" : \"only-if-cached,\", Integer.valueOf(31536000)));\n                request.addHeader(\"Connection\", \"keep-alive\");\n            }\n        };\n\n        RestAdapter.Builder builder = new RestAdapter.Builder()\n                .setEndpoint(baseUrl)\n                .setRequestInterceptor(interceptor)\n                .setClient(new OkClient(okHttpClient));\n\n        return builder\n                .build()\n                .create(clazz);\n\n    }\n\n    public static <T> T create(final Context context, String baseUrl, Class<T> clazz) {\n\n        RestAdapter.Builder builder = new RestAdapter.Builder()\n                .setEndpoint(baseUrl);\n\n        return builder\n                .build()\n                .create(clazz);\n\n    }\n\n\n}\n"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/lastfmapi/callbacks/AlbumInfoListener.java",
    "content": "/*\n * Copyright (C) 2015 Naman Dwivedi\n *\n * Licensed under the GNU General Public License v3\n *\n * This is free software: you can redistribute it and/or modify it\n * under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;\n * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n * See the GNU General Public License for more details.\n */\n\npackage com.naman14.timber.lastfmapi.callbacks;\n\nimport com.naman14.timber.lastfmapi.models.LastfmAlbum;\n\npublic interface AlbumInfoListener {\n\n    void albumInfoSuccess(LastfmAlbum album);\n\n    void albumInfoFailed();\n\n}\n"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/lastfmapi/callbacks/ArtistInfoListener.java",
    "content": "/*\n * Copyright (C) 2015 Naman Dwivedi\n *\n * Licensed under the GNU General Public License v3\n *\n * This is free software: you can redistribute it and/or modify it\n * under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;\n * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n * See the GNU General Public License for more details.\n */\n\npackage com.naman14.timber.lastfmapi.callbacks;\n\nimport com.naman14.timber.lastfmapi.models.LastfmArtist;\n\npublic interface ArtistInfoListener {\n\n    void artistInfoSucess(LastfmArtist artist);\n\n    void artistInfoFailed();\n\n}\n"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/lastfmapi/callbacks/UserListener.java",
    "content": "package com.naman14.timber.lastfmapi.callbacks;\n\n\n\n/**\n * Created by christoph on 17.07.16.\n */\npublic interface UserListener {\n    void userSuccess();\n\n    void userInfoFailed();\n\n}\n"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/lastfmapi/models/AlbumBio.java",
    "content": "/*\n * Copyright (C) 2015 Naman Dwivedi\n *\n * Licensed under the GNU General Public License v3\n *\n * This is free software: you can redistribute it and/or modify it\n * under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;\n * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n * See the GNU General Public License for more details.\n */\n\npackage com.naman14.timber.lastfmapi.models;\n\npublic class AlbumBio {\n}\n"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/lastfmapi/models/AlbumInfo.java",
    "content": "/*\n * Copyright (C) 2015 Naman Dwivedi\n *\n * Licensed under the GNU General Public License v3\n *\n * This is free software: you can redistribute it and/or modify it\n * under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;\n * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n * See the GNU General Public License for more details.\n */\n\npackage com.naman14.timber.lastfmapi.models;\n\nimport com.google.gson.annotations.SerializedName;\n\npublic class AlbumInfo {\n\n    private static final String ALBUM = \"album\";\n\n\n    @SerializedName(ALBUM)\n    public LastfmAlbum mAlbum;\n}\n"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/lastfmapi/models/AlbumQuery.java",
    "content": "/*\n * Copyright (C) 2015 Naman Dwivedi\n *\n * Licensed under the GNU General Public License v3\n *\n * This is free software: you can redistribute it and/or modify it\n * under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;\n * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n * See the GNU General Public License for more details.\n */\n\npackage com.naman14.timber.lastfmapi.models;\n\nimport com.google.gson.annotations.SerializedName;\n\npublic class AlbumQuery {\n\n    private static final String ALBUM_NAME = \"album\";\n    private static final String ARTIST_NAME = \"artist\";\n\n    @SerializedName(ALBUM_NAME)\n    public String mALbum;\n\n    @SerializedName(ARTIST_NAME)\n    public String mArtist;\n\n    public AlbumQuery(String album, String artist) {\n        this.mALbum = album;\n        this.mArtist = artist;\n    }\n\n\n}\n"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/lastfmapi/models/AlbumTracks.java",
    "content": "/*\n * Copyright (C) 2015 Naman Dwivedi\n *\n * Licensed under the GNU General Public License v3\n *\n * This is free software: you can redistribute it and/or modify it\n * under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;\n * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n * See the GNU General Public License for more details.\n */\n\npackage com.naman14.timber.lastfmapi.models;\n\npublic class AlbumTracks {\n}\n"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/lastfmapi/models/ArtistBio.java",
    "content": "/*\n * Copyright (C) 2015 Naman Dwivedi\n *\n * Licensed under the GNU General Public License v3\n *\n * This is free software: you can redistribute it and/or modify it\n * under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;\n * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n * See the GNU General Public License for more details.\n */\n\npackage com.naman14.timber.lastfmapi.models;\n\nimport com.google.gson.annotations.SerializedName;\n\npublic class ArtistBio {\n\n    private static final String PUBLISHED = \"published\";\n    private static final String SUMMARY = \"summary\";\n    private static final String CONTENT = \"content\";\n    private static final String YEARFORMED = \"yearformed\";\n\n    @SerializedName(PUBLISHED)\n    public String mPublished;\n\n    @SerializedName(SUMMARY)\n    public String mSummary;\n\n    @SerializedName(CONTENT)\n    public String mContent;\n\n    @SerializedName(YEARFORMED)\n    public String mYearFormed;\n\n}\n"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/lastfmapi/models/ArtistInfo.java",
    "content": "/*\n * Copyright (C) 2015 Naman Dwivedi\n *\n * Licensed under the GNU General Public License v3\n *\n * This is free software: you can redistribute it and/or modify it\n * under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;\n * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n * See the GNU General Public License for more details.\n */\n\npackage com.naman14.timber.lastfmapi.models;\n\nimport com.google.gson.annotations.SerializedName;\n\npublic class ArtistInfo {\n\n    private static final String ARTIST = \"artist\";\n\n    @SerializedName(ARTIST)\n    public LastfmArtist mArtist;\n\n}\n"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/lastfmapi/models/ArtistQuery.java",
    "content": "/*\n * Copyright (C) 2015 Naman Dwivedi\n *\n * Licensed under the GNU General Public License v3\n *\n * This is free software: you can redistribute it and/or modify it\n * under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;\n * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n * See the GNU General Public License for more details.\n */\n\npackage com.naman14.timber.lastfmapi.models;\n\nimport com.google.gson.annotations.SerializedName;\n\npublic class ArtistQuery {\n\n    private static final String ARTIST_NAME = \"artist\";\n\n    @SerializedName(ARTIST_NAME)\n    public String mArtist;\n\n    public ArtistQuery(String artist) {\n        this.mArtist = artist;\n    }\n\n\n}\n"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/lastfmapi/models/ArtistTag.java",
    "content": "/*\n * Copyright (C) 2015 Naman Dwivedi\n *\n * Licensed under the GNU General Public License v3\n *\n * This is free software: you can redistribute it and/or modify it\n * under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;\n * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n * See the GNU General Public License for more details.\n */\n\npackage com.naman14.timber.lastfmapi.models;\n\nimport com.google.gson.annotations.SerializedName;\n\npublic class ArtistTag {\n\n    private static final String NAME = \"name\";\n\n    @SerializedName(NAME)\n    public String mName;\n}\n"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/lastfmapi/models/Artwork.java",
    "content": "/*\n * Copyright (C) 2015 Naman Dwivedi\n *\n * Licensed under the GNU General Public License v3\n *\n * This is free software: you can redistribute it and/or modify it\n * under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;\n * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n * See the GNU General Public License for more details.\n */\n\npackage com.naman14.timber.lastfmapi.models;\n\nimport com.google.gson.annotations.SerializedName;\n\npublic class Artwork {\n\n    private static final String URL = \"#text\";\n    private static final String SIZE = \"size\";\n\n    @SerializedName(URL)\n    public String mUrl;\n\n    @SerializedName(SIZE)\n    public String mSize;\n}\n"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/lastfmapi/models/LastfmAlbum.java",
    "content": "/*\n * Copyright (C) 2015 Naman Dwivedi\n *\n * Licensed under the GNU General Public License v3\n *\n * This is free software: you can redistribute it and/or modify it\n * under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;\n * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n * See the GNU General Public License for more details.\n */\n\npackage com.naman14.timber.lastfmapi.models;\n\nimport com.google.gson.annotations.SerializedName;\n\nimport java.util.List;\n\npublic class LastfmAlbum {\n    private static final String IMAGE = \"image\";\n\n    @SerializedName(IMAGE)\n    public List<Artwork> mArtwork;\n\n    // Only needed fields have been defined. See https://www.last.fm/api/show/album.getInfo\n}\n"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/lastfmapi/models/LastfmArtist.java",
    "content": "/*\n * Copyright (C) 2015 Naman Dwivedi\n *\n * Licensed under the GNU General Public License v3\n *\n * This is free software: you can redistribute it and/or modify it\n * under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;\n * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n * See the GNU General Public License for more details.\n */\n\npackage com.naman14.timber.lastfmapi.models;\n\nimport com.google.gson.annotations.SerializedName;\n\nimport java.util.List;\n\npublic class LastfmArtist {\n\n    private static final String NAME = \"name\";\n    private static final String IMAGE = \"image\";\n    private static final String SIMILAR = \"similar\";\n    private static final String TAGS = \"tags\";\n    private static final String BIO = \"bio\";\n\n    @SerializedName(NAME)\n    public String mName;\n\n    @SerializedName(IMAGE)\n    public List<Artwork> mArtwork;\n\n    @SerializedName(SIMILAR)\n    public SimilarArtist mSimilarArtist;\n\n    @SerializedName(TAGS)\n    public ArtistTag mArtistTags;\n\n    @SerializedName(BIO)\n    public ArtistBio mArtistBio;\n\n\n    public class SimilarArtist {\n\n        public static final String ARTIST = \"artist\";\n\n        @SerializedName(ARTIST)\n        public List<LastfmArtist> mSimilarArtist;\n    }\n\n    public class ArtistTag {\n\n        public static final String TAG = \"tag\";\n\n        @SerializedName(TAG)\n        public List<com.naman14.timber.lastfmapi.models.ArtistTag> mTags;\n    }\n\n\n}\n"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/lastfmapi/models/LastfmUserSession.java",
    "content": "package com.naman14.timber.lastfmapi.models;\n\nimport android.content.Context;\nimport android.content.SharedPreferences;\n\nimport com.google.gson.annotations.SerializedName;\nimport com.naman14.timber.lastfmapi.LastFmClient;\n\n/**\n * Created by christoph on 17.07.16.\n */\npublic class LastfmUserSession {\n    private static final String USERNAME = \"name\";\n    private static final String TOKEN = \"key\";\n    private static LastfmUserSession session;\n\n\n    public static LastfmUserSession getSession(Context context) {\n        if (session != null) return session;\n        SharedPreferences preferences = context.getSharedPreferences(LastFmClient.PREFERENCES_NAME, Context.MODE_PRIVATE);\n        session = new LastfmUserSession();\n        session.mToken = preferences.getString(TOKEN, null);\n        session.mUsername = preferences.getString(USERNAME, null);\n        return session;\n    }\n\n    public boolean isLogedin(){\n        return session.mToken != null && session.mUsername != null;\n    }\n\n    public void update(Context context) {\n        SharedPreferences preferences = context.getSharedPreferences(LastFmClient.PREFERENCES_NAME, Context.MODE_PRIVATE);\n        SharedPreferences.Editor editor = preferences.edit();\n        if (this.mToken == null || this.mUsername == null) {\n            editor.clear();\n        } else {\n            editor.putString(TOKEN, this.mToken);\n            editor.putString(USERNAME, this.mUsername);\n        }\n        editor.apply();\n    }\n\n    @SerializedName(USERNAME)\n    public String mUsername;\n\n    @SerializedName(TOKEN)\n    public String mToken;\n}\n"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/lastfmapi/models/ScrobbleInfo.java",
    "content": "package com.naman14.timber.lastfmapi.models;\n\n/**\n * Created by christoph on 17.07.16.\n */\npublic class ScrobbleInfo {\n}\n"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/lastfmapi/models/ScrobbleQuery.java",
    "content": "package com.naman14.timber.lastfmapi.models;\n\nimport com.google.gson.annotations.SerializedName;\nimport com.naman14.timber.lastfmapi.LastFmClient;\n\nimport java.io.UnsupportedEncodingException;\nimport java.net.URLDecoder;\nimport java.net.URLEncoder;\n\n/**\n * Created by christoph on 17.07.16.\n */\npublic class ScrobbleQuery {\n    private static final String ARTIST_NAME = \"artist\";\n    private static final String TRACK_NAME = \"track\";\n    private static final String TIMESTAMP_NAME = \"timestamp\";\n\n    @SerializedName(ARTIST_NAME)\n    public String mArtist;\n\n    @SerializedName(TRACK_NAME)\n    public String mTrack;\n\n    @SerializedName(TIMESTAMP_NAME)\n    public long mTimestamp;\n\n    public static final String Method = \"track.scrobble\";\n\n    public ScrobbleQuery(String in) {\n        String[] arr = in.split(\",\");\n        try {\n            this.mArtist = URLDecoder.decode(arr[0],\"UTF-8\");\n            this.mTrack = URLDecoder.decode(arr[1],\"UTF-8\");\n            this.mTimestamp = Long.parseLong(arr[2],16);\n        } catch (UnsupportedEncodingException ignored) { }\n    }\n\n    public ScrobbleQuery(String artist, String track, long timestamp) {\n        this.mArtist = artist;\n        this.mTrack = track;\n        this.mTimestamp = timestamp;\n    }\n\n    @Override\n    public String toString(){\n        try {\n            return URLEncoder.encode(mArtist,\"UTF-8\")+','+URLEncoder.encode(mTrack,\"UTF-8\")+','+Long.toHexString(mTimestamp);\n        } catch (UnsupportedEncodingException ignored) {\n            return \"\";\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/lastfmapi/models/UserLoginInfo.java",
    "content": "package com.naman14.timber.lastfmapi.models;\n\nimport com.google.gson.annotations.SerializedName;\nimport com.naman14.timber.lastfmapi.LastFmClient;\n\n/**\n * Created by christoph on 17.07.16.\n */\npublic class UserLoginInfo {\n    private static final String SESSION = \"session\";\n\n    @SerializedName(SESSION)\n    public LastfmUserSession mSession;\n\n\n}\n"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/lastfmapi/models/UserLoginQuery.java",
    "content": "package com.naman14.timber.lastfmapi.models;\n\nimport com.google.gson.annotations.SerializedName;\nimport com.naman14.timber.lastfmapi.LastFmClient;\n\n/**\n * Created by christoph on 17.07.16.\n */\npublic class UserLoginQuery {\n    private static final String USERNAME_NAME = \"username\";\n    private static final String PASSWORD_NAME = \"password\";\n\n    @SerializedName(USERNAME_NAME)\n    public String mUsername;\n\n    @SerializedName(PASSWORD_NAME)\n    public String mPassword;\n\n    public static final String Method = \"auth.getMobileSession\";\n\n    public UserLoginQuery(String username, String password) {\n        this.mUsername = username;\n        this.mPassword = password;\n    }\n\n    public String getSignature() {\n        return \"api_key\" + LastFmClient.API_KEY  + \"method\" + Method + \"password\" + mPassword + \"username\" + mUsername + LastFmClient.API_SECRET;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/listeners/MusicStateListener.java",
    "content": "/*\n* Copyright (C) 2014 The CyanogenMod Project\n*\n* Licensed under the Apache License, Version 2.0 (the \"License\");\n* you may not use this file except in compliance with the License.\n* You may obtain a copy of the License at\n*\n* http://www.apache.org/licenses/LICENSE-2.0\n*\n* Unless required by applicable law or agreed to in writing, software\n* distributed under the License is distributed on an \"AS IS\" BASIS,\n* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n* See the License for the specific language governing permissions and\n* limitations under the License.\n*/\n\npackage com.naman14.timber.listeners;\n\n/**\n * Listens for playback changes to send the the fragments bound to this activity\n */\npublic interface MusicStateListener {\n\n    /**\n     * Called when {@link com.naman14.timber.MusicService#REFRESH} is invoked\n     */\n    void restartLoader();\n\n    /**\n     * Called when {@link com.naman14.timber.MusicService#PLAYLIST_CHANGED} is invoked\n     */\n    void onPlaylistChanged();\n\n    /**\n     * Called when {@link com.naman14.timber.MusicService#META_CHANGED} is invoked\n     */\n    void onMetaChanged();\n\n}\n"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/listeners/SimplelTransitionListener.java",
    "content": "/*\n * Copyright (C) 2015 Naman Dwivedi\n *\n * Licensed under the GNU General Public License v3\n *\n * This is free software: you can redistribute it and/or modify it\n * under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;\n * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n * See the GNU General Public License for more details.\n */\n\npackage com.naman14.timber.listeners;\n\nimport android.annotation.TargetApi;\nimport android.transition.Transition;\n\n@TargetApi(21)\npublic class SimplelTransitionListener\n        implements Transition.TransitionListener {\n    public void onTransitionCancel(Transition paramTransition) {\n    }\n\n    public void onTransitionEnd(Transition paramTransition) {\n    }\n\n    public void onTransitionPause(Transition paramTransition) {\n    }\n\n    public void onTransitionResume(Transition paramTransition) {\n    }\n\n    public void onTransitionStart(Transition paramTransition) {\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/models/Album.java",
    "content": "/*\n * Copyright (C) 2015 Naman Dwivedi\n *\n * Licensed under the GNU General Public License v3\n *\n * This is free software: you can redistribute it and/or modify it\n * under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;\n * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n * See the GNU General Public License for more details.\n */\n\npackage com.naman14.timber.models;\n\npublic class Album {\n    public final long artistId;\n    public final String artistName;\n    public final long id;\n    public final int songCount;\n    public final String title;\n    public final int year;\n\n    public Album() {\n        this.id = -1;\n        this.title = \"\";\n        this.artistName = \"\";\n        this.artistId = -1;\n        this.songCount = -1;\n        this.year = -1;\n    }\n\n    public Album(long _id, String _title, String _artistName, long _artistId, int _songCount, int _year) {\n        this.id = _id;\n        this.title = _title;\n        this.artistName = _artistName;\n        this.artistId = _artistId;\n        this.songCount = _songCount;\n        this.year = _year;\n    }\n\n\n}\n"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/models/Artist.java",
    "content": "/*\n * Copyright (C) 2015 Naman Dwivedi\n *\n * Licensed under the GNU General Public License v3\n *\n * This is free software: you can redistribute it and/or modify it\n * under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;\n * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n * See the GNU General Public License for more details.\n */\n\npackage com.naman14.timber.models;\n\npublic class Artist {\n\n    public final int albumCount;\n    public final long id;\n    public final String name;\n    public final int songCount;\n\n    public Artist() {\n        this.id = -1;\n        this.name = \"\";\n        this.songCount = -1;\n        this.albumCount = -1;\n    }\n\n    public Artist(long _id, String _name, int _albumCount, int _songCount) {\n        this.id = _id;\n        this.name = _name;\n        this.songCount = _songCount;\n        this.albumCount = _albumCount;\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/models/Playlist.java",
    "content": "/*\n * Copyright (C) 2015 Naman Dwivedi\n *\n * Licensed under the GNU General Public License v3\n *\n * This is free software: you can redistribute it and/or modify it\n * under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;\n * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n * See the GNU General Public License for more details.\n */\n\npackage com.naman14.timber.models;\n\npublic class Playlist {\n\n    public final long id;\n    public final String name;\n    public final int songCount;\n\n    public Playlist() {\n        this.id = -1;\n        this.name = \"\";\n        this.songCount = -1;\n    }\n\n    public Playlist(long _id, String _name, int _songCount) {\n        this.id = _id;\n        this.name = _name;\n        this.songCount = _songCount;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/models/Song.java",
    "content": "/*\n * Copyright (C) 2015 Naman Dwivedi\n *\n * Licensed under the GNU General Public License v3\n *\n * This is free software: you can redistribute it and/or modify it\n * under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;\n * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n * See the GNU General Public License for more details.\n */\n\npackage com.naman14.timber.models;\n\npublic class Song {\n\n    public final long albumId;\n    public final String albumName;\n    public final long artistId;\n    public final String artistName;\n    public final int duration;\n    public final long id;\n    public final String title;\n    public final int trackNumber;\n\n    public Song() {\n        this.id = -1;\n        this.albumId = -1;\n        this.artistId = -1;\n        this.title = \"\";\n        this.artistName = \"\";\n        this.albumName = \"\";\n        this.duration = -1;\n        this.trackNumber = -1;\n    }\n\n    public Song(long _id, long _albumId, long _artistId, String _title, String _artistName, String _albumName, int _duration, int _trackNumber) {\n        this.id = _id;\n        this.albumId = _albumId;\n        this.artistId = _artistId;\n        this.title = _title;\n        this.artistName = _artistName;\n        this.albumName = _albumName;\n        this.duration = _duration;\n        this.trackNumber = _trackNumber;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/nowplaying/BaseNowplayingFragment.java",
    "content": "/*\n * Copyright (C) 2015 Naman Dwivedi\n *\n * Licensed under the GNU General Public License v3\n *\n * This is free software: you can redistribute it and/or modify it\n * under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;\n * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n * See the GNU General Public License for more details.\n */\n\npackage com.naman14.timber.nowplaying;\n\nimport android.animation.ObjectAnimator;\nimport android.graphics.Bitmap;\nimport android.graphics.PorterDuff;\nimport android.os.AsyncTask;\nimport android.os.Bundle;\nimport android.os.Handler;\nimport android.preference.PreferenceManager;\nimport androidx.annotation.Nullable;\nimport com.google.android.material.floatingactionbutton.FloatingActionButton;\nimport androidx.fragment.app.Fragment;\nimport androidx.core.content.ContextCompat;\nimport androidx.appcompat.app.ActionBar;\nimport androidx.appcompat.app.AppCompatActivity;\nimport androidx.recyclerview.widget.LinearLayoutManager;\nimport androidx.recyclerview.widget.RecyclerView;\nimport androidx.appcompat.widget.Toolbar;\nimport android.util.Log;\nimport android.view.Menu;\nimport android.view.MenuInflater;\nimport android.view.MenuItem;\nimport android.view.View;\nimport android.widget.ImageView;\nimport android.widget.SeekBar;\nimport android.widget.TextView;\nimport android.widget.Toast;\n\nimport com.afollestad.appthemeengine.ATE;\nimport com.afollestad.appthemeengine.Config;\nimport com.naman14.timber.MusicPlayer;\nimport com.naman14.timber.MusicService;\nimport com.naman14.timber.R;\nimport com.naman14.timber.activities.BaseActivity;\nimport com.naman14.timber.adapters.BaseQueueAdapter;\nimport com.naman14.timber.adapters.SlidingQueueAdapter;\nimport com.naman14.timber.dataloaders.QueueLoader;\nimport com.naman14.timber.listeners.MusicStateListener;\nimport com.naman14.timber.timely.TimelyView;\nimport com.naman14.timber.utils.Helpers;\nimport com.naman14.timber.utils.NavigationUtils;\nimport com.naman14.timber.utils.PreferencesUtility;\nimport com.naman14.timber.utils.SlideTrackSwitcher;\nimport com.naman14.timber.utils.TimberUtils;\nimport com.naman14.timber.widgets.CircularSeekBar;\nimport com.naman14.timber.widgets.DividerItemDecoration;\nimport com.naman14.timber.widgets.PlayPauseButton;\nimport com.naman14.timber.widgets.PlayPauseDrawable;\nimport com.nostra13.universalimageloader.core.DisplayImageOptions;\nimport com.nostra13.universalimageloader.core.ImageLoader;\nimport com.nostra13.universalimageloader.core.assist.FailReason;\nimport com.nostra13.universalimageloader.core.listener.SimpleImageLoadingListener;\n\nimport net.steamcrafted.materialiconlib.MaterialDrawableBuilder;\nimport net.steamcrafted.materialiconlib.MaterialIconView;\n\nimport java.security.InvalidParameterException;\n\npublic class BaseNowplayingFragment extends Fragment implements MusicStateListener {\n\n    private MaterialIconView previous, next;\n    private PlayPauseButton mPlayPause;\n    private PlayPauseDrawable playPauseDrawable = new PlayPauseDrawable();\n    private FloatingActionButton playPauseFloating;\n    private View playPauseWrapper;\n\n    private String ateKey;\n    private int overflowcounter = 0;\n    private TextView songtitle, songalbum, songartist, songduration, elapsedtime;\n    private SeekBar mProgress;\n    boolean fragmentPaused = false;\n\n    private CircularSeekBar mCircularProgress;\n    private BaseQueueAdapter mAdapter;\n    private SlidingQueueAdapter slidingQueueAdapter;\n\n    private TimelyView timelyView11, timelyView12, timelyView13, timelyView14, timelyView15;\n    private TextView hourColon;\n    private int[] timeArr = new int[]{0, 0, 0, 0, 0};\n    private Handler mElapsedTimeHandler;\n    private boolean duetoplaypause = false;\n\n    public ImageView albumart, shuffle, repeat;\n    public int accentColor;\n    public RecyclerView recyclerView;\n\n    //seekbar\n    public Runnable mUpdateProgress = new Runnable() {\n\n        @Override\n        public void run() {\n\n            long position = MusicPlayer.position();\n            if (mProgress != null) {\n                mProgress.setProgress((int) position);\n                if (elapsedtime != null && getActivity() != null)\n                    elapsedtime.setText(TimberUtils.makeShortTimeString(getActivity(), position / 1000));\n            }\n            overflowcounter--;\n            int delay = 250; //not sure why this delay was so high before\n            if (overflowcounter < 0 && !fragmentPaused) {\n                    overflowcounter++;\n                    mProgress.postDelayed(mUpdateProgress, delay); //delay\n            }\n        }\n    };\n\n    //circular seekbar\n    public Runnable mUpdateCircularProgress = new Runnable() {\n\n        @Override\n        public void run() {\n            long position = MusicPlayer.position();\n            if (mCircularProgress != null) {\n                mCircularProgress.setProgress((int) position);\n                if (elapsedtime != null && getActivity() != null)\n                    elapsedtime.setText(TimberUtils.makeShortTimeString(getActivity(), position / 1000));\n\n            }\n            overflowcounter--;\n            if (MusicPlayer.isPlaying()) {\n                int delay = (int) (1500 - (position % 1000));\n                if (overflowcounter < 0 && !fragmentPaused) {\n                    overflowcounter++;\n                    mCircularProgress.postDelayed(mUpdateCircularProgress, delay);\n                }\n            }\n\n        }\n    };\n\n    public Runnable mUpdateElapsedTime = new Runnable() {\n        @Override\n        public void run() {\n            if (getActivity() != null) {\n                String time = TimberUtils.makeShortTimeString(getActivity(), MusicPlayer.position() / 1000);\n                if (time.length() < 5) {\n                    timelyView11.setVisibility(View.GONE);\n                    timelyView12.setVisibility(View.GONE);\n                    hourColon.setVisibility(View.GONE);\n                    tv13(time.charAt(0) - '0');\n                    tv14(time.charAt(2) - '0');\n                    tv15(time.charAt(3) - '0');\n                } else if (time.length() == 5) {\n                    timelyView12.setVisibility(View.VISIBLE);\n                    tv12(time.charAt(0) - '0');\n                    tv13(time.charAt(1) - '0');\n                    tv14(time.charAt(3) - '0');\n                    tv15(time.charAt(4) - '0');\n                } else {\n                    timelyView11.setVisibility(View.VISIBLE);\n                    hourColon.setVisibility(View.VISIBLE);\n                    tv11(time.charAt(0) - '0');\n                    tv12(time.charAt(2) - '0');\n                    tv13(time.charAt(3) - '0');\n                    tv14(time.charAt(5) - '0');\n                    tv15(time.charAt(6) - '0');\n                }\n                mElapsedTimeHandler.postDelayed(this, 600);\n            }\n\n        }\n    };\n\n    private final View.OnClickListener mButtonListener = new View.OnClickListener() {\n        @Override\n        public void onClick(View v) {\n            duetoplaypause = true;\n            if (!mPlayPause.isPlayed()) {\n                mPlayPause.setPlayed(true);\n                mPlayPause.startAnimation();\n            } else {\n                mPlayPause.setPlayed(false);\n                mPlayPause.startAnimation();\n            }\n            Handler handler = new Handler();\n            handler.postDelayed(new Runnable() {\n                @Override\n                public void run() {\n                    MusicPlayer.playOrPause();\n                    if (recyclerView != null && recyclerView.getAdapter() != null)\n                        recyclerView.getAdapter().notifyDataSetChanged();\n                }\n            }, 200);\n\n\n        }\n    };\n\n    private final View.OnClickListener mFLoatingButtonListener = new View.OnClickListener() {\n        @Override\n        public void onClick(View v) {\n            duetoplaypause = true;\n            if(MusicPlayer.getCurrentTrack() == null) {\n                Toast.makeText(getContext(), getString(R.string.now_playing_no_track_selected), Toast.LENGTH_SHORT).show();\n            } else {\n                playPauseDrawable.transformToPlay(true);\n                playPauseDrawable.transformToPause(true);\n                Handler handler = new Handler();\n                handler.postDelayed(new Runnable() {\n                    @Override\n                    public void run() {\n                        MusicPlayer.playOrPause();\n                        if (recyclerView != null && recyclerView.getAdapter() != null)\n                            recyclerView.getAdapter().notifyDataSetChanged();\n                    }\n                }, 250);\n            }\n\n\n\n        }\n    };\n\n    @Override\n    public void onCreate(@Nullable Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        ateKey = Helpers.getATEKey(getActivity());\n        accentColor = Config.accentColor(getActivity(), ateKey);\n    }\n\n    @Override\n    public void onActivityCreated(@Nullable Bundle savedInstanceState) {\n        super.onActivityCreated(savedInstanceState);\n        setHasOptionsMenu(true);\n    }\n\n    @Override\n    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {\n        super.onCreateOptionsMenu(menu, inflater);\n        inflater.inflate(R.menu.now_playing, menu);\n    }\n\n    @Override\n    public boolean onOptionsItemSelected(MenuItem item) {\n        switch (item.getItemId()) {\n            case R.id.menu_go_to_album:\n                NavigationUtils.goToAlbum(getContext(), MusicPlayer.getCurrentAlbumId());\n                break;\n            case R.id.menu_go_to_artist:\n                NavigationUtils.goToArtist(getContext(), MusicPlayer.getCurrentArtistId());\n                break;\n            case R.id.action_lyrics:\n                NavigationUtils.goToLyrics(getContext());\n                break;\n        }\n        return super.onOptionsItemSelected(item);\n    }\n\n    @Override\n    public void onPause() {\n        super.onPause();\n        fragmentPaused = true;\n    }\n\n    @Override\n    public void onResume() {\n        super.onResume();\n        fragmentPaused = false;\n        if (mProgress != null)\n            mProgress.postDelayed(mUpdateProgress, 10);\n\n        if (mCircularProgress != null)\n            mCircularProgress.postDelayed(mUpdateCircularProgress, 10);\n    }\n\n    public void setSongDetails(View view) {\n\n        albumart = (ImageView) view.findViewById(R.id.album_art);\n        shuffle = (ImageView) view.findViewById(R.id.shuffle);\n        repeat = (ImageView) view.findViewById(R.id.repeat);\n        next = (MaterialIconView) view.findViewById(R.id.next);\n        previous = (MaterialIconView) view.findViewById(R.id.previous);\n        mPlayPause = (PlayPauseButton) view.findViewById(R.id.playpause);\n        playPauseFloating = (FloatingActionButton) view.findViewById(R.id.playpausefloating);\n        playPauseWrapper = view.findViewById(R.id.playpausewrapper);\n\n        songtitle = (TextView) view.findViewById(R.id.song_title);\n        songalbum = (TextView) view.findViewById(R.id.song_album);\n        songartist = (TextView) view.findViewById(R.id.song_artist);\n        songduration = (TextView) view.findViewById(R.id.song_duration);\n        elapsedtime = (TextView) view.findViewById(R.id.song_elapsed_time);\n\n        timelyView11 = (TimelyView) view.findViewById(R.id.timelyView11);\n        timelyView12 = (TimelyView) view.findViewById(R.id.timelyView12);\n        timelyView13 = (TimelyView) view.findViewById(R.id.timelyView13);\n        timelyView14 = (TimelyView) view.findViewById(R.id.timelyView14);\n        timelyView15 = (TimelyView) view.findViewById(R.id.timelyView15);\n        hourColon = (TextView) view.findViewById(R.id.hour_colon);\n\n        mProgress = (SeekBar) view.findViewById(R.id.song_progress);\n        mCircularProgress = (CircularSeekBar) view.findViewById(R.id.song_progress_circular);\n\n        recyclerView = (RecyclerView) view.findViewById(R.id.queue_recyclerview);\n\n\n        songtitle.setSelected(true);\n\n\n        Toolbar toolbar = (Toolbar) view.findViewById(R.id.toolbar);\n        if (toolbar != null) {\n            ((AppCompatActivity) getActivity()).setSupportActionBar(toolbar);\n            final ActionBar ab = ((AppCompatActivity) getActivity()).getSupportActionBar();\n            ab.setDisplayHomeAsUpEnabled(true);\n            ab.setTitle(\"\");\n        }\n        if (mPlayPause != null && getActivity() != null) {\n            mPlayPause.setColor(ContextCompat.getColor(getContext(), android.R.color.white));\n        }\n\n        if (playPauseFloating != null) {\n            playPauseDrawable.setColorFilter(TimberUtils.getBlackWhiteColor(accentColor), PorterDuff.Mode.MULTIPLY);\n            playPauseFloating.setImageDrawable(playPauseDrawable);\n            if (MusicPlayer.isPlaying())\n                playPauseDrawable.transformToPause(false);\n            else playPauseDrawable.transformToPlay(false);\n        }\n\n        if (mCircularProgress != null) {\n            mCircularProgress.setCircleProgressColor(accentColor);\n            mCircularProgress.setPointerColor(accentColor);\n            mCircularProgress.setPointerHaloColor(accentColor);\n        }\n\n        if (timelyView11 != null) {\n            String time = TimberUtils.makeShortTimeString(getActivity(), MusicPlayer.position() / 1000);\n            if (time.length() < 5) {\n                timelyView11.setVisibility(View.GONE);\n                timelyView12.setVisibility(View.GONE);\n                hourColon.setVisibility(View.GONE);\n\n                changeDigit(timelyView13, time.charAt(0) - '0');\n                changeDigit(timelyView14, time.charAt(2) - '0');\n                changeDigit(timelyView15, time.charAt(3) - '0');\n\n            } else if (time.length() == 5) {\n                timelyView12.setVisibility(View.VISIBLE);\n                changeDigit(timelyView12, time.charAt(0) - '0');\n                changeDigit(timelyView13, time.charAt(1) - '0');\n                changeDigit(timelyView14, time.charAt(3) - '0');\n                changeDigit(timelyView15, time.charAt(4) - '0');\n            } else {\n                timelyView11.setVisibility(View.VISIBLE);\n                hourColon.setVisibility(View.VISIBLE);\n                changeDigit(timelyView11, time.charAt(0) - '0');\n                changeDigit(timelyView12, time.charAt(2) - '0');\n                changeDigit(timelyView13, time.charAt(3) - '0');\n                changeDigit(timelyView14, time.charAt(5) - '0');\n                changeDigit(timelyView15, time.charAt(6) - '0');\n            }\n        }\n\n        setSongDetails();\n\n    }\n\n    @Override\n    public void onViewCreated(View view, Bundle savedInstanceState) {\n        super.onViewCreated(view, savedInstanceState);\n        if (PreferenceManager.getDefaultSharedPreferences(getActivity()).getBoolean(\"dark_theme\", false)) {\n            ATE.apply(this, \"dark_theme\");\n        } else {\n            ATE.apply(this, \"light_theme\");\n        }\n    }\n\n    private void setSongDetails() {\n        updateSongDetails();\n\n        if (recyclerView != null)\n            setQueueSongs();\n\n        setSeekBarListener();\n\n        if (next != null) {\n            next.setOnClickListener(new View.OnClickListener() {\n                @Override\n                public void onClick(View view) {\n                    Handler handler = new Handler();\n                    handler.postDelayed(new Runnable() {\n                        @Override\n                        public void run() {\n                            MusicPlayer.next();\n                            notifyPlayingDrawableChange();\n                        }\n                    }, 200);\n\n                }\n            });\n        }\n        if (previous != null) {\n            previous.setOnClickListener(new View.OnClickListener() {\n                @Override\n                public void onClick(View view) {\n                    Handler handler = new Handler();\n                    handler.postDelayed(new Runnable() {\n                        @Override\n                        public void run() {\n                            MusicPlayer.previous(getActivity(), false);\n                            notifyPlayingDrawableChange();\n                        }\n                    }, 200);\n\n                }\n            });\n        }\n\n        if (playPauseWrapper != null)\n            playPauseWrapper.setOnClickListener(mButtonListener);\n\n        if (playPauseFloating != null)\n            playPauseFloating.setOnClickListener(mFLoatingButtonListener);\n\n        updateShuffleState();\n        updateRepeatState();\n\n    }\n\n    public void updateShuffleState() {\n        if (shuffle != null && getActivity() != null) {\n            MaterialDrawableBuilder builder = MaterialDrawableBuilder.with(getActivity())\n                    .setIcon(MaterialDrawableBuilder.IconValue.SHUFFLE)\n                    .setSizeDp(30);\n\n            if (getActivity() != null) {\n                if (MusicPlayer.getShuffleMode() == 0) {\n                    builder.setColor(Config.textColorPrimary(getActivity(), ateKey));\n                } else builder.setColor(Config.accentColor(getActivity(), ateKey));\n            }\n\n            shuffle.setImageDrawable(builder.build());\n            shuffle.setOnClickListener(new View.OnClickListener() {\n                @Override\n                public void onClick(View view) {\n                    MusicPlayer.cycleShuffle();\n                    updateShuffleState();\n                    updateRepeatState();\n                }\n            });\n        }\n    }\n\n    public void updateRepeatState() {\n        if (repeat != null && getActivity() != null) {\n            MaterialDrawableBuilder builder = MaterialDrawableBuilder.with(getActivity())\n                    .setSizeDp(30);\n\n                if (MusicPlayer.getRepeatMode() == MusicService.REPEAT_NONE) {\n                    builder.setIcon(MaterialDrawableBuilder.IconValue.REPEAT);\n                    builder.setColor(Config.textColorPrimary(getActivity(), ateKey));\n                } else if (MusicPlayer.getRepeatMode() == MusicService.REPEAT_CURRENT) {\n                    builder.setIcon(MaterialDrawableBuilder.IconValue.REPEAT_ONCE);\n                    builder.setColor(Config.accentColor(getActivity(), ateKey));\n                } else if (MusicPlayer.getRepeatMode() == MusicService.REPEAT_ALL) {\n                    builder.setColor(Config.accentColor(getActivity(), ateKey));\n                    builder.setIcon(MaterialDrawableBuilder.IconValue.REPEAT);\n                }\n\n\n            repeat.setImageDrawable(builder.build());\n            repeat.setOnClickListener(new View.OnClickListener() {\n                @Override\n                public void onClick(View view) {\n                    MusicPlayer.cycleRepeat();\n                    updateRepeatState();\n                    updateShuffleState();\n                }\n            });\n        }\n    }\n\n    private void setSeekBarListener() {\n        if (mProgress != null)\n            mProgress.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {\n                @Override\n                public void onProgressChanged(SeekBar seekBar, int i, boolean b) {\n                    if (b) {\n                        MusicPlayer.seek((long) i);\n                    }\n                }\n\n                @Override\n                public void onStartTrackingTouch(SeekBar seekBar) {\n                }\n\n                @Override\n                public void onStopTrackingTouch(SeekBar seekBar) {\n                }\n            });\n        if (mCircularProgress != null) {\n            mCircularProgress.setOnSeekBarChangeListener(new CircularSeekBar.OnCircularSeekBarChangeListener() {\n                @Override\n                public void onProgressChanged(CircularSeekBar circularSeekBar, int progress, boolean fromUser) {\n                    if (fromUser) {\n                        MusicPlayer.seek((long) progress);\n                    }\n                }\n\n                @Override\n                public void onStopTrackingTouch(CircularSeekBar seekBar) {\n\n                }\n\n                @Override\n                public void onStartTrackingTouch(CircularSeekBar seekBar) {\n\n                }\n            });\n        }\n    }\n\n    public void updateSongDetails() {\n        //do not reload image if it was a play/pause change\n        if (!duetoplaypause) {\n            if (albumart != null) {\n                ImageLoader.getInstance().displayImage(TimberUtils.getAlbumArtUri(MusicPlayer.getCurrentAlbumId()).toString(), albumart,\n                        new DisplayImageOptions.Builder().cacheInMemory(true)\n                                .showImageOnFail(R.drawable.ic_empty_music2)\n                                .build(), new SimpleImageLoadingListener() {\n\n                            @Override\n                            public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {\n                                doAlbumArtStuff(loadedImage);\n                            }\n\n                            @Override\n                            public void onLoadingFailed(String imageUri, View view, FailReason failReason) {\n                                Bitmap failedBitmap = ImageLoader.getInstance().loadImageSync(\"drawable://\" + R.drawable.ic_empty_music2);\n                                doAlbumArtStuff(failedBitmap);\n                            }\n\n                        });\n            }\n            if (songtitle != null && MusicPlayer.getTrackName() != null) {\n                    songtitle.setText(MusicPlayer.getTrackName());\n                    if(MusicPlayer.getTrackName().length() <= 23){\n                        songtitle.setTextSize(25);\n                    }\n                    else if(MusicPlayer.getTrackName().length() >= 30){\n                        songtitle.setTextSize(18);\n                    }\n                    else{\n                        songtitle.setTextSize(18 + (MusicPlayer.getTrackName().length() - 24));\n                    }\n                    Log.v(\"BaseNowPlayingFrag\", \"Title Text Size: \" + songtitle.getTextSize());\n            }\n            if (songartist != null) {\n                songartist.setText(MusicPlayer.getArtistName());\n                songartist.setOnClickListener(new View.OnClickListener() {\n                    @Override\n                    public void onClick(View v) {\n                        NavigationUtils.goToArtist(getContext(), MusicPlayer.getCurrentArtistId());\n                    }\n                });\n            }\n            if (songalbum != null)\n                songalbum.setText(MusicPlayer.getAlbumName());\n\n        }\n        duetoplaypause = false;\n\n        if (mPlayPause != null)\n            updatePlayPauseButton();\n\n        if (playPauseFloating != null)\n            updatePlayPauseFloatingButton();\n\n        if (songduration != null && getActivity() != null)\n            songduration.setText(TimberUtils.makeShortTimeString(getActivity(), MusicPlayer.duration() / 1000));\n\n        if (mProgress != null) {\n            mProgress.setMax((int) MusicPlayer.duration());\n            if (mUpdateProgress != null) {\n                mProgress.removeCallbacks(mUpdateProgress);\n            }\n            mProgress.postDelayed(mUpdateProgress, 10);\n        }\n        if (mCircularProgress != null) {\n            mCircularProgress.setMax((int) MusicPlayer.duration());\n            if (mUpdateCircularProgress != null) {\n                mCircularProgress.removeCallbacks(mUpdateCircularProgress);\n            }\n            mCircularProgress.postDelayed(mUpdateCircularProgress, 10);\n        }\n\n        if (timelyView11 != null) {\n            mElapsedTimeHandler = new Handler();\n            mElapsedTimeHandler.postDelayed(mUpdateElapsedTime, 600);\n        }\n    }\n\n    public void setQueueSongs() {\n        recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));\n        //load queue songs in asynctask\n        if (getActivity() != null)\n            new loadQueueSongs().execute(\"\");\n\n    }\n\n    public void updatePlayPauseButton() {\n        if (MusicPlayer.isPlaying()) {\n            if (!mPlayPause.isPlayed()) {\n                mPlayPause.setPlayed(true);\n                mPlayPause.startAnimation();\n            }\n        } else {\n            if (mPlayPause.isPlayed()) {\n                mPlayPause.setPlayed(false);\n                mPlayPause.startAnimation();\n            }\n        }\n    }\n\n    public void updatePlayPauseFloatingButton() {\n        if (MusicPlayer.isPlaying()) {\n            playPauseDrawable.transformToPause(false);\n        } else {\n            playPauseDrawable.transformToPlay(false);\n        }\n    }\n\n    public void notifyPlayingDrawableChange() {\n        int position = MusicPlayer.getQueuePosition();\n        BaseQueueAdapter.currentlyPlayingPosition = position;\n    }\n\n    public void restartLoader() {\n\n    }\n\n    public void onPlaylistChanged() {\n\n    }\n\n    public void onMetaChanged() {\n        updateSongDetails();\n\n        if (recyclerView != null && recyclerView.getAdapter() != null)\n            recyclerView.getAdapter().notifyDataSetChanged();\n    }\n\n    public void setMusicStateListener() {\n        ((BaseActivity) getActivity()).setMusicStateListenerListener(this);\n    }\n\n    public void doAlbumArtStuff(Bitmap loadedImage) {\n\n    }\n\n    public void changeDigit(TimelyView tv, int end) {\n        ObjectAnimator obja = tv.animate(end);\n        obja.setDuration(400);\n        obja.start();\n    }\n\n    public void changeDigit(TimelyView tv, int start, int end) {\n        try {\n            ObjectAnimator obja = tv.animate(start, end);\n            obja.setDuration(400);\n            obja.start();\n        } catch (InvalidParameterException e) {\n            e.printStackTrace();\n        }\n    }\n\n    public void tv11(int a) {\n        if (a != timeArr[0]) {\n            changeDigit(timelyView11, timeArr[0], a);\n            timeArr[0] = a;\n        }\n    }\n\n    public void tv12(int a) {\n        if (a != timeArr[1]) {\n            changeDigit(timelyView12, timeArr[1], a);\n            timeArr[1] = a;\n        }\n    }\n\n    public void tv13(int a) {\n        if (a != timeArr[2]) {\n            changeDigit(timelyView13, timeArr[2], a);\n            timeArr[2] = a;\n        }\n    }\n\n    public void tv14(int a) {\n        if (a != timeArr[3]) {\n            changeDigit(timelyView14, timeArr[3], a);\n            timeArr[3] = a;\n        }\n    }\n\n    public void tv15(int a) {\n        if (a != timeArr[4]) {\n            changeDigit(timelyView15, timeArr[4], a);\n            timeArr[4] = a;\n        }\n    }\n\n    protected void initGestures(View v) {\n        if (PreferencesUtility.getInstance(v.getContext()).isGesturesEnabled()) {\n            new SlideTrackSwitcher() {\n                @Override\n                public void onSwipeBottom() {\n                    getActivity().finish();\n                }\n            }.attach(v);\n        }\n    }\n\n    private class loadQueueSongs extends AsyncTask<String, Void, String> {\n\n        @Override\n        protected String doInBackground(String... params) {\n            if (getActivity() != null) {\n                mAdapter = new BaseQueueAdapter((AppCompatActivity) getActivity(), QueueLoader.getQueueSongs(getActivity()));\n                return \"Executed\";\n            } else return null;\n        }\n\n        @Override\n        protected void onPostExecute(String result) {\n            if (result != null) {\n                recyclerView.setAdapter(mAdapter);\n                if (getActivity() != null)\n                    recyclerView.addItemDecoration(new DividerItemDecoration(getActivity(), DividerItemDecoration.VERTICAL_LIST));\n                recyclerView.scrollToPosition(MusicPlayer.getQueuePosition() - 1);\n            }\n\n        }\n\n        @Override\n        protected void onPreExecute() {\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/nowplaying/Timber1.java",
    "content": "/*\n * Copyright (C) 2015 Naman Dwivedi\n *\n * Licensed under the GNU General Public License v3\n *\n * This is free software: you can redistribute it and/or modify it\n * under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;\n * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n * See the GNU General Public License for more details.\n */\n\npackage com.naman14.timber.nowplaying;\n\nimport android.os.Bundle;\nimport android.os.Handler;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\n\nimport com.naman14.timber.MusicPlayer;\nimport com.naman14.timber.MusicService;\nimport com.naman14.timber.R;\nimport com.naman14.timber.utils.TimberUtils;\n\nimport net.steamcrafted.materialiconlib.MaterialDrawableBuilder;\n\npublic class Timber1 extends BaseNowplayingFragment {\n\n    @Override\n    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {\n        View rootView = inflater.inflate(\n                R.layout.fragment_timber1, container, false);\n\n        setMusicStateListener();\n        setSongDetails(rootView);\n        initGestures(rootView.findViewById(R.id.album_art));\n\n        return rootView;\n    }\n\n    @Override\n    public void updateShuffleState() {\n        if (shuffle != null && getActivity() != null) {\n            MaterialDrawableBuilder builder = MaterialDrawableBuilder.with(getActivity())\n                    .setIcon(MaterialDrawableBuilder.IconValue.SHUFFLE)\n                    .setSizeDp(30);\n\n            builder.setColor(TimberUtils.getBlackWhiteColor(accentColor));\n\n            shuffle.setImageDrawable(builder.build());\n            shuffle.setOnClickListener(new View.OnClickListener() {\n                @Override\n                public void onClick(View view) {\n                    Handler handler = new Handler();\n                    handler.postDelayed(new Runnable() {\n                        @Override\n                        public void run() {\n                            MusicPlayer.setShuffleMode(MusicService.SHUFFLE_NORMAL);\n                            MusicPlayer.next();\n                            recyclerView.scrollToPosition(MusicPlayer.getQueuePosition());\n                        }\n                    }, 150);\n\n                }\n            });\n        }\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/nowplaying/Timber2.java",
    "content": "/*\n * Copyright (C) 2015 Naman Dwivedi\n *\n * Licensed under the GNU General Public License v3\n *\n * This is free software: you can redistribute it and/or modify it\n * under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;\n * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n * See the GNU General Public License for more details.\n */\n\npackage com.naman14.timber.nowplaying;\n\nimport android.graphics.Bitmap;\nimport android.graphics.Color;\nimport android.graphics.drawable.Drawable;\nimport android.graphics.drawable.TransitionDrawable;\nimport android.os.AsyncTask;\nimport android.os.Bundle;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.ImageView;\n\nimport com.afollestad.appthemeengine.Config;\nimport com.naman14.timber.MusicPlayer;\nimport com.naman14.timber.MusicService;\nimport com.naman14.timber.R;\nimport com.naman14.timber.utils.ImageUtils;\n\nimport net.steamcrafted.materialiconlib.MaterialDrawableBuilder;\n\npublic class Timber2 extends BaseNowplayingFragment {\n\n    ImageView mBlurredArt;\n\n    @Override\n    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {\n        View rootView = inflater.inflate(\n                R.layout.fragment_timber2, container, false);\n\n        setMusicStateListener();\n        setSongDetails(rootView);\n        mBlurredArt = (ImageView) rootView.findViewById(R.id.album_art_blurred);\n\n        initGestures(mBlurredArt);\n\n        return rootView;\n    }\n\n    @Override\n    public void updateShuffleState() {\n        if (shuffle != null && getActivity() != null) {\n            MaterialDrawableBuilder builder = MaterialDrawableBuilder.with(getActivity())\n                    .setIcon(MaterialDrawableBuilder.IconValue.SHUFFLE)\n                    .setSizeDp(30);\n\n            if (MusicPlayer.getShuffleMode() == 0) {\n                builder.setColor(Color.WHITE);\n            } else builder.setColor(accentColor);\n\n            shuffle.setImageDrawable(builder.build());\n            shuffle.setOnClickListener(new View.OnClickListener() {\n                @Override\n                public void onClick(View view) {\n                    MusicPlayer.cycleShuffle();\n                    updateShuffleState();\n                    updateRepeatState();\n                }\n            });\n        }\n    }\n\n    @Override\n    public void updateRepeatState() {\n        if (repeat != null && getActivity() != null) {\n            MaterialDrawableBuilder builder = MaterialDrawableBuilder.with(getActivity())\n                    .setSizeDp(30);\n\n            if (MusicPlayer.getRepeatMode() == 0) {\n                builder.setColor(Color.WHITE);\n            } else builder.setColor(accentColor);\n\n            if (MusicPlayer.getRepeatMode() == MusicService.REPEAT_NONE) {\n                builder.setIcon(MaterialDrawableBuilder.IconValue.REPEAT);\n                builder.setColor(Color.WHITE);\n            } else if (MusicPlayer.getRepeatMode() == MusicService.REPEAT_CURRENT) {\n                builder.setIcon(MaterialDrawableBuilder.IconValue.REPEAT_ONCE);\n                builder.setColor(accentColor);\n            } else if (MusicPlayer.getRepeatMode() == MusicService.REPEAT_ALL) {\n                builder.setColor(accentColor);\n                builder.setIcon(MaterialDrawableBuilder.IconValue.REPEAT);\n            }\n\n            repeat.setImageDrawable(builder.build());\n            repeat.setOnClickListener(new View.OnClickListener() {\n                @Override\n                public void onClick(View view) {\n                    MusicPlayer.cycleRepeat();\n                    updateRepeatState();\n                    updateShuffleState();\n                }\n            });\n        }\n    }\n\n    @Override\n    public void doAlbumArtStuff(Bitmap loadedImage) {\n        setBlurredAlbumArt blurredAlbumArt = new setBlurredAlbumArt();\n        blurredAlbumArt.execute(loadedImage);\n    }\n\n    private class setBlurredAlbumArt extends AsyncTask<Bitmap, Void, Drawable> {\n\n        @Override\n        protected Drawable doInBackground(Bitmap... loadedImage) {\n            Drawable drawable = null;\n            try {\n                drawable = ImageUtils.createBlurredImageFromBitmap(loadedImage[0], getActivity(), 6);\n            } catch (Exception e) {\n                e.printStackTrace();\n            }\n            return drawable;\n        }\n\n        @Override\n        protected void onPostExecute(Drawable result) {\n            if (result != null) {\n                if (mBlurredArt.getDrawable() != null) {\n                    final TransitionDrawable td =\n                            new TransitionDrawable(new Drawable[]{\n                                    mBlurredArt.getDrawable(),\n                                    result\n                            });\n                    mBlurredArt.setImageDrawable(td);\n                    td.startTransition(200);\n\n                } else {\n                    mBlurredArt.setImageDrawable(result);\n                }\n            }\n        }\n\n        @Override\n        protected void onPreExecute() {\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/nowplaying/Timber3.java",
    "content": "/*\n * Copyright (C) 2015 Naman Dwivedi\n *\n * Licensed under the GNU General Public License v3\n *\n * This is free software: you can redistribute it and/or modify it\n * under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;\n * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n * See the GNU General Public License for more details.\n */\n\npackage com.naman14.timber.nowplaying;\n\nimport android.os.Bundle;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\n\nimport com.naman14.timber.R;\n\npublic class Timber3 extends BaseNowplayingFragment {\n\n    @Override\n    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {\n        View rootView = inflater.inflate(\n                R.layout.fragment_timber3, container, false);\n\n        setMusicStateListener();\n        setSongDetails(rootView);\n\n        initGestures(rootView.findViewById(R.id.album_art));\n\n        return rootView;\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/nowplaying/Timber4.java",
    "content": "/*\n * Copyright (C) 2015 Naman Dwivedi\n *\n * Licensed under the GNU General Public License v3\n *\n * This is free software: you can redistribute it and/or modify it\n * under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;\n * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n * See the GNU General Public License for more details.\n */\n\npackage com.naman14.timber.nowplaying;\n\nimport android.graphics.Bitmap;\nimport android.graphics.Color;\nimport android.graphics.drawable.Drawable;\nimport android.graphics.drawable.TransitionDrawable;\nimport android.os.AsyncTask;\nimport android.os.Bundle;\nimport androidx.recyclerview.widget.LinearLayoutManager;\nimport androidx.recyclerview.widget.RecyclerView;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.ImageView;\n\nimport com.naman14.timber.MusicPlayer;\nimport com.naman14.timber.MusicService;\nimport com.naman14.timber.R;\nimport com.naman14.timber.adapters.SlidingQueueAdapter;\nimport com.naman14.timber.dataloaders.QueueLoader;\nimport com.naman14.timber.utils.ImageUtils;\n\nimport net.steamcrafted.materialiconlib.MaterialDrawableBuilder;\n\npublic class Timber4 extends BaseNowplayingFragment {\n\n    ImageView mBlurredArt;\n    RecyclerView horizontalRecyclerview;\n    SlidingQueueAdapter horizontalAdapter;\n\n    @Override\n    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {\n        View rootView = inflater.inflate(\n                R.layout.fragment_timber4, container, false);\n\n        setMusicStateListener();\n        setSongDetails(rootView);\n\n        mBlurredArt = (ImageView) rootView.findViewById(R.id.album_art_blurred);\n        horizontalRecyclerview = (RecyclerView) rootView.findViewById(R.id.queue_recyclerview_horizontal);\n\n        setupHorizontalQueue();\n        initGestures(mBlurredArt);\n\n        return rootView;\n    }\n\n    @Override\n    public void updateShuffleState() {\n        if (shuffle != null && getActivity() != null) {\n            MaterialDrawableBuilder builder = MaterialDrawableBuilder.with(getActivity())\n                    .setIcon(MaterialDrawableBuilder.IconValue.SHUFFLE)\n                    .setSizeDp(30);\n\n            if (MusicPlayer.getShuffleMode() == 0) {\n                builder.setColor(Color.WHITE);\n            } else builder.setColor(accentColor);\n\n            shuffle.setImageDrawable(builder.build());\n            shuffle.setOnClickListener(new View.OnClickListener() {\n                @Override\n                public void onClick(View view) {\n                    MusicPlayer.cycleShuffle();\n                    updateShuffleState();\n                    updateRepeatState();\n                }\n            });\n        }\n    }\n\n    @Override\n    public void updateRepeatState() {\n        if (repeat != null && getActivity() != null) {\n            MaterialDrawableBuilder builder = MaterialDrawableBuilder.with(getActivity())\n                    .setSizeDp(30);\n\n            if (MusicPlayer.getRepeatMode() == 0) {\n                builder.setColor(Color.WHITE);\n            } else builder.setColor(accentColor);\n\n            if (MusicPlayer.getRepeatMode() == MusicService.REPEAT_NONE) {\n                builder.setIcon(MaterialDrawableBuilder.IconValue.REPEAT);\n                builder.setColor(Color.WHITE);\n            } else if (MusicPlayer.getRepeatMode() == MusicService.REPEAT_CURRENT) {\n                builder.setIcon(MaterialDrawableBuilder.IconValue.REPEAT_ONCE);\n                builder.setColor(accentColor);\n            } else if (MusicPlayer.getRepeatMode() == MusicService.REPEAT_ALL) {\n                builder.setColor(accentColor);\n                builder.setIcon(MaterialDrawableBuilder.IconValue.REPEAT);\n            }\n            repeat.setImageDrawable(builder.build());\n            repeat.setOnClickListener(new View.OnClickListener() {\n                @Override\n                public void onClick(View view) {\n                    MusicPlayer.cycleRepeat();\n                    updateRepeatState();\n                    updateShuffleState();\n                }\n            });\n        }\n    }\n\n    @Override\n    public void doAlbumArtStuff(Bitmap loadedImage) {\n        setBlurredAlbumArt blurredAlbumArt = new setBlurredAlbumArt();\n        blurredAlbumArt.execute(loadedImage);\n    }\n\n    private void setupHorizontalQueue() {\n        horizontalRecyclerview.setLayoutManager(new LinearLayoutManager(getActivity(), LinearLayoutManager.HORIZONTAL, false));\n        horizontalAdapter = new SlidingQueueAdapter(getActivity(), QueueLoader.getQueueSongs(getActivity()));\n        horizontalRecyclerview.setAdapter(horizontalAdapter);\n        horizontalRecyclerview.scrollToPosition(MusicPlayer.getQueuePosition() - 3);\n    }\n\n    private class setBlurredAlbumArt extends AsyncTask<Bitmap, Void, Drawable> {\n\n        @Override\n        protected Drawable doInBackground(Bitmap... loadedImage) {\n            Drawable drawable = null;\n            try {\n                drawable = ImageUtils.createBlurredImageFromBitmap(loadedImage[0], getActivity(), 6);\n            } catch (Exception e) {\n                e.printStackTrace();\n            }\n            return drawable;\n        }\n\n        @Override\n        protected void onPostExecute(Drawable result) {\n            if (result != null) {\n                if (mBlurredArt.getDrawable() != null) {\n                    final TransitionDrawable td =\n                            new TransitionDrawable(new Drawable[]{\n                                    mBlurredArt.getDrawable(),\n                                    result\n                            });\n                    mBlurredArt.setImageDrawable(td);\n                    td.startTransition(200);\n\n                } else {\n                    mBlurredArt.setImageDrawable(result);\n                }\n            }\n        }\n\n        @Override\n        protected void onPreExecute() {\n        }\n    }\n\n\n}\n"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/nowplaying/Timber5.java",
    "content": "package com.naman14.timber.nowplaying;\n\nimport android.graphics.Bitmap;\nimport android.graphics.Color;\nimport android.graphics.drawable.Drawable;\nimport android.graphics.drawable.TransitionDrawable;\nimport android.os.AsyncTask;\nimport android.os.Bundle;\nimport androidx.appcompat.app.AppCompatActivity;\nimport androidx.recyclerview.widget.LinearLayoutManager;\nimport androidx.recyclerview.widget.RecyclerView;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.ImageView;\n\nimport com.naman14.timber.MusicPlayer;\nimport com.naman14.timber.MusicService;\nimport com.naman14.timber.R;\nimport com.naman14.timber.adapters.SlidingQueueAdapter;\nimport com.naman14.timber.dataloaders.QueueLoader;\nimport com.naman14.timber.utils.ImageUtils;\n\nimport net.steamcrafted.materialiconlib.MaterialDrawableBuilder;\n\n/**\n * Created by naman on 22/02/17.\n */\n\npublic class Timber5 extends BaseNowplayingFragment {\n\n    ImageView mBlurredArt;\n    RecyclerView recyclerView;\n    SlidingQueueAdapter adapter;\n\n    @Override\n    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {\n        View rootView = inflater.inflate(\n                R.layout.fragment_timber5, container, false);\n\n        setMusicStateListener();\n        setSongDetails(rootView);\n\n        mBlurredArt = (ImageView) rootView.findViewById(R.id.album_art_blurred);\n        recyclerView = (RecyclerView) rootView.findViewById(R.id.queue_recyclerview_horizontal) ;\n        initGestures(mBlurredArt);\n        setupSlidingQueue();\n\n        return rootView;\n    }\n\n    @Override\n    public void updateShuffleState() {\n        if (shuffle != null && getActivity() != null) {\n            MaterialDrawableBuilder builder = MaterialDrawableBuilder.with(getActivity())\n                    .setIcon(MaterialDrawableBuilder.IconValue.SHUFFLE)\n                    .setSizeDp(30);\n\n            if (MusicPlayer.getShuffleMode() == 0) {\n                builder.setColor(Color.WHITE);\n            } else builder.setColor(accentColor);\n\n            shuffle.setImageDrawable(builder.build());\n            shuffle.setOnClickListener(new View.OnClickListener() {\n                @Override\n                public void onClick(View view) {\n                    MusicPlayer.cycleShuffle();\n                    updateShuffleState();\n                    updateRepeatState();\n                }\n            });\n        }\n    }\n\n    @Override\n    public void updateRepeatState() {\n        if (repeat != null && getActivity() != null) {\n            MaterialDrawableBuilder builder = MaterialDrawableBuilder.with(getActivity())\n                    .setSizeDp(30);\n\n            if (MusicPlayer.getRepeatMode() == 0) {\n                builder.setColor(Color.WHITE);\n            } else builder.setColor(accentColor);\n\n            if (MusicPlayer.getRepeatMode() == MusicService.REPEAT_NONE) {\n                builder.setIcon(MaterialDrawableBuilder.IconValue.REPEAT);\n                builder.setColor(Color.WHITE);\n            } else if (MusicPlayer.getRepeatMode() == MusicService.REPEAT_CURRENT) {\n                builder.setIcon(MaterialDrawableBuilder.IconValue.REPEAT_ONCE);\n                builder.setColor(accentColor);\n            } else if (MusicPlayer.getRepeatMode() == MusicService.REPEAT_ALL) {\n                builder.setColor(accentColor);\n                builder.setIcon(MaterialDrawableBuilder.IconValue.REPEAT);\n            }\n\n            repeat.setImageDrawable(builder.build());\n            repeat.setOnClickListener(new View.OnClickListener() {\n                @Override\n                public void onClick(View view) {\n                    MusicPlayer.cycleRepeat();\n                    updateRepeatState();\n                    updateShuffleState();\n                }\n            });\n        }\n    }\n\n    @Override\n    public void doAlbumArtStuff(Bitmap loadedImage) {\n        setBlurredAlbumArt blurredAlbumArt = new setBlurredAlbumArt();\n        blurredAlbumArt.execute(loadedImage);\n    }\n\n    private void setupSlidingQueue() {\n        recyclerView.setLayoutManager(new LinearLayoutManager(getActivity(), LinearLayoutManager.HORIZONTAL, false));\n        adapter = new SlidingQueueAdapter((AppCompatActivity) getActivity(), QueueLoader.getQueueSongs(getActivity()));\n        recyclerView.setAdapter(adapter);\n        recyclerView.scrollToPosition(MusicPlayer.getQueuePosition() - 3);\n    }\n\n\n    private class setBlurredAlbumArt extends AsyncTask<Bitmap, Void, Drawable> {\n\n        @Override\n        protected Drawable doInBackground(Bitmap... loadedImage) {\n            Drawable drawable = null;\n            try {\n                drawable = ImageUtils.createBlurredImageFromBitmap(loadedImage[0], getActivity(), 12);\n            } catch (Exception e) {\n                e.printStackTrace();\n            }\n            return drawable;\n        }\n\n        @Override\n        protected void onPostExecute(Drawable result) {\n            if (result != null) {\n                if (mBlurredArt.getDrawable() != null) {\n                    final TransitionDrawable td =\n                            new TransitionDrawable(new Drawable[]{\n                                    mBlurredArt.getDrawable(),\n                                    result\n                            });\n                    mBlurredArt.setImageDrawable(td);\n                    td.startTransition(200);\n\n                } else {\n                    mBlurredArt.setImageDrawable(result);\n                }\n            }\n        }\n\n        @Override\n        protected void onPreExecute() {\n        }\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/nowplaying/Timber6.java",
    "content": "package com.naman14.timber.nowplaying;\n\nimport android.graphics.Color;\nimport android.graphics.PorterDuff;\nimport android.graphics.PorterDuffColorFilter;\nimport android.os.Bundle;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.SeekBar;\nimport android.widget.TextView;\n\nimport com.naman14.timber.MusicPlayer;\nimport com.naman14.timber.MusicService;\nimport com.naman14.timber.R;\nimport com.naman14.timber.dataloaders.SongLoader;\nimport com.naman14.timber.models.Song;\nimport com.naman14.timber.utils.TimberUtils;\nimport com.naman14.timber.widgets.CircleImageView;\n\nimport net.steamcrafted.materialiconlib.MaterialDrawableBuilder;\n\n/**\n * Created by naman on 22/02/17.\n */\n\npublic class Timber6 extends BaseNowplayingFragment {\n\n    TextView nextSong;\n    CircleImageView nextArt;\n\n    @Override\n    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {\n        View rootView = inflater.inflate(\n                R.layout.fragment_timber6, container, false);\n\n        setMusicStateListener();\n        setSongDetails(rootView);\n\n        initGestures(rootView.findViewById(R.id.album_art));\n\n        ((SeekBar) rootView.findViewById(R.id.song_progress)).getProgressDrawable().setColorFilter(new PorterDuffColorFilter(Color.WHITE, PorterDuff.Mode.MULTIPLY));\n        ((SeekBar) rootView.findViewById(R.id.song_progress)).getThumb().setColorFilter(new PorterDuffColorFilter(Color.WHITE, PorterDuff.Mode.SRC_ATOP));\n\n        nextSong = (TextView) rootView.findViewById(R.id.title_next);\n        nextArt = (CircleImageView) rootView.findViewById(R.id.album_art_next);\n\n        rootView.findViewById(R.id.nextView).setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View view) {\n                MusicPlayer.next();\n            }\n        });\n\n        return rootView;\n    }\n\n    @Override\n    public void updateShuffleState() {\n        if (shuffle != null && getActivity() != null) {\n            MaterialDrawableBuilder builder = MaterialDrawableBuilder.with(getActivity())\n                    .setIcon(MaterialDrawableBuilder.IconValue.SHUFFLE)\n                    .setSizeDp(30);\n\n            if (MusicPlayer.getShuffleMode() == 0) {\n                builder.setColor(Color.WHITE);\n            } else builder.setColor(accentColor);\n\n            shuffle.setImageDrawable(builder.build());\n            shuffle.setOnClickListener(new View.OnClickListener() {\n                @Override\n                public void onClick(View view) {\n                    MusicPlayer.cycleShuffle();\n                    updateShuffleState();\n                    updateRepeatState();\n                }\n            });\n        }\n    }\n\n    @Override\n    public void updateRepeatState() {\n        if (repeat != null && getActivity() != null) {\n            MaterialDrawableBuilder builder = MaterialDrawableBuilder.with(getActivity())\n                    .setSizeDp(30);\n\n            if (MusicPlayer.getRepeatMode() == 0) {\n                builder.setColor(Color.WHITE);\n            } else builder.setColor(accentColor);\n\n            if (MusicPlayer.getRepeatMode() == MusicService.REPEAT_NONE) {\n                builder.setIcon(MaterialDrawableBuilder.IconValue.REPEAT);\n                builder.setColor(Color.WHITE);\n            } else if (MusicPlayer.getRepeatMode() == MusicService.REPEAT_CURRENT) {\n                builder.setIcon(MaterialDrawableBuilder.IconValue.REPEAT_ONCE);\n                builder.setColor(accentColor);\n            } else if (MusicPlayer.getRepeatMode() == MusicService.REPEAT_ALL) {\n                builder.setColor(accentColor);\n                builder.setIcon(MaterialDrawableBuilder.IconValue.REPEAT);\n            }\n\n            repeat.setImageDrawable(builder.build());\n            repeat.setOnClickListener(new View.OnClickListener() {\n                @Override\n                public void onClick(View view) {\n                    MusicPlayer.cycleRepeat();\n                    updateRepeatState();\n                    updateShuffleState();\n                }\n            });\n        }\n    }\n\n    @Override\n    public void onMetaChanged() {\n        super.onMetaChanged();\n        if (getActivity() != null) {\n            long nextId = MusicPlayer.getNextAudioId();\n            Song next = SongLoader.getSongForID(getActivity(), nextId);\n            nextSong.setText(next.title);\n            nextArt.setImageURI(TimberUtils.getAlbumArtUri(next.albumId));\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/permissions/Nammu.java",
    "content": "/*\n* The MIT License (MIT)\n\n* Copyright (c) 2015 Michal Tajchert\n\n* Permission is hereby granted, free of charge, to any person obtaining a copy\n* of this software and associated documentation files (the \"Software\"), to deal\n* in the Software without restriction, including without limitation the rights\n* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n* copies of the Software, and to permit persons to whom the Software is\n* furnished to do so, subject to the following conditions:\n*\n* The above copyright notice and this permission notice shall be included in all\n* copies or substantial portions of the Software.\n*\n* THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n* SOFTWARE.\n*/\n\npackage com.naman14.timber.permissions;\n\nimport android.Manifest;\nimport android.app.Activity;\nimport android.content.Context;\nimport android.content.SharedPreferences;\nimport android.content.pm.PackageManager;\nimport android.os.Build;\n\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.HashSet;\nimport java.util.Set;\n\n/**\n * Created by Michal Tajchert on 2015-06-04.\n */\npublic class Nammu {\n    private static final String TAG = Nammu.class.getSimpleName();\n    private static final String KEY_PREV_PERMISSIONS = \"previous_permissions\";\n    private static final String KEY_IGNORED_PERMISSIONS = \"ignored_permissions\";\n    private static Context context;\n    private static SharedPreferences sharedPreferences;\n    private static ArrayList<PermissionRequest> permissionRequests = new ArrayList<PermissionRequest>();\n\n    public static void init(Context context) {\n        sharedPreferences = context.getSharedPreferences(\"pl.tajchert.runtimepermissionhelper\", Context.MODE_PRIVATE);\n        Nammu.context = context;\n    }\n\n    /**\n     * Check that all given permissions have been granted by verifying that each entry in the\n     * given array is of the value {@link PackageManager#PERMISSION_GRANTED}.\n     */\n    public static boolean verifyPermissions(int[] grantResults) {\n        for (int result : grantResults) {\n            if (result != PackageManager.PERMISSION_GRANTED) {\n                return false;\n            }\n        }\n        return true;\n    }\n\n    /**\n     * Returns true if the Activity has access to given permissions.\n     */\n    public static boolean hasPermission(Activity activity, String permission) {\n        return activity.checkSelfPermission(permission) == PackageManager.PERMISSION_GRANTED;\n    }\n\n    /**\n     * Returns true if the Activity has access to a all given permission.\n     */\n    public static boolean hasPermission(Activity activity, String[] permissions) {\n        for (String permission : permissions) {\n            if (activity.checkSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) {\n                return false;\n            }\n        }\n        return true;\n    }\n\n    /*\n     * If we override other methods, lets do it as well, and keep name same as it is already weird enough.\n     * Returns true if we should show explanation why we need this permission.\n     */\n    public static boolean shouldShowRequestPermissionRationale(Activity activity, String permissions) {\n        return activity.shouldShowRequestPermissionRationale(permissions);\n    }\n\n    public static void askForPermission(Activity activity, String permission, PermissionCallback permissionCallback) {\n        askForPermission(activity, new String[]{permission}, permissionCallback);\n    }\n\n    public static void askForPermission(Activity activity, String[] permissions, PermissionCallback permissionCallback) {\n        if (permissionCallback == null) {\n            return;\n        }\n        if (hasPermission(activity, permissions)) {\n            permissionCallback.permissionGranted();\n            return;\n        }\n        PermissionRequest permissionRequest = new PermissionRequest(new ArrayList<String>(Arrays.asList(permissions)), permissionCallback);\n        permissionRequests.add(permissionRequest);\n\n        activity.requestPermissions(permissions, permissionRequest.getRequestCode());\n    }\n\n    public static void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {\n        PermissionRequest requestResult = new PermissionRequest(requestCode);\n        if (permissionRequests.contains(requestResult)) {\n            PermissionRequest permissionRequest = permissionRequests.get(permissionRequests.indexOf(requestResult));\n            if (verifyPermissions(grantResults)) {\n                //Permission has been granted\n                permissionRequest.getPermissionCallback().permissionGranted();\n            } else {\n                permissionRequest.getPermissionCallback().permissionRefused();\n\n            }\n            permissionRequests.remove(requestResult);\n        }\n        refreshMonitoredList();\n    }\n\n\n    //Permission monitoring part below\n\n    /**\n     * Get list of currently granted permissions, without saving it inside Nammu\n     *\n     * @return currently granted permissions\n     */\n    public static ArrayList<String> getGrantedPermissions() {\n        if (context == null) {\n            throw new RuntimeException(\"Must call init() earlier\");\n        }\n        ArrayList<String> permissions = new ArrayList<String>();\n        ArrayList<String> permissionsGranted = new ArrayList<String>();\n        //Group location\n        permissions.add(Manifest.permission.ACCESS_FINE_LOCATION);\n        permissions.add(Manifest.permission.ACCESS_COARSE_LOCATION);\n        //Group Calendar\n        permissions.add(Manifest.permission.WRITE_CALENDAR);\n        permissions.add(Manifest.permission.READ_CALENDAR);\n        //Group Camera\n        permissions.add(Manifest.permission.CAMERA);\n        //Group Contacts\n        permissions.add(Manifest.permission.WRITE_CONTACTS);\n        permissions.add(Manifest.permission.READ_CONTACTS);\n        permissions.add(Manifest.permission.GET_ACCOUNTS);\n        //Group Microphone\n        permissions.add(Manifest.permission.RECORD_AUDIO);\n        //Group Phone\n        permissions.add(Manifest.permission.CALL_PHONE);\n        permissions.add(Manifest.permission.READ_PHONE_STATE);\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {\n            permissions.add(Manifest.permission.READ_CALL_LOG);\n        }\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {\n            permissions.add(Manifest.permission.WRITE_CALL_LOG);\n        }\n        permissions.add(Manifest.permission.ADD_VOICEMAIL);\n        permissions.add(Manifest.permission.USE_SIP);\n        permissions.add(Manifest.permission.PROCESS_OUTGOING_CALLS);\n        //Group Body sensors\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT_WATCH) {\n            permissions.add(Manifest.permission.BODY_SENSORS);\n        }\n        //Group SMS\n        permissions.add(Manifest.permission.SEND_SMS);\n        permissions.add(Manifest.permission.READ_SMS);\n        permissions.add(Manifest.permission.RECEIVE_SMS);\n        permissions.add(Manifest.permission.RECEIVE_WAP_PUSH);\n        permissions.add(Manifest.permission.RECEIVE_MMS);\n        //Group Storage\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {\n            permissions.add(Manifest.permission.READ_EXTERNAL_STORAGE);\n        }\n        permissions.add(Manifest.permission.WRITE_EXTERNAL_STORAGE);\n        for (String permission : permissions) {\n            if (context.checkSelfPermission(permission) == PackageManager.PERMISSION_GRANTED) {\n                permissionsGranted.add(permission);\n            }\n        }\n        return permissionsGranted;\n    }\n\n    /**\n     * Refresh currently granted permission list, and save it for later comparing using @permissionCompare()\n     */\n    public static void refreshMonitoredList() {\n        ArrayList<String> permissions = getGrantedPermissions();\n        Set<String> set = new HashSet<String>();\n        for (String perm : permissions) {\n            set.add(perm);\n        }\n        sharedPreferences.edit().putStringSet(KEY_PREV_PERMISSIONS, set).apply();\n    }\n\n    /**\n     * Get list of previous Permissions, from last refreshMonitoredList() call and they may be outdated,\n     * use getGrantedPermissions() to get current\n     */\n    public static ArrayList<String> getPreviousPermissions() {\n        ArrayList<String> prevPermissions = new ArrayList<String>();\n        prevPermissions.addAll(sharedPreferences.getStringSet(KEY_PREV_PERMISSIONS, new HashSet<String>()));\n        return prevPermissions;\n    }\n\n    public static ArrayList<String> getIgnoredPermissions() {\n        ArrayList<String> ignoredPermissions = new ArrayList<String>();\n        ignoredPermissions.addAll(sharedPreferences.getStringSet(KEY_IGNORED_PERMISSIONS, new HashSet<String>()));\n        return ignoredPermissions;\n    }\n\n    /**\n     * Lets see if we already ignore this permission\n     */\n    public static boolean isIgnoredPermission(String permission) {\n        if (permission == null) {\n            return false;\n        }\n        return getIgnoredPermissions().contains(permission);\n    }\n\n    /**\n     * Use to ignore to particular Permission - even if user will deny or add it we won't receive a callback.\n     *\n     * @param permission Permission to ignore\n     */\n    public static void ignorePermission(String permission) {\n        if (!isIgnoredPermission(permission)) {\n            ArrayList<String> ignoredPermissions = getIgnoredPermissions();\n            ignoredPermissions.add(permission);\n            Set<String> set = new HashSet<String>();\n            set.addAll(ignoredPermissions);\n            sharedPreferences.edit().putStringSet(KEY_IGNORED_PERMISSIONS, set).apply();\n        }\n    }\n\n    /**\n     * Used to trigger comparing process - @permissionListener will be called each time Permission was revoked, or added (but only once).\n     *\n     * @param permissionListener Callback that handles all permission changes\n     */\n    public static void permissionCompare(PermissionListener permissionListener) {\n        if (context == null) {\n            throw new RuntimeException(\"Before comparing permissions you need to call Nammu.init(context)\");\n\n        }\n        ArrayList<String> previouslyGranted = getPreviousPermissions();\n        ArrayList<String> currentPermissions = getGrantedPermissions();\n        ArrayList<String> ignoredPermissions = getIgnoredPermissions();\n        for (String permission : ignoredPermissions) {\n            if (previouslyGranted != null && !previouslyGranted.isEmpty()) {\n                if (previouslyGranted.contains(permission)) {\n                    previouslyGranted.remove(permission);\n                }\n            }\n\n            if (currentPermissions != null && !currentPermissions.isEmpty()) {\n                if (currentPermissions.contains(permission)) {\n                    currentPermissions.remove(permission);\n                }\n            }\n        }\n        for (String permission : currentPermissions) {\n            if (previouslyGranted.contains(permission)) {\n                //All is fine, was granted and still is\n                previouslyGranted.remove(permission);\n            } else {\n                //We didn't have it last time\n                if (permissionListener != null) {\n                    permissionListener.permissionsChanged(permission);\n                    permissionListener.permissionsGranted(permission);\n                }\n            }\n        }\n        if (previouslyGranted != null && !previouslyGranted.isEmpty()) {\n            //Something was granted and removed\n            for (String permission : previouslyGranted) {\n                if (permissionListener != null) {\n                    permissionListener.permissionsChanged(permission);\n                    permissionListener.permissionsRemoved(permission);\n                }\n            }\n        }\n        refreshMonitoredList();\n    }\n\n    /**\n     * Not that needed method but if we override others it is good to keep same.\n     */\n    public static boolean checkPermission(String permissionName) {\n        if (context == null) {\n            throw new RuntimeException(\"Before comparing permissions you need to call Nammu.init(context)\");\n        }\n        return PackageManager.PERMISSION_GRANTED == context.checkSelfPermission(permissionName);\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/permissions/PermissionCallback.java",
    "content": "/*\n* The MIT License (MIT)\n\n* Copyright (c) 2015 Michal Tajchert\n\n* Permission is hereby granted, free of charge, to any person obtaining a copy\n* of this software and associated documentation files (the \"Software\"), to deal\n* in the Software without restriction, including without limitation the rights\n* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n* copies of the Software, and to permit persons to whom the Software is\n* furnished to do so, subject to the following conditions:\n*\n* The above copyright notice and this permission notice shall be included in all\n* copies or substantial portions of the Software.\n*\n* THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n* SOFTWARE.\n*/\n\npackage com.naman14.timber.permissions;\n\npublic interface PermissionCallback {\n    void permissionGranted();\n\n    void permissionRefused();\n}"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/permissions/PermissionListener.java",
    "content": "/*\n* The MIT License (MIT)\n\n* Copyright (c) 2015 Michal Tajchert\n\n* Permission is hereby granted, free of charge, to any person obtaining a copy\n* of this software and associated documentation files (the \"Software\"), to deal\n* in the Software without restriction, including without limitation the rights\n* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n* copies of the Software, and to permit persons to whom the Software is\n* furnished to do so, subject to the following conditions:\n*\n* The above copyright notice and this permission notice shall be included in all\n* copies or substantial portions of the Software.\n*\n* THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n* SOFTWARE.\n*/\n\npackage com.naman14.timber.permissions;\n\npublic interface PermissionListener {\n    /**\n     * Gets called each time we run Nammu.permissionCompare() and some Permission is revoke/granted to us\n     *\n     * @param permissionChanged\n     */\n    void permissionsChanged(String permissionChanged);\n\n    /**\n     * Gets called each time we run Nammu.permissionCompare() and some Permission is granted\n     *\n     * @param permissionGranted\n     */\n    void permissionsGranted(String permissionGranted);\n\n    /**\n     * Gets called each time we run Nammu.permissionCompare() and some Permission is removed\n     *\n     * @param permissionRemoved\n     */\n    void permissionsRemoved(String permissionRemoved);\n}"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/permissions/PermissionRequest.java",
    "content": "/*\n* The MIT License (MIT)\n\n* Copyright (c) 2015 Michal Tajchert\n\n* Permission is hereby granted, free of charge, to any person obtaining a copy\n* of this software and associated documentation files (the \"Software\"), to deal\n* in the Software without restriction, including without limitation the rights\n* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n* copies of the Software, and to permit persons to whom the Software is\n* furnished to do so, subject to the following conditions:\n*\n* The above copyright notice and this permission notice shall be included in all\n* copies or substantial portions of the Software.\n*\n* THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n* SOFTWARE.\n*/\n\npackage com.naman14.timber.permissions;\n\nimport java.util.ArrayList;\nimport java.util.Random;\n\npublic class PermissionRequest {\n    private static Random random;\n    private ArrayList<String> permissions;\n    private int requestCode;\n    private PermissionCallback permissionCallback;\n\n    public PermissionRequest(int requestCode) {\n        this.requestCode = requestCode;\n    }\n\n    public PermissionRequest(ArrayList<String> permissions, PermissionCallback permissionCallback) {\n        this.permissions = permissions;\n        this.permissionCallback = permissionCallback;\n        if (random == null) {\n            random = new Random();\n        }\n        this.requestCode = random.nextInt(32768);\n    }\n\n    public ArrayList<String> getPermissions() {\n        return permissions;\n    }\n\n    public int getRequestCode() {\n        return requestCode;\n    }\n\n    public PermissionCallback getPermissionCallback() {\n        return permissionCallback;\n    }\n\n    public boolean equals(Object object) {\n        if (object == null) {\n            return false;\n        }\n        if (object instanceof PermissionRequest) {\n            return ((PermissionRequest) object).requestCode == this.requestCode;\n        }\n        return false;\n    }\n\n    @Override\n    public int hashCode() {\n        return requestCode;\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/provider/MusicDB.java",
    "content": "/*\n* Copyright (C) 2014 The CyanogenMod Project\n*\n* Licensed under the Apache License, Version 2.0 (the \"License\");\n* you may not use this file except in compliance with the License.\n* You may obtain a copy of the License at\n*\n* http://www.apache.org/licenses/LICENSE-2.0\n*\n* Unless required by applicable law or agreed to in writing, software\n* distributed under the License is distributed on an \"AS IS\" BASIS,\n* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n* See the License for the specific language governing permissions and\n* limitations under the License.\n*/\n\npackage com.naman14.timber.provider;\n\nimport android.content.Context;\nimport android.database.sqlite.SQLiteDatabase;\nimport android.database.sqlite.SQLiteOpenHelper;\n\npublic class MusicDB extends SQLiteOpenHelper {\n\n    public static final String DATABASENAME = \"musicdb.db\";\n    private static final int VERSION = 4;\n    private static MusicDB sInstance = null;\n\n    private final Context mContext;\n\n    public MusicDB(final Context context) {\n        super(context, DATABASENAME, null, VERSION);\n\n        mContext = context;\n    }\n\n    public static final synchronized MusicDB getInstance(final Context context) {\n        if (sInstance == null) {\n            sInstance = new MusicDB(context.getApplicationContext());\n        }\n        return sInstance;\n    }\n\n    @Override\n    public void onCreate(SQLiteDatabase db) {\n        MusicPlaybackState.getInstance(mContext).onCreate(db);\n        RecentStore.getInstance(mContext).onCreate(db);\n        SongPlayCount.getInstance(mContext).onCreate(db);\n        SearchHistory.getInstance(mContext).onCreate(db);\n    }\n\n    @Override\n    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {\n        MusicPlaybackState.getInstance(mContext).onUpgrade(db, oldVersion, newVersion);\n        RecentStore.getInstance(mContext).onUpgrade(db, oldVersion, newVersion);\n        SongPlayCount.getInstance(mContext).onUpgrade(db, oldVersion, newVersion);\n        SearchHistory.getInstance(mContext).onUpgrade(db, oldVersion, newVersion);\n    }\n\n    @Override\n    public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {\n        MusicPlaybackState.getInstance(mContext).onDowngrade(db, oldVersion, newVersion);\n        RecentStore.getInstance(mContext).onDowngrade(db, oldVersion, newVersion);\n        SongPlayCount.getInstance(mContext).onDowngrade(db, oldVersion, newVersion);\n        SearchHistory.getInstance(mContext).onDowngrade(db, oldVersion, newVersion);\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/provider/MusicPlaybackState.java",
    "content": "/*\n* Copyright (C) 2014 The CyanogenMod Project\n*\n* Licensed under the Apache License, Version 2.0 (the \"License\");\n* you may not use this file except in compliance with the License.\n* You may obtain a copy of the License at\n*\n* http://www.apache.org/licenses/LICENSE-2.0\n*\n* Unless required by applicable law or agreed to in writing, software\n* distributed under the License is distributed on an \"AS IS\" BASIS,\n* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n* See the License for the specific language governing permissions and\n* limitations under the License.\n*/\n\npackage com.naman14.timber.provider;\n\nimport android.content.ContentValues;\nimport android.content.Context;\nimport android.database.Cursor;\nimport android.database.sqlite.SQLiteDatabase;\n\nimport com.naman14.timber.helpers.MusicPlaybackTrack;\nimport com.naman14.timber.utils.TimberUtils;\n\nimport java.util.ArrayList;\nimport java.util.Iterator;\nimport java.util.LinkedList;\n\n/**\n * This keeps track of the music playback and history state of the playback service\n */\npublic class MusicPlaybackState {\n    private static MusicPlaybackState sInstance = null;\n\n    private MusicDB mMusicDatabase = null;\n\n    public MusicPlaybackState(final Context context) {\n        mMusicDatabase = MusicDB.getInstance(context);\n    }\n\n    public static final synchronized MusicPlaybackState getInstance(final Context context) {\n        if (sInstance == null) {\n            sInstance = new MusicPlaybackState(context.getApplicationContext());\n        }\n        return sInstance;\n    }\n\n    public void onCreate(final SQLiteDatabase db) {\n        StringBuilder builder = new StringBuilder();\n        builder.append(\"CREATE TABLE IF NOT EXISTS \");\n        builder.append(PlaybackQueueColumns.NAME);\n        builder.append(\"(\");\n\n        builder.append(PlaybackQueueColumns.TRACK_ID);\n        builder.append(\" LONG NOT NULL,\");\n\n        builder.append(PlaybackQueueColumns.SOURCE_ID);\n        builder.append(\" LONG NOT NULL,\");\n\n        builder.append(PlaybackQueueColumns.SOURCE_TYPE);\n        builder.append(\" INT NOT NULL,\");\n\n        builder.append(PlaybackQueueColumns.SOURCE_POSITION);\n        builder.append(\" INT NOT NULL);\");\n\n        db.execSQL(builder.toString());\n\n        builder = new StringBuilder();\n        builder.append(\"CREATE TABLE IF NOT EXISTS \");\n        builder.append(PlaybackHistoryColumns.NAME);\n        builder.append(\"(\");\n\n        builder.append(PlaybackHistoryColumns.POSITION);\n        builder.append(\" INT NOT NULL);\");\n\n        db.execSQL(builder.toString());\n    }\n\n    public void onUpgrade(final SQLiteDatabase db, final int oldVersion, final int newVersion) {\n        // this table was created in version 2 so call the onCreate method if we hit that scenario\n        if (oldVersion < 2 && newVersion >= 2) {\n            onCreate(db);\n        }\n    }\n\n    public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {\n\n        db.execSQL(\"DROP TABLE IF EXISTS \" + PlaybackQueueColumns.NAME);\n        db.execSQL(\"DROP TABLE IF EXISTS \" + PlaybackHistoryColumns.NAME);\n        onCreate(db);\n    }\n\n    public synchronized void saveState(final ArrayList<MusicPlaybackTrack> queue,\n                                       LinkedList<Integer> history) {\n        final SQLiteDatabase database = mMusicDatabase.getWritableDatabase();\n        database.beginTransaction();\n\n        try {\n            database.delete(PlaybackQueueColumns.NAME, null, null);\n            database.delete(PlaybackHistoryColumns.NAME, null, null);\n            database.setTransactionSuccessful();\n        } finally {\n            database.endTransaction();\n        }\n\n        final int NUM_PROCESS = 20;\n        int position = 0;\n        while (position < queue.size()) {\n            database.beginTransaction();\n            try {\n                for (int i = position; i < queue.size() && i < position + NUM_PROCESS; i++) {\n                    MusicPlaybackTrack track = queue.get(i);\n                    ContentValues values = new ContentValues(4);\n\n                    values.put(PlaybackQueueColumns.TRACK_ID, track.mId);\n                    values.put(PlaybackQueueColumns.SOURCE_ID, track.mSourceId);\n                    values.put(PlaybackQueueColumns.SOURCE_TYPE, track.mSourceType.mId);\n                    values.put(PlaybackQueueColumns.SOURCE_POSITION, track.mSourcePosition);\n\n                    database.insert(PlaybackQueueColumns.NAME, null, values);\n                }\n                database.setTransactionSuccessful();\n            } finally {\n                database.endTransaction();\n                position += NUM_PROCESS;\n            }\n        }\n\n        if (history != null) {\n            Iterator<Integer> iter = history.iterator();\n            while (iter.hasNext()) {\n                database.beginTransaction();\n                try {\n                    for (int i = 0; iter.hasNext() && i < NUM_PROCESS; i++) {\n                        ContentValues values = new ContentValues(1);\n                        values.put(PlaybackHistoryColumns.POSITION, iter.next());\n\n                        database.insert(PlaybackHistoryColumns.NAME, null, values);\n                    }\n\n                    database.setTransactionSuccessful();\n                } finally {\n                    database.endTransaction();\n                }\n            }\n        }\n    }\n\n    public ArrayList<MusicPlaybackTrack> getQueue() {\n        ArrayList<MusicPlaybackTrack> results = new ArrayList<>();\n\n        Cursor cursor = null;\n        try {\n            cursor = mMusicDatabase.getReadableDatabase().query(PlaybackQueueColumns.NAME, null,\n                    null, null, null, null, null);\n\n            if (cursor != null && cursor.moveToFirst()) {\n                results.ensureCapacity(cursor.getCount());\n\n                do {\n                    results.add(new MusicPlaybackTrack(cursor.getLong(0), cursor.getLong(1),\n                            TimberUtils.IdType.getTypeById(cursor.getInt(2)), cursor.getInt(3)));\n                } while (cursor.moveToNext());\n            }\n\n            return results;\n        } finally {\n            if (cursor != null) {\n                cursor.close();\n                cursor = null;\n            }\n        }\n    }\n\n    public LinkedList<Integer> getHistory(final int playlistSize) {\n        LinkedList<Integer> results = new LinkedList<>();\n\n        Cursor cursor = null;\n        try {\n            cursor = mMusicDatabase.getReadableDatabase().query(PlaybackHistoryColumns.NAME, null,\n                    null, null, null, null, null);\n\n            if (cursor != null && cursor.moveToFirst()) {\n                do {\n                    int pos = cursor.getInt(0);\n                    if (pos >= 0 && pos < playlistSize) {\n                        results.add(pos);\n                    }\n                } while (cursor.moveToNext());\n            }\n\n            return results;\n        } finally {\n            if (cursor != null) {\n                cursor.close();\n                cursor = null;\n            }\n        }\n    }\n\n    public class PlaybackQueueColumns {\n\n        public static final String NAME = \"playbackqueue\";\n        public static final String TRACK_ID = \"trackid\";\n        public static final String SOURCE_ID = \"sourceid\";\n        public static final String SOURCE_TYPE = \"sourcetype\";\n        public static final String SOURCE_POSITION = \"sourceposition\";\n    }\n\n    public class PlaybackHistoryColumns {\n\n        public static final String NAME = \"playbackhistory\";\n\n        public static final String POSITION = \"position\";\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/provider/RecentStore.java",
    "content": "/*\n* Copyright (C) 2014 The CyanogenMod Project\n*\n* Licensed under the Apache License, Version 2.0 (the \"License\");\n* you may not use this file except in compliance with the License.\n* You may obtain a copy of the License at\n*\n* http://www.apache.org/licenses/LICENSE-2.0\n*\n* Unless required by applicable law or agreed to in writing, software\n* distributed under the License is distributed on an \"AS IS\" BASIS,\n* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n* See the License for the specific language governing permissions and\n* limitations under the License.\n*/\n\npackage com.naman14.timber.provider;\n\nimport android.content.ContentValues;\nimport android.content.Context;\nimport android.database.Cursor;\nimport android.database.sqlite.SQLiteDatabase;\n\npublic class RecentStore {\n\n    private static final int MAX_ITEMS_IN_DB = 100;\n\n    private static RecentStore sInstance = null;\n\n    private MusicDB mMusicDatabase = null;\n\n    public RecentStore(final Context context) {\n        mMusicDatabase = MusicDB.getInstance(context);\n    }\n\n    public static final synchronized RecentStore getInstance(final Context context) {\n        if (sInstance == null) {\n            sInstance = new RecentStore(context.getApplicationContext());\n        }\n        return sInstance;\n    }\n\n    public void onCreate(final SQLiteDatabase db) {\n        db.execSQL(\"CREATE TABLE IF NOT EXISTS \" + RecentStoreColumns.NAME + \" (\"\n                + RecentStoreColumns.ID + \" LONG NOT NULL,\" + RecentStoreColumns.TIMEPLAYED\n                + \" LONG NOT NULL);\");\n    }\n\n    public void onUpgrade(final SQLiteDatabase db, final int oldVersion, final int newVersion) {\n    }\n\n    public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {\n        db.execSQL(\"DROP TABLE IF EXISTS \" + RecentStoreColumns.NAME);\n        onCreate(db);\n    }\n\n    public void addSongId(final long songId) {\n        final SQLiteDatabase database = mMusicDatabase.getWritableDatabase();\n        database.beginTransaction();\n\n        try {\n\n            Cursor mostRecentItem = null;\n            try {\n                mostRecentItem = queryRecentIds(\"1\");\n                if (mostRecentItem != null && mostRecentItem.moveToFirst()) {\n                    if (songId == mostRecentItem.getLong(0)) {\n                        return;\n                    }\n                }\n            } finally {\n                if (mostRecentItem != null) {\n                    mostRecentItem.close();\n                    mostRecentItem = null;\n                }\n            }\n\n\n            final ContentValues values = new ContentValues(2);\n            values.put(RecentStoreColumns.ID, songId);\n            values.put(RecentStoreColumns.TIMEPLAYED, System.currentTimeMillis());\n            database.insert(RecentStoreColumns.NAME, null, values);\n\n            Cursor oldest = null;\n            try {\n                oldest = database.query(RecentStoreColumns.NAME,\n                        new String[]{RecentStoreColumns.TIMEPLAYED}, null, null, null, null,\n                        RecentStoreColumns.TIMEPLAYED + \" ASC\");\n\n                if (oldest != null && oldest.getCount() > MAX_ITEMS_IN_DB) {\n                    oldest.moveToPosition(oldest.getCount() - MAX_ITEMS_IN_DB);\n                    long timeOfRecordToKeep = oldest.getLong(0);\n\n                    database.delete(RecentStoreColumns.NAME,\n                            RecentStoreColumns.TIMEPLAYED + \" < ?\",\n                            new String[]{String.valueOf(timeOfRecordToKeep)});\n\n                }\n            } finally {\n                if (oldest != null) {\n                    oldest.close();\n                    oldest = null;\n                }\n            }\n        } finally {\n            database.setTransactionSuccessful();\n            database.endTransaction();\n        }\n    }\n\n\n    public void removeItem(final long songId) {\n        final SQLiteDatabase database = mMusicDatabase.getWritableDatabase();\n        database.delete(RecentStoreColumns.NAME, RecentStoreColumns.ID + \" = ?\", new String[]{\n                String.valueOf(songId)\n        });\n\n    }\n\n    public void deleteAll() {\n        final SQLiteDatabase database = mMusicDatabase.getWritableDatabase();\n        database.delete(RecentStoreColumns.NAME, null, null);\n    }\n\n\n    public Cursor queryRecentIds(final String limit) {\n        final SQLiteDatabase database = mMusicDatabase.getReadableDatabase();\n        return database.query(RecentStoreColumns.NAME,\n                new String[]{RecentStoreColumns.ID}, null, null, null, null,\n                RecentStoreColumns.TIMEPLAYED + \" DESC\", limit);\n    }\n\n    public interface RecentStoreColumns {\n        /* Table name */\n        String NAME = \"recenthistory\";\n\n        /* Album IDs column */\n        String ID = \"songid\";\n\n        /* Time played column */\n        String TIMEPLAYED = \"timeplayed\";\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/provider/SearchHistory.java",
    "content": "/*\n* Copyright (C) 2014 The CyanogenMod Project\n*\n* Licensed under the Apache License, Version 2.0 (the \"License\");\n* you may not use this file except in compliance with the License.\n* You may obtain a copy of the License at\n*\n* http://www.apache.org/licenses/LICENSE-2.0\n*\n* Unless required by applicable law or agreed to in writing, software\n* distributed under the License is distributed on an \"AS IS\" BASIS,\n* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n* See the License for the specific language governing permissions and\n* limitations under the License.\n*/\n\npackage com.naman14.timber.provider;\n\nimport android.content.ContentValues;\nimport android.content.Context;\nimport android.database.Cursor;\nimport android.database.sqlite.SQLiteDatabase;\n\nimport java.util.ArrayList;\n\npublic class SearchHistory {\n\n    private static final int MAX_ITEMS_IN_DB = 25;\n\n    private static SearchHistory sInstance = null;\n\n    private MusicDB mMusicDatabase = null;\n\n    public SearchHistory(final Context context) {\n        mMusicDatabase = MusicDB.getInstance(context);\n    }\n\n    public static final synchronized SearchHistory getInstance(final Context context) {\n        if (sInstance == null) {\n            sInstance = new SearchHistory(context.getApplicationContext());\n        }\n        return sInstance;\n    }\n\n    public void onCreate(final SQLiteDatabase db) {\n        db.execSQL(\"CREATE TABLE IF NOT EXISTS \" + SearchHistoryColumns.NAME + \" (\"\n                + SearchHistoryColumns.SEARCHSTRING + \" STRING NOT NULL,\"\n                + SearchHistoryColumns.TIMESEARCHED + \" LONG NOT NULL);\");\n    }\n\n    public void onUpgrade(final SQLiteDatabase db, final int oldVersion, final int newVersion) {\n    }\n\n    public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {\n        db.execSQL(\"DROP TABLE IF EXISTS \" + SearchHistoryColumns.NAME);\n        onCreate(db);\n    }\n\n    public void addSearchString(final String searchString) {\n        if (searchString == null) {\n            return;\n        }\n\n        String trimmedString = searchString.trim();\n\n        if (trimmedString.isEmpty()) {\n            return;\n        }\n\n        final SQLiteDatabase database = mMusicDatabase.getWritableDatabase();\n        database.beginTransaction();\n\n        try {\n\n            database.delete(SearchHistoryColumns.NAME,\n                    SearchHistoryColumns.SEARCHSTRING + \" = ? COLLATE NOCASE\",\n                    new String[]{trimmedString});\n\n            final ContentValues values = new ContentValues(2);\n            values.put(SearchHistoryColumns.SEARCHSTRING, trimmedString);\n            values.put(SearchHistoryColumns.TIMESEARCHED, System.currentTimeMillis());\n            database.insert(SearchHistoryColumns.NAME, null, values);\n\n            Cursor oldest = null;\n            try {\n                database.query(SearchHistoryColumns.NAME,\n                        new String[]{SearchHistoryColumns.TIMESEARCHED}, null, null, null, null,\n                        SearchHistoryColumns.TIMESEARCHED + \" ASC\");\n\n                if (oldest != null && oldest.getCount() > MAX_ITEMS_IN_DB) {\n                    oldest.moveToPosition(oldest.getCount() - MAX_ITEMS_IN_DB);\n                    long timeOfRecordToKeep = oldest.getLong(0);\n\n                    database.delete(SearchHistoryColumns.NAME,\n                            SearchHistoryColumns.TIMESEARCHED + \" < ?\",\n                            new String[]{String.valueOf(timeOfRecordToKeep)});\n\n                }\n            } finally {\n                if (oldest != null) {\n                    oldest.close();\n                    oldest = null;\n                }\n            }\n        } finally {\n            database.setTransactionSuccessful();\n            database.endTransaction();\n        }\n    }\n\n\n    public Cursor queryRecentSearches(final String limit) {\n        final SQLiteDatabase database = mMusicDatabase.getReadableDatabase();\n        return database.query(SearchHistoryColumns.NAME,\n                new String[]{SearchHistoryColumns.SEARCHSTRING}, null, null, null, null,\n                SearchHistoryColumns.TIMESEARCHED + \" DESC\", limit);\n    }\n\n    public ArrayList<String> getRecentSearches() {\n        Cursor searches = queryRecentSearches(String.valueOf(MAX_ITEMS_IN_DB));\n\n        ArrayList<String> results = new ArrayList<String>(MAX_ITEMS_IN_DB);\n\n        try {\n            if (searches != null && searches.moveToFirst()) {\n                int colIdx = searches.getColumnIndex(SearchHistoryColumns.SEARCHSTRING);\n\n                do {\n                    results.add(searches.getString(colIdx));\n                } while (searches.moveToNext());\n            }\n        } finally {\n            if (searches != null) {\n                searches.close();\n                searches = null;\n            }\n        }\n\n        return results;\n    }\n\n    public interface SearchHistoryColumns {\n        /* Table name */\n        String NAME = \"searchhistory\";\n\n        /* What was searched */\n        String SEARCHSTRING = \"searchstring\";\n\n        /* Time of search */\n        String TIMESEARCHED = \"timesearched\";\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/provider/SongPlayCount.java",
    "content": "/*\n* Copyright (C) 2014 The CyanogenMod Project\n*\n* Licensed under the Apache License, Version 2.0 (the \"License\");\n* you may not use this file except in compliance with the License.\n* You may obtain a copy of the License at\n*\n* http://www.apache.org/licenses/LICENSE-2.0\n*\n* Unless required by applicable law or agreed to in writing, software\n* distributed under the License is distributed on an \"AS IS\" BASIS,\n* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n* See the License for the specific language governing permissions and\n* limitations under the License.\n*/\n\npackage com.naman14.timber.provider;\n\nimport android.content.ContentValues;\nimport android.content.Context;\nimport android.database.Cursor;\nimport android.database.sqlite.SQLiteDatabase;\nimport android.view.animation.AccelerateInterpolator;\nimport android.view.animation.Interpolator;\n\nimport java.util.HashSet;\nimport java.util.Iterator;\n\n/**\n * This database tracks the number of play counts for an individual song.  This is used to drive\n * the top played tracks as well as the playlist images\n */\npublic class SongPlayCount {\n    // how many weeks worth of playback to track\n    private static final int NUM_WEEKS = 52;\n    private static SongPlayCount sInstance = null;\n    // interpolator curve applied for measuring the curve\n    private static Interpolator sInterpolator = new AccelerateInterpolator(1.5f);\n    // how high to multiply the interpolation curve\n    private static int INTERPOLATOR_HEIGHT = 50;\n    // how high the base value is. The ratio of the Height to Base is what really matters\n    private static int INTERPOLATOR_BASE = 25;\n    private static int ONE_WEEK_IN_MS = 1000 * 60 * 60 * 24 * 7;\n    private static String WHERE_ID_EQUALS = SongPlayCountColumns.ID + \"=?\";\n    private MusicDB mMusicDatabase = null;\n    // number of weeks since epoch time\n    private int mNumberOfWeeksSinceEpoch;\n\n    // used to track if we've walkd through the db and updated all the rows\n    private boolean mDatabaseUpdated;\n\n    /**\n     * Constructor of <code>RecentStore</code>\n     *\n     * @param context The {@link android.content.Context} to use\n     */\n    public SongPlayCount(final Context context) {\n        mMusicDatabase = MusicDB.getInstance(context);\n\n        long msSinceEpoch = System.currentTimeMillis();\n        mNumberOfWeeksSinceEpoch = (int) (msSinceEpoch / ONE_WEEK_IN_MS);\n\n        mDatabaseUpdated = false;\n    }\n\n    /**\n     * @param context The {@link android.content.Context} to use\n     * @return A new instance of this class.\n     */\n    public static final synchronized SongPlayCount getInstance(final Context context) {\n        if (sInstance == null) {\n            sInstance = new SongPlayCount(context.getApplicationContext());\n        }\n        return sInstance;\n    }\n\n    /**\n     * Calculates the score of the song given the play counts\n     *\n     * @param playCounts an array of the # of times a song has been played for each week\n     *                   where playCounts[N] is the # of times it was played N weeks ago\n     * @return the score\n     */\n    private static float calculateScore(final int[] playCounts) {\n        if (playCounts == null) {\n            return 0;\n        }\n\n        float score = 0;\n        for (int i = 0; i < Math.min(playCounts.length, NUM_WEEKS); i++) {\n            score += playCounts[i] * getScoreMultiplierForWeek(i);\n        }\n\n        return score;\n    }\n\n    /**\n     * Gets the column name for each week #\n     *\n     * @param week number\n     * @return the column name\n     */\n    private static String getColumnNameForWeek(final int week) {\n        return SongPlayCountColumns.WEEK_PLAY_COUNT + String.valueOf(week);\n    }\n\n    /**\n     * Gets the score multiplier for each week\n     *\n     * @param week number\n     * @return the multiplier to apply\n     */\n    private static float getScoreMultiplierForWeek(final int week) {\n        return sInterpolator.getInterpolation(1 - (week / (float) NUM_WEEKS)) * INTERPOLATOR_HEIGHT\n                + INTERPOLATOR_BASE;\n    }\n\n    /**\n     * For some performance gain, return a static value for the column index for a week\n     * WARNIGN: This function assumes you have selected all columns for it to work\n     *\n     * @param week number\n     * @return column index of that week\n     */\n    private static int getColumnIndexForWeek(final int week) {\n        // ID, followed by the weeks columns\n        return 1 + week;\n    }\n\n    public void onCreate(final SQLiteDatabase db) {\n        // create the play count table\n        // WARNING: If you change the order of these columns\n        // please update getColumnIndexForWeek\n        StringBuilder builder = new StringBuilder();\n        builder.append(\"CREATE TABLE IF NOT EXISTS \");\n        builder.append(SongPlayCountColumns.NAME);\n        builder.append(\"(\");\n        builder.append(SongPlayCountColumns.ID);\n        builder.append(\" INT UNIQUE,\");\n\n        for (int i = 0; i < NUM_WEEKS; i++) {\n            builder.append(getColumnNameForWeek(i));\n            builder.append(\" INT DEFAULT 0,\");\n        }\n\n        builder.append(SongPlayCountColumns.LAST_UPDATED_WEEK_INDEX);\n        builder.append(\" INT NOT NULL,\");\n\n        builder.append(SongPlayCountColumns.PLAYCOUNTSCORE);\n        builder.append(\" REAL DEFAULT 0);\");\n\n        db.execSQL(builder.toString());\n    }\n\n    public void onUpgrade(final SQLiteDatabase db, final int oldVersion, final int newVersion) {\n        // No upgrade path needed yet\n    }\n\n    public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {\n        // If we ever have downgrade, drop the table to be safe\n        db.execSQL(\"DROP TABLE IF EXISTS \" + SongPlayCountColumns.NAME);\n        onCreate(db);\n    }\n\n    /**\n     * Increases the play count of a song by 1\n     *\n     * @param songId The song id to increase the play count\n     */\n    public void bumpSongCount(final long songId) {\n        if (songId < 0) {\n            return;\n        }\n\n        final SQLiteDatabase database = mMusicDatabase.getWritableDatabase();\n        updateExistingRow(database, songId, true);\n    }\n\n    /**\n     * This creates a new entry that indicates a song has been played once as well as its score\n     *\n     * @param database a writeable database\n     * @param songId   the id of the track\n     */\n    private void createNewPlayedEntry(final SQLiteDatabase database, final long songId) {\n        // no row exists, create a new one\n        float newScore = getScoreMultiplierForWeek(0);\n        int newPlayCount = 1;\n\n        final ContentValues values = new ContentValues(3);\n        values.put(SongPlayCountColumns.ID, songId);\n        values.put(SongPlayCountColumns.PLAYCOUNTSCORE, newScore);\n        values.put(SongPlayCountColumns.LAST_UPDATED_WEEK_INDEX, mNumberOfWeeksSinceEpoch);\n        values.put(getColumnNameForWeek(0), newPlayCount);\n\n        database.insert(SongPlayCountColumns.NAME, null, values);\n    }\n\n    /**\n     * This function will take a song entry and update it to the latest week and increase the count\n     * for the current week by 1 if necessary\n     *\n     * @param database  a writeable database\n     * @param id        the id of the track to bump\n     * @param bumpCount whether to bump the current's week play count by 1 and adjust the score\n     */\n    private void updateExistingRow(final SQLiteDatabase database, final long id, boolean bumpCount) {\n        String stringId = String.valueOf(id);\n\n        // begin the transaction\n        database.beginTransaction();\n\n        // get the cursor of this content inside the transaction\n        final Cursor cursor = database.query(SongPlayCountColumns.NAME, null, WHERE_ID_EQUALS,\n                new String[]{stringId}, null, null, null);\n\n        // if we have a result\n        if (cursor != null && cursor.moveToFirst()) {\n            // figure how many weeks since we last updated\n            int lastUpdatedIndex = cursor.getColumnIndex(SongPlayCountColumns.LAST_UPDATED_WEEK_INDEX);\n            int lastUpdatedWeek = cursor.getInt(lastUpdatedIndex);\n            int weekDiff = mNumberOfWeeksSinceEpoch - lastUpdatedWeek;\n\n            // if it's more than the number of weeks we track, delete it and create a new entry\n            if (Math.abs(weekDiff) >= NUM_WEEKS) {\n                // this entry needs to be dropped since it is too outdated\n                deleteEntry(database, stringId);\n                if (bumpCount) {\n                    createNewPlayedEntry(database, id);\n                }\n            } else if (weekDiff != 0) {\n                // else, shift the weeks\n                int[] playCounts = new int[NUM_WEEKS];\n\n                if (weekDiff > 0) {\n                    // time is shifted forwards\n                    for (int i = 0; i < NUM_WEEKS - weekDiff; i++) {\n                        playCounts[i + weekDiff] = cursor.getInt(getColumnIndexForWeek(i));\n                    }\n                } else if (weekDiff < 0) {\n                    // time is shifted backwards (by user) - nor typical behavior but we\n                    // will still handle it\n\n                    // since weekDiff is -ve, NUM_WEEKS + weekDiff is the real # of weeks we have to\n                    // transfer.  Then we transfer the old week i - weekDiff to week i\n                    // for example if the user shifted back 2 weeks, ie -2, then for 0 to\n                    // NUM_WEEKS + (-2) we set the new week i = old week i - (-2) or i+2\n                    for (int i = 0; i < NUM_WEEKS + weekDiff; i++) {\n                        playCounts[i] = cursor.getInt(getColumnIndexForWeek(i - weekDiff));\n                    }\n                }\n\n                // bump the count\n                if (bumpCount) {\n                    playCounts[0]++;\n                }\n\n                float score = calculateScore(playCounts);\n\n                // if the score is non-existant, then delete it\n                if (score < .01f) {\n                    deleteEntry(database, stringId);\n                } else {\n                    // create the content values\n                    ContentValues values = new ContentValues(NUM_WEEKS + 2);\n                    values.put(SongPlayCountColumns.LAST_UPDATED_WEEK_INDEX, mNumberOfWeeksSinceEpoch);\n                    values.put(SongPlayCountColumns.PLAYCOUNTSCORE, score);\n\n                    for (int i = 0; i < NUM_WEEKS; i++) {\n                        values.put(getColumnNameForWeek(i), playCounts[i]);\n                    }\n\n                    // update the entry\n                    database.update(SongPlayCountColumns.NAME, values, WHERE_ID_EQUALS,\n                            new String[]{stringId});\n                }\n            } else if (bumpCount) {\n                // else no shifting, just update the scores\n                ContentValues values = new ContentValues(2);\n\n                // increase the score by a single score amount\n                int scoreIndex = cursor.getColumnIndex(SongPlayCountColumns.PLAYCOUNTSCORE);\n                float score = cursor.getFloat(scoreIndex) + getScoreMultiplierForWeek(0);\n                values.put(SongPlayCountColumns.PLAYCOUNTSCORE, score);\n\n                // increase the play count by 1\n                values.put(getColumnNameForWeek(0), cursor.getInt(getColumnIndexForWeek(0)) + 1);\n\n                // update the entry\n                database.update(SongPlayCountColumns.NAME, values, WHERE_ID_EQUALS,\n                        new String[]{stringId});\n            }\n\n            cursor.close();\n        } else if (bumpCount) {\n            // if we have no existing results, create a new one\n            createNewPlayedEntry(database, id);\n        }\n\n        database.setTransactionSuccessful();\n        database.endTransaction();\n    }\n\n    public void deleteAll() {\n        final SQLiteDatabase database = mMusicDatabase.getWritableDatabase();\n        database.delete(SongPlayCountColumns.NAME, null, null);\n    }\n\n    /**\n     * Gets a cursor containing the top songs played.  Note this only returns songs that have been\n     * played at least once in the past NUM_WEEKS\n     *\n     * @param numResults number of results to limit by.  If <= 0 it returns all results\n     * @return the top tracks\n     */\n    public Cursor getTopPlayedResults(int numResults) {\n        updateResults();\n\n        final SQLiteDatabase database = mMusicDatabase.getReadableDatabase();\n        return database.query(SongPlayCountColumns.NAME, new String[]{SongPlayCountColumns.ID},\n                null, null, null, null, SongPlayCountColumns.PLAYCOUNTSCORE + \" DESC\",\n                (numResults <= 0 ? null : String.valueOf(numResults)));\n    }\n\n    /**\n     * Given a list of ids, it sorts the results based on the most played results\n     *\n     * @param ids list\n     * @return sorted list - this may be smaller than the list passed in for performance reasons\n     */\n    public long[] getTopPlayedResultsForList(long[] ids) {\n        final int MAX_NUMBER_SONGS_TO_ANALYZE = 250;\n\n        if (ids == null || ids.length == 0) {\n            return null;\n        }\n\n        HashSet<Long> uniqueIds = new HashSet<Long>(ids.length);\n\n        // create the list of ids to select against\n        StringBuilder selection = new StringBuilder();\n        selection.append(SongPlayCountColumns.ID);\n        selection.append(\" IN (\");\n\n        // add the first element to handle the separator case for the first element\n        uniqueIds.add(ids[0]);\n        selection.append(ids[0]);\n\n        for (int i = 1; i < ids.length; i++) {\n            // if the new id doesn't exist\n            if (uniqueIds.add(ids[i])) {\n                // append a separator\n                selection.append(\",\");\n\n                // append the id\n                selection.append(ids[i]);\n\n                // for performance reasons, only look at a certain number of songs\n                // in case their playlist is ridiculously large\n                if (uniqueIds.size() >= MAX_NUMBER_SONGS_TO_ANALYZE) {\n                    break;\n                }\n            }\n        }\n\n        // close out the selection\n        selection.append(\")\");\n\n        long[] sortedList = new long[uniqueIds.size()];\n\n        // now query for the songs\n        final SQLiteDatabase database = mMusicDatabase.getReadableDatabase();\n        Cursor topSongsCursor = null;\n        int idx = 0;\n\n        try {\n            topSongsCursor = database.query(SongPlayCountColumns.NAME,\n                    new String[]{SongPlayCountColumns.ID}, selection.toString(), null, null,\n                    null, SongPlayCountColumns.PLAYCOUNTSCORE + \" DESC\");\n\n            if (topSongsCursor != null && topSongsCursor.moveToFirst()) {\n                do {\n                    // for each id found, add it to the list and remove it from the unique ids\n                    long id = topSongsCursor.getLong(0);\n                    sortedList[idx++] = id;\n                    uniqueIds.remove(id);\n                } while (topSongsCursor.moveToNext());\n            }\n        } finally {\n            if (topSongsCursor != null) {\n                topSongsCursor.close();\n                topSongsCursor = null;\n            }\n        }\n\n        // append the remaining items - these are songs that haven't been played recently\n        Iterator<Long> iter = uniqueIds.iterator();\n        while (iter.hasNext()) {\n            sortedList[idx++] = iter.next();\n        }\n\n        return sortedList;\n    }\n\n    /**\n     * This updates all the results for the getTopPlayedResults so that we can get an\n     * accurate list of the top played results\n     */\n    private synchronized void updateResults() {\n        if (mDatabaseUpdated) {\n            return;\n        }\n\n        final SQLiteDatabase database = mMusicDatabase.getWritableDatabase();\n\n        database.beginTransaction();\n\n        int oldestWeekWeCareAbout = mNumberOfWeeksSinceEpoch - NUM_WEEKS + 1;\n        // delete rows we don't care about anymore\n        database.delete(SongPlayCountColumns.NAME, SongPlayCountColumns.LAST_UPDATED_WEEK_INDEX\n                + \" < \" + oldestWeekWeCareAbout, null);\n\n        // get the remaining rows\n        Cursor cursor = database.query(SongPlayCountColumns.NAME,\n                new String[]{SongPlayCountColumns.ID},\n                null, null, null, null, null);\n\n        if (cursor != null && cursor.moveToFirst()) {\n            // for each row, update it\n            do {\n                updateExistingRow(database, cursor.getLong(0), false);\n            } while (cursor.moveToNext());\n\n            cursor.close();\n            cursor = null;\n        }\n\n        mDatabaseUpdated = true;\n        database.setTransactionSuccessful();\n        database.endTransaction();\n    }\n\n    /**\n     * @param songId The song Id to remove.\n     */\n    public void removeItem(final long songId) {\n        final SQLiteDatabase database = mMusicDatabase.getWritableDatabase();\n        deleteEntry(database, String.valueOf(songId));\n    }\n\n    /**\n     * Deletes the entry\n     *\n     * @param database database to use\n     * @param stringId id to delete\n     */\n    private void deleteEntry(final SQLiteDatabase database, final String stringId) {\n        database.delete(SongPlayCountColumns.NAME, WHERE_ID_EQUALS, new String[]{stringId});\n    }\n\n    public interface SongPlayCountColumns {\n\n        /* Table name */\n        String NAME = \"songplaycount\";\n\n        /* Song IDs column */\n        String ID = \"songid\";\n\n        /* Week Play Count */\n        String WEEK_PLAY_COUNT = \"week\";\n\n        /* Weeks since Epoch */\n        String LAST_UPDATED_WEEK_INDEX = \"weekindex\";\n\n        /* Play count */\n        String PLAYCOUNTSCORE = \"playcountscore\";\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/slidinguppanel/SlidingUpPanelLayout.java",
    "content": "package com.naman14.timber.slidinguppanel;\n\nimport android.annotation.SuppressLint;\nimport android.content.Context;\nimport android.content.res.TypedArray;\nimport android.graphics.Canvas;\nimport android.graphics.Paint;\nimport android.graphics.PixelFormat;\nimport android.graphics.Rect;\nimport android.graphics.drawable.Drawable;\nimport android.os.Parcel;\nimport android.os.Parcelable;\nimport androidx.core.content.ContextCompat;\nimport androidx.core.view.MotionEventCompat;\nimport androidx.core.view.ViewCompat;\nimport android.util.AttributeSet;\nimport android.view.Gravity;\nimport android.view.MotionEvent;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.view.accessibility.AccessibilityEvent;\n\nimport com.naman14.timber.R;\n\npublic class SlidingUpPanelLayout extends ViewGroup {\n\n    private static final String TAG = SlidingUpPanelLayout.class.getSimpleName();\n\n    /**\n     * Default peeking out panel height\n     */\n    private static final int DEFAULT_PANEL_HEIGHT = 68; // dp;\n\n    /**\n     * Default anchor point height\n     */\n    private static final float DEFAULT_ANCHOR_POINT = 1.0f; // In relative %\n    /**\n     * Default height of the shadow above the peeking out panel\n     */\n    private static final int DEFAULT_SHADOW_HEIGHT = 4; // dp;\n    /**\n     * If no fade color is given by default it will fade to 80% gray.\n     */\n    private static final int DEFAULT_FADE_COLOR = 0x99000000;\n    /**\n     * Whether we should hook up the drag view clickable state\n     */\n    private static final boolean DEFAULT_DRAG_VIEW_CLICKABLE = true;\n    /**\n     * Default Minimum velocity that will be detected as a fling\n     */\n    private static final int DEFAULT_MIN_FLING_VELOCITY = 400; // dips per second\n    /**\n     * Default is set to false because that is how it was written\n     */\n    private static final boolean DEFAULT_OVERLAY_FLAG = false;\n    /**\n     * Default attributes for layout\n     */\n    private static final int[] DEFAULT_ATTRS = new int[]{\n            android.R.attr.gravity\n    };\n    /**\n     * Default paralax length of the main view\n     */\n    private static final int DEFAULT_PARALAX_OFFSET = 0;\n    /**\n     * Default slide panel offset when collapsed\n     */\n    private static final int DEFAULT_SLIDE_PANEL_OFFSET = 0;\n    /**\n     * Default direct offset flag\n     */\n    private static final boolean DEFAULT_DIRECT_OFFSET_FLAG = false;\n    /**\n     * Default initial state for the component\n     */\n    private static SlideState DEFAULT_SLIDE_STATE = SlideState.COLLAPSED;\n    /**\n     * The paint used to dim the main layout when sliding\n     */\n    private final Paint mCoveredFadePaint = new Paint();\n    /**\n     * Drawable used to draw the shadow between panes.\n     */\n    private final Drawable mShadowDrawable;\n    private final ViewDragHelper mDragHelper;\n    private final Rect mTmpRect = new Rect();\n    /**\n     * Minimum velocity that will be detected as a fling\n     */\n    private int mMinFlingVelocity = DEFAULT_MIN_FLING_VELOCITY;\n    /**\n     * The fade color used for the panel covered by the slider. 0 = no fading.\n     */\n    private int mCoveredFadeColor = DEFAULT_FADE_COLOR;\n    /**\n     * The size of the overhang in pixels.\n     */\n    private int mPanelHeight = -1;\n    /**\n     * Determines how much to slide the panel off when expanded\n     */\n    private int mSlidePanelOffset = 0;\n    /**\n     * The size of the shadow in pixels.\n     */\n    private int mShadowHeight = -1;\n    /**\n     * Paralax offset\n     */\n    private int mParallaxOffset = -1;\n    /**\n     * Clamps the Main view to the slideable view\n     */\n    private boolean mDirectOffset = false;\n    /**\n     * True if the collapsed panel should be dragged up.\n     */\n    private boolean mIsSlidingUp;\n    /**\n     * Panel overlays the windows instead of putting it underneath it.\n     */\n    private boolean mOverlayContent = DEFAULT_OVERLAY_FLAG;\n    /**\n     * If provided, the panel can be dragged by only this view. Otherwise, the entire panel can be\n     * used for dragging.\n     */\n    private View mDragView;\n    /**\n     * If provided, the panel can be dragged by only this view. Otherwise, the entire panel can be\n     * used for dragging.\n     */\n    private int mDragViewResId = -1;\n    /**\n     * Whether clicking on the drag view will expand/collapse\n     */\n    private boolean mDragViewClickable = DEFAULT_DRAG_VIEW_CLICKABLE;\n    /**\n     * The child view that can slide, if any.\n     */\n    private View mSlideableView;\n    /**\n     * The main view\n     */\n    private View mMainView;\n    /**\n     * The background view\n     */\n    private View mBackgroundView;\n    private SlideState mSlideState = SlideState.COLLAPSED;\n    /**\n     * How far the panel is offset from its expanded position.\n     * range [0, 1] where 0 = collapsed, 1 = expanded.\n     */\n    private float mSlideOffset;\n    /**\n     * How far in pixels the slideable panel may move.\n     */\n    private int mSlideRange;\n    /**\n     * A panel view is locked into internal scrolling or another condition that\n     * is preventing a drag.\n     */\n    private boolean mIsUnableToDrag;\n    /**\n     * Flag indicating that sliding feature is enabled\\disabled\n     */\n    private boolean mIsSlidingEnabled;\n    /**\n     * Flag indicating if a drag view can have its own touch events.  If set\n     * to true, a drag view can scroll horizontally and have its own click listener.\n     * <p/>\n     * Default is set to false.\n     */\n    private boolean mIsUsingDragViewTouchEvents;\n    private float mInitialMotionX;\n    private float mInitialMotionY;\n    private float mAnchorPoint = 1.f;\n    private PanelSlideListener mPanelSlideListener;\n    /**\n     * Stores whether or not the pane was expanded the last time it was slideable.\n     * If expand/collapse operations are invoked this state is modified. Used by\n     * instance state save/restore.\n     */\n    private boolean mFirstLayout = true;\n\n    public SlidingUpPanelLayout(Context context) {\n        this(context, null);\n    }\n\n    public SlidingUpPanelLayout(Context context, AttributeSet attrs) {\n        this(context, attrs, 0);\n    }\n\n    public SlidingUpPanelLayout(Context context, AttributeSet attrs, int defStyle) {\n        super(context, attrs, defStyle);\n\n        if (isInEditMode()) {\n            mShadowDrawable = null;\n            mDragHelper = null;\n            return;\n        }\n\n        if (attrs != null) {\n            TypedArray defAttrs = context.obtainStyledAttributes(attrs, DEFAULT_ATTRS);\n\n            if (defAttrs != null) {\n                int gravity = defAttrs.getInt(0, Gravity.NO_GRAVITY);\n                if (gravity != Gravity.TOP && gravity != Gravity.BOTTOM) {\n                    throw new IllegalArgumentException(\"gravity must be set to either top or bottom\");\n                }\n                mIsSlidingUp = gravity == Gravity.BOTTOM;\n            }\n\n            defAttrs.recycle();\n\n            TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.SlidingUpPanelLayout);\n\n            if (ta != null) {\n                mPanelHeight = ta.getDimensionPixelSize(R.styleable.SlidingUpPanelLayout_panelHeight, -1);\n                mSlidePanelOffset = ta.getDimensionPixelSize(R.styleable.SlidingUpPanelLayout_slidePanelOffset, DEFAULT_SLIDE_PANEL_OFFSET);\n                mShadowHeight = ta.getDimensionPixelSize(R.styleable.SlidingUpPanelLayout_shadowHeight, -1);\n                mParallaxOffset = ta.getDimensionPixelSize(R.styleable.SlidingUpPanelLayout_paralaxOffset, -1);\n                mDirectOffset = ta.getBoolean(R.styleable.SlidingUpPanelLayout_directOffset, DEFAULT_DIRECT_OFFSET_FLAG);\n\n                mMinFlingVelocity = ta.getInt(R.styleable.SlidingUpPanelLayout_flingVelocity, DEFAULT_MIN_FLING_VELOCITY);\n                mCoveredFadeColor = ta.getColor(R.styleable.SlidingUpPanelLayout_fadeColor, DEFAULT_FADE_COLOR);\n\n                mDragViewResId = ta.getResourceId(R.styleable.SlidingUpPanelLayout_dragView, -1);\n                mDragViewClickable = ta.getBoolean(R.styleable.SlidingUpPanelLayout_dragViewClickable, DEFAULT_DRAG_VIEW_CLICKABLE);\n\n                mOverlayContent = ta.getBoolean(R.styleable.SlidingUpPanelLayout_overlay, DEFAULT_OVERLAY_FLAG);\n\n                mAnchorPoint = ta.getFloat(R.styleable.SlidingUpPanelLayout_anchorPoint, DEFAULT_ANCHOR_POINT);\n\n                mSlideState = SlideState.values()[ta.getInt(R.styleable.SlidingUpPanelLayout_initialState, DEFAULT_SLIDE_STATE.ordinal())];\n            }\n\n            ta.recycle();\n        }\n\n        final float density = context.getResources().getDisplayMetrics().density;\n        if (mPanelHeight == -1) {\n            mPanelHeight = (int) (DEFAULT_PANEL_HEIGHT * density + 0.5f);\n        }\n        if (mShadowHeight == -1) {\n            mShadowHeight = (int) (DEFAULT_SHADOW_HEIGHT * density + 0.5f);\n        }\n        if (mParallaxOffset == -1) {\n            mParallaxOffset = (int) (DEFAULT_PARALAX_OFFSET * density);\n        }\n        // If the shadow height is zero, don't show the shadow\n        if (mShadowHeight > 0) {\n            if (mIsSlidingUp) {\n                mShadowDrawable = ContextCompat.getDrawable(context, R.drawable.above_shadow);\n            } else {\n                mShadowDrawable = ContextCompat.getDrawable(context, R.drawable.below_shadow);\n            }\n\n        } else {\n            mShadowDrawable = null;\n        }\n\n        setWillNotDraw(false);\n\n        mDragHelper = ViewDragHelper.create(this, 0.5f, new DragHelperCallback());\n        mDragHelper.setMinVelocity(mMinFlingVelocity * density);\n\n        mIsSlidingEnabled = true;\n    }\n\n    private static boolean hasOpaqueBackground(View v) {\n        final Drawable bg = v.getBackground();\n        return bg != null && bg.getOpacity() == PixelFormat.OPAQUE;\n    }\n\n    /**\n     * Set the Drag View after the view is inflated\n     */\n    @Override\n    protected void onFinishInflate() {\n        super.onFinishInflate();\n        if (mDragViewResId != -1) {\n            setDragView(findViewById(mDragViewResId));\n        }\n    }\n\n    /**\n     * @return The ARGB-packed color value used to fade the fixed pane\n     */\n    public int getCoveredFadeColor() {\n        return mCoveredFadeColor;\n    }\n\n    /**\n     * Set the color used to fade the pane covered by the sliding pane out when the pane\n     * will become fully covered in the expanded state.\n     *\n     * @param color An ARGB-packed color value\n     */\n    public void setCoveredFadeColor(int color) {\n        mCoveredFadeColor = color;\n        invalidate();\n    }\n\n    public boolean isSlidingEnabled() {\n        return mIsSlidingEnabled && mSlideableView != null;\n    }\n\n    /**\n     * Set sliding enabled flag\n     *\n     * @param enabled flag value\n     */\n    public void setSlidingEnabled(boolean enabled) {\n        mIsSlidingEnabled = enabled;\n    }\n\n    /**\n     * @return The current collapsed panel height\n     */\n    public int getPanelHeight() {\n        return mPanelHeight;\n    }\n\n    /**\n     * Set the collapsed panel height in pixels\n     *\n     * @param val A height in pixels\n     */\n    public void setPanelHeight(int val) {\n        mPanelHeight = val;\n        requestLayout();\n    }\n\n    /**\n     * Sets the panel offset when collapsed so you can exit\n     * the boundaries of the top of the screen\n     *\n     * @param val Offset in pixels\n     */\n    public void setSlidePanelOffset(int val) {\n        mSlidePanelOffset = val;\n        requestLayout();\n    }\n\n    /**\n     * @return The current paralax offset\n     */\n    public int getCurrentParalaxOffset() {\n        if (mParallaxOffset < 0) {\n            return 0;\n        }\n\n        return (int) (mParallaxOffset * getDirectionalSlideOffset());\n    }\n\n    /**\n     * @return The directional slide offset\n     */\n    protected float getDirectionalSlideOffset() {\n        return mIsSlidingUp ? -mSlideOffset : mSlideOffset;\n    }\n\n    /**\n     * Sets the panel slide listener\n     *\n     * @param listener\n     */\n    public void setPanelSlideListener(PanelSlideListener listener) {\n        mPanelSlideListener = listener;\n    }\n\n    /**\n     * Set the draggable view portion. Use to null, to allow the whole panel to be draggable\n     *\n     * @param dragView A view that will be used to drag the panel.\n     */\n    public void setDragView(View dragView) {\n        if (mDragView != null && mDragViewClickable) {\n            mDragView.setOnClickListener(null);\n        }\n        mDragView = dragView;\n        if (mDragView != null) {\n            mDragView.setClickable(true);\n            mDragView.setFocusable(false);\n            mDragView.setFocusableInTouchMode(false);\n            if (mDragViewClickable) {\n                mDragView.setOnClickListener(new OnClickListener() {\n                    @Override\n                    public void onClick(View v) {\n                        if (!isEnabled()) return;\n                        if (!isPanelExpanded() && !isPanelAnchored()) {\n                            expandPanel(mAnchorPoint);\n                        } else {\n                            collapsePanel();\n                        }\n                    }\n                });\n            }\n        }\n    }\n\n    /**\n     * Gets the currently set anchor point\n     *\n     * @return the currently set anchor point\n     */\n    public float getAnchorPoint() {\n        return mAnchorPoint;\n    }\n\n    /**\n     * Set an anchor point where the panel can stop during sliding\n     *\n     * @param anchorPoint A value between 0 and 1, determining the position of the anchor point\n     *                    starting from the top of the layout.\n     */\n    public void setAnchorPoint(float anchorPoint) {\n        if (anchorPoint > 0 && anchorPoint <= 1) {\n            mAnchorPoint = anchorPoint;\n        }\n    }\n\n    /**\n     * Check if the panel is set as an overlay.\n     */\n    public boolean isOverlayed() {\n        return mOverlayContent;\n    }\n\n    /**\n     * Sets whether or not the panel overlays the content\n     *\n     * @param overlayed\n     */\n    public void setOverlayed(boolean overlayed) {\n        mOverlayContent = overlayed;\n    }\n\n    void dispatchOnPanelSlide(View panel) {\n        if (mPanelSlideListener != null) {\n            mPanelSlideListener.onPanelSlide(panel, mSlideOffset);\n        }\n    }\n\n    void dispatchOnPanelExpanded(View panel) {\n        if (mPanelSlideListener != null) {\n            mPanelSlideListener.onPanelExpanded(panel);\n        }\n        sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);\n    }\n\n    void dispatchOnPanelCollapsed(View panel) {\n        if (mPanelSlideListener != null) {\n            mPanelSlideListener.onPanelCollapsed(panel);\n        }\n        sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);\n    }\n\n    void dispatchOnPanelAnchored(View panel) {\n        if (mPanelSlideListener != null) {\n            mPanelSlideListener.onPanelAnchored(panel);\n        }\n        sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);\n    }\n\n    void dispatchOnPanelHidden(View panel) {\n        if (mPanelSlideListener != null) {\n            mPanelSlideListener.onPanelHidden(panel);\n        }\n        sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);\n    }\n\n    void updateObscuredViewVisibility() {\n        if (getChildCount() == 0) {\n            return;\n        }\n        final int leftBound = getPaddingLeft();\n        final int rightBound = getWidth() - getPaddingRight();\n        final int topBound = getPaddingTop();\n        final int bottomBound = getHeight() - getPaddingBottom();\n        final int left;\n        final int right;\n        final int top;\n        final int bottom;\n        if (mSlideableView != null && hasOpaqueBackground(mSlideableView)) {\n            left = mSlideableView.getLeft();\n            right = mSlideableView.getRight();\n            top = mSlideableView.getTop();\n            bottom = mSlideableView.getBottom();\n        } else {\n            left = right = top = bottom = 0;\n        }\n        View child = mMainView;\n        final int clampedChildLeft = Math.max(leftBound, child.getLeft());\n        final int clampedChildTop = Math.max(topBound, child.getTop());\n        final int clampedChildRight = Math.min(rightBound, child.getRight());\n        final int clampedChildBottom = Math.min(bottomBound, child.getBottom());\n        final int vis;\n        if (clampedChildLeft >= left && clampedChildTop >= top &&\n                clampedChildRight <= right && clampedChildBottom <= bottom) {\n            vis = INVISIBLE;\n        } else {\n            vis = VISIBLE;\n        }\n        child.setVisibility(vis);\n    }\n\n    void setAllChildrenVisible() {\n        for (int i = 0, childCount = getChildCount(); i < childCount; i++) {\n            final View child = getChildAt(i);\n            if (child.getVisibility() == INVISIBLE) {\n                child.setVisibility(VISIBLE);\n            }\n        }\n    }\n\n    @Override\n    protected void onAttachedToWindow() {\n        super.onAttachedToWindow();\n        mFirstLayout = true;\n    }\n\n    @Override\n    protected void onDetachedFromWindow() {\n        super.onDetachedFromWindow();\n        mFirstLayout = true;\n    }\n\n    @Override\n    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {\n        final int widthMode = MeasureSpec.getMode(widthMeasureSpec);\n        final int widthSize = MeasureSpec.getSize(widthMeasureSpec);\n        final int heightMode = MeasureSpec.getMode(heightMeasureSpec);\n        final int heightSize = MeasureSpec.getSize(heightMeasureSpec);\n\n        if (widthMode != MeasureSpec.EXACTLY) {\n            throw new IllegalStateException(\"Width must have an exact value or MATCH_PARENT\");\n        } else if (heightMode != MeasureSpec.EXACTLY) {\n            throw new IllegalStateException(\"Height must have an exact value or MATCH_PARENT\");\n        }\n\n        final int childCount = getChildCount();\n\n        if (childCount != 2 && childCount != 3) {\n            throw new IllegalStateException(\"Sliding up panel layout must have exactly 2 or 3 children!\");\n        }\n\n        if (childCount == 2) {\n            mMainView = getChildAt(0);\n            mSlideableView = getChildAt(1);\n        } else {\n            mBackgroundView = getChildAt(0);\n            mMainView = getChildAt(1);\n            mSlideableView = getChildAt(2);\n        }\n\n        if (mDragView == null) {\n            setDragView(mSlideableView);\n        }\n\n        // If the sliding panel is not visible, then put the whole view in the hidden state\n        if (mSlideableView.getVisibility() == GONE) {\n            mSlideState = SlideState.HIDDEN;\n        }\n\n        int layoutHeight = heightSize - getPaddingTop() - getPaddingBottom();\n\n        // First pass. Measure based on child LayoutParams width/height.\n        for (int i = 0; i < childCount; i++) {\n            final View child = getChildAt(i);\n            final LayoutParams lp = (LayoutParams) child.getLayoutParams();\n\n            // We always measure the sliding panel in order to know it's height (needed for show panel)\n            if (child.getVisibility() == GONE && child == mMainView) {\n                continue;\n            }\n\n            int height = layoutHeight;\n            if (child == mMainView && !mOverlayContent && mSlideState != SlideState.HIDDEN) {\n                height -= mPanelHeight;\n            }\n\n            int childWidthSpec;\n            if (lp.width == LayoutParams.WRAP_CONTENT) {\n                childWidthSpec = MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.AT_MOST);\n            } else if (lp.width == LayoutParams.MATCH_PARENT) {\n                childWidthSpec = MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.EXACTLY);\n            } else {\n                childWidthSpec = MeasureSpec.makeMeasureSpec(lp.width, MeasureSpec.EXACTLY);\n            }\n\n            int childHeightSpec;\n            if (lp.height == LayoutParams.WRAP_CONTENT) {\n                childHeightSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST);\n            } else if (lp.height == LayoutParams.MATCH_PARENT) {\n                childHeightSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);\n            } else {\n                childHeightSpec = MeasureSpec.makeMeasureSpec(lp.height, MeasureSpec.EXACTLY);\n            }\n\n            if (child == mSlideableView) {\n                mSlideRange = MeasureSpec.getSize(childHeightSpec) - mPanelHeight + mSlidePanelOffset;\n                childHeightSpec += mSlidePanelOffset;\n            }\n\n            child.measure(childWidthSpec, childHeightSpec);\n        }\n\n        setMeasuredDimension(widthSize, heightSize);\n    }\n\n    @Override\n    protected void onLayout(boolean changed, int l, int t, int r, int b) {\n        final int paddingLeft = getPaddingLeft();\n        final int paddingTop = getPaddingTop();\n\n        final int childCount = getChildCount();\n\n        if (mFirstLayout) {\n            switch (mSlideState) {\n                case EXPANDED:\n                    mSlideOffset = 1.0f;\n                    break;\n                case ANCHORED:\n                    mSlideOffset = mAnchorPoint;\n                    break;\n                case HIDDEN:\n                    int newTop = computePanelTopPosition(0.0f) + (mIsSlidingUp ? +mPanelHeight : -mPanelHeight);\n                    mSlideOffset = computeSlideOffset(newTop);\n                    break;\n                default:\n                    mSlideOffset = 0.f;\n                    break;\n            }\n        }\n\n        for (int i = 0; i < childCount; i++) {\n            final View child = getChildAt(i);\n\n            // Always layout the sliding view on the first layout\n            if (child.getVisibility() == GONE && (child == mMainView || mFirstLayout)) {\n                continue;\n            }\n\n            final int childHeight = child.getMeasuredHeight();\n            int childTop = paddingTop;\n\n            if (child == mSlideableView) {\n                childTop = computePanelTopPosition(mSlideOffset);\n            }\n\n            if (!mIsSlidingUp) {\n                if (child == mMainView && !mOverlayContent) {\n                    childTop = computePanelTopPosition(mSlideOffset) + mSlideableView.getMeasuredHeight();\n                }\n            }\n            final int childBottom = childTop + childHeight;\n            final int childLeft = paddingLeft;\n            final int childRight = childLeft + child.getMeasuredWidth();\n\n            child.layout(childLeft, childTop, childRight, childBottom);\n        }\n\n        if (mFirstLayout) {\n            updateObscuredViewVisibility();\n        }\n\n        mFirstLayout = false;\n    }\n\n    @Override\n    protected void onSizeChanged(int w, int h, int oldw, int oldh) {\n        super.onSizeChanged(w, h, oldw, oldh);\n        // Recalculate sliding panes and their details\n        if (h != oldh) {\n            mFirstLayout = true;\n        }\n    }\n\n    /**\n     * Set if the drag view can have its own touch events.  If set\n     * to true, a drag view can scroll horizontally and have its own click listener.\n     * <p/>\n     * Default is set to false.\n     */\n    public void setEnableDragViewTouchEvents(boolean enabled) {\n        mIsUsingDragViewTouchEvents = enabled;\n    }\n\n    @Override\n    public void setEnabled(boolean enabled) {\n        if (!enabled) {\n            collapsePanel();\n        }\n        super.setEnabled(enabled);\n    }\n\n    @Override\n    public boolean onInterceptTouchEvent(MotionEvent ev) {\n        final int action = MotionEventCompat.getActionMasked(ev);\n\n\n        if (!isEnabled() || !mIsSlidingEnabled || (mIsUnableToDrag && action != MotionEvent.ACTION_DOWN)) {\n            mDragHelper.cancel();\n            return super.onInterceptTouchEvent(ev);\n        }\n\n        if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {\n            mDragHelper.cancel();\n            return false;\n        }\n\n        final float x = ev.getX();\n        final float y = ev.getY();\n\n        switch (action) {\n            case MotionEvent.ACTION_DOWN: {\n                mIsUnableToDrag = false;\n                mInitialMotionX = x;\n                mInitialMotionY = y;\n                break;\n            }\n\n            case MotionEvent.ACTION_MOVE: {\n                final float adx = Math.abs(x - mInitialMotionX);\n                final float ady = Math.abs(y - mInitialMotionY);\n                final int dragSlop = mDragHelper.getTouchSlop();\n\n                // Handle any horizontal scrolling on the drag view.\n                if (mIsUsingDragViewTouchEvents && adx > dragSlop && ady < dragSlop) {\n                    return super.onInterceptTouchEvent(ev);\n                }\n\n                if ((ady > dragSlop && adx > ady) || !isDragViewUnder((int) mInitialMotionX, (int) mInitialMotionY)) {\n                    mDragHelper.cancel();\n                    mIsUnableToDrag = true;\n                    return false;\n                }\n                break;\n            }\n        }\n\n        return mDragHelper.shouldInterceptTouchEvent(ev);\n    }\n\n    @Override\n    public boolean onTouchEvent(MotionEvent ev) {\n        if (!isSlidingEnabled()) {\n            return super.onTouchEvent(ev);\n        }\n        mDragHelper.processTouchEvent(ev);\n        return true;\n    }\n\n    private boolean isDragViewUnder(int x, int y) {\n        if (mDragView == null) return false;\n        int[] viewLocation = new int[2];\n        mDragView.getLocationOnScreen(viewLocation);\n        int[] parentLocation = new int[2];\n        this.getLocationOnScreen(parentLocation);\n        int screenX = parentLocation[0] + x;\n        int screenY = parentLocation[1] + y;\n        return screenX >= viewLocation[0] && screenX < viewLocation[0] + mDragView.getWidth() &&\n                screenY >= viewLocation[1] && screenY < viewLocation[1] + mDragView.getHeight();\n    }\n\n    private boolean expandPanel(View pane, int initialVelocity, float mSlideOffset) {\n        return mFirstLayout || smoothSlideTo(mSlideOffset, initialVelocity);\n    }\n\n    private boolean collapsePanel(View pane, int initialVelocity) {\n        return mFirstLayout || smoothSlideTo(0.0f, initialVelocity);\n    }\n\n    /*\n     * Computes the top position of the panel based on the slide offset.\n     */\n    private int computePanelTopPosition(float slideOffset) {\n        int slidingViewHeight = mSlideableView != null ? mSlideableView.getMeasuredHeight() : 0;\n        int slidePixelOffset = (int) (slideOffset * mSlideRange);\n        // Compute the top of the panel if its collapsed\n        return mIsSlidingUp\n                ? getMeasuredHeight() - getPaddingBottom() - mPanelHeight - slidePixelOffset\n                : getPaddingTop() - slidingViewHeight + mPanelHeight + slidePixelOffset;\n    }\n\n    /*\n     * Computes the slide offset based on the top position of the panel\n     */\n    private float computeSlideOffset(int topPosition) {\n        // Compute the panel top position if the panel is collapsed (offset 0)\n        final int topBoundCollapsed = computePanelTopPosition(0);\n\n        // Determine the new slide offset based on the collapsed top position and the new required\n        // top position\n        return (mIsSlidingUp\n                ? (float) (topBoundCollapsed - topPosition) / mSlideRange\n                : (float) (topPosition - topBoundCollapsed) / mSlideRange);\n    }\n\n    /**\n     * Collapse the sliding pane if it is currently slideable. If first layout\n     * has already completed this will animate.\n     *\n     * @return true if the pane was slideable and is now collapsed/in the process of collapsing\n     */\n    public boolean collapsePanel() {\n        if (mFirstLayout) {\n            mSlideState = SlideState.COLLAPSED;\n            return true;\n        } else {\n            if (mSlideState == SlideState.HIDDEN || mSlideState == SlideState.COLLAPSED)\n                return false;\n            return collapsePanel(mSlideableView, 0);\n        }\n    }\n\n    /**\n     * Expand the sliding pane if it is currently slideable.\n     *\n     * @return true if the pane was slideable and is now expanded/in the process of expading\n     */\n    public boolean expandPanel() {\n        if (mFirstLayout) {\n            mSlideState = SlideState.EXPANDED;\n            return true;\n        } else {\n            return expandPanel(1.0f);\n        }\n    }\n\n    /**\n     * Expand the sliding pane to the anchor point if it is currently slideable.\n     *\n     * @return true if the pane was slideable and is now expanded/in the process of expading\n     */\n    public boolean anchorPanel() {\n        if (mFirstLayout) {\n            mSlideState = SlideState.ANCHORED;\n            return true;\n        } else {\n            return expandPanel(mAnchorPoint);\n        }\n    }\n\n    /**\n     * Partially expand the sliding panel up to a specific offset\n     *\n     * @param mSlideOffset Value between 0 and 1, where 0 is completely expanded.\n     * @return true if the pane was slideable and is now expanded/in the process of expanding\n     */\n    public boolean expandPanel(float mSlideOffset) {\n        if (mSlideableView == null || mSlideState == SlideState.EXPANDED) return false;\n        mSlideableView.setVisibility(View.VISIBLE);\n        return expandPanel(mSlideableView, 0, mSlideOffset);\n    }\n\n    /**\n     * Check if the sliding panel in this layout is fully expanded.\n     *\n     * @return true if sliding panel is completely expanded\n     */\n    public boolean isPanelExpanded() {\n        return mSlideState == SlideState.EXPANDED;\n    }\n\n    /**\n     * Check if the sliding panel in this layout is anchored.\n     *\n     * @return true if sliding panel is anchored\n     */\n    public boolean isPanelAnchored() {\n        return mSlideState == SlideState.ANCHORED;\n    }\n\n    /**\n     * Check if the sliding panel in this layout is currently visible.\n     *\n     * @return true if the sliding panel is visible.\n     */\n    public boolean isPanelHidden() {\n        return mSlideState == SlideState.HIDDEN;\n    }\n\n    /**\n     * Shows the panel from the hidden state\n     */\n    public void showPanel() {\n        if (mFirstLayout) {\n            mSlideState = SlideState.COLLAPSED;\n        } else {\n            if (mSlideableView == null || mSlideState != SlideState.HIDDEN) return;\n            mSlideableView.setVisibility(View.VISIBLE);\n            requestLayout();\n            smoothSlideTo(0, 0);\n        }\n    }\n\n    /**\n     * Hides the sliding panel entirely.\n     */\n    public void hidePanel() {\n        if (mFirstLayout) {\n            mSlideState = SlideState.HIDDEN;\n        } else {\n            if (mSlideState == SlideState.DRAGGING || mSlideState == SlideState.HIDDEN) return;\n            int newTop = computePanelTopPosition(0.0f) + (mIsSlidingUp ? +mPanelHeight : -mPanelHeight);\n            smoothSlideTo(computeSlideOffset(newTop), 0);\n        }\n    }\n\n    @SuppressLint(\"NewApi\")\n    private void onPanelDragged(int newTop) {\n        mSlideState = SlideState.DRAGGING;\n        // Recompute the slide offset based on the new top position\n        mSlideOffset = computeSlideOffset(newTop);\n        // Update the parallax based on the new slide offset\n        if ((mParallaxOffset > 0 || mDirectOffset) && mSlideOffset >= 0) {\n            int mainViewOffset = 0;\n            if (mParallaxOffset > 0) {\n                mainViewOffset = getCurrentParalaxOffset();\n            } else {\n                mainViewOffset = (int) (getDirectionalSlideOffset() * mSlideRange);\n            }\n\n            mMainView.setTranslationY(mainViewOffset);\n        }\n\n        // Dispatch the slide event\n        dispatchOnPanelSlide(mSlideableView);\n        // If the slide offset is negative, and overlay is not on, we need to increase the\n        // height of the main content\n        if (mSlideOffset <= 0 && !mOverlayContent) {\n            // expand the main view\n            LayoutParams lp = (LayoutParams) mMainView.getLayoutParams();\n            lp.height = mIsSlidingUp ? (newTop - getPaddingBottom()) : (getHeight() - getPaddingBottom() - mSlideableView.getMeasuredHeight() - newTop);\n            mMainView.requestLayout();\n        }\n    }\n\n    @Override\n    protected boolean drawChild(Canvas canvas, View child, long drawingTime) {\n        boolean result;\n        final int save = canvas.save();\n\n        if (isSlidingEnabled() && mMainView == child) {\n            // Clip against the slider; no sense drawing what will immediately be covered,\n            // Unless the panel is set to overlay content\n            if (!mOverlayContent) {\n                canvas.getClipBounds(mTmpRect);\n                if (mIsSlidingUp) {\n                    mTmpRect.bottom = Math.min(mTmpRect.bottom, mSlideableView.getTop());\n                } else {\n                    mTmpRect.top = Math.max(mTmpRect.top, mSlideableView.getBottom());\n                }\n                canvas.clipRect(mTmpRect);\n            }\n        }\n\n        result = super.drawChild(canvas, child, drawingTime);\n        canvas.restoreToCount(save);\n\n        if (mCoveredFadeColor != 0 && mSlideOffset > 0) {\n            final int baseAlpha = (mCoveredFadeColor & 0xff000000) >>> 24;\n            final int imag = (int) (baseAlpha * mSlideOffset);\n            final int color = imag << 24 | (mCoveredFadeColor & 0xffffff);\n            mCoveredFadePaint.setColor(color);\n            canvas.drawRect(mTmpRect, mCoveredFadePaint);\n        }\n\n        return result;\n    }\n\n    /**\n     * Smoothly animate mDraggingPane to the target X position within its range.\n     *\n     * @param slideOffset position to animate to\n     * @param velocity    initial velocity in case of fling, or 0.\n     */\n    boolean smoothSlideTo(float slideOffset, int velocity) {\n        if (!isSlidingEnabled()) {\n            // Nothing to do.\n            return false;\n        }\n\n        int panelTop = computePanelTopPosition(slideOffset);\n        if (mDragHelper.smoothSlideViewTo(mSlideableView, mSlideableView.getLeft(), panelTop)) {\n            setAllChildrenVisible();\n            ViewCompat.postInvalidateOnAnimation(this);\n            return true;\n        }\n        return false;\n    }\n\n    @Override\n    public void computeScroll() {\n        if (mDragHelper != null && mDragHelper.continueSettling(true)) {\n            if (!isSlidingEnabled()) {\n                mDragHelper.abort();\n                return;\n            }\n\n            ViewCompat.postInvalidateOnAnimation(this);\n        }\n    }\n\n    @Override\n    public void draw(Canvas c) {\n        super.draw(c);\n\n        if (!isSlidingEnabled()) {\n            // No need to draw a shadow if we don't have one.\n            return;\n        }\n\n        final int right = mSlideableView.getRight();\n        final int top;\n        final int bottom;\n        if (mIsSlidingUp) {\n            top = mSlideableView.getTop() - mShadowHeight;\n            bottom = mSlideableView.getTop();\n        } else {\n            top = mSlideableView.getBottom();\n            bottom = mSlideableView.getBottom() + mShadowHeight;\n        }\n        final int left = mSlideableView.getLeft();\n\n        if (mShadowDrawable != null) {\n            mShadowDrawable.setBounds(left, top, right, bottom);\n            mShadowDrawable.draw(c);\n        }\n    }\n\n    /**\n     * Tests scrollability within child views of v given a delta of dx.\n     *\n     * @param v      View to test for horizontal scrollability\n     * @param checkV Whether the view v passed should itself be checked for scrollability (true),\n     *               or just its children (false).\n     * @param dx     Delta scrolled in pixels\n     * @param x      X coordinate of the active touch point\n     * @param y      Y coordinate of the active touch point\n     * @return true if child views of v can be scrolled by delta of dx.\n     */\n    protected boolean canScroll(View v, boolean checkV, int dx, int x, int y) {\n        if (v instanceof ViewGroup) {\n            final ViewGroup group = (ViewGroup) v;\n            final int scrollX = v.getScrollX();\n            final int scrollY = v.getScrollY();\n            final int count = group.getChildCount();\n            // Count backwards - let topmost views consume scroll distance first.\n            for (int i = count - 1; i >= 0; i--) {\n                final View child = group.getChildAt(i);\n                if (x + scrollX >= child.getLeft() && x + scrollX < child.getRight() &&\n                        y + scrollY >= child.getTop() && y + scrollY < child.getBottom() &&\n                        canScroll(child, true, dx, x + scrollX - child.getLeft(),\n                                y + scrollY - child.getTop())) {\n                    return true;\n                }\n            }\n        }\n        return checkV && ViewCompat.canScrollHorizontally(v, -dx);\n    }\n\n    @Override\n    protected ViewGroup.LayoutParams generateDefaultLayoutParams() {\n        return new LayoutParams();\n    }\n\n    @Override\n    protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {\n        return p instanceof MarginLayoutParams\n                ? new LayoutParams((MarginLayoutParams) p)\n                : new LayoutParams(p);\n    }\n\n    @Override\n    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {\n        return p instanceof LayoutParams && super.checkLayoutParams(p);\n    }\n\n    @Override\n    public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {\n        return new LayoutParams(getContext(), attrs);\n    }\n\n    @Override\n    public Parcelable onSaveInstanceState() {\n        Parcelable superState = super.onSaveInstanceState();\n\n        SavedState ss = new SavedState(superState);\n        ss.mSlideState = mSlideState;\n\n        return ss;\n    }\n\n    @Override\n    public void onRestoreInstanceState(Parcelable state) {\n        SavedState ss = (SavedState) state;\n        super.onRestoreInstanceState(ss.getSuperState());\n        mSlideState = ss.mSlideState;\n    }\n\n    /**\n     * Current state of the slideable view.\n     */\n    private enum SlideState {\n        EXPANDED,\n        COLLAPSED,\n        ANCHORED,\n        HIDDEN,\n        DRAGGING\n    }\n\n    /**\n     * Listener for monitoring events about sliding panes.\n     */\n    public interface PanelSlideListener {\n        /**\n         * Called when a sliding pane's position changes.\n         *\n         * @param panel       The child view that was moved\n         * @param slideOffset The new offset of this sliding pane within its range, from 0-1\n         */\n        void onPanelSlide(View panel, float slideOffset);\n\n        /**\n         * Called when a sliding panel becomes slid completely collapsed.\n         *\n         * @param panel The child view that was slid to an collapsed position\n         */\n        void onPanelCollapsed(View panel);\n\n        /**\n         * Called when a sliding panel becomes slid completely expanded.\n         *\n         * @param panel The child view that was slid to a expanded position\n         */\n        void onPanelExpanded(View panel);\n\n        /**\n         * Called when a sliding panel becomes anchored.\n         *\n         * @param panel The child view that was slid to a anchored position\n         */\n        void onPanelAnchored(View panel);\n\n        /**\n         * Called when a sliding panel becomes completely hidden.\n         *\n         * @param panel The child view that was slid to a hidden position\n         */\n        void onPanelHidden(View panel);\n    }\n\n    /**\n     * No-op stubs for {@link PanelSlideListener}. If you only want to implement a subset\n     * of the listener methods you can extend this instead of implement the full interface.\n     */\n    public static class SimplePanelSlideListener implements PanelSlideListener {\n        @Override\n        public void onPanelSlide(View panel, float slideOffset) {\n        }\n\n        @Override\n        public void onPanelCollapsed(View panel) {\n        }\n\n        @Override\n        public void onPanelExpanded(View panel) {\n        }\n\n        @Override\n        public void onPanelAnchored(View panel) {\n        }\n\n        @Override\n        public void onPanelHidden(View panel) {\n        }\n    }\n\n    public static class LayoutParams extends MarginLayoutParams {\n        private static final int[] ATTRS = new int[]{\n                android.R.attr.layout_weight\n        };\n\n        public LayoutParams() {\n            super(MATCH_PARENT, MATCH_PARENT);\n        }\n\n        public LayoutParams(int width, int height) {\n            super(width, height);\n        }\n\n        public LayoutParams(ViewGroup.LayoutParams source) {\n            super(source);\n        }\n\n        public LayoutParams(MarginLayoutParams source) {\n            super(source);\n        }\n\n        public LayoutParams(LayoutParams source) {\n            super(source);\n        }\n\n        public LayoutParams(Context c, AttributeSet attrs) {\n            super(c, attrs);\n\n            final TypedArray a = c.obtainStyledAttributes(attrs, ATTRS);\n            a.recycle();\n        }\n\n    }\n\n    static class SavedState extends BaseSavedState {\n        public static final Creator<SavedState> CREATOR =\n                new Creator<SavedState>() {\n                    @Override\n                    public SavedState createFromParcel(Parcel in) {\n                        return new SavedState(in);\n                    }\n\n                    @Override\n                    public SavedState[] newArray(int size) {\n                        return new SavedState[size];\n                    }\n                };\n        SlideState mSlideState;\n\n        SavedState(Parcelable superState) {\n            super(superState);\n        }\n\n        private SavedState(Parcel in) {\n            super(in);\n            try {\n                mSlideState = Enum.valueOf(SlideState.class, in.readString());\n            } catch (IllegalArgumentException e) {\n                mSlideState = SlideState.COLLAPSED;\n            }\n        }\n\n        @Override\n        public void writeToParcel(Parcel out, int flags) {\n            super.writeToParcel(out, flags);\n            out.writeString(mSlideState.toString());\n        }\n    }\n\n    private class DragHelperCallback extends ViewDragHelper.Callback {\n\n        @Override\n        public boolean tryCaptureView(View child, int pointerId) {\n            if (mIsUnableToDrag) {\n                return false;\n            }\n\n            return child == mSlideableView;\n        }\n\n        @Override\n        public void onViewDragStateChanged(int state) {\n            if (mDragHelper.getViewDragState() == ViewDragHelper.STATE_IDLE) {\n                mSlideOffset = computeSlideOffset(mSlideableView.getTop());\n\n                if (mSlideOffset == 1) {\n                    if (mSlideState != SlideState.EXPANDED) {\n                        updateObscuredViewVisibility();\n                        mSlideState = SlideState.EXPANDED;\n                        dispatchOnPanelExpanded(mSlideableView);\n                    }\n                } else if (mSlideOffset == 0) {\n                    if (mSlideState != SlideState.COLLAPSED) {\n                        mSlideState = SlideState.COLLAPSED;\n                        dispatchOnPanelCollapsed(mSlideableView);\n                    }\n                } else if (mSlideOffset < 0) {\n                    mSlideState = SlideState.HIDDEN;\n                    mSlideableView.setVisibility(View.GONE);\n                    dispatchOnPanelHidden(mSlideableView);\n                } else if (mSlideState != SlideState.ANCHORED) {\n                    updateObscuredViewVisibility();\n                    mSlideState = SlideState.ANCHORED;\n                    dispatchOnPanelAnchored(mSlideableView);\n                }\n            }\n        }\n\n        @Override\n        public void onViewCaptured(View capturedChild, int activePointerId) {\n            setAllChildrenVisible();\n        }\n\n        @Override\n        public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {\n            onPanelDragged(top);\n            invalidate();\n        }\n\n        @Override\n        public void onViewReleased(View releasedChild, float xvel, float yvel) {\n            int target = 0;\n\n            // direction is always positive if we are sliding in the expanded direction\n            float direction = mIsSlidingUp ? -yvel : yvel;\n\n            if (direction > 0) {\n                // swipe up -> expand\n                target = computePanelTopPosition(1.0f);\n            } else if (direction < 0) {\n                // swipe down -> collapse\n                target = computePanelTopPosition(0.0f);\n            } else if (mAnchorPoint != 1 && mSlideOffset >= (1.f + mAnchorPoint) / 2) {\n                // zero velocity, and far enough from anchor point => expand to the top\n                target = computePanelTopPosition(1.0f);\n            } else if (mAnchorPoint == 1 && mSlideOffset >= 0.5f) {\n                // zero velocity, and far enough from anchor point => expand to the top\n                target = computePanelTopPosition(1.0f);\n            } else if (mAnchorPoint != 1 && mSlideOffset >= mAnchorPoint) {\n                target = computePanelTopPosition(mAnchorPoint);\n            } else if (mAnchorPoint != 1 && mSlideOffset >= mAnchorPoint / 2) {\n                target = computePanelTopPosition(mAnchorPoint);\n            } else {\n                // settle at the bottom\n                target = computePanelTopPosition(0.0f);\n            }\n\n            mDragHelper.settleCapturedViewAt(releasedChild.getLeft(), target);\n            invalidate();\n        }\n\n        @Override\n        public int getViewVerticalDragRange(View child) {\n            return mSlideRange;\n        }\n\n        @Override\n        public int clampViewPositionVertical(View child, int top, int dy) {\n            final int collapsedTop = computePanelTopPosition(0.f);\n            final int expandedTop = computePanelTopPosition(1.0f);\n            if (mIsSlidingUp) {\n                return Math.min(Math.max(top, expandedTop), collapsedTop);\n            } else {\n                return Math.min(Math.max(top, collapsedTop), expandedTop);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/slidinguppanel/ViewDragHelper.java",
    "content": "/*\n * Copyright (C) 2013 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n\npackage com.naman14.timber.slidinguppanel;\n\nimport android.content.Context;\nimport androidx.core.view.MotionEventCompat;\nimport androidx.core.view.VelocityTrackerCompat;\nimport androidx.core.view.ViewCompat;\nimport androidx.core.widget.ScrollerCompat;\nimport android.view.MotionEvent;\nimport android.view.VelocityTracker;\nimport android.view.View;\nimport android.view.ViewConfiguration;\nimport android.view.ViewGroup;\nimport android.view.animation.Interpolator;\n\nimport java.util.Arrays;\n\n/**\n * ViewDragHelper is a utility class for writing custom ViewGroups. It offers a number\n * of useful operations and state tracking for allowing a user to drag and reposition\n * views within their parent ViewGroup.\n */\npublic class ViewDragHelper {\n    /**\n     * A null/invalid pointer ID.\n     */\n    public static final int INVALID_POINTER = -1;\n    /**\n     * A view is not currently being dragged or animating as a result of a fling/snap.\n     */\n    public static final int STATE_IDLE = 0;\n    /**\n     * A view is currently being dragged. The position is currently changing as a result\n     * of user input or simulated user input.\n     */\n    public static final int STATE_DRAGGING = 1;\n    /**\n     * A view is currently settling into place as a result of a fling or\n     * predefined non-interactive motion.\n     */\n    public static final int STATE_SETTLING = 2;\n    /**\n     * Edge flag indicating that the left edge should be affected.\n     */\n    public static final int EDGE_LEFT = 1 << 0;\n    /**\n     * Edge flag indicating that the right edge should be affected.\n     */\n    public static final int EDGE_RIGHT = 1 << 1;\n    /**\n     * Edge flag indicating that the top edge should be affected.\n     */\n    public static final int EDGE_TOP = 1 << 2;\n    /**\n     * Edge flag indicating that the bottom edge should be affected.\n     */\n    public static final int EDGE_BOTTOM = 1 << 3;\n    /**\n     * Edge flag set indicating all edges should be affected.\n     */\n    public static final int EDGE_ALL = EDGE_LEFT | EDGE_TOP | EDGE_RIGHT | EDGE_BOTTOM;\n    /**\n     * Indicates that a check should occur along the horizontal axis\n     */\n    public static final int DIRECTION_HORIZONTAL = 1 << 0;\n    /**\n     * Indicates that a check should occur along the vertical axis\n     */\n    public static final int DIRECTION_VERTICAL = 1 << 1;\n    /**\n     * Indicates that a check should occur along all axes\n     */\n    public static final int DIRECTION_ALL = DIRECTION_HORIZONTAL | DIRECTION_VERTICAL;\n    private static final String TAG = \"ViewDragHelper\";\n    private static final int EDGE_SIZE = 20; // dp\n\n    private static final int BASE_SETTLE_DURATION = 256; // ms\n    private static final int MAX_SETTLE_DURATION = 600; // ms\n    /**\n     * Interpolator defining the animation curve for mScroller\n     */\n    private static final Interpolator sInterpolator = new Interpolator() {\n        public float getInterpolation(float t) {\n            t -= 1.0f;\n            return t * t * t * t * t + 1.0f;\n        }\n    };\n    private final Callback mCallback;\n    private final ViewGroup mParentView;\n    // Current drag state; idle, dragging or settling\n    private int mDragState;\n    // Distance to travel before a drag may begin\n    private int mTouchSlop;\n    // Last known position/pointer tracking\n    private int mActivePointerId = INVALID_POINTER;\n    private float[] mInitialMotionX;\n    private float[] mInitialMotionY;\n    private float[] mLastMotionX;\n    private float[] mLastMotionY;\n    private int[] mInitialEdgesTouched;\n    private int[] mEdgeDragsInProgress;\n    private int[] mEdgeDragsLocked;\n    private int mPointersDown;\n    private VelocityTracker mVelocityTracker;\n    private float mMaxVelocity;\n    private float mMinVelocity;\n    private int mEdgeSize;\n    private int mTrackingEdges;\n    private ScrollerCompat mScroller;\n    private View mCapturedView;\n    private final Runnable mSetIdleRunnable = new Runnable() {\n        public void run() {\n            setDragState(STATE_IDLE);\n        }\n    };\n    private boolean mReleaseInProgress;\n\n    /**\n     * Apps should use ViewDragHelper.create() to get a new instance.\n     * This will allow VDH to use internal compatibility implementations for different\n     * platform versions.\n     *\n     * @param context   Context to initialize config-dependent params from\n     * @param forParent Parent view to monitor\n     */\n    private ViewDragHelper(Context context, ViewGroup forParent, Callback cb) {\n        if (forParent == null) {\n            throw new IllegalArgumentException(\"Parent view may not be null\");\n        }\n        if (cb == null) {\n            throw new IllegalArgumentException(\"Callback may not be null\");\n        }\n\n        mParentView = forParent;\n        mCallback = cb;\n\n        final ViewConfiguration vc = ViewConfiguration.get(context);\n        final float density = context.getResources().getDisplayMetrics().density;\n        mEdgeSize = (int) (EDGE_SIZE * density + 0.5f);\n\n        mTouchSlop = vc.getScaledTouchSlop();\n        mMaxVelocity = vc.getScaledMaximumFlingVelocity();\n        mMinVelocity = vc.getScaledMinimumFlingVelocity();\n        mScroller = ScrollerCompat.create(context, sInterpolator);\n    }\n\n    /**\n     * Factory method to create a new ViewDragHelper.\n     *\n     * @param forParent Parent view to monitor\n     * @param cb        Callback to provide information and receive events\n     * @return a new ViewDragHelper instance\n     */\n    public static ViewDragHelper create(ViewGroup forParent, Callback cb) {\n        return new ViewDragHelper(forParent.getContext(), forParent, cb);\n    }\n\n    /**\n     * Factory method to create a new ViewDragHelper.\n     *\n     * @param forParent   Parent view to monitor\n     * @param sensitivity Multiplier for how sensitive the helper should be about detecting\n     *                    the start of a drag. Larger values are more sensitive. 1.0f is normal.\n     * @param cb          Callback to provide information and receive events\n     * @return a new ViewDragHelper instance\n     */\n    public static ViewDragHelper create(ViewGroup forParent, float sensitivity, Callback cb) {\n        final ViewDragHelper helper = create(forParent, cb);\n        helper.mTouchSlop = (int) (helper.mTouchSlop * (1 / sensitivity));\n        return helper;\n    }\n\n    /**\n     * Return the currently configured minimum velocity. Any flings with a magnitude less\n     * than this value in pixels per second. Callback methods accepting a velocity will receive\n     * zero as a velocity value if the real detected velocity was below this threshold.\n     *\n     * @return the minimum velocity that will be detected\n     */\n    public float getMinVelocity() {\n        return mMinVelocity;\n    }\n\n    /**\n     * Set the minimum velocity that will be detected as having a magnitude greater than zero\n     * in pixels per second. Callback methods accepting a velocity will be clamped appropriately.\n     *\n     * @param minVel Minimum velocity to detect\n     */\n    public void setMinVelocity(float minVel) {\n        mMinVelocity = minVel;\n    }\n\n    /**\n     * Retrieve the current drag state of this helper. This will return one of\n     * {@link #STATE_IDLE}, {@link #STATE_DRAGGING} or {@link #STATE_SETTLING}.\n     *\n     * @return The current drag state\n     */\n    public int getViewDragState() {\n        return mDragState;\n    }\n\n    /**\n     * Enable edge tracking for the selected edges of the parent view.\n     * The callback's {@link Callback#onEdgeTouched(int, int)} and\n     * {@link Callback#onEdgeDragStarted(int, int)} methods will only be invoked\n     * for edges for which edge tracking has been enabled.\n     *\n     * @param edgeFlags Combination of edge flags describing the edges to watch\n     * @see #EDGE_LEFT\n     * @see #EDGE_TOP\n     * @see #EDGE_RIGHT\n     * @see #EDGE_BOTTOM\n     */\n    public void setEdgeTrackingEnabled(int edgeFlags) {\n        mTrackingEdges = edgeFlags;\n    }\n\n    /**\n     * Return the size of an edge. This is the range in pixels along the edges of this view\n     * that will actively detect edge touches or drags if edge tracking is enabled.\n     *\n     * @return The size of an edge in pixels\n     * @see #setEdgeTrackingEnabled(int)\n     */\n    public int getEdgeSize() {\n        return mEdgeSize;\n    }\n\n    /**\n     * Capture a specific child view for dragging within the parent. The callback will be notified\n     * but {@link Callback#tryCaptureView(android.view.View, int)} will not be asked permission to\n     * capture this view.\n     *\n     * @param childView       Child view to capture\n     * @param activePointerId ID of the pointer that is dragging the captured child view\n     */\n    public void captureChildView(View childView, int activePointerId) {\n        if (childView.getParent() != mParentView) {\n            throw new IllegalArgumentException(\"captureChildView: parameter must be a descendant \" +\n                    \"of the ViewDragHelper's tracked parent view (\" + mParentView + \")\");\n        }\n\n        mCapturedView = childView;\n        mActivePointerId = activePointerId;\n        mCallback.onViewCaptured(childView, activePointerId);\n        setDragState(STATE_DRAGGING);\n    }\n\n    /**\n     * @return The currently captured view, or null if no view has been captured.\n     */\n    public View getCapturedView() {\n        return mCapturedView;\n    }\n\n    /**\n     * @return The ID of the pointer currently dragging the captured view,\n     * or {@link #INVALID_POINTER}.\n     */\n    public int getActivePointerId() {\n        return mActivePointerId;\n    }\n\n    /**\n     * @return The minimum distance in pixels that the user must travel to initiate a drag\n     */\n    public int getTouchSlop() {\n        return mTouchSlop;\n    }\n\n    /**\n     * The result of a call to this method is equivalent to\n     * {@link #processTouchEvent(android.view.MotionEvent)} receiving an ACTION_CANCEL event.\n     */\n    public void cancel() {\n        mActivePointerId = INVALID_POINTER;\n        clearMotionHistory();\n\n        if (mVelocityTracker != null) {\n            mVelocityTracker.recycle();\n            mVelocityTracker = null;\n        }\n    }\n\n    /**\n     * {@link #cancel()}, but also abort all motion in progress and snap to the end of any\n     * animation.\n     */\n    public void abort() {\n        cancel();\n        if (mDragState == STATE_SETTLING) {\n            final int oldX = mScroller.getCurrX();\n            final int oldY = mScroller.getCurrY();\n            mScroller.abortAnimation();\n            final int newX = mScroller.getCurrX();\n            final int newY = mScroller.getCurrY();\n            mCallback.onViewPositionChanged(mCapturedView, newX, newY, newX - oldX, newY - oldY);\n        }\n        setDragState(STATE_IDLE);\n    }\n\n    /**\n     * Animate the view <code>child</code> to the given (left, top) position.\n     * If this method returns true, the caller should invoke {@link #continueSettling(boolean)}\n     * on each subsequent frame to continue the motion until it returns false. If this method\n     * returns false there is no further work to do to complete the movement.\n     * <p/>\n     * <p>This operation does not count as a capture event, though {@link #getCapturedView()}\n     * will still report the sliding view while the slide is in progress.</p>\n     *\n     * @param child     Child view to capture and animate\n     * @param finalLeft Final left position of child\n     * @param finalTop  Final top position of child\n     * @return true if animation should continue through {@link #continueSettling(boolean)} calls\n     */\n    public boolean smoothSlideViewTo(View child, int finalLeft, int finalTop) {\n        mCapturedView = child;\n        mActivePointerId = INVALID_POINTER;\n\n        return forceSettleCapturedViewAt(finalLeft, finalTop, 0, 0);\n    }\n\n    /**\n     * Settle the captured view at the given (left, top) position.\n     * The appropriate velocity from prior motion will be taken into account.\n     * If this method returns true, the caller should invoke {@link #continueSettling(boolean)}\n     * on each subsequent frame to continue the motion until it returns false. If this method\n     * returns false there is no further work to do to complete the movement.\n     *\n     * @param finalLeft Settled left edge position for the captured view\n     * @param finalTop  Settled top edge position for the captured view\n     * @return true if animation should continue through {@link #continueSettling(boolean)} calls\n     */\n    public boolean settleCapturedViewAt(int finalLeft, int finalTop) {\n        if (!mReleaseInProgress) {\n            throw new IllegalStateException(\"Cannot settleCapturedViewAt outside of a call to \" +\n                    \"Callback#onViewReleased\");\n        }\n\n        return forceSettleCapturedViewAt(finalLeft, finalTop,\n                (int) VelocityTrackerCompat.getXVelocity(mVelocityTracker, mActivePointerId),\n                (int) VelocityTrackerCompat.getYVelocity(mVelocityTracker, mActivePointerId));\n    }\n\n    /**\n     * Settle the captured view at the given (left, top) position.\n     *\n     * @param finalLeft Target left position for the captured view\n     * @param finalTop  Target top position for the captured view\n     * @param xvel      Horizontal velocity\n     * @param yvel      Vertical velocity\n     * @return true if animation should continue through {@link #continueSettling(boolean)} calls\n     */\n    private boolean forceSettleCapturedViewAt(int finalLeft, int finalTop, int xvel, int yvel) {\n        final int startLeft = mCapturedView.getLeft();\n        final int startTop = mCapturedView.getTop();\n        final int dx = finalLeft - startLeft;\n        final int dy = finalTop - startTop;\n\n        if (dx == 0 && dy == 0) {\n            // Nothing to do. Send callbacks, be done.\n            mScroller.abortAnimation();\n            setDragState(STATE_IDLE);\n            return false;\n        }\n\n        final int duration = computeSettleDuration(mCapturedView, dx, dy, xvel, yvel);\n        mScroller.startScroll(startLeft, startTop, dx, dy, duration);\n\n        setDragState(STATE_SETTLING);\n        return true;\n    }\n\n    private int computeSettleDuration(View child, int dx, int dy, int xvel, int yvel) {\n        xvel = clampMag(xvel, (int) mMinVelocity, (int) mMaxVelocity);\n        yvel = clampMag(yvel, (int) mMinVelocity, (int) mMaxVelocity);\n        final int absDx = Math.abs(dx);\n        final int absDy = Math.abs(dy);\n        final int absXVel = Math.abs(xvel);\n        final int absYVel = Math.abs(yvel);\n        final int addedVel = absXVel + absYVel;\n        final int addedDistance = absDx + absDy;\n\n        final float xweight = xvel != 0 ? (float) absXVel / addedVel :\n                (float) absDx / addedDistance;\n        final float yweight = yvel != 0 ? (float) absYVel / addedVel :\n                (float) absDy / addedDistance;\n\n        int xduration = computeAxisDuration(dx, xvel, mCallback.getViewHorizontalDragRange(child));\n        int yduration = computeAxisDuration(dy, yvel, mCallback.getViewVerticalDragRange(child));\n\n        return (int) (xduration * xweight + yduration * yweight);\n    }\n\n    private int computeAxisDuration(int delta, int velocity, int motionRange) {\n        if (delta == 0) {\n            return 0;\n        }\n\n        final int width = mParentView.getWidth();\n        final int halfWidth = width / 2;\n        final float distanceRatio = Math.min(1f, (float) Math.abs(delta) / width);\n        final float distance = halfWidth + halfWidth *\n                distanceInfluenceForSnapDuration(distanceRatio);\n\n        int duration;\n        velocity = Math.abs(velocity);\n        if (velocity > 0) {\n            duration = 4 * Math.round(1000 * Math.abs(distance / velocity));\n        } else {\n            final float range = (float) Math.abs(delta) / motionRange;\n            duration = (int) ((range + 1) * BASE_SETTLE_DURATION);\n        }\n        return Math.min(duration, MAX_SETTLE_DURATION);\n    }\n\n    /**\n     * Clamp the magnitude of value for absMin and absMax.\n     * If the value is below the minimum, it will be clamped to zero.\n     * If the value is above the maximum, it will be clamped to the maximum.\n     *\n     * @param value  Value to clamp\n     * @param absMin Absolute value of the minimum significant value to return\n     * @param absMax Absolute value of the maximum value to return\n     * @return The clamped value with the same sign as <code>value</code>\n     */\n    private int clampMag(int value, int absMin, int absMax) {\n        final int absValue = Math.abs(value);\n        if (absValue < absMin) return 0;\n        if (absValue > absMax) return value > 0 ? absMax : -absMax;\n        return value;\n    }\n\n    /**\n     * Clamp the magnitude of value for absMin and absMax.\n     * If the value is below the minimum, it will be clamped to zero.\n     * If the value is above the maximum, it will be clamped to the maximum.\n     *\n     * @param value  Value to clamp\n     * @param absMin Absolute value of the minimum significant value to return\n     * @param absMax Absolute value of the maximum value to return\n     * @return The clamped value with the same sign as <code>value</code>\n     */\n    private float clampMag(float value, float absMin, float absMax) {\n        final float absValue = Math.abs(value);\n        if (absValue < absMin) return 0;\n        if (absValue > absMax) return value > 0 ? absMax : -absMax;\n        return value;\n    }\n\n    private float distanceInfluenceForSnapDuration(float f) {\n        f -= 0.5f; // center the values about 0.\n        f *= 0.3f * Math.PI / 2.0f;\n        return (float) Math.sin(f);\n    }\n\n    /**\n     * Settle the captured view based on standard free-moving fling behavior.\n     * The caller should invoke {@link #continueSettling(boolean)} on each subsequent frame\n     * to continue the motion until it returns false.\n     *\n     * @param minLeft Minimum X position for the view's left edge\n     * @param minTop  Minimum Y position for the view's top edge\n     * @param maxLeft Maximum X position for the view's left edge\n     * @param maxTop  Maximum Y position for the view's top edge\n     */\n    public void flingCapturedView(int minLeft, int minTop, int maxLeft, int maxTop) {\n        if (!mReleaseInProgress) {\n            throw new IllegalStateException(\"Cannot flingCapturedView outside of a call to \" +\n                    \"Callback#onViewReleased\");\n        }\n\n        mScroller.fling(mCapturedView.getLeft(), mCapturedView.getTop(),\n                (int) VelocityTrackerCompat.getXVelocity(mVelocityTracker, mActivePointerId),\n                (int) VelocityTrackerCompat.getYVelocity(mVelocityTracker, mActivePointerId),\n                minLeft, maxLeft, minTop, maxTop);\n\n        setDragState(STATE_SETTLING);\n    }\n\n    /**\n     * Move the captured settling view by the appropriate amount for the current time.\n     * If <code>continueSettling</code> returns true, the caller should call it again\n     * on the next frame to continue.\n     *\n     * @param deferCallbacks true if state callbacks should be deferred via posted message.\n     *                       Set this to true if you are calling this method from\n     *                       {@link android.view.View#computeScroll()} or similar methods\n     *                       invoked as part of layout or drawing.\n     * @return true if settle is still in progress\n     */\n    public boolean continueSettling(boolean deferCallbacks) {\n        if (mDragState == STATE_SETTLING) {\n            boolean keepGoing = mScroller.computeScrollOffset();\n            final int x = mScroller.getCurrX();\n            final int y = mScroller.getCurrY();\n            final int dx = x - mCapturedView.getLeft();\n            final int dy = y - mCapturedView.getTop();\n\n            if (dx != 0) {\n                mCapturedView.offsetLeftAndRight(dx);\n            }\n            if (dy != 0) {\n                mCapturedView.offsetTopAndBottom(dy);\n            }\n\n            if (dx != 0 || dy != 0) {\n                mCallback.onViewPositionChanged(mCapturedView, x, y, dx, dy);\n            }\n\n            if (keepGoing && x == mScroller.getFinalX() && y == mScroller.getFinalY()) {\n                // Close enough. The interpolator/scroller might think we're still moving\n                // but the user sure doesn't.\n                mScroller.abortAnimation();\n                keepGoing = mScroller.isFinished();\n            }\n\n            if (!keepGoing) {\n                if (deferCallbacks) {\n                    mParentView.post(mSetIdleRunnable);\n                } else {\n                    setDragState(STATE_IDLE);\n                }\n            }\n        }\n\n        return mDragState == STATE_SETTLING;\n    }\n\n    /**\n     * Like all callback events this must happen on the UI thread, but release\n     * involves some extra semantics. During a release (mReleaseInProgress)\n     * is the only time it is valid to call {@link #settleCapturedViewAt(int, int)}\n     * or {@link #flingCapturedView(int, int, int, int)}.\n     */\n    private void dispatchViewReleased(float xvel, float yvel) {\n        mReleaseInProgress = true;\n        mCallback.onViewReleased(mCapturedView, xvel, yvel);\n        mReleaseInProgress = false;\n\n        if (mDragState == STATE_DRAGGING) {\n            // onViewReleased didn't call a method that would have changed this. Go idle.\n            setDragState(STATE_IDLE);\n        }\n    }\n\n    private void clearMotionHistory() {\n        if (mInitialMotionX == null) {\n            return;\n        }\n        Arrays.fill(mInitialMotionX, 0);\n        Arrays.fill(mInitialMotionY, 0);\n        Arrays.fill(mLastMotionX, 0);\n        Arrays.fill(mLastMotionY, 0);\n        Arrays.fill(mInitialEdgesTouched, 0);\n        Arrays.fill(mEdgeDragsInProgress, 0);\n        Arrays.fill(mEdgeDragsLocked, 0);\n        mPointersDown = 0;\n    }\n\n    private void clearMotionHistory(int pointerId) {\n        if (mInitialMotionX == null) {\n            return;\n        }\n        mInitialMotionX[pointerId] = 0;\n        mInitialMotionY[pointerId] = 0;\n        mLastMotionX[pointerId] = 0;\n        mLastMotionY[pointerId] = 0;\n        mInitialEdgesTouched[pointerId] = 0;\n        mEdgeDragsInProgress[pointerId] = 0;\n        mEdgeDragsLocked[pointerId] = 0;\n        mPointersDown &= ~(1 << pointerId);\n    }\n\n    private void ensureMotionHistorySizeForId(int pointerId) {\n        if (mInitialMotionX == null || mInitialMotionX.length <= pointerId) {\n            float[] imx = new float[pointerId + 1];\n            float[] imy = new float[pointerId + 1];\n            float[] lmx = new float[pointerId + 1];\n            float[] lmy = new float[pointerId + 1];\n            int[] iit = new int[pointerId + 1];\n            int[] edip = new int[pointerId + 1];\n            int[] edl = new int[pointerId + 1];\n\n            if (mInitialMotionX != null) {\n                System.arraycopy(mInitialMotionX, 0, imx, 0, mInitialMotionX.length);\n                System.arraycopy(mInitialMotionY, 0, imy, 0, mInitialMotionY.length);\n                System.arraycopy(mLastMotionX, 0, lmx, 0, mLastMotionX.length);\n                System.arraycopy(mLastMotionY, 0, lmy, 0, mLastMotionY.length);\n                System.arraycopy(mInitialEdgesTouched, 0, iit, 0, mInitialEdgesTouched.length);\n                System.arraycopy(mEdgeDragsInProgress, 0, edip, 0, mEdgeDragsInProgress.length);\n                System.arraycopy(mEdgeDragsLocked, 0, edl, 0, mEdgeDragsLocked.length);\n            }\n\n            mInitialMotionX = imx;\n            mInitialMotionY = imy;\n            mLastMotionX = lmx;\n            mLastMotionY = lmy;\n            mInitialEdgesTouched = iit;\n            mEdgeDragsInProgress = edip;\n            mEdgeDragsLocked = edl;\n        }\n    }\n\n    private void saveInitialMotion(float x, float y, int pointerId) {\n        ensureMotionHistorySizeForId(pointerId);\n        mInitialMotionX[pointerId] = mLastMotionX[pointerId] = x;\n        mInitialMotionY[pointerId] = mLastMotionY[pointerId] = y;\n        mInitialEdgesTouched[pointerId] = getEdgesTouched((int) x, (int) y);\n        mPointersDown |= 1 << pointerId;\n    }\n\n    private void saveLastMotion(MotionEvent ev) {\n        final int pointerCount = MotionEventCompat.getPointerCount(ev);\n        for (int i = 0; i < pointerCount; i++) {\n            final int pointerId = MotionEventCompat.getPointerId(ev, i);\n            final float x = MotionEventCompat.getX(ev, i);\n            final float y = MotionEventCompat.getY(ev, i);\n            mLastMotionX[pointerId] = x;\n            mLastMotionY[pointerId] = y;\n        }\n    }\n\n    /**\n     * Check if the given pointer ID represents a pointer that is currently down (to the best\n     * of the ViewDragHelper's knowledge).\n     * <p/>\n     * <p>The state used to report this information is populated by the methods\n     * {@link #shouldInterceptTouchEvent(android.view.MotionEvent)} or\n     * {@link #processTouchEvent(android.view.MotionEvent)}. If one of these methods has not\n     * been called for all relevant MotionEvents to track, the information reported\n     * by this method may be stale or incorrect.</p>\n     *\n     * @param pointerId pointer ID to check; corresponds to IDs provided by MotionEvent\n     * @return true if the pointer with the given ID is still down\n     */\n    public boolean isPointerDown(int pointerId) {\n        return (mPointersDown & 1 << pointerId) != 0;\n    }\n\n    void setDragState(int state) {\n        if (mDragState != state) {\n            mDragState = state;\n            mCallback.onViewDragStateChanged(state);\n            if (state == STATE_IDLE) {\n                mCapturedView = null;\n            }\n        }\n    }\n\n    /**\n     * Attempt to capture the view with the given pointer ID. The callback will be involved.\n     * This will put us into the \"dragging\" state. If we've already captured this view with\n     * this pointer this method will immediately return true without consulting the callback.\n     *\n     * @param toCapture View to capture\n     * @param pointerId Pointer to capture with\n     * @return true if capture was successful\n     */\n    boolean tryCaptureViewForDrag(View toCapture, int pointerId) {\n        if (toCapture == mCapturedView && mActivePointerId == pointerId) {\n            // Already done!\n            return true;\n        }\n        if (toCapture != null && mCallback.tryCaptureView(toCapture, pointerId)) {\n            mActivePointerId = pointerId;\n            captureChildView(toCapture, pointerId);\n            return true;\n        }\n        return false;\n    }\n\n    /**\n     * Tests scrollability within child views of v given a delta of dx.\n     *\n     * @param v      View to test for horizontal scrollability\n     * @param checkV Whether the view v passed should itself be checked for scrollability (true),\n     *               or just its children (false).\n     * @param dx     Delta scrolled in pixels along the X axis\n     * @param dy     Delta scrolled in pixels along the Y axis\n     * @param x      X coordinate of the active touch point\n     * @param y      Y coordinate of the active touch point\n     * @return true if child views of v can be scrolled by delta of dx.\n     */\n    protected boolean canScroll(View v, boolean checkV, int dx, int dy, int x, int y) {\n        if (v instanceof ViewGroup) {\n            final ViewGroup group = (ViewGroup) v;\n            final int scrollX = v.getScrollX();\n            final int scrollY = v.getScrollY();\n            final int count = group.getChildCount();\n            // Count backwards - let topmost views consume scroll distance first.\n            for (int i = count - 1; i >= 0; i--) {\n                // TODO: Add versioned support here for transformed views.\n                // This will not work for transformed views in Honeycomb+\n                final View child = group.getChildAt(i);\n                if (x + scrollX >= child.getLeft() && x + scrollX < child.getRight() &&\n                        y + scrollY >= child.getTop() && y + scrollY < child.getBottom() &&\n                        canScroll(child, true, dx, dy, x + scrollX - child.getLeft(),\n                                y + scrollY - child.getTop())) {\n                    return true;\n                }\n            }\n        }\n\n        return checkV && (ViewCompat.canScrollHorizontally(v, -dx) ||\n                ViewCompat.canScrollVertically(v, -dy));\n    }\n\n    /**\n     * Check if this event as provided to the parent view's onInterceptTouchEvent should\n     * cause the parent to intercept the touch event stream.\n     *\n     * @param ev MotionEvent provided to onInterceptTouchEvent\n     * @return true if the parent view should return true from onInterceptTouchEvent\n     */\n    public boolean shouldInterceptTouchEvent(MotionEvent ev) {\n        final int action = MotionEventCompat.getActionMasked(ev);\n        final int actionIndex = MotionEventCompat.getActionIndex(ev);\n\n        if (action == MotionEvent.ACTION_DOWN) {\n            // Reset things for a new event stream, just in case we didn't get\n            // the whole previous stream.\n            cancel();\n        }\n\n        if (mVelocityTracker == null) {\n            mVelocityTracker = VelocityTracker.obtain();\n        }\n        mVelocityTracker.addMovement(ev);\n\n        switch (action) {\n            case MotionEvent.ACTION_DOWN: {\n                final float x = ev.getX();\n                final float y = ev.getY();\n                final int pointerId = MotionEventCompat.getPointerId(ev, 0);\n                saveInitialMotion(x, y, pointerId);\n\n                final View toCapture = findTopChildUnder((int) x, (int) y);\n\n                // Catch a settling view if possible.\n                if (toCapture == mCapturedView && mDragState == STATE_SETTLING) {\n                    tryCaptureViewForDrag(toCapture, pointerId);\n                }\n\n                final int edgesTouched = mInitialEdgesTouched[pointerId];\n                if ((edgesTouched & mTrackingEdges) != 0) {\n                    mCallback.onEdgeTouched(edgesTouched & mTrackingEdges, pointerId);\n                }\n                break;\n            }\n\n            case MotionEventCompat.ACTION_POINTER_DOWN: {\n                final int pointerId = MotionEventCompat.getPointerId(ev, actionIndex);\n                final float x = MotionEventCompat.getX(ev, actionIndex);\n                final float y = MotionEventCompat.getY(ev, actionIndex);\n\n                saveInitialMotion(x, y, pointerId);\n\n                // A ViewDragHelper can only manipulate one view at a time.\n                if (mDragState == STATE_IDLE) {\n                    final int edgesTouched = mInitialEdgesTouched[pointerId];\n                    if ((edgesTouched & mTrackingEdges) != 0) {\n                        mCallback.onEdgeTouched(edgesTouched & mTrackingEdges, pointerId);\n                    }\n                } else if (mDragState == STATE_SETTLING) {\n                    // Catch a settling view if possible.\n                    final View toCapture = findTopChildUnder((int) x, (int) y);\n                    if (toCapture == mCapturedView) {\n                        tryCaptureViewForDrag(toCapture, pointerId);\n                    }\n                }\n                break;\n            }\n\n            case MotionEvent.ACTION_MOVE: {\n                // First to cross a touch slop over a draggable view wins. Also report edge drags.\n                final int pointerCount = MotionEventCompat.getPointerCount(ev);\n                for (int i = 0; i < pointerCount && mInitialMotionX != null && mInitialMotionY != null; i++) {\n                    final int pointerId = MotionEventCompat.getPointerId(ev, i);\n                    final float x = MotionEventCompat.getX(ev, i);\n                    final float y = MotionEventCompat.getY(ev, i);\n                    final float dx = x - mInitialMotionX[pointerId];\n                    final float dy = y - mInitialMotionY[pointerId];\n\n                    reportNewEdgeDrags(dx, dy, pointerId);\n                    if (mDragState == STATE_DRAGGING) {\n                        // Callback might have started an edge drag\n                        break;\n                    }\n\n                    final View toCapture = findTopChildUnder((int) mInitialMotionX[pointerId], (int) mInitialMotionY[pointerId]);\n                    if (toCapture != null && checkTouchSlop(toCapture, dx, dy) &&\n                            tryCaptureViewForDrag(toCapture, pointerId)) {\n                        break;\n                    }\n                }\n                saveLastMotion(ev);\n                break;\n            }\n\n            case MotionEventCompat.ACTION_POINTER_UP: {\n                final int pointerId = MotionEventCompat.getPointerId(ev, actionIndex);\n                clearMotionHistory(pointerId);\n                break;\n            }\n\n            case MotionEvent.ACTION_UP:\n            case MotionEvent.ACTION_CANCEL: {\n                cancel();\n                break;\n            }\n        }\n\n        return mDragState == STATE_DRAGGING;\n    }\n\n    /**\n     * Process a touch event received by the parent view. This method will dispatch callback events\n     * as needed before returning. The parent view's onTouchEvent implementation should call this.\n     *\n     * @param ev The touch event received by the parent view\n     */\n    public void processTouchEvent(MotionEvent ev) {\n        final int action = MotionEventCompat.getActionMasked(ev);\n        final int actionIndex = MotionEventCompat.getActionIndex(ev);\n\n        if (action == MotionEvent.ACTION_DOWN) {\n            // Reset things for a new event stream, just in case we didn't get\n            // the whole previous stream.\n            cancel();\n        }\n\n        if (mVelocityTracker == null) {\n            mVelocityTracker = VelocityTracker.obtain();\n        }\n        mVelocityTracker.addMovement(ev);\n\n        switch (action) {\n            case MotionEvent.ACTION_DOWN: {\n                final float x = ev.getX();\n                final float y = ev.getY();\n                final int pointerId = MotionEventCompat.getPointerId(ev, 0);\n                final View toCapture = findTopChildUnder((int) x, (int) y);\n\n                saveInitialMotion(x, y, pointerId);\n\n                // Since the parent is already directly processing this touch event,\n                // there is no reason to delay for a slop before dragging.\n                // Start immediately if possible.\n                tryCaptureViewForDrag(toCapture, pointerId);\n\n                final int edgesTouched = mInitialEdgesTouched[pointerId];\n                if ((edgesTouched & mTrackingEdges) != 0) {\n                    mCallback.onEdgeTouched(edgesTouched & mTrackingEdges, pointerId);\n                }\n                break;\n            }\n\n            case MotionEventCompat.ACTION_POINTER_DOWN: {\n                final int pointerId = MotionEventCompat.getPointerId(ev, actionIndex);\n                final float x = MotionEventCompat.getX(ev, actionIndex);\n                final float y = MotionEventCompat.getY(ev, actionIndex);\n\n                saveInitialMotion(x, y, pointerId);\n\n                // A ViewDragHelper can only manipulate one view at a time.\n                if (mDragState == STATE_IDLE) {\n                    // If we're idle we can do anything! Treat it like a normal down event.\n\n                    final View toCapture = findTopChildUnder((int) x, (int) y);\n                    tryCaptureViewForDrag(toCapture, pointerId);\n\n                    final int edgesTouched = mInitialEdgesTouched[pointerId];\n                    if ((edgesTouched & mTrackingEdges) != 0) {\n                        mCallback.onEdgeTouched(edgesTouched & mTrackingEdges, pointerId);\n                    }\n                } else if (isCapturedViewUnder((int) x, (int) y)) {\n                    // We're still tracking a captured view. If the same view is under this\n                    // point, we'll swap to controlling it with this pointer instead.\n                    // (This will still work if we're \"catching\" a settling view.)\n\n                    tryCaptureViewForDrag(mCapturedView, pointerId);\n                }\n                break;\n            }\n\n            case MotionEvent.ACTION_MOVE: {\n                if (mDragState == STATE_DRAGGING) {\n                    final int index = MotionEventCompat.findPointerIndex(ev, mActivePointerId);\n                    final float x = MotionEventCompat.getX(ev, index);\n                    final float y = MotionEventCompat.getY(ev, index);\n                    final int idx = (int) (x - mLastMotionX[mActivePointerId]);\n                    final int idy = (int) (y - mLastMotionY[mActivePointerId]);\n\n                    dragTo(mCapturedView.getLeft() + idx, mCapturedView.getTop() + idy, idx, idy);\n\n                    saveLastMotion(ev);\n                } else {\n                    // Check to see if any pointer is now over a draggable view.\n                    final int pointerCount = MotionEventCompat.getPointerCount(ev);\n                    for (int i = 0; i < pointerCount; i++) {\n                        final int pointerId = MotionEventCompat.getPointerId(ev, i);\n                        final float x = MotionEventCompat.getX(ev, i);\n                        final float y = MotionEventCompat.getY(ev, i);\n                        final float dx = x - mInitialMotionX[pointerId];\n                        final float dy = y - mInitialMotionY[pointerId];\n\n                        reportNewEdgeDrags(dx, dy, pointerId);\n                        if (mDragState == STATE_DRAGGING) {\n                            // Callback might have started an edge drag.\n                            break;\n                        }\n\n                        final View toCapture = findTopChildUnder((int) x, (int) y);\n                        if (checkTouchSlop(toCapture, dx, dy) &&\n                                tryCaptureViewForDrag(toCapture, pointerId)) {\n                            break;\n                        }\n                    }\n                    saveLastMotion(ev);\n                }\n                break;\n            }\n\n            case MotionEventCompat.ACTION_POINTER_UP: {\n                final int pointerId = MotionEventCompat.getPointerId(ev, actionIndex);\n                if (mDragState == STATE_DRAGGING && pointerId == mActivePointerId) {\n                    // Try to find another pointer that's still holding on to the captured view.\n                    int newActivePointer = INVALID_POINTER;\n                    final int pointerCount = MotionEventCompat.getPointerCount(ev);\n                    for (int i = 0; i < pointerCount; i++) {\n                        final int id = MotionEventCompat.getPointerId(ev, i);\n                        if (id == mActivePointerId) {\n                            // This one's going away, skip.\n                            continue;\n                        }\n\n                        final float x = MotionEventCompat.getX(ev, i);\n                        final float y = MotionEventCompat.getY(ev, i);\n                        if (findTopChildUnder((int) x, (int) y) == mCapturedView &&\n                                tryCaptureViewForDrag(mCapturedView, id)) {\n                            newActivePointer = mActivePointerId;\n                            break;\n                        }\n                    }\n\n                    if (newActivePointer == INVALID_POINTER) {\n                        // We didn't find another pointer still touching the view, release it.\n                        releaseViewForPointerUp();\n                    }\n                }\n                clearMotionHistory(pointerId);\n                break;\n            }\n\n            case MotionEvent.ACTION_UP: {\n                if (mDragState == STATE_DRAGGING) {\n                    releaseViewForPointerUp();\n                }\n                cancel();\n                break;\n            }\n\n            case MotionEvent.ACTION_CANCEL: {\n                if (mDragState == STATE_DRAGGING) {\n                    dispatchViewReleased(0, 0);\n                }\n                cancel();\n                break;\n            }\n        }\n    }\n\n    private void reportNewEdgeDrags(float dx, float dy, int pointerId) {\n        int dragsStarted = 0;\n        if (checkNewEdgeDrag(dx, dy, pointerId, EDGE_LEFT)) {\n            dragsStarted |= EDGE_LEFT;\n        }\n        if (checkNewEdgeDrag(dy, dx, pointerId, EDGE_TOP)) {\n            dragsStarted |= EDGE_TOP;\n        }\n        if (checkNewEdgeDrag(dx, dy, pointerId, EDGE_RIGHT)) {\n            dragsStarted |= EDGE_RIGHT;\n        }\n        if (checkNewEdgeDrag(dy, dx, pointerId, EDGE_BOTTOM)) {\n            dragsStarted |= EDGE_BOTTOM;\n        }\n\n        if (dragsStarted != 0) {\n            mEdgeDragsInProgress[pointerId] |= dragsStarted;\n            mCallback.onEdgeDragStarted(dragsStarted, pointerId);\n        }\n    }\n\n    private boolean checkNewEdgeDrag(float delta, float odelta, int pointerId, int edge) {\n        final float absDelta = Math.abs(delta);\n        final float absODelta = Math.abs(odelta);\n\n        if ((mInitialEdgesTouched[pointerId] & edge) != edge || (mTrackingEdges & edge) == 0 ||\n                (mEdgeDragsLocked[pointerId] & edge) == edge ||\n                (mEdgeDragsInProgress[pointerId] & edge) == edge ||\n                (absDelta <= mTouchSlop && absODelta <= mTouchSlop)) {\n            return false;\n        }\n        if (absDelta < absODelta * 0.5f && mCallback.onEdgeLock(edge)) {\n            mEdgeDragsLocked[pointerId] |= edge;\n            return false;\n        }\n        return (mEdgeDragsInProgress[pointerId] & edge) == 0 && absDelta > mTouchSlop;\n    }\n\n    /**\n     * Check if we've crossed a reasonable touch slop for the given child view.\n     * If the child cannot be dragged along the horizontal or vertical axis, motion\n     * along that axis will not count toward the slop check.\n     *\n     * @param child Child to check\n     * @param dx    Motion since initial position along X axis\n     * @param dy    Motion since initial position along Y axis\n     * @return true if the touch slop has been crossed\n     */\n    private boolean checkTouchSlop(View child, float dx, float dy) {\n        if (child == null) {\n            return false;\n        }\n        final boolean checkHorizontal = mCallback.getViewHorizontalDragRange(child) > 0;\n        final boolean checkVertical = mCallback.getViewVerticalDragRange(child) > 0;\n\n        if (checkHorizontal && checkVertical) {\n            return dx * dx + dy * dy > mTouchSlop * mTouchSlop;\n        } else if (checkHorizontal) {\n            return Math.abs(dx) > mTouchSlop;\n        } else if (checkVertical) {\n            return Math.abs(dy) > mTouchSlop;\n        }\n        return false;\n    }\n\n    /**\n     * Check if any pointer tracked in the current gesture has crossed\n     * the required slop threshold.\n     * <p/>\n     * <p>This depends on internal state populated by\n     * {@link #shouldInterceptTouchEvent(android.view.MotionEvent)} or\n     * {@link #processTouchEvent(android.view.MotionEvent)}. You should only rely on\n     * the results of this method after all currently available touch data\n     * has been provided to one of these two methods.</p>\n     *\n     * @param directions Combination of direction flags, see {@link #DIRECTION_HORIZONTAL},\n     *                   {@link #DIRECTION_VERTICAL}, {@link #DIRECTION_ALL}\n     * @return true if the slop threshold has been crossed, false otherwise\n     */\n    public boolean checkTouchSlop(int directions) {\n        final int count = mInitialMotionX.length;\n        for (int i = 0; i < count; i++) {\n            if (checkTouchSlop(directions, i)) {\n                return true;\n            }\n        }\n        return false;\n    }\n\n    /**\n     * Check if the specified pointer tracked in the current gesture has crossed\n     * the required slop threshold.\n     * <p/>\n     * <p>This depends on internal state populated by\n     * {@link #shouldInterceptTouchEvent(android.view.MotionEvent)} or\n     * {@link #processTouchEvent(android.view.MotionEvent)}. You should only rely on\n     * the results of this method after all currently available touch data\n     * has been provided to one of these two methods.</p>\n     *\n     * @param directions Combination of direction flags, see {@link #DIRECTION_HORIZONTAL},\n     *                   {@link #DIRECTION_VERTICAL}, {@link #DIRECTION_ALL}\n     * @param pointerId  ID of the pointer to slop check as specified by MotionEvent\n     * @return true if the slop threshold has been crossed, false otherwise\n     */\n    public boolean checkTouchSlop(int directions, int pointerId) {\n        if (!isPointerDown(pointerId)) {\n            return false;\n        }\n\n        final boolean checkHorizontal = (directions & DIRECTION_HORIZONTAL) == DIRECTION_HORIZONTAL;\n        final boolean checkVertical = (directions & DIRECTION_VERTICAL) == DIRECTION_VERTICAL;\n\n        final float dx = mLastMotionX[pointerId] - mInitialMotionX[pointerId];\n        final float dy = mLastMotionY[pointerId] - mInitialMotionY[pointerId];\n\n        if (checkHorizontal && checkVertical) {\n            return dx * dx + dy * dy > mTouchSlop * mTouchSlop;\n        } else if (checkHorizontal) {\n            return Math.abs(dx) > mTouchSlop;\n        } else if (checkVertical) {\n            return Math.abs(dy) > mTouchSlop;\n        }\n        return false;\n    }\n\n    /**\n     * Check if any of the edges specified were initially touched in the currently active gesture.\n     * If there is no currently active gesture this method will return false.\n     *\n     * @param edges Edges to check for an initial edge touch. See {@link #EDGE_LEFT},\n     *              {@link #EDGE_TOP}, {@link #EDGE_RIGHT}, {@link #EDGE_BOTTOM} and\n     *              {@link #EDGE_ALL}\n     * @return true if any of the edges specified were initially touched in the current gesture\n     */\n    public boolean isEdgeTouched(int edges) {\n        final int count = mInitialEdgesTouched.length;\n        for (int i = 0; i < count; i++) {\n            if (isEdgeTouched(edges, i)) {\n                return true;\n            }\n        }\n        return false;\n    }\n\n    /**\n     * Check if any of the edges specified were initially touched by the pointer with\n     * the specified ID. If there is no currently active gesture or if there is no pointer with\n     * the given ID currently down this method will return false.\n     *\n     * @param edges Edges to check for an initial edge touch. See {@link #EDGE_LEFT},\n     *              {@link #EDGE_TOP}, {@link #EDGE_RIGHT}, {@link #EDGE_BOTTOM} and\n     *              {@link #EDGE_ALL}\n     * @return true if any of the edges specified were initially touched in the current gesture\n     */\n    public boolean isEdgeTouched(int edges, int pointerId) {\n        return isPointerDown(pointerId) && (mInitialEdgesTouched[pointerId] & edges) != 0;\n    }\n\n    private void releaseViewForPointerUp() {\n        mVelocityTracker.computeCurrentVelocity(1000, mMaxVelocity);\n        final float xvel = clampMag(\n                VelocityTrackerCompat.getXVelocity(mVelocityTracker, mActivePointerId),\n                mMinVelocity, mMaxVelocity);\n        final float yvel = clampMag(\n                VelocityTrackerCompat.getYVelocity(mVelocityTracker, mActivePointerId),\n                mMinVelocity, mMaxVelocity);\n        dispatchViewReleased(xvel, yvel);\n    }\n\n    private void dragTo(int left, int top, int dx, int dy) {\n        int clampedX = left;\n        int clampedY = top;\n        final int oldLeft = mCapturedView.getLeft();\n        final int oldTop = mCapturedView.getTop();\n        if (dx != 0) {\n            clampedX = mCallback.clampViewPositionHorizontal(mCapturedView, left, dx);\n            mCapturedView.offsetLeftAndRight(clampedX - oldLeft);\n        }\n        if (dy != 0) {\n            clampedY = mCallback.clampViewPositionVertical(mCapturedView, top, dy);\n            mCapturedView.offsetTopAndBottom(clampedY - oldTop);\n        }\n\n        if (dx != 0 || dy != 0) {\n            final int clampedDx = clampedX - oldLeft;\n            final int clampedDy = clampedY - oldTop;\n            mCallback.onViewPositionChanged(mCapturedView, clampedX, clampedY,\n                    clampedDx, clampedDy);\n        }\n    }\n\n    /**\n     * Determine if the currently captured view is under the given point in the\n     * parent view's coordinate system. If there is no captured view this method\n     * will return false.\n     *\n     * @param x X position to test in the parent's coordinate system\n     * @param y Y position to test in the parent's coordinate system\n     * @return true if the captured view is under the given point, false otherwise\n     */\n    public boolean isCapturedViewUnder(int x, int y) {\n        return isViewUnder(mCapturedView, x, y);\n    }\n\n    /**\n     * Determine if the supplied view is under the given point in the\n     * parent view's coordinate system.\n     *\n     * @param view Child view of the parent to hit test\n     * @param x    X position to test in the parent's coordinate system\n     * @param y    Y position to test in the parent's coordinate system\n     * @return true if the supplied view is under the given point, false otherwise\n     */\n    public boolean isViewUnder(View view, int x, int y) {\n        if (view == null) {\n            return false;\n        }\n        return x >= view.getLeft() &&\n                x < view.getRight() &&\n                y >= view.getTop() &&\n                y < view.getBottom();\n    }\n\n    /**\n     * Find the topmost child under the given point within the parent view's coordinate system.\n     * The child order is determined using {@link Callback#getOrderedChildIndex(int)}.\n     *\n     * @param x X position to test in the parent's coordinate system\n     * @param y Y position to test in the parent's coordinate system\n     * @return The topmost child view under (x, y) or null if none found.\n     */\n    public View findTopChildUnder(int x, int y) {\n        final int childCount = mParentView.getChildCount();\n        for (int i = childCount - 1; i >= 0; i--) {\n            final View child = mParentView.getChildAt(mCallback.getOrderedChildIndex(i));\n            if (x >= child.getLeft() && x < child.getRight() &&\n                    y >= child.getTop() && y < child.getBottom()) {\n                return child;\n            }\n        }\n        return null;\n    }\n\n    private int getEdgesTouched(int x, int y) {\n        int result = 0;\n\n        if (x < mParentView.getLeft() + mEdgeSize) result |= EDGE_LEFT;\n        if (y < mParentView.getTop() + mEdgeSize) result |= EDGE_TOP;\n        if (x > mParentView.getRight() - mEdgeSize) result |= EDGE_RIGHT;\n        if (y > mParentView.getBottom() - mEdgeSize) result |= EDGE_BOTTOM;\n\n        return result;\n    }\n\n    /**\n     * A Callback is used as a communication channel with the ViewDragHelper back to the\n     * parent view using it. <code>on*</code>methods are invoked on siginficant events and several\n     * accessor methods are expected to provide the ViewDragHelper with more information\n     * about the state of the parent view upon request. The callback also makes decisions\n     * governing the range and draggability of child views.\n     */\n    public static abstract class Callback {\n        /**\n         * Called when the drag state changes. See the <code>STATE_*</code> constants\n         * for more information.\n         *\n         * @param state The new drag state\n         * @see #STATE_IDLE\n         * @see #STATE_DRAGGING\n         * @see #STATE_SETTLING\n         */\n        public void onViewDragStateChanged(int state) {\n        }\n\n        /**\n         * Called when the captured view's position changes as the result of a drag or settle.\n         *\n         * @param changedView View whose position changed\n         * @param left        New X coordinate of the left edge of the view\n         * @param top         New Y coordinate of the top edge of the view\n         * @param dx          Change in X position from the last call\n         * @param dy          Change in Y position from the last call\n         */\n        public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {\n        }\n\n        /**\n         * Called when a child view is captured for dragging or settling. The ID of the pointer\n         * currently dragging the captured view is supplied. If activePointerId is\n         * identified as {@link #INVALID_POINTER} the capture is programmatic instead of\n         * pointer-initiated.\n         *\n         * @param capturedChild   Child view that was captured\n         * @param activePointerId Pointer id tracking the child capture\n         */\n        public void onViewCaptured(View capturedChild, int activePointerId) {\n        }\n\n        /**\n         * Called when the child view is no longer being actively dragged.\n         * The fling velocity is also supplied, if relevant. The velocity values may\n         * be clamped to system minimums or maximums.\n         * <p/>\n         * <p>Calling code may decide to fling or otherwise release the view to let it\n         * settle into place. It should do so using {@link #settleCapturedViewAt(int, int)}\n         * or {@link #flingCapturedView(int, int, int, int)}. If the Callback invokes\n         * one of these methods, the ViewDragHelper will enter {@link #STATE_SETTLING}\n         * and the view capture will not fully end until it comes to a complete stop.\n         * If neither of these methods is invoked before <code>onViewReleased</code> returns,\n         * the view will stop in place and the ViewDragHelper will return to\n         * {@link #STATE_IDLE}.</p>\n         *\n         * @param releasedChild The captured child view now being released\n         * @param xvel          X velocity of the pointer as it left the screen in pixels per second.\n         * @param yvel          Y velocity of the pointer as it left the screen in pixels per second.\n         */\n        public void onViewReleased(View releasedChild, float xvel, float yvel) {\n        }\n\n        /**\n         * Called when one of the subscribed edges in the parent view has been touched\n         * by the user while no child view is currently captured.\n         *\n         * @param edgeFlags A combination of edge flags describing the edge(s) currently touched\n         * @param pointerId ID of the pointer touching the described edge(s)\n         * @see #EDGE_LEFT\n         * @see #EDGE_TOP\n         * @see #EDGE_RIGHT\n         * @see #EDGE_BOTTOM\n         */\n        public void onEdgeTouched(int edgeFlags, int pointerId) {\n        }\n\n        /**\n         * Called when the given edge may become locked. This can happen if an edge drag\n         * was preliminarily rejected before beginning, but after {@link #onEdgeTouched(int, int)}\n         * was called. This method should return true to lock this edge or false to leave it\n         * unlocked. The default behavior is to leave edges unlocked.\n         *\n         * @param edgeFlags A combination of edge flags describing the edge(s) locked\n         * @return true to lock the edge, false to leave it unlocked\n         */\n        public boolean onEdgeLock(int edgeFlags) {\n            return false;\n        }\n\n        /**\n         * Called when the user has started a deliberate drag away from one\n         * of the subscribed edges in the parent view while no child view is currently captured.\n         *\n         * @param edgeFlags A combination of edge flags describing the edge(s) dragged\n         * @param pointerId ID of the pointer touching the described edge(s)\n         * @see #EDGE_LEFT\n         * @see #EDGE_TOP\n         * @see #EDGE_RIGHT\n         * @see #EDGE_BOTTOM\n         */\n        public void onEdgeDragStarted(int edgeFlags, int pointerId) {\n        }\n\n        /**\n         * Called to determine the Z-order of child views.\n         *\n         * @param index the ordered position to query for\n         * @return index of the view that should be ordered at position <code>index</code>\n         */\n        public int getOrderedChildIndex(int index) {\n            return index;\n        }\n\n        /**\n         * Return the magnitude of a draggable child view's horizontal range of motion in pixels.\n         * This method should return 0 for views that cannot move horizontally.\n         *\n         * @param child Child view to check\n         * @return range of horizontal motion in pixels\n         */\n        public int getViewHorizontalDragRange(View child) {\n            return 0;\n        }\n\n        /**\n         * Return the magnitude of a draggable child view's vertical range of motion in pixels.\n         * This method should return 0 for views that cannot move vertically.\n         *\n         * @param child Child view to check\n         * @return range of vertical motion in pixels\n         */\n        public int getViewVerticalDragRange(View child) {\n            return 0;\n        }\n\n        /**\n         * Called when the user's input indicates that they want to capture the given child view\n         * with the pointer indicated by pointerId. The callback should return true if the user\n         * is permitted to drag the given view with the indicated pointer.\n         * <p/>\n         * <p>ViewDragHelper may call this method multiple times for the same view even if\n         * the view is already captured; this indicates that a new pointer is trying to take\n         * control of the view.</p>\n         * <p/>\n         * <p>If this method returns true, a call to {@link #onViewCaptured(android.view.View, int)}\n         * will follow if the capture is successful.</p>\n         *\n         * @param child     Child the user is attempting to capture\n         * @param pointerId ID of the pointer attempting the capture\n         * @return true if capture should be allowed, false otherwise\n         */\n        public abstract boolean tryCaptureView(View child, int pointerId);\n\n        /**\n         * Restrict the motion of the dragged child view along the horizontal axis.\n         * The default implementation does not allow horizontal motion; the extending\n         * class must override this method and provide the desired clamping.\n         *\n         * @param child Child view being dragged\n         * @param left  Attempted motion along the X axis\n         * @param dx    Proposed change in position for left\n         * @return The new clamped position for left\n         */\n        public int clampViewPositionHorizontal(View child, int left, int dx) {\n            return 0;\n        }\n\n        /**\n         * Restrict the motion of the dragged child view along the vertical axis.\n         * The default implementation does not allow vertical motion; the extending\n         * class must override this method and provide the desired clamping.\n         *\n         * @param child Child view being dragged\n         * @param top   Attempted motion along the Y axis\n         * @param dy    Proposed change in position for top\n         * @return The new clamped position for top\n         */\n        public int clampViewPositionVertical(View child, int top, int dy) {\n            return 0;\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/subfragments/ArtistTagFragment.java",
    "content": "/*\n * Copyright (C) 2015 Naman Dwivedi\n *\n * Licensed under the GNU General Public License v3\n *\n * This is free software: you can redistribute it and/or modify it\n * under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;\n * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n * See the GNU General Public License for more details.\n */\n\npackage com.naman14.timber.subfragments;\n\nimport android.os.Bundle;\nimport androidx.annotation.Nullable;\nimport androidx.fragment.app.Fragment;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\n\nimport com.naman14.timber.R;\n\npublic class ArtistTagFragment extends Fragment {\n\n    private static final String ARG_PAGE_NUMBER = \"pageNumber\";\n\n    public static ArtistTagFragment newInstance(int pageNumber) {\n        ArtistTagFragment fragment = new ArtistTagFragment();\n        Bundle bundle = new Bundle();\n        bundle.putInt(ARG_PAGE_NUMBER, pageNumber);\n        fragment.setArguments(bundle);\n        return fragment;\n    }\n\n    @Override\n    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,\n                             @Nullable Bundle savedInstanceState) {\n        View rootView = inflater.inflate(R.layout.layout_artist_tag, container, false);\n        return rootView;\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/subfragments/LyricsFragment.java",
    "content": "package com.naman14.timber.subfragments;\n\nimport android.content.CursorLoader;\nimport android.database.Cursor;\nimport android.graphics.Color;\nimport android.net.Uri;\nimport android.os.Bundle;\nimport android.provider.MediaStore;\nimport androidx.annotation.Nullable;\nimport androidx.fragment.app.Fragment;\nimport androidx.appcompat.app.ActionBar;\nimport androidx.appcompat.app.AppCompatActivity;\nimport androidx.appcompat.widget.Toolbar;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.TextView;\n\nimport com.naman14.timber.MusicPlayer;\nimport com.naman14.timber.R;\nimport com.naman14.timber.utils.LyricsExtractor;\nimport com.naman14.timber.utils.LyricsLoader;\n\nimport java.io.File;\n\nimport retrofit.Callback;\nimport retrofit.RetrofitError;\nimport retrofit.client.Response;\n\n/**\n * Created by christoph on 10.12.16.\n */\n\npublic class LyricsFragment extends Fragment {\n\n    private String lyrics = null;\n    private Toolbar toolbar;\n    private View rootView;\n\n    @Nullable\n    @Override\n    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {\n        rootView = inflater.inflate(R.layout.fragment_lyrics,container,false);\n\n        toolbar = (Toolbar) rootView.findViewById(R.id.toolbar);\n        setupToolbar();\n\n        loadLyrics();\n\n        return rootView;\n    }\n\n    private void loadLyrics() {\n\n        final View lyricsView = rootView.findViewById(R.id.lyrics);\n        final TextView poweredbyTextView = (TextView) lyricsView.findViewById(R.id.lyrics_makeitpersonal);\n        poweredbyTextView.setVisibility(View.GONE);\n        final TextView lyricsTextView = (TextView) lyricsView.findViewById(R.id.lyrics_text);\n        lyricsTextView.setText(getString(R.string.lyrics_loading));\n        String filename = getRealPathFromURI(Uri.parse(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI + \"/\" + MusicPlayer.getCurrentAudioId()));\n        if (filename != null && lyrics == null) {\n            lyrics = LyricsExtractor.getLyrics(new File(filename));\n        }\n\n        if (lyrics != null) {\n            lyricsTextView.setText(lyrics);\n        } else {\n            String artist = MusicPlayer.getArtistName();\n            if (artist != null) {\n                int i = artist.lastIndexOf(\" feat\");\n                if (i != -1) {\n                    artist = artist.substring(0, i);\n                }\n\n                LyricsLoader.getInstance(this.getContext()).getLyrics(artist, MusicPlayer.getTrackName(), new Callback<String>() {\n                    @Override\n                    public void success(String s, Response response) {\n                        lyrics = s;\n                        if (s.equals(\"Sorry, We don't have lyrics for this song yet.\\n\")) {\n                            lyricsTextView.setText(R.string.no_lyrics);\n                        } else {\n                            lyricsTextView.setText(s);\n                            poweredbyTextView.setVisibility(View.VISIBLE);\n                        }\n                    }\n\n                    @Override\n                    public void failure(RetrofitError error) {\n                        lyricsTextView.setText(R.string.no_lyrics);\n                    }\n                });\n\n            } else {\n                lyricsTextView.setText(R.string.no_lyrics);\n            }\n        }\n    }\n\n    private void setupToolbar() {\n\n        ((AppCompatActivity) getActivity()).setSupportActionBar(toolbar);\n\n        final ActionBar ab = ((AppCompatActivity) getActivity()).getSupportActionBar();\n        ab.setDisplayHomeAsUpEnabled(true);\n        if (MusicPlayer.getTrackName() != null) {\n            ab.setTitle(MusicPlayer.getTrackName());\n        }\n    }\n\n    @Override\n    public void onResume() {\n        super.onResume();\n        toolbar.setBackgroundColor(Color.TRANSPARENT);\n    }\n\n    private String getRealPathFromURI(Uri contentUri) {\n        String[] proj = {MediaStore.Audio.Media.DATA};\n        CursorLoader loader = new CursorLoader(this.getContext(), contentUri, proj, null, null, null);\n        Cursor cursor = loader.loadInBackground();\n        int column_index = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.DATA);\n        cursor.moveToFirst();\n        String result = cursor.getString(column_index);\n        cursor.close();\n        return result;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/subfragments/PlaylistPagerFragment.java",
    "content": "/*\n * Copyright (C) 2015 Naman Dwivedi\n *\n * Licensed under the GNU General Public License v3\n *\n * This is free software: you can redistribute it and/or modify it\n * under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;\n * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n * See the GNU General Public License for more details.\n */\n\npackage com.naman14.timber.subfragments;\n\nimport android.content.Context;\nimport android.graphics.Bitmap;\nimport android.os.AsyncTask;\nimport android.os.Bundle;\nimport androidx.annotation.Nullable;\nimport androidx.fragment.app.Fragment;\nimport android.util.Pair;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.ImageView;\nimport android.widget.TextView;\n\nimport com.naman14.timber.R;\nimport com.naman14.timber.dataloaders.LastAddedLoader;\nimport com.naman14.timber.dataloaders.PlaylistLoader;\nimport com.naman14.timber.dataloaders.PlaylistSongLoader;\nimport com.naman14.timber.dataloaders.SongLoader;\nimport com.naman14.timber.dataloaders.TopTracksLoader;\nimport com.naman14.timber.models.Playlist;\nimport com.naman14.timber.models.Song;\nimport com.naman14.timber.utils.Constants;\nimport com.naman14.timber.utils.NavigationUtils;\nimport com.naman14.timber.utils.PreferencesUtility;\nimport com.naman14.timber.utils.TimberUtils;\nimport com.nostra13.universalimageloader.core.DisplayImageOptions;\nimport com.nostra13.universalimageloader.core.ImageLoader;\nimport com.nostra13.universalimageloader.core.listener.SimpleImageLoadingListener;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Random;\n\npublic class PlaylistPagerFragment extends Fragment {\n\n    private static final String ARG_PAGE_NUMBER = \"pageNumber\";\n    private int[] foregroundColors = {R.color.pink_transparent, R.color.green_transparent, R.color.blue_transparent, R.color.red_transparent, R.color.purple_transparent};\n    private int pageNumber, songCountInt, totalRuntime;\n    private int foregroundColor;\n    private long firstAlbumID = -1;\n    private Playlist playlist;\n    private TextView playlistame, songcount, playlistnumber, playlisttype, runtime;\n    private ImageView playlistImage;\n    private View foreground;\n    private Context mContext;\n    private boolean showAuto;\n\n    public static PlaylistPagerFragment newInstance(int pageNumber) {\n        PlaylistPagerFragment fragment = new PlaylistPagerFragment();\n        Bundle bundle = new Bundle();\n        bundle.putInt(ARG_PAGE_NUMBER, pageNumber);\n        fragment.setArguments(bundle);\n        return fragment;\n    }\n\n    @Override\n    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,\n                             @Nullable Bundle savedInstanceState) {\n        showAuto = PreferencesUtility.getInstance(getActivity()).showAutoPlaylist();\n        View rootView = inflater.inflate(R.layout.fragment_playlist_pager, container, false);\n\n        final List<Playlist> playlists = PlaylistLoader.getPlaylists(getActivity(), showAuto);\n\n        pageNumber = getArguments().getInt(ARG_PAGE_NUMBER);\n        playlist = playlists.get(pageNumber);\n\n        playlistame = (TextView) rootView.findViewById(R.id.name);\n        playlistnumber = (TextView) rootView.findViewById(R.id.number);\n        songcount = (TextView) rootView.findViewById(R.id.songcount);\n        runtime = (TextView) rootView.findViewById(R.id.runtime);\n        playlisttype = (TextView) rootView.findViewById(R.id.playlisttype);\n        playlistImage = (ImageView) rootView.findViewById(R.id.playlist_image);\n        foreground = rootView.findViewById(R.id.foreground);\n\n        playlistImage.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View view) {\n                ArrayList<Pair> tranitionViews = new ArrayList<>();\n                tranitionViews.add(0, Pair.create((View) playlistame, \"transition_playlist_name\"));\n                tranitionViews.add(1, Pair.create((View) playlistImage, \"transition_album_art\"));\n                tranitionViews.add(2, Pair.create(foreground, \"transition_foreground\"));\n                NavigationUtils.navigateToPlaylistDetail(getActivity(), getPlaylistType(), firstAlbumID, String.valueOf(playlistame.getText()), foregroundColor, playlist.id, tranitionViews);\n            }\n        });\n\n        mContext = this.getContext();\n        setUpPlaylistDetails();\n        return rootView;\n    }\n\n\n    @Override\n    public void onViewCreated(View view, Bundle savedinstancestate) {\n        new loadPlaylistImage().execute(\"\");\n    }\n\n    private void setUpPlaylistDetails() {\n        playlistame.setText(playlist.name);\n\n        int number = getArguments().getInt(ARG_PAGE_NUMBER) + 1;\n        String playlistnumberstring;\n\n        if (number > 9) {\n            playlistnumberstring = String.valueOf(number);\n        } else {\n            playlistnumberstring = \"0\" + String.valueOf(number);\n        }\n        playlistnumber.setText(playlistnumberstring);\n\n        Random random = new Random();\n        int rndInt = random.nextInt(foregroundColors.length);\n\n        foregroundColor = foregroundColors[rndInt];\n        foreground.setBackgroundColor(foregroundColor);\n\n        if (showAuto) {\n            if (pageNumber <= 2)\n                playlisttype.setVisibility(View.VISIBLE);\n        }\n\n    }\n\n    private String getPlaylistType() {\n        if (showAuto) {\n            switch (pageNumber) {\n                case 0:\n                    return Constants.NAVIGATE_PLAYLIST_LASTADDED;\n                case 1:\n                    return Constants.NAVIGATE_PLAYLIST_RECENT;\n                case 2:\n                    return Constants.NAVIGATE_PLAYLIST_TOPTRACKS;\n                default:\n                    return Constants.NAVIGATE_PLAYLIST_USERCREATED;\n            }\n        } else return Constants.NAVIGATE_PLAYLIST_USERCREATED;\n    }\n\n\n    private class loadPlaylistImage extends AsyncTask<String, Void, String> {\n\n        @Override\n        protected String doInBackground(String... params) {\n            if (getActivity() != null) {\n                if (showAuto) {\n                    switch (pageNumber) {\n                        case 0:\n                            List<Song> lastAddedSongs = LastAddedLoader.getLastAddedSongs(getActivity());\n                            songCountInt = lastAddedSongs.size();\n                            for(Song song : lastAddedSongs) {\n                                totalRuntime += song.duration / 1000; //for some reason default playlists have songs with durations 1000x larger than they should be\n                            }\n                            if (songCountInt != 0) {\n                                firstAlbumID = lastAddedSongs.get(0).albumId;\n                                return TimberUtils.getAlbumArtUri(firstAlbumID).toString();\n                            } else return \"nosongs\";\n                        case 1:\n                            TopTracksLoader recentloader = new TopTracksLoader(getActivity(), TopTracksLoader.QueryType.RecentSongs);\n                            List<Song> recentsongs = SongLoader.getSongsForCursor(TopTracksLoader.getCursor());\n                            songCountInt = recentsongs.size();\n                            for(Song song : recentsongs){\n                                    totalRuntime += song.duration / 1000;\n                            }\n\n                            if (songCountInt != 0) {\n                                firstAlbumID = recentsongs.get(0).albumId;\n                                return TimberUtils.getAlbumArtUri(firstAlbumID).toString();\n                            } else return \"nosongs\";\n                        case 2:\n                            TopTracksLoader topTracksLoader = new TopTracksLoader(getActivity(), TopTracksLoader.QueryType.TopTracks);\n                            List<Song> topsongs = SongLoader.getSongsForCursor(TopTracksLoader.getCursor());\n                            songCountInt = topsongs.size();\n                            for(Song song : topsongs){\n                                    totalRuntime += song.duration / 1000;\n                            }\n                            if (songCountInt != 0) {\n                                firstAlbumID = topsongs.get(0).albumId;\n                                return TimberUtils.getAlbumArtUri(firstAlbumID).toString();\n                            } else return \"nosongs\";\n                        default:\n                            List<Song> playlistsongs = PlaylistSongLoader.getSongsInPlaylist(getActivity(), playlist.id);\n                            songCountInt = playlistsongs.size();\n                            for(Song song : playlistsongs){\n                                totalRuntime += song.duration;\n                            }\n                            if (songCountInt != 0) {\n                                firstAlbumID = playlistsongs.get(0).albumId;\n                                return TimberUtils.getAlbumArtUri(firstAlbumID).toString();\n                            } else return \"nosongs\";\n\n                    }\n                } else {\n                    List<Song> playlistsongs = PlaylistSongLoader.getSongsInPlaylist(getActivity(), playlist.id);\n                    songCountInt = playlistsongs.size();\n                    for(Song song : playlistsongs){\n                        totalRuntime += song.duration;\n                    }\n                    if (songCountInt != 0) {\n                        firstAlbumID = playlistsongs.get(0).albumId;\n                        return TimberUtils.getAlbumArtUri(firstAlbumID).toString();\n                    } else return \"nosongs\";\n                }\n            } else return \"context is null\";\n\n        }\n\n        @Override\n        protected void onPostExecute(String uri) {\n            ImageLoader.getInstance().displayImage(uri, playlistImage,\n                    new DisplayImageOptions.Builder().cacheInMemory(true)\n                            .showImageOnFail(R.drawable.ic_empty_music2)\n                            .resetViewBeforeLoading(true)\n                            .build(), new SimpleImageLoadingListener() {\n                        @Override\n                        public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {\n                        }\n                    });\n            songcount.setText(\" \" + String.valueOf(songCountInt) + \" \" + mContext.getString(R.string.songs));\n            runtime.setText(\" \" + TimberUtils.makeShortTimeString(mContext, totalRuntime));\n        }\n\n        @Override\n        protected void onPreExecute() {\n        }\n    }\n\n\n}\n\n"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/subfragments/QuickControlsFragment.java",
    "content": "/*\n * Copyright (C) 2015 Naman Dwivedi\n *\n * Licensed under the GNU General Public License v3\n *\n * This is free software: you can redistribute it and/or modify it\n * under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;\n * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n * See the GNU General Public License for more details.\n */\n\npackage com.naman14.timber.subfragments;\n\nimport android.graphics.Bitmap;\nimport android.graphics.Color;\nimport android.graphics.drawable.Drawable;\nimport android.graphics.drawable.TransitionDrawable;\nimport android.os.AsyncTask;\nimport android.os.Bundle;\nimport android.os.Handler;\nimport androidx.fragment.app.Fragment;\n\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.ImageView;\nimport android.widget.LinearLayout;\nimport android.widget.ProgressBar;\nimport android.widget.SeekBar;\nimport android.widget.TextView;\n\nimport com.afollestad.appthemeengine.Config;\nimport com.naman14.timber.MusicPlayer;\nimport com.naman14.timber.R;\nimport com.naman14.timber.activities.BaseActivity;\nimport com.naman14.timber.listeners.MusicStateListener;\nimport com.naman14.timber.utils.Helpers;\nimport com.naman14.timber.utils.ImageUtils;\nimport com.naman14.timber.utils.NavigationUtils;\nimport com.naman14.timber.utils.PreferencesUtility;\nimport com.naman14.timber.utils.SlideTrackSwitcher;\nimport com.naman14.timber.utils.TimberUtils;\nimport com.naman14.timber.widgets.PlayPauseButton;\nimport com.nostra13.universalimageloader.core.DisplayImageOptions;\nimport com.nostra13.universalimageloader.core.ImageLoader;\nimport com.nostra13.universalimageloader.core.assist.FailReason;\nimport com.nostra13.universalimageloader.core.listener.ImageLoadingListener;\n\nimport net.steamcrafted.materialiconlib.MaterialIconView;\n\npublic class QuickControlsFragment extends Fragment implements MusicStateListener {\n\n\n    public static View topContainer;\n    private ProgressBar mProgress;\n    private SeekBar mSeekBar;\n    private int overflowcounter = 0;\n    private PlayPauseButton mPlayPause, mPlayPauseExpanded;\n    private TextView mTitle, mTitleExpanded;\n    private TextView mArtist, mArtistExpanded;\n    private ImageView mAlbumArt, mBlurredArt;\n    private View rootView;\n    private View playPauseWrapper, playPauseWrapperExpanded;\n    private MaterialIconView previous, next;\n    private boolean duetoplaypause = false;\n    private boolean fragmentPaused = false;\n\n    public Runnable mUpdateProgress = new Runnable() {\n\n        @Override\n        public void run() {\n\n            long position = MusicPlayer.position();\n            mProgress.setProgress((int) position);\n            mSeekBar.setProgress((int) position);\n\n            overflowcounter--;\n            if (MusicPlayer.isPlaying()) {\n                int delay = (int) (1500 - (position % 1000));\n                if (overflowcounter < 0 && !fragmentPaused) {\n                    overflowcounter++;\n                    mProgress.postDelayed(mUpdateProgress, delay);\n                }\n            } else mProgress.removeCallbacks(this);\n\n        }\n    };\n\n    private final View.OnClickListener mPlayPauseListener = new View.OnClickListener() {\n        @Override\n        public void onClick(View v) {\n            duetoplaypause = true;\n            if (!mPlayPause.isPlayed()) {\n                mPlayPause.setPlayed(true);\n                mPlayPause.startAnimation();\n            } else {\n                mPlayPause.setPlayed(false);\n                mPlayPause.startAnimation();\n            }\n            Handler handler = new Handler();\n            handler.postDelayed(new Runnable() {\n                @Override\n                public void run() {\n                    MusicPlayer.playOrPause();\n                }\n            }, 200);\n\n        }\n    };\n\n    private final View.OnClickListener mPlayPauseExpandedListener = new View.OnClickListener() {\n        @Override\n        public void onClick(View v) {\n            duetoplaypause = true;\n            if (!mPlayPauseExpanded.isPlayed()) {\n                mPlayPauseExpanded.setPlayed(true);\n                mPlayPauseExpanded.startAnimation();\n            } else {\n                mPlayPauseExpanded.setPlayed(false);\n                mPlayPauseExpanded.startAnimation();\n            }\n            Handler handler = new Handler();\n            handler.postDelayed(new Runnable() {\n                @Override\n                public void run() {\n                    MusicPlayer.playOrPause();\n                }\n            }, 200);\n\n        }\n    };\n\n    @Override\n    public View onCreateView(LayoutInflater inflater, ViewGroup container,\n                             Bundle savedInstanceState) {\n        View rootView = inflater.inflate(R.layout.fragment_playback_controls, container, false);\n        this.rootView = rootView;\n\n        mPlayPause = (PlayPauseButton) rootView.findViewById(R.id.play_pause);\n        mPlayPauseExpanded = (PlayPauseButton) rootView.findViewById(R.id.playpause);\n        playPauseWrapper = rootView.findViewById(R.id.play_pause_wrapper);\n        playPauseWrapperExpanded = rootView.findViewById(R.id.playpausewrapper);\n        playPauseWrapper.setOnClickListener(mPlayPauseListener);\n        playPauseWrapperExpanded.setOnClickListener(mPlayPauseExpandedListener);\n        mProgress = (ProgressBar) rootView.findViewById(R.id.song_progress_normal);\n        mSeekBar = (SeekBar) rootView.findViewById(R.id.song_progress);\n        mTitle = (TextView) rootView.findViewById(R.id.title);\n        mArtist = (TextView) rootView.findViewById(R.id.artist);\n        mTitleExpanded = (TextView) rootView.findViewById(R.id.song_title);\n        mArtistExpanded = (TextView) rootView.findViewById(R.id.song_artist);\n        mAlbumArt = (ImageView) rootView.findViewById(R.id.album_art_nowplayingcard);\n        mBlurredArt = (ImageView) rootView.findViewById(R.id.blurredAlbumart);\n        next = (MaterialIconView) rootView.findViewById(R.id.next);\n        previous = (MaterialIconView) rootView.findViewById(R.id.previous);\n        topContainer = rootView.findViewById(R.id.topContainer);\n\n        LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) mProgress.getLayoutParams();\n        mProgress.measure(0, 0);\n        layoutParams.setMargins(0, -(mProgress.getMeasuredHeight() / 2), 0, 0);\n        mProgress.setLayoutParams(layoutParams);\n\n        mPlayPause.setColor(Config.accentColor(getActivity(), Helpers.getATEKey(getActivity())));\n        mPlayPauseExpanded.setColor(Color.WHITE);\n\n        mSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {\n            @Override\n            public void onProgressChanged(SeekBar seekBar, int i, boolean b) {\n                if (b) {\n                    MusicPlayer.seek((long) i);\n                }\n            }\n\n            @Override\n            public void onStartTrackingTouch(SeekBar seekBar) {\n            }\n\n            @Override\n            public void onStopTrackingTouch(SeekBar seekBar) {\n            }\n        });\n\n        next.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View view) {\n                Handler handler = new Handler();\n                handler.postDelayed(new Runnable() {\n                    @Override\n                    public void run() {\n                        MusicPlayer.next();\n                    }\n                }, 200);\n\n            }\n        });\n\n        previous.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View view) {\n                Handler handler = new Handler();\n                handler.postDelayed(new Runnable() {\n                    @Override\n                    public void run() {\n                        MusicPlayer.previous(getActivity(), false);\n                    }\n                }, 200);\n\n            }\n        });\n\n\n        ((BaseActivity) getActivity()).setMusicStateListenerListener(this);\n\n        if (PreferencesUtility.getInstance(getActivity()).isGesturesEnabled()) {\n            new SlideTrackSwitcher() {\n                @Override\n                public void onClick() {\n                    NavigationUtils.navigateToNowplaying(getActivity(), false);\n                }\n            }.attach(rootView.findViewById(R.id.root_view));\n        }\n\n\n        return rootView;\n    }\n\n    @Override\n    public void onPause() {\n        super.onPause();\n        fragmentPaused = true;\n    }\n\n    public void updateNowplayingCard() {\n        mTitle.setText(MusicPlayer.getTrackName());\n        mArtist.setText(MusicPlayer.getArtistName());\n        mTitleExpanded.setText(MusicPlayer.getTrackName());\n        mArtistExpanded.setText(MusicPlayer.getArtistName());\n        if (!duetoplaypause) {\n            ImageLoader.getInstance().displayImage(TimberUtils.getAlbumArtUri(MusicPlayer.getCurrentAlbumId()).toString(), mAlbumArt,\n                    new DisplayImageOptions.Builder().cacheInMemory(true)\n                            .showImageOnFail(R.drawable.ic_empty_music2)\n                            .resetViewBeforeLoading(true)\n                            .build(), new ImageLoadingListener() {\n                        @Override\n                        public void onLoadingStarted(String imageUri, View view) {\n\n                        }\n\n                        @Override\n                        public void onLoadingFailed(String imageUri, View view, FailReason failReason) {\n                            Bitmap failedBitmap = ImageLoader.getInstance().loadImageSync(\"drawable://\" + R.drawable.ic_empty_music2);\n                            if (getActivity() != null)\n                                new setBlurredAlbumArt().execute(failedBitmap);\n                        }\n\n                        @Override\n                        public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {\n                            if (getActivity() != null)\n                                new setBlurredAlbumArt().execute(loadedImage);\n\n                        }\n\n                        @Override\n                        public void onLoadingCancelled(String imageUri, View view) {\n\n                        }\n                    });\n        }\n        duetoplaypause = false;\n        mProgress.setMax((int) MusicPlayer.duration());\n        mSeekBar.setMax((int) MusicPlayer.duration());\n        mProgress.postDelayed(mUpdateProgress, 10);\n    }\n\n    @Override\n    public void onStart() {\n        super.onStart();\n\n    }\n\n    @Override\n    public void onStop() {\n        super.onStop();\n\n    }\n\n    @Override\n    public void onResume() {\n        super.onResume();\n        topContainer = rootView.findViewById(R.id.topContainer);\n        fragmentPaused = false;\n        if (mProgress != null)\n            mProgress.postDelayed(mUpdateProgress, 10);\n\n    }\n\n    public void updateState() {\n        if (MusicPlayer.isPlaying()) {\n            if (!mPlayPause.isPlayed()) {\n                mPlayPause.setPlayed(true);\n                mPlayPause.startAnimation();\n            }\n            if (!mPlayPauseExpanded.isPlayed()) {\n                mPlayPauseExpanded.setPlayed(true);\n                mPlayPauseExpanded.startAnimation();\n            }\n        } else {\n            if (mPlayPause.isPlayed()) {\n                mPlayPause.setPlayed(false);\n                mPlayPause.startAnimation();\n            }\n            if (mPlayPauseExpanded.isPlayed()) {\n                mPlayPauseExpanded.setPlayed(false);\n                mPlayPauseExpanded.startAnimation();\n            }\n        }\n    }\n\n    public void restartLoader() {\n\n    }\n\n    public void onPlaylistChanged() {\n\n    }\n\n    public void onMetaChanged() {\n        updateNowplayingCard();\n        updateState();\n    }\n\n    private class setBlurredAlbumArt extends AsyncTask<Bitmap, Void, Drawable> {\n\n        @Override\n        protected Drawable doInBackground(Bitmap... loadedImage) {\n            Drawable drawable = null;\n            try {\n                drawable = ImageUtils.createBlurredImageFromBitmap(loadedImage[0], getActivity(), 6);\n            } catch (Exception e) {\n                e.printStackTrace();\n            }\n            return drawable;\n        }\n\n        @Override\n        protected void onPostExecute(Drawable result) {\n            if (result != null) {\n                if (mBlurredArt.getDrawable() != null) {\n                    final TransitionDrawable td =\n                            new TransitionDrawable(new Drawable[]{\n                                    mBlurredArt.getDrawable(),\n                                    result\n                            });\n                    mBlurredArt.setImageDrawable(td);\n                    td.startTransition(400);\n\n                } else {\n                    mBlurredArt.setImageDrawable(result);\n                }\n            }\n        }\n\n        @Override\n        protected void onPreExecute() {\n        }\n    }\n\n\n}\n"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/subfragments/StyleSelectorFragment.java",
    "content": "/*\n * Copyright (C) 2015 Naman Dwivedi\n *\n * Licensed under the GNU General Public License v3\n *\n * This is free software: you can redistribute it and/or modify it\n * under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;\n * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n * See the GNU General Public License for more details.\n */\n\npackage com.naman14.timber.subfragments;\n\nimport android.content.Context;\nimport android.content.SharedPreferences;\nimport android.os.Bundle;\nimport androidx.annotation.Nullable;\nimport androidx.fragment.app.Fragment;\nimport androidx.fragment.app.FragmentStatePagerAdapter;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\n\nimport com.naman14.timber.R;\nimport com.naman14.timber.utils.Constants;\nimport com.naman14.timber.utils.NavigationUtils;\nimport com.naman14.timber.widgets.MultiViewPager;\n\npublic class StyleSelectorFragment extends Fragment {\n\n    public String ACTION = \"action\";\n    private FragmentStatePagerAdapter adapter;\n    private MultiViewPager pager;\n    private SubStyleSelectorFragment selectorFragment;\n    private SharedPreferences preferences;\n\n    public static StyleSelectorFragment newInstance(String what) {\n        StyleSelectorFragment fragment = new StyleSelectorFragment();\n        Bundle bundle = new Bundle();\n        bundle.putString(Constants.SETTINGS_STYLE_SELECTOR_WHAT, what);\n        fragment.setArguments(bundle);\n        return fragment;\n    }\n\n    @Override\n    public void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        if (getArguments() != null) {\n            ACTION = getArguments().getString(Constants.SETTINGS_STYLE_SELECTOR_WHAT);\n        }\n        preferences = getActivity().getSharedPreferences(Constants.FRAGMENT_ID, Context.MODE_PRIVATE);\n    }\n\n\n    @Override\n    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,\n                             @Nullable Bundle savedInstanceState) {\n        View rootView = inflater.inflate(R.layout.fragment_style_selector, container, false);\n\n        if (ACTION.equals(Constants.SETTINGS_STYLE_SELECTOR_NOWPLAYING)) {\n\n        }\n        pager = (MultiViewPager) rootView.findViewById(R.id.pager);\n\n        adapter = new FragmentStatePagerAdapter(getChildFragmentManager()) {\n\n            @Override\n            public int getCount() {\n                return 6;\n            }\n\n            @Override\n            public Fragment getItem(int position) {\n                selectorFragment = SubStyleSelectorFragment.newInstance(position, ACTION);\n                return selectorFragment;\n            }\n\n            @Override\n            public int getItemPosition(Object object) {\n                return POSITION_NONE;\n            }\n        };\n        pager.setAdapter(adapter);\n        scrollToCurrentStyle();\n\n        return rootView;\n    }\n\n    public void updateCurrentStyle() {\n        if (selectorFragment != null) {\n            adapter.notifyDataSetChanged();\n            scrollToCurrentStyle();\n        }\n\n    }\n\n    public void scrollToCurrentStyle() {\n        String fragmentID = preferences.getString(Constants.NOWPLAYING_FRAGMENT_ID, Constants.TIMBER3);\n        pager.setCurrentItem(NavigationUtils.getIntForCurrentNowplaying(fragmentID));\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/subfragments/SubStyleSelectorFragment.java",
    "content": "/*\n * Copyright (C) 2015 Naman Dwivedi\n *\n * Licensed under the GNU General Public License v3\n *\n * This is free software: you can redistribute it and/or modify it\n * under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;\n * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n * See the GNU General Public License for more details.\n */\n\npackage com.naman14.timber.subfragments;\n\nimport android.content.Context;\nimport android.content.Intent;\nimport android.content.SharedPreferences;\nimport android.os.Bundle;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.fragment.app.Fragment;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.ImageView;\nimport android.widget.LinearLayout;\nimport android.widget.TextView;\n\nimport com.afollestad.materialdialogs.DialogAction;\nimport com.afollestad.materialdialogs.MaterialDialog;\nimport com.naman14.timber.R;\nimport com.naman14.timber.activities.DonateActivity;\nimport com.naman14.timber.utils.Constants;\nimport com.naman14.timber.utils.NavigationUtils;\nimport com.naman14.timber.utils.PreferencesUtility;\n\npublic class SubStyleSelectorFragment extends Fragment {\n\n    private static final String ARG_PAGE_NUMBER = \"pageNumber\";\n    private static final String WHAT = \"what\";\n    private SharedPreferences.Editor editor;\n    private SharedPreferences preferences;\n    private LinearLayout currentStyle;\n    private View foreground;\n    private ImageView styleImage, imgLock;\n\n    public static SubStyleSelectorFragment newInstance(int pageNumber, String what) {\n        SubStyleSelectorFragment fragment = new SubStyleSelectorFragment();\n        Bundle bundle = new Bundle();\n        bundle.putInt(ARG_PAGE_NUMBER, pageNumber);\n        bundle.putString(WHAT, what);\n        fragment.setArguments(bundle);\n        return fragment;\n    }\n\n    @Override\n    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,\n                             @Nullable Bundle savedInstanceState) {\n        View rootView = inflater.inflate(R.layout.fragment_style_selector_pager, container, false);\n\n        TextView styleName = (TextView) rootView.findViewById(R.id.style_name);\n        styleName.setText(String.valueOf(getArguments().getInt(ARG_PAGE_NUMBER) + 1));\n        preferences = getActivity().getSharedPreferences(Constants.FRAGMENT_ID, Context.MODE_PRIVATE);\n\n        styleImage = (ImageView) rootView.findViewById(R.id.style_image);\n        imgLock = (ImageView) rootView.findViewById(R.id.img_lock);\n\n        styleImage.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View view) {\n                if (getArguments().getInt(ARG_PAGE_NUMBER) >= 4) {\n                    if (isUnlocked()) {\n                        setPreferences();\n                    } else {\n                        showPurchaseDialog();\n                    }\n                } else\n                    setPreferences();\n            }\n        });\n\n        switch (getArguments().getInt(ARG_PAGE_NUMBER)) {\n            case 0:\n                styleImage.setImageResource(R.drawable.timber_1_nowplaying_x);\n                break;\n            case 1:\n                styleImage.setImageResource(R.drawable.timber_2_nowplaying_x);\n                break;\n            case 2:\n                styleImage.setImageResource(R.drawable.timber_3_nowplaying_x);\n                break;\n            case 3:\n                styleImage.setImageResource(R.drawable.timber_4_nowplaying_x);\n                break;\n            case 4:\n                styleImage.setImageResource(R.drawable.timber_5_nowplaying_x);\n                break;\n            case 5:\n                styleImage.setImageResource(R.drawable.timber_6_nowplaying_x);\n                break;\n        }\n\n        currentStyle = (LinearLayout) rootView.findViewById(R.id.currentStyle);\n        foreground = rootView.findViewById(R.id.foreground);\n\n        setCurrentStyle();\n\n        return rootView;\n    }\n\n    private boolean isUnlocked() {\n        return getActivity() != null && PreferencesUtility.getInstance(getActivity()).fullUnlocked();\n    }\n\n    @Override\n    public void onResume() {\n        super.onResume();\n        updateLockedStatus();\n    }\n\n    private void updateLockedStatus() {\n        if (getArguments().getInt(ARG_PAGE_NUMBER) >= 4 && !isUnlocked()) {\n            imgLock.setVisibility(View.VISIBLE);\n            foreground.setVisibility(View.VISIBLE);\n        }\n        else {\n            imgLock.setVisibility(View.GONE);\n            foreground.setVisibility(View.GONE);\n        }\n    }\n    private void showPurchaseDialog() {\n        MaterialDialog dialog = new MaterialDialog.Builder(getActivity())\n                .title(\"Purchase\")\n                .content(\"This now playing style is available after a one time purchase of any amount. Support development and unlock this style?\")\n                .positiveText(\"Support development\")\n                .neutralText(\"Restore purchases\")\n                .onPositive(new MaterialDialog.SingleButtonCallback() {\n                    @Override\n                    public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) {\n                        startActivity(new Intent(getActivity(), DonateActivity.class));\n                        dialog.dismiss();\n                    }\n                }).onNeutral(new MaterialDialog.SingleButtonCallback() {\n                    @Override\n                    public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) {\n                        Intent intent = new Intent(getActivity(), DonateActivity.class);\n                        intent.putExtra(\"title\", \"Restoring purchases..\");\n                        intent.setAction(\"restore\");\n                        startActivity(intent);\n                        dialog.dismiss();\n                    }\n                })\n                .show();\n    }\n\n    public void setCurrentStyle() {\n        String fragmentID = preferences.getString(Constants.NOWPLAYING_FRAGMENT_ID, Constants.TIMBER3);\n\n        if (getArguments().getInt(ARG_PAGE_NUMBER) == NavigationUtils.getIntForCurrentNowplaying(fragmentID)) {\n            currentStyle.setVisibility(View.VISIBLE);\n            foreground.setVisibility(View.VISIBLE);\n        } else {\n            currentStyle.setVisibility(View.GONE);\n            foreground.setVisibility(View.GONE);\n        }\n\n    }\n\n    private void setPreferences() {\n\n        if (getArguments().getString(WHAT).equals(Constants.SETTINGS_STYLE_SELECTOR_NOWPLAYING)) {\n            editor = getActivity().getSharedPreferences(Constants.FRAGMENT_ID, Context.MODE_PRIVATE).edit();\n            editor.putString(Constants.NOWPLAYING_FRAGMENT_ID, getStyleForPageNumber());\n            editor.apply();\n            if (getActivity() != null)\n                PreferencesUtility.getInstance(getActivity()).setNowPlayingThemeChanged(true);\n            setCurrentStyle();\n            ((StyleSelectorFragment) getParentFragment()).updateCurrentStyle();\n        }\n    }\n\n    private String getStyleForPageNumber() {\n        switch (getArguments().getInt(ARG_PAGE_NUMBER)) {\n            case 0:\n                return Constants.TIMBER1;\n            case 1:\n                return Constants.TIMBER2;\n            case 2:\n                return Constants.TIMBER3;\n            case 3:\n                return Constants.TIMBER4;\n            case 4:\n                return Constants.TIMBER5;\n            case 5:\n                return Constants.TIMBER6;\n            default:\n                return Constants.TIMBER3;\n        }\n    }\n\n\n}\n"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/timely/TimelyView.java",
    "content": "/*\n* Copyright 2014 Adnan A M.\n* Copyright 2015 Naman Dwivedi.\n\n* Licensed under the Apache License, Version 2.0 (the \"License\");\n* you may not use this file except in compliance with the License.\n* You may obtain a copy of the License at\n\n*   http://www.apache.org/licenses/LICENSE-2.0\n\n* Unless required by applicable law or agreed to in writing, software\n* distributed under the License is distributed on an \"AS IS\" BASIS,\n* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n* See the License for the specific language governing permissions and\n* limitations under the License.\n*/\n\npackage com.naman14.timber.timely;\n\nimport android.animation.ObjectAnimator;\nimport android.content.Context;\nimport android.content.res.TypedArray;\nimport android.graphics.Canvas;\nimport android.graphics.Color;\nimport android.graphics.Paint;\nimport android.graphics.Path;\nimport android.util.AttributeSet;\nimport android.util.Property;\nimport android.view.View;\n\nimport com.naman14.timber.R;\nimport com.naman14.timber.timely.animation.TimelyEvaluator;\nimport com.naman14.timber.timely.model.NumberUtils;\n\npublic class TimelyView extends View {\n    private static final float RATIO = 1f;\n    private static final Property<TimelyView, float[][]> CONTROL_POINTS_PROPERTY = new Property<TimelyView, float[][]>(float[][].class, \"controlPoints\") {\n        @Override\n        public float[][] get(TimelyView object) {\n            return object.getControlPoints();\n        }\n\n        @Override\n        public void set(TimelyView object, float[][] value) {\n            object.setControlPoints(value);\n        }\n    };\n    private Paint mPaint = null;\n    private Path mPath = null;\n    private float[][] controlPoints = null;\n\n    private int textColor;\n\n    public TimelyView(Context context) {\n        super(context);\n        init();\n    }\n\n    public TimelyView(Context context, AttributeSet attrs) {\n        super(context, attrs);\n        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.TimelyView);\n        textColor = typedArray.getColor(R.styleable.TimelyView_text_color, Color.BLACK);\n        init();\n    }\n\n    public TimelyView(Context context, AttributeSet attrs, int defStyleAttr) {\n        super(context, attrs, defStyleAttr);\n        init();\n    }\n\n    public float[][] getControlPoints() {\n        return controlPoints;\n    }\n\n    public void setControlPoints(float[][] controlPoints) {\n        this.controlPoints = controlPoints;\n        invalidate();\n    }\n\n    public ObjectAnimator animate(int start, int end) {\n        float[][] startPoints = NumberUtils.getControlPointsFor(start);\n        float[][] endPoints = NumberUtils.getControlPointsFor(end);\n\n        return ObjectAnimator.ofObject(this, CONTROL_POINTS_PROPERTY, new TimelyEvaluator(), startPoints, endPoints);\n    }\n\n    public ObjectAnimator animate(int end) {\n        float[][] startPoints = NumberUtils.getControlPointsFor(-1);\n        float[][] endPoints = NumberUtils.getControlPointsFor(end);\n\n        return ObjectAnimator.ofObject(this, CONTROL_POINTS_PROPERTY, new TimelyEvaluator(), startPoints, endPoints);\n    }\n\n    @Override\n    protected void onDraw(Canvas canvas) {\n        super.onDraw(canvas);\n        if (controlPoints == null) return;\n\n        int length = controlPoints.length;\n\n        int height = getMeasuredHeight();\n        int width = getMeasuredWidth();\n\n        float minDimen = height > width ? width : height;\n\n        mPath.reset();\n        mPath.moveTo(minDimen * controlPoints[0][0], minDimen * controlPoints[0][1]);\n        for (int i = 1; i < length; i += 3) {\n            mPath.cubicTo(minDimen * controlPoints[i][0], minDimen * controlPoints[i][1],\n                    minDimen * controlPoints[i + 1][0], minDimen * controlPoints[i + 1][1],\n                    minDimen * controlPoints[i + 2][0], minDimen * controlPoints[i + 2][1]);\n        }\n        canvas.drawPath(mPath, mPaint);\n    }\n\n    @Override\n    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {\n        super.onMeasure(widthMeasureSpec, heightMeasureSpec);\n\n        int width = getMeasuredWidth();\n        int height = getMeasuredHeight();\n        int widthWithoutPadding = width - getPaddingLeft() - getPaddingRight();\n        int heigthWithoutPadding = height - getPaddingTop() - getPaddingBottom();\n\n        int maxWidth = (int) (heigthWithoutPadding * RATIO);\n        int maxHeight = (int) (widthWithoutPadding / RATIO);\n\n        if (widthWithoutPadding > maxWidth) {\n            width = maxWidth + getPaddingLeft() + getPaddingRight();\n        } else {\n            height = maxHeight + getPaddingTop() + getPaddingBottom();\n        }\n\n        setMeasuredDimension(width, height);\n    }\n\n    private void init() {\n        // A new paint with the style as stroke.\n        mPaint = new Paint();\n        mPaint.setAntiAlias(true);\n        mPaint.setColor(textColor);\n        mPaint.setStrokeWidth(5.0f);\n        mPaint.setStyle(Paint.Style.STROKE);\n        mPath = new Path();\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/timely/animation/TimelyEvaluator.java",
    "content": "/*\n* Copyright 2014 Adnan A M.\n\n* Licensed under the Apache License, Version 2.0 (the \"License\");\n* you may not use this file except in compliance with the License.\n* You may obtain a copy of the License at\n\n*   http://www.apache.org/licenses/LICENSE-2.0\n\n* Unless required by applicable law or agreed to in writing, software\n* distributed under the License is distributed on an \"AS IS\" BASIS,\n* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n* See the License for the specific language governing permissions and\n* limitations under the License.\n*/\n\npackage com.naman14.timber.timely.animation;\n\nimport android.animation.TypeEvaluator;\n\npublic class TimelyEvaluator implements TypeEvaluator<float[][]> {\n    private float[][] _cachedPoints = null;\n\n    @Override\n    public float[][] evaluate(float fraction, float[][] startValue, float[][] endValue) {\n        int pointsCount = startValue.length;\n        initCache(pointsCount);\n\n        for (int i = 0; i < pointsCount; i++) {\n            _cachedPoints[i][0] = startValue[i][0] + fraction * (endValue[i][0] - startValue[i][0]);\n            _cachedPoints[i][1] = startValue[i][1] + fraction * (endValue[i][1] - startValue[i][1]);\n        }\n\n        return _cachedPoints;\n    }\n\n    private void initCache(int pointsCount) {\n        if (_cachedPoints == null || _cachedPoints.length != pointsCount) {\n            _cachedPoints = new float[pointsCount][2];\n        }\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/timely/model/NumberUtils.java",
    "content": "package com.naman14.timber.timely.model;\n\nimport com.naman14.timber.timely.model.number.*;\n\nimport java.security.InvalidParameterException;\n\npublic class NumberUtils {\n\n    public static float[][] getControlPointsFor(int start) {\n        switch (start) {\n            case (-1):\n                return Null.getInstance().getControlPoints();\n            case 0:\n                return Zero.getInstance().getControlPoints();\n            case 1:\n                return One.getInstance().getControlPoints();\n            case 2:\n                return Two.getInstance().getControlPoints();\n            case 3:\n                return Three.getInstance().getControlPoints();\n            case 4:\n                return Four.getInstance().getControlPoints();\n            case 5:\n                return Five.getInstance().getControlPoints();\n            case 6:\n                return Six.getInstance().getControlPoints();\n            case 7:\n                return Seven.getInstance().getControlPoints();\n            case 8:\n                return Eight.getInstance().getControlPoints();\n            case 9:\n                return Nine.getInstance().getControlPoints();\n            default:\n                throw new InvalidParameterException(\"Unsupported number requested\");\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/timely/model/core/Figure.java",
    "content": "/*\n* Copyright 2014 Adnan A M.\n\n* Licensed under the Apache License, Version 2.0 (the \"License\");\n* you may not use this file except in compliance with the License.\n* You may obtain a copy of the License at\n\n*   http://www.apache.org/licenses/LICENSE-2.0\n\n* Unless required by applicable law or agreed to in writing, software\n* distributed under the License is distributed on an \"AS IS\" BASIS,\n* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n* See the License for the specific language governing permissions and\n* limitations under the License.\n*/\n\npackage com.naman14.timber.timely.model.core;\n\n/**\n * Model class for cubic bezier figure\n */\npublic abstract class Figure {\n    public static final int NO_VALUE = -1;\n\n    protected int pointsCount = NO_VALUE;\n\n    //A chained sequence of points P0,P1,P2,P3/0,P1,P2,P3/0,...\n    protected float[][] controlPoints = null;\n\n    protected Figure(float[][] controlPoints) {\n        this.controlPoints = controlPoints;\n        this.pointsCount = (controlPoints.length + 2) / 3;\n    }\n\n    public int getPointsCount() {\n        return pointsCount;\n    }\n\n    public float[][] getControlPoints() {\n        return controlPoints;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/timely/model/number/Eight.java",
    "content": "package com.naman14.timber.timely.model.number;\n\nimport com.naman14.timber.timely.model.core.Figure;\n\npublic class Eight extends Figure {\n    private static final float[][] POINTS = {\n            {0.558011049723757f, 0.530386740331492f}, {0.243093922651934f, 0.524861878453039f}, {0.243093922651934f, 0.104972375690608f},\n            {0.558011049723757f, 0.104972375690608f}, {0.850828729281768f, 0.104972375690608f}, {0.850828729281768f, 0.530386740331492f},\n            {0.558011049723757f, 0.530386740331492f}, {0.243093922651934f, 0.530386740331492f}, {0.198895027624309f, 0.988950276243094f},\n            {0.558011049723757f, 0.988950276243094f}, {0.850828729281768f, 0.988950276243094f}, {0.850828729281768f, 0.530386740331492f},\n            {0.558011049723757f, 0.530386740331492f}\n    };\n\n    private static Eight INSTANCE = new Eight();\n\n    protected Eight() {\n        super(POINTS);\n    }\n\n    public static Eight getInstance() {\n        return INSTANCE;\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/timely/model/number/Five.java",
    "content": "package com.naman14.timber.timely.model.number;\n\nimport com.naman14.timber.timely.model.core.Figure;\n\npublic class Five extends Figure {\n    private static final float[][] POINTS = {\n            {0.806629834254144f, 0.110497237569061f}, {0.502762430939227f, 0.110497237569061f}, {0.502762430939227f, 0.110497237569061f},\n            {0.502762430939227f, 0.110497237569061f}, {0.397790055248619f, 0.430939226519337f}, {0.397790055248619f, 0.430939226519337f},\n            {0.397790055248619f, 0.430939226519337f}, {0.535911602209945f, 0.364640883977901f}, {0.801104972375691f, 0.469613259668508f},\n            {0.801104972375691f, 0.712707182320442f}, {0.773480662983425f, 1.01104972375691f}, {0.375690607734807f, 1.0939226519337f},\n            {0.248618784530387f, 0.850828729281768f}\n    };\n\n    private static Five INSTANCE = new Five();\n\n    protected Five() {\n        super(POINTS);\n    }\n\n    public static Five getInstance() {\n        return INSTANCE;\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/timely/model/number/Four.java",
    "content": "package com.naman14.timber.timely.model.number;\n\nimport com.naman14.timber.timely.model.core.Figure;\n\npublic class Four extends Figure {\n    private static final float[][] POINTS = {\n            {0.856353591160221f, 0.806629834254144f}, {0.856353591160221f, 0.806629834254144f}, {0.237569060773481f, 0.806629834254144f},\n            {0.237569060773481f, 0.806629834254144f}, {0.237569060773481f, 0.806629834254144f}, {0.712707182320442f, 0.138121546961326f},\n            {0.712707182320442f, 0.138121546961326f}, {0.712707182320442f, 0.138121546961326f}, {0.712707182320442f, 0.806629834254144f},\n            {0.712707182320442f, 0.806629834254144f}, {0.712707182320442f, 0.806629834254144f}, {0.712707182320442f, 0.988950276243094f},\n            {0.712707182320442f, 0.988950276243094f}\n\n    };\n\n    private static Four INSTANCE = new Four();\n\n    protected Four() {\n        super(POINTS);\n    }\n\n    public static Four getInstance() {\n        return INSTANCE;\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/timely/model/number/Nine.java",
    "content": "package com.naman14.timber.timely.model.number;\n\nimport com.naman14.timber.timely.model.core.Figure;\n\npublic class Nine extends Figure {\n    private static final float[][] POINTS = {\n            {0.80939226519337f, 0.552486187845304f}, {0.685082872928177f, 0.751381215469613f}, {0.298342541436464f, 0.740331491712707f},\n            {0.259668508287293f, 0.408839779005525f}, {0.232044198895028f, 0.0441988950276243f}, {0.81767955801105f, -0.0441988950276243f},\n            {0.850828729281768f, 0.408839779005525f}, {0.839779005524862f, 0.596685082872928f}, {0.712707182320442f, 0.668508287292818f},\n            {0.497237569060773f, 0.994475138121547f}, {0.497237569060773f, 0.994475138121547f}, {0.497237569060773f, 0.994475138121547f},\n            {0.497237569060773f, 0.994475138121547f}\n    };\n\n    private static Nine INSTANCE = new Nine();\n\n    protected Nine() {\n        super(POINTS);\n    }\n\n    public static Nine getInstance() {\n        return INSTANCE;\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/timely/model/number/Null.java",
    "content": "package com.naman14.timber.timely.model.number;\n\n\nimport com.naman14.timber.timely.model.core.Figure;\n\npublic class Null extends Figure {\n    private static final float[][] POINTS = {\n            {0.5f, 0.5f}, {0.5f, 0.5f}, {0.5f, 0.5f},\n            {0.5f, 0.5f}, {0.5f, 0.5f}, {0.5f, 0.5f},\n            {0.5f, 0.5f}, {0.5f, 0.5f}, {0.5f, 0.5f},\n            {0.5f, 0.5f}, {0.5f, 0.5f}, {0.5f, 0.5f},\n            {0.5f, 0.5f}\n    };\n\n    private static final Null INSTANCE = new Null();\n\n    protected Null() {\n        super(POINTS);\n    }\n\n    public static Null getInstance() {\n        return INSTANCE;\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/timely/model/number/One.java",
    "content": "package com.naman14.timber.timely.model.number;\n\nimport com.naman14.timber.timely.model.core.Figure;\n\npublic class One extends Figure {\n    private static final float[][] POINTS = {\n            {0.425414364640884f, 0.113259668508287f}, {0.425414364640884f, 0.113259668508287f}, {0.577348066298343f, 0.113259668508287f},\n            {0.577348066298343f, 0.113259668508287f}, {0.577348066298343f, 0.113259668508287f}, {0.577348066298343f, 1f},\n            {0.577348066298343f, 1f}, {0.577348066298343f, 1f}, {0.577348066298343f, 1f},\n            {0.577348066298343f, 1f}, {0.577348066298343f, 1f}, {0.577348066298343f, 1f},\n            {0.577348066298343f, 1f}\n    };\n\n    private static One INSTANCE = new One();\n\n    protected One() {\n        super(POINTS);\n    }\n\n    public static One getInstance() {\n        return INSTANCE;\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/timely/model/number/Seven.java",
    "content": "package com.naman14.timber.timely.model.number;\n\nimport com.naman14.timber.timely.model.core.Figure;\n\npublic class Seven extends Figure {\n    private static final float[][] POINTS = {\n            {0.259668508287293f, 0.116022099447514f}, {0.259668508287293f, 0.116022099447514f}, {0.87292817679558f, 0.116022099447514f},\n            {0.87292817679558f, 0.116022099447514f}, {0.87292817679558f, 0.116022099447514f}, {0.7f, 0.422099447513812f},\n            {0.7f, 0.422099447513812f}, {0.7f, 0.422099447513812f}, {0.477348066298343f, 0.733149171270718f},\n            {0.477348066298343f, 0.733149171270718f}, {0.477348066298343f, 0.733149171270718f}, {0.25414364640884f, 1f},\n            {0.25414364640884f, 1f}\n    };\n\n    private static Seven INSTANCE = new Seven();\n\n    protected Seven() {\n        super(POINTS);\n    }\n\n    public static Seven getInstance() {\n        return INSTANCE;\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/timely/model/number/Six.java",
    "content": "package com.naman14.timber.timely.model.number;\n\nimport com.naman14.timber.timely.model.core.Figure;\n\npublic class Six extends Figure {\n    private static final float[][] POINTS = {\n            {0.607734806629834f, 0.110497237569061f}, {0.607734806629834f, 0.110497237569061f}, {0.607734806629834f, 0.110497237569061f},\n            {0.607734806629834f, 0.110497237569061f}, {0.392265193370166f, 0.43646408839779f}, {0.265193370165746f, 0.50828729281768f},\n            {0.25414364640884f, 0.696132596685083f}, {0.287292817679558f, 1.13017127071823f}, {0.87292817679558f, 1.06077348066298f},\n            {0.845303867403315f, 0.696132596685083f}, {0.806629834254144f, 0.364640883977901f}, {0.419889502762431f, 0.353591160220994f},\n            {0.295580110497238f, 0.552486187845304f}\n    };\n\n    private static Six INSTANCE = new Six();\n\n    protected Six() {\n        super(POINTS);\n    }\n\n    public static Six getInstance() {\n        return INSTANCE;\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/timely/model/number/Three.java",
    "content": "package com.naman14.timber.timely.model.number;\n\nimport com.naman14.timber.timely.model.core.Figure;\n\npublic class Three extends Figure {\n    private static final float[][] POINTS = {\n            {0.361878453038674f, 0.298342541436464f}, {0.348066298342541f, 0.149171270718232f}, {0.475138121546961f, 0.0994475138121547f},\n            {0.549723756906077f, 0.0994475138121547f}, {0.861878453038674f, 0.0994475138121547f}, {0.806629834254144f, 0.530386740331492f},\n            {0.549723756906077f, 0.530386740331492f}, {0.87292817679558f, 0.530386740331492f}, {0.828729281767956f, 0.994475138121547f},\n            {0.552486187845304f, 0.994475138121547f}, {0.298342541436464f, 0.994475138121547f}, {0.30939226519337f, 0.828729281767956f},\n            {0.312154696132597f, 0.790055248618785f}\n    };\n\n    private static Three INSTANCE = new Three();\n\n    protected Three() {\n        super(POINTS);\n    }\n\n    public static Three getInstance() {\n        return INSTANCE;\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/timely/model/number/Two.java",
    "content": "package com.naman14.timber.timely.model.number;\n\nimport com.naman14.timber.timely.model.core.Figure;\n\npublic class Two extends Figure {\n    private static final float[][] POINTS = {\n            {0.30939226519337f, 0.331491712707182f}, {0.325966850828729f, 0.0110497237569061f}, {0.790055248618785f, 0.0220994475138122f},\n            {0.798342541436464f, 0.337016574585635f}, {0.798342541436464f, 0.430939226519337f}, {0.718232044198895f, 0.541436464088398f},\n            {0.596685082872928f, 0.674033149171271f}, {0.519337016574586f, 0.762430939226519f}, {0.408839779005525f, 0.856353591160221f},\n            {0.314917127071823f, 0.977900552486188f}, {0.314917127071823f, 0.977900552486188f}, {0.812154696132597f, 0.977900552486188f},\n            {0.812154696132597f, 0.977900552486188f}\n    };\n\n    private static Two INSTANCE = new Two();\n\n    protected Two() {\n        super(POINTS);\n    }\n\n    public static Two getInstance() {\n        return INSTANCE;\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/timely/model/number/Zero.java",
    "content": "package com.naman14.timber.timely.model.number;\n\nimport com.naman14.timber.timely.model.core.Figure;\n\npublic class Zero extends Figure {\n    private static final float[][] POINTS = {\n            {0.24585635359116f, 0.552486187845304f}, {0.24585635359116f, 0.331491712707182f}, {0.370165745856354f, 0.0994475138121547f},\n            {0.552486187845304f, 0.0994475138121547f}, {0.734806629834254f, 0.0994475138121547f}, {0.861878453038674f, 0.331491712707182f},\n            {0.861878453038674f, 0.552486187845304f}, {0.861878453038674f, 0.773480662983425f}, {0.734806629834254f, 0.994475138121547f},\n            {0.552486187845304f, 0.994475138121547f}, {0.370165745856354f, 0.994475138121547f}, {0.24585635359116f, 0.773480662983425f},\n            {0.24585635359116f, 0.552486187845304f}\n    };\n\n    private static Zero INSTANCE = new Zero();\n\n    protected Zero() {\n        super(POINTS);\n    }\n\n    public static Zero getInstance() {\n        return INSTANCE;\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/transition/PlayTransition.java",
    "content": "/*\n * Copyright (C) 2015 Naman Dwivedi\n *\n * Licensed under the GNU General Public License v3\n *\n * This is free software: you can redistribute it and/or modify it\n * under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;\n * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n * See the GNU General Public License for more details.\n */\n\npackage com.naman14.timber.transition;\n\nimport android.animation.Animator;\nimport android.animation.AnimatorListenerAdapter;\nimport android.animation.AnimatorSet;\nimport android.animation.ObjectAnimator;\nimport android.animation.TimeInterpolator;\nimport android.annotation.TargetApi;\nimport android.content.Context;\nimport android.content.res.TypedArray;\nimport android.graphics.Bitmap;\nimport android.graphics.Canvas;\nimport android.graphics.Color;\nimport android.graphics.Path;\nimport android.graphics.Rect;\nimport android.graphics.drawable.BitmapDrawable;\nimport android.graphics.drawable.ColorDrawable;\nimport android.graphics.drawable.Drawable;\nimport android.graphics.drawable.ShapeDrawable;\nimport android.graphics.drawable.shapes.OvalShape;\nimport android.transition.Transition;\nimport android.transition.TransitionValues;\nimport android.util.ArrayMap;\nimport android.util.AttributeSet;\nimport android.view.View;\nimport android.view.ViewAnimationUtils;\nimport android.view.ViewGroup;\n\nimport com.naman14.timber.R;\n\nimport java.util.ArrayList;\n\n@TargetApi(21)\npublic class PlayTransition extends Transition {\n    private static final String PROPERTY_BOUNDS = \"circleTransition:bounds\";\n    private static final String PROPERTY_POSITION = \"circleTransition:position\";\n    private static final String PROPERTY_IMAGE = \"circleTransition:image\";\n    private static final String[] TRANSITION_PROPERTIES = {\n            PROPERTY_BOUNDS,\n            PROPERTY_POSITION,\n    };\n\n    private int mColor = Color.parseColor(\"#6c1622\");\n\n    public PlayTransition() {\n    }\n\n    public PlayTransition(Context context, AttributeSet attrs) {\n        super(context, attrs);\n        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.PlayTransition);\n        setColor(a.getColor(R.styleable.PlayTransition_colorCT, getColor()));\n        a.recycle();\n    }\n\n    public void setColor(int color) {\n        mColor = color;\n    }\n\n    public int getColor() {\n        return mColor;\n    }\n\n    @Override\n    public String[] getTransitionProperties() {\n        return TRANSITION_PROPERTIES;\n    }\n\n    private void captureValues(TransitionValues transitionValues) {\n        final View view = transitionValues.view;\n        transitionValues.values.put(PROPERTY_BOUNDS, new Rect(\n                view.getLeft(), view.getTop(), view.getRight(), view.getBottom()\n        ));\n        int[] position = new int[2];\n        transitionValues.view.getLocationInWindow(position);\n        transitionValues.values.put(PROPERTY_POSITION, position);\n    }\n\n    @Override\n    public void captureEndValues(TransitionValues transitionValues) {\n        final View view = transitionValues.view;\n        if (view.getWidth() <= 0 || view.getHeight() <= 0) {\n            return;\n        }\n        captureValues(transitionValues);\n    }\n\n    @Override\n    public void captureStartValues(TransitionValues transitionValues) {\n        final View view = transitionValues.view;\n        if (view.getWidth() <= 0 || view.getHeight() <= 0) {\n            return;\n        }\n        captureValues(transitionValues);\n        Bitmap bitmap = Bitmap.createBitmap(view.getWidth(), view.getHeight(),\n                Bitmap.Config.ARGB_8888);\n        Canvas canvas = new Canvas(bitmap);\n        view.draw(canvas);\n        transitionValues.values.put(PROPERTY_IMAGE, bitmap);\n    }\n\n    @Override\n    public Animator createAnimator(final ViewGroup sceneRoot, TransitionValues startValues,\n                                   final TransitionValues endValues) {\n        if (startValues == null || endValues == null) {\n            return null;\n        }\n        Rect startBounds = (Rect) startValues.values.get(PROPERTY_BOUNDS);\n        Rect endBounds = (Rect) endValues.values.get(PROPERTY_BOUNDS);\n        if (startBounds == null || endBounds == null || startBounds.equals(endBounds)) {\n            return null;\n        }\n        Bitmap startImage = (Bitmap) startValues.values.get(PROPERTY_IMAGE);\n        Drawable startBackground = new BitmapDrawable(sceneRoot.getContext().getResources(), startImage);\n        final View startView = addViewToOverlay(sceneRoot, startImage.getWidth(),\n                startImage.getHeight(), startBackground);\n        Drawable shrinkingBackground = new ColorDrawable(mColor);\n        final View shrinkingView = addViewToOverlay(sceneRoot, startImage.getWidth(),\n                startImage.getHeight(), shrinkingBackground);\n\n        int[] sceneRootLoc = new int[2];\n        sceneRoot.getLocationInWindow(sceneRootLoc);\n        int[] startLoc = (int[]) startValues.values.get(PROPERTY_POSITION);\n        int startTranslationX = startLoc[0] - sceneRootLoc[0];\n        int startTranslationY = startLoc[1] - sceneRootLoc[1];\n\n        startView.setTranslationX(startTranslationX);\n        startView.setTranslationY(startTranslationY);\n        shrinkingView.setTranslationX(startTranslationX);\n        shrinkingView.setTranslationY(startTranslationY);\n\n        final View endView = endValues.view;\n        float startRadius = calculateMaxRadius(shrinkingView);\n        int minRadius = Math.min(calculateMinRadius(shrinkingView), calculateMinRadius(endView));\n\n        ShapeDrawable circleBackground = new ShapeDrawable(new OvalShape());\n        circleBackground.getPaint().setColor(mColor);\n        final View circleView = addViewToOverlay(sceneRoot, minRadius * 2, minRadius * 2,\n                circleBackground);\n        float circleStartX = startLoc[0] - sceneRootLoc[0] +\n                ((startView.getWidth() - circleView.getWidth()) / 2);\n        float circleStartY = startLoc[1] - sceneRootLoc[1] +\n                ((startView.getHeight() - circleView.getHeight()) / 2);\n        circleView.setTranslationX(circleStartX);\n        circleView.setTranslationY(circleStartY);\n\n        circleView.setVisibility(View.INVISIBLE);\n        shrinkingView.setAlpha(0f);\n        endView.setAlpha(0f);\n\n        Animator shrinkingAnimator = createCircularReveal(shrinkingView, startRadius, minRadius);\n        shrinkingAnimator.addListener(new AnimatorListenerAdapter() {\n\n            @Override\n            public void onAnimationEnd(Animator animation) {\n                shrinkingView.setVisibility(View.INVISIBLE);\n                startView.setVisibility(View.INVISIBLE);\n                circleView.setVisibility(View.VISIBLE);\n            }\n        });\n\n        Animator startAnimator = createCircularReveal(startView, startRadius, minRadius);\n        Animator fadeInAnimator = ObjectAnimator.ofFloat(shrinkingView, View.ALPHA, 0, 1);\n\n        AnimatorSet shrinkFadeSet = new AnimatorSet();\n        shrinkFadeSet.playTogether(shrinkingAnimator, startAnimator,\n                fadeInAnimator);\n\n        int[] endLoc = (int[]) endValues.values.get(PROPERTY_POSITION);\n        float circleEndX = endLoc[0] - sceneRootLoc[0] +\n                ((endView.getWidth() - circleView.getWidth()) / 2);\n        float circleEndY = endLoc[1] - sceneRootLoc[1] +\n                ((endView.getHeight() - circleView.getHeight()) / 2);\n        Path circlePath = getPathMotion().getPath(circleStartX, circleStartY, circleEndX,\n                circleEndY);\n        Animator circleAnimator = ObjectAnimator.ofFloat(circleView, View.TRANSLATION_X,\n                View.TRANSLATION_Y, circlePath);\n\n        final View growingView = addViewToOverlay(sceneRoot, endView.getWidth(),\n                endView.getHeight(), shrinkingBackground);\n        growingView.setVisibility(View.INVISIBLE);\n        float endTranslationX = endLoc[0] - sceneRootLoc[0];\n        float endTranslationY = endLoc[1] - sceneRootLoc[1];\n        growingView.setTranslationX(endTranslationX);\n        growingView.setTranslationY(endTranslationY);\n\n        float endRadius = calculateMaxRadius(endView);\n\n        circleAnimator.addListener(new AnimatorListenerAdapter() {\n            @Override\n            public void onAnimationEnd(Animator animation) {\n                circleView.setVisibility(View.INVISIBLE);\n                growingView.setVisibility(View.VISIBLE);\n                endView.setAlpha(1f);\n            }\n        });\n\n        Animator fadeOutAnimator = ObjectAnimator.ofFloat(growingView, View.ALPHA, 1, 0);\n        Animator endAnimator = createCircularReveal(endView, minRadius, endRadius);\n        Animator growingAnimator = createCircularReveal(growingView, minRadius, endRadius);\n\n        growingAnimator.addListener(new AnimatorListenerAdapter() {\n            @Override\n            public void onAnimationEnd(Animator animation) {\n                sceneRoot.getOverlay().remove(startView);\n                sceneRoot.getOverlay().remove(shrinkingView);\n                sceneRoot.getOverlay().remove(circleView);\n                sceneRoot.getOverlay().remove(growingView);\n            }\n        });\n        AnimatorSet growingFadeSet = new AnimatorSet();\n        growingFadeSet.playTogether(fadeOutAnimator, endAnimator, growingAnimator);\n\n        AnimatorSet animatorSet = new AnimatorSet();\n        animatorSet.playSequentially(shrinkFadeSet, circleAnimator, growingFadeSet);\n        return animatorSet;\n    }\n\n    private View addViewToOverlay(ViewGroup sceneRoot, int width, int height, Drawable background) {\n        View view = new NoOverlapView(sceneRoot.getContext());\n        view.setBackground(background);\n        int widthSpec = View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY);\n        int heightSpec = View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY);\n        view.measure(widthSpec, heightSpec);\n        view.layout(0, 0, width, height);\n        sceneRoot.getOverlay().add(view);\n        return view;\n    }\n\n    private Animator createCircularReveal(View view, float startRadius, float endRadius) {\n        int centerX = view.getWidth() / 2;\n        int centerY = view.getHeight() / 2;\n\n        Animator reveal = ViewAnimationUtils.createCircularReveal(view, centerX, centerY,\n                startRadius, endRadius);\n        return new NoPauseAnimator(reveal);\n    }\n\n    static float calculateMaxRadius(View view) {\n        float widthSquared = view.getWidth() * view.getWidth();\n        float heightSquared = view.getHeight() * view.getHeight();\n        float radius = (float) Math.sqrt(widthSquared + heightSquared) / 2;\n        return radius;\n    }\n\n    static int calculateMinRadius(View view) {\n        return Math.min(view.getWidth() / 2, view.getHeight() / 2);\n    }\n\n    private static class NoPauseAnimator extends Animator {\n        private final Animator mAnimator;\n        private final ArrayMap<AnimatorListener, AnimatorListener> mListeners =\n                new ArrayMap<AnimatorListener, AnimatorListener>();\n\n        public NoPauseAnimator(Animator animator) {\n            mAnimator = animator;\n        }\n\n        @Override\n        public void addListener(AnimatorListener listener) {\n            AnimatorListener wrapper = new AnimatorListenerWrapper(this, listener);\n            if (!mListeners.containsKey(listener)) {\n                mListeners.put(listener, wrapper);\n                mAnimator.addListener(wrapper);\n            }\n        }\n\n        @Override\n        public void cancel() {\n            mAnimator.cancel();\n        }\n\n        @Override\n        public void end() {\n            mAnimator.end();\n        }\n\n        @Override\n        public long getDuration() {\n            return mAnimator.getDuration();\n        }\n\n        @Override\n        public TimeInterpolator getInterpolator() {\n            return mAnimator.getInterpolator();\n        }\n\n        @Override\n        public ArrayList<AnimatorListener> getListeners() {\n            return new ArrayList<AnimatorListener>(mListeners.keySet());\n        }\n\n        @Override\n        public long getStartDelay() {\n            return mAnimator.getStartDelay();\n        }\n\n        @Override\n        public boolean isPaused() {\n            return mAnimator.isPaused();\n        }\n\n        @Override\n        public boolean isRunning() {\n            return mAnimator.isRunning();\n        }\n\n        @Override\n        public boolean isStarted() {\n            return mAnimator.isStarted();\n        }\n\n        @Override\n        public void removeAllListeners() {\n            mListeners.clear();\n            mAnimator.removeAllListeners();\n        }\n\n        @Override\n        public void removeListener(AnimatorListener listener) {\n            AnimatorListener wrapper = mListeners.get(listener);\n            if (wrapper != null) {\n                mListeners.remove(listener);\n                mAnimator.removeListener(wrapper);\n            }\n        }\n\n        @Override\n        public Animator setDuration(long durationMS) {\n            mAnimator.setDuration(durationMS);\n            return this;\n        }\n\n        @Override\n        public void setInterpolator(TimeInterpolator timeInterpolator) {\n            mAnimator.setInterpolator(timeInterpolator);\n        }\n\n        @Override\n        public void setStartDelay(long delayMS) {\n            mAnimator.setStartDelay(delayMS);\n        }\n\n        @Override\n        public void setTarget(Object target) {\n            mAnimator.setTarget(target);\n        }\n\n        @Override\n        public void setupEndValues() {\n            mAnimator.setupEndValues();\n        }\n\n        @Override\n        public void setupStartValues() {\n            mAnimator.setupStartValues();\n        }\n\n        @Override\n        public void start() {\n            mAnimator.start();\n        }\n    }\n\n    private static class AnimatorListenerWrapper implements Animator.AnimatorListener {\n        private final Animator mAnimator;\n        private final Animator.AnimatorListener mListener;\n\n        public AnimatorListenerWrapper(Animator animator, Animator.AnimatorListener listener) {\n            mAnimator = animator;\n            mListener = listener;\n        }\n\n        @Override\n        public void onAnimationStart(Animator animator) {\n            mListener.onAnimationStart(mAnimator);\n        }\n\n        @Override\n        public void onAnimationEnd(Animator animator) {\n            mListener.onAnimationEnd(mAnimator);\n        }\n\n        @Override\n        public void onAnimationCancel(Animator animator) {\n            mListener.onAnimationCancel(mAnimator);\n        }\n\n        @Override\n        public void onAnimationRepeat(Animator animator) {\n            mListener.onAnimationRepeat(mAnimator);\n        }\n    }\n\n    private static class NoOverlapView extends View {\n        public NoOverlapView(Context context) {\n            super(context);\n        }\n\n        @Override\n        public boolean hasOverlappingRendering() {\n            return false;\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/utils/ATEUtils.java",
    "content": "package com.naman14.timber.utils;\n\nimport android.annotation.TargetApi;\nimport android.app.Activity;\nimport android.app.ActivityManager;\nimport android.content.res.ColorStateList;\nimport android.graphics.Color;\nimport android.graphics.drawable.BitmapDrawable;\nimport android.os.Build;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport com.google.android.material.floatingactionbutton.FloatingActionButton;\nimport androidx.drawerlayout.widget.DrawerLayout;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.view.Window;\n\nimport com.afollestad.appthemeengine.Config;\nimport com.afollestad.appthemeengine.util.Util;\n\n/**\n * Created by naman on 02/01/16.\n */\npublic class ATEUtils {\n\n    public static void setStatusBarColor(Activity activity, String key, int color) {\n        try {\n            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {\n                final Window window = activity.getWindow();\n                if (Config.coloredStatusBar(activity, key))\n                    window.setStatusBarColor(getStatusBarColor(color));\n                else window.setStatusBarColor(Color.BLACK);\n                if (Config.coloredNavigationBar(activity, key))\n                    window.setNavigationBarColor(color);\n                else window.setNavigationBarColor(Color.BLACK);\n                applyTaskDescription(activity, key, color);\n            }\n            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {\n                final View decorView = activity.getWindow().getDecorView();\n                final int lightStatusMode = Config.lightStatusBarMode(activity, key);\n                boolean lightStatusEnabled = false;\n                switch (lightStatusMode) {\n                    case Config.LIGHT_STATUS_BAR_OFF:\n                    default:\n                        break;\n                    case Config.LIGHT_STATUS_BAR_ON:\n                        lightStatusEnabled = true;\n                        break;\n                    case Config.LIGHT_STATUS_BAR_AUTO:\n                        lightStatusEnabled = Util.isColorLight(color);\n                        break;\n                }\n\n                final int systemUiVisibility = decorView.getSystemUiVisibility();\n                if (lightStatusEnabled) {\n                    decorView.setSystemUiVisibility(systemUiVisibility | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);\n                } else {\n                    decorView.setSystemUiVisibility(systemUiVisibility & ~View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);\n                }\n            }\n\n            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {\n                final int color2 = Config.coloredStatusBar(activity, key) ?\n                        Color.TRANSPARENT : Color.BLACK;\n                activity.getWindow().setStatusBarColor(color2);\n            }\n            if (Config.coloredStatusBar(activity, key))\n                ((DrawerLayout) ((ViewGroup) activity.findViewById(android.R.id.content)).getChildAt(0)).setStatusBarBackgroundColor(getStatusBarColor(color));\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n    }\n\n    @TargetApi(Build.VERSION_CODES.LOLLIPOP)\n    private static void applyTaskDescription(@NonNull Activity activity, @Nullable String key, int color) {\n        // Sets color of entry in the system recents page\n        try {\n            ActivityManager.TaskDescription td = new ActivityManager.TaskDescription(\n                    (String) activity.getTitle(),\n                    ((BitmapDrawable) activity.getApplicationInfo().loadIcon(activity.getPackageManager())).getBitmap(),\n                    color);\n            activity.setTaskDescription(td);\n        } catch (Exception ignored) {\n\n        }\n    }\n\n    public static int getStatusBarColor(int primaryColor) {\n        float[] arrayOfFloat = new float[3];\n        Color.colorToHSV(primaryColor, arrayOfFloat);\n        arrayOfFloat[2] *= 0.9F;\n        return Color.HSVToColor(arrayOfFloat);\n    }\n\n    public static void setFabBackgroundTint(FloatingActionButton fab, int color) {\n        ColorStateList fabColorStateList = new ColorStateList(\n                new int[][]{\n                        new int[]{}\n                },\n                new int[]{\n                        color,\n                }\n        );\n        fab.setBackgroundTintList(fabColorStateList);\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/utils/Constants.java",
    "content": "/*\n * Copyright (C) 2015 Naman Dwivedi\n *\n * Licensed under the GNU General Public License v3\n *\n * This is free software: you can redistribute it and/or modify it\n * under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;\n * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n * See the GNU General Public License for more details.\n */\n\npackage com.naman14.timber.utils;\n\npublic class Constants {\n\n    public static final String NAVIGATE_LIBRARY = \"navigate_library\";\n    public static final String NAVIGATE_PLAYLIST = \"navigate_playlist\";\n    public static final String NAVIGATE_QUEUE = \"navigate_queue\";\n    public static final String NAVIGATE_ALBUM = \"navigate_album\";\n    public static final String NAVIGATE_ARTIST = \"navigate_artist\";\n    public static final String NAVIGATE_NOWPLAYING = \"navigate_nowplaying\";\n    public static final String NAVIGATE_LYRICS = \"navigate_lyrics\";\n\n    public static final String NAVIGATE_PLAYLIST_RECENT = \"navigate_playlist_recent\";\n    public static final String NAVIGATE_PLAYLIST_LASTADDED = \"navigate_playlist_lastadded\";\n    public static final String NAVIGATE_PLAYLIST_TOPTRACKS = \"navigate_playlist_toptracks\";\n    public static final String NAVIGATE_PLAYLIST_USERCREATED = \"navigate_playlist\";\n    public static final String PLAYLIST_FOREGROUND_COLOR = \"foreground_color\";\n    public static final String PLAYLIST_NAME = \"playlist_name\";\n\n    public static final String ALBUM_ID = \"album_id\";\n    public static final String ARTIST_ID = \"artist_id\";\n    public static final String PLAYLIST_ID = \"playlist_id\";\n\n    public static final String FRAGMENT_ID = \"fragment_id\";\n    public static final String NOWPLAYING_FRAGMENT_ID = \"nowplaying_fragment_id\";\n\n    public static final String WITH_ANIMATIONS = \"with_animations\";\n\n    public static final String TIMBER1 = \"timber1\";\n    public static final String TIMBER2 = \"timber2\";\n    public static final String TIMBER3 = \"timber3\";\n    public static final String TIMBER4 = \"timber4\";\n    public static final String TIMBER5 = \"timber5\";\n    public static final String TIMBER6 = \"timber6\";\n\n    public static final String NAVIGATE_SETTINGS = \"navigate_settings\";\n    public static final String NAVIGATE_SEARCH = \"navigate_search\";\n\n    public static final String SETTINGS_STYLE_SELECTOR_NOWPLAYING = \"style_selector_nowplaying\";\n    public static final String SETTINGS_STYLE_SELECTOR_ARTIST = \"style_selector_artist\";\n    public static final String SETTINGS_STYLE_SELECTOR_ALBUM = \"style_selector_album\";\n    public static final String SETTINGS_STYLE_SELECTOR_WHAT = \"style_selector_what\";\n\n    public static final String SETTINGS_STYLE_SELECTOR = \"settings_style_selector\";\n\n    public static final int PLAYLIST_VIEW_DEFAULT = 0;\n    public static final int PLAYLIST_VIEW_LIST = 1;\n    public static final int PLAYLIST_VIEW_GRID = 2;\n\n    public static final int PLAYLIST_ALBUM_ART_TAG = 888;\n    public static final int ACTION_DELETE_PLAYLIST = 111;\n\n\n    public static final String ACTIVITY_TRANSITION = \"activity_transition\";\n\n    public static final int CAST_SERVER_PORT = 8080;\n\n\n\n}\n"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/utils/FabAnimationUtils.java",
    "content": "/*\n * Copyright (C) 2015 Naman Dwivedi\n *\n * Licensed under the GNU General Public License v3\n *\n * This is free software: you can redistribute it and/or modify it\n * under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;\n * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n * See the GNU General Public License for more details.\n */\n\npackage com.naman14.timber.utils;\n\nimport android.os.Build;\nimport androidx.core.view.ViewCompat;\nimport androidx.core.view.ViewPropertyAnimatorListener;\nimport androidx.interpolator.view.animation.FastOutSlowInInterpolator;\nimport android.view.View;\nimport android.view.animation.Animation;\nimport android.view.animation.AnimationUtils;\nimport android.view.animation.Interpolator;\n\nimport com.naman14.timber.R;\n\npublic class FabAnimationUtils {\n\n    private static final long DEFAULT_DURATION = 200L;\n    private static final Interpolator FAST_OUT_SLOW_IN_INTERPOLATOR = new FastOutSlowInInterpolator();\n\n    public static void scaleIn(final View fab) {\n        scaleIn(fab, DEFAULT_DURATION, null);\n    }\n\n    public static void scaleIn(final View fab, long duration, final ScaleCallback callback) {\n        fab.setVisibility(View.VISIBLE);\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {\n            ViewCompat.animate(fab)\n                    .scaleX(1.0F)\n                    .scaleY(1.0F)\n                    .alpha(1.0F)\n                    .setDuration(duration)\n                    .setInterpolator(FAST_OUT_SLOW_IN_INTERPOLATOR)\n                    .withLayer()\n                    .setListener(new ViewPropertyAnimatorListener() {\n                        public void onAnimationStart(View view) {\n                            if (callback != null) callback.onAnimationStart();\n                        }\n\n                        public void onAnimationCancel(View view) {\n                        }\n\n                        public void onAnimationEnd(View view) {\n                            view.setVisibility(View.VISIBLE);\n                            if (callback != null) callback.onAnimationEnd();\n                        }\n                    }).start();\n        } else {\n            Animation anim = AnimationUtils.loadAnimation(fab.getContext(), R.anim.design_fab_out);\n            anim.setDuration(duration);\n            anim.setInterpolator(FAST_OUT_SLOW_IN_INTERPOLATOR);\n            anim.setAnimationListener(new Animation.AnimationListener() {\n                public void onAnimationStart(Animation animation) {\n                    if (callback != null) callback.onAnimationStart();\n                }\n\n                public void onAnimationEnd(Animation animation) {\n                    fab.setVisibility(View.VISIBLE);\n                    if (callback != null) callback.onAnimationEnd();\n                }\n\n                @Override\n                public void onAnimationRepeat(Animation animation) {\n                    //\n                }\n            });\n            fab.startAnimation(anim);\n        }\n    }\n\n    public static void scaleOut(final View fab) {\n        scaleOut(fab, DEFAULT_DURATION, null);\n    }\n\n    public static void scaleOut(final View fab, final ScaleCallback callback) {\n        scaleOut(fab, DEFAULT_DURATION, callback);\n    }\n\n    public static void scaleOut(final View fab, long duration, final ScaleCallback callback) {\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {\n            ViewCompat.animate(fab)\n                    .scaleX(0.0F)\n                    .scaleY(0.0F).alpha(0.0F)\n                    .setInterpolator(FAST_OUT_SLOW_IN_INTERPOLATOR)\n                    .setDuration(duration)\n                    .withLayer()\n                    .setListener(new ViewPropertyAnimatorListener() {\n                        public void onAnimationStart(View view) {\n                            if (callback != null) callback.onAnimationStart();\n                        }\n\n                        public void onAnimationCancel(View view) {\n                        }\n\n                        public void onAnimationEnd(View view) {\n                            view.setVisibility(View.INVISIBLE);\n                            if (callback != null) callback.onAnimationEnd();\n                        }\n                    }).start();\n        } else {\n            Animation anim = AnimationUtils.loadAnimation(fab.getContext(), R.anim.design_fab_out);\n            anim.setInterpolator(FAST_OUT_SLOW_IN_INTERPOLATOR);\n            anim.setDuration(duration);\n            anim.setAnimationListener(new Animation.AnimationListener() {\n                public void onAnimationStart(Animation animation) {\n                    if (callback != null) callback.onAnimationStart();\n                }\n\n                public void onAnimationEnd(Animation animation) {\n                    fab.setVisibility(View.INVISIBLE);\n                    if (callback != null) callback.onAnimationEnd();\n                }\n\n                @Override\n                public void onAnimationRepeat(Animation animation) {\n                    //\n                }\n            });\n            fab.startAnimation(anim);\n        }\n    }\n\n    public interface ScaleCallback {\n        void onAnimationStart();\n\n        void onAnimationEnd();\n    }\n\n\n}\n"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/utils/Helpers.java",
    "content": "/*\n * Copyright (C) 2015 Naman Dwivedi\n *\n * Licensed under the GNU General Public License v3\n *\n * This is free software: you can redistribute it and/or modify it\n * under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;\n * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n * See the GNU General Public License for more details.\n */\n\npackage com.naman14.timber.utils;\n\nimport android.app.Dialog;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.content.pm.PackageInfo;\nimport android.content.pm.PackageManager;\nimport android.graphics.Paint;\nimport android.net.Uri;\nimport android.os.Bundle;\nimport android.preference.PreferenceManager;\nimport androidx.fragment.app.DialogFragment;\nimport androidx.fragment.app.Fragment;\nimport androidx.fragment.app.FragmentManager;\nimport androidx.fragment.app.FragmentTransaction;\nimport androidx.appcompat.app.AlertDialog;\nimport androidx.appcompat.app.AppCompatActivity;\n\nimport android.text.Html;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.widget.LinearLayout;\nimport android.widget.TextView;\n\nimport com.afollestad.materialdialogs.MaterialDialog;\nimport com.naman14.timber.R;\n\npublic class Helpers {\n\n    public static void showAbout(AppCompatActivity activity) {\n        FragmentManager fm = activity.getSupportFragmentManager();\n        FragmentTransaction ft = fm.beginTransaction();\n        Fragment prev = fm.findFragmentByTag(\"dialog_about\");\n        if (prev != null) {\n            ft.remove(prev);\n        }\n        ft.addToBackStack(null);\n\n        new AboutDialog().show(ft, \"dialog_about\");\n    }\n\n    public static String getATEKey(Context context) {\n        return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(\"dark_theme\", false) ?\n                \"dark_theme\" : \"light_theme\";\n    }\n\n    public static class AboutDialog extends DialogFragment {\n\n        public AboutDialog() {\n        }\n\n        @Override\n        public Dialog onCreateDialog(Bundle savedInstanceState) {\n\n            String appName = \"Timber\";\n            try {\n                PackageInfo pInfo = getActivity().getPackageManager().getPackageInfo(getActivity().getPackageName(), 0);\n                String version = pInfo.versionName;\n                int versionCode = pInfo.versionCode;\n                appName = \"Timber \" + version;\n            } catch (PackageManager.NameNotFoundException e) {\n                e.printStackTrace();\n            }\n\n            return new MaterialDialog.Builder(getActivity())\n                    .title(appName)\n                    .content(Html.fromHtml(getString(R.string.about_dialog_body)))\n                    .positiveText(\"Dismiss\")\n                    .build();\n        }\n\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/utils/ImageUtils.java",
    "content": "/*\n * Copyright (C) 2015 Naman Dwivedi\n *\n * Licensed under the GNU General Public License v3\n *\n * This is free software: you can redistribute it and/or modify it\n * under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;\n * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n * See the GNU General Public License for more details.\n */\n\npackage com.naman14.timber.utils;\n\nimport android.content.Context;\nimport android.graphics.Bitmap;\nimport android.graphics.BitmapFactory;\nimport android.graphics.drawable.BitmapDrawable;\nimport android.graphics.drawable.Drawable;\nimport android.renderscript.Allocation;\nimport android.renderscript.Element;\nimport android.renderscript.RenderScript;\nimport android.renderscript.ScriptIntrinsicBlur;\nimport android.view.View;\nimport android.widget.ImageView;\n\nimport com.naman14.timber.R;\nimport com.naman14.timber.dataloaders.AlbumLoader;\nimport com.naman14.timber.lastfmapi.LastFmClient;\nimport com.naman14.timber.lastfmapi.callbacks.AlbumInfoListener;\nimport com.naman14.timber.lastfmapi.models.AlbumQuery;\nimport com.naman14.timber.lastfmapi.models.LastfmAlbum;\nimport com.naman14.timber.models.Album;\nimport com.nostra13.universalimageloader.core.DisplayImageOptions;\nimport com.nostra13.universalimageloader.core.ImageLoader;\nimport com.nostra13.universalimageloader.core.assist.FailReason;\nimport com.nostra13.universalimageloader.core.listener.ImageLoadingListener;\nimport com.nostra13.universalimageloader.core.listener.SimpleImageLoadingListener;\n\nimport java.io.ByteArrayInputStream;\nimport java.io.ByteArrayOutputStream;\n\npublic class ImageUtils {\n    private static final DisplayImageOptions lastfmDisplayImageOptions =\n                                                new DisplayImageOptions.Builder()\n                                                        .cacheInMemory(true)\n                                                        .cacheOnDisk(true)\n                                                        .showImageOnFail(R.drawable.ic_empty_music2)\n                                                        .build();\n\n    private static final DisplayImageOptions diskDisplayImageOptions =\n                                                new DisplayImageOptions.Builder()\n                                                        .cacheInMemory(true)\n                                                        .build();\n\n    public static void loadAlbumArtIntoView(final long albumId, final ImageView view) {\n        loadAlbumArtIntoView(albumId, view, new SimpleImageLoadingListener());\n    }\n\n    public static void loadAlbumArtIntoView(final long albumId, final ImageView view,\n                                            final ImageLoadingListener listener) {\n        if (PreferencesUtility.getInstance(view.getContext()).alwaysLoadAlbumImagesFromLastfm()) {\n            loadAlbumArtFromLastfm(albumId, view, listener);\n        } else {\n            loadAlbumArtFromDiskWithLastfmFallback(albumId, view, listener);\n        }\n    }\n\n    private static void loadAlbumArtFromDiskWithLastfmFallback(final long albumId, ImageView view,\n                                                               final ImageLoadingListener listener) {\n        ImageLoader.getInstance()\n                .displayImage(TimberUtils.getAlbumArtUri(albumId).toString(),\n                              view,\n                              diskDisplayImageOptions,\n                              new SimpleImageLoadingListener() {\n                                  @Override\n                                  public void onLoadingFailed(String imageUri, View view,\n                                                              FailReason failReason) {\n                                      loadAlbumArtFromLastfm(albumId, (ImageView) view, listener);\n                                      listener.onLoadingFailed(imageUri, view, failReason);\n                                  }\n\n                                  @Override\n                                  public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {\n                                      listener.onLoadingComplete(imageUri, view, loadedImage);\n                                  }\n                              });\n    }\n\n    private static void loadAlbumArtFromLastfm(long albumId, final ImageView albumArt, final ImageLoadingListener listener) {\n        Album album = AlbumLoader.getAlbum(albumArt.getContext(), albumId);\n        LastFmClient.getInstance(albumArt.getContext())\n                .getAlbumInfo(new AlbumQuery(album.title, album.artistName),\n                              new AlbumInfoListener() {\n                                  @Override\n                                  public void albumInfoSuccess(final LastfmAlbum album) {\n                                      if (album != null) {\n                                          ImageLoader.getInstance()\n                                                  .displayImage(album.mArtwork.get(4).mUrl,\n                                                                albumArt,\n                                                                lastfmDisplayImageOptions, new SimpleImageLoadingListener(){\n                                                              @Override\n                                                              public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {\n                                                                  listener.onLoadingComplete(imageUri, view, loadedImage);\n                                                              }\n\n                                                              @Override\n                                                              public void onLoadingFailed(String imageUri, View view, FailReason failReason) {\n                                                                  listener.onLoadingFailed(imageUri, view, failReason);\n                                                              }\n                                                          });\n                                      }\n                                  }\n\n                                  @Override\n                                  public void albumInfoFailed() { }\n                              });\n    }\n\n    public static Drawable createBlurredImageFromBitmap(Bitmap bitmap, Context context, int inSampleSize) {\n\n        RenderScript rs = RenderScript.create(context);\n        final BitmapFactory.Options options = new BitmapFactory.Options();\n        options.inSampleSize = inSampleSize;\n\n        ByteArrayOutputStream stream = new ByteArrayOutputStream();\n        bitmap.compress(Bitmap.CompressFormat.JPEG, 100, stream);\n        byte[] imageInByte = stream.toByteArray();\n        ByteArrayInputStream bis = new ByteArrayInputStream(imageInByte);\n        Bitmap blurTemplate = BitmapFactory.decodeStream(bis, null, options);\n\n        final Allocation input = Allocation.createFromBitmap(rs, blurTemplate);\n        final Allocation output = Allocation.createTyped(rs, input.getType());\n        final ScriptIntrinsicBlur script = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs));\n        script.setRadius(8f);\n        script.setInput(input);\n        script.forEach(output);\n        output.copyTo(blurTemplate);\n\n        return new BitmapDrawable(context.getResources(), blurTemplate);\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/utils/LyricsExtractor.java",
    "content": "package com.naman14.timber.utils;\n\nimport java.io.File;\nimport java.io.FileInputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.nio.charset.Charset;\nimport java.util.Arrays;\n\n/**\n * Created by Christoph Walcher on 03.12.16.\n */\n\npublic class LyricsExtractor {\n    public static String getLyrics(File file){\n        String filename = file.getName();\n        String fileending = filename.substring(filename.lastIndexOf('.')+1,filename.length()).toLowerCase();\n        try{\n            switch(fileending){\n                case \"mp3\":\n                    return getLyricsID3(file);\n                case \"mp4\":\n                case \"m4a\":\n                case \"aac\":\n                    return getLyricsMP4(file);\n                case \"ogg\":\n                case \"oga\":\n                    return getLyricsVorbis(file);\n            }\n        }catch(Exception e){}\n        return null;\n    }\n\n    private static int readOgg(byte[] buf, InputStream in, int bytesinpage, int skip) throws IOException {\n        int toread = skip!=-1?skip:buf.length;\n        int offset = 0;\n        while(toread>0){\n            if(bytesinpage==0){\n                byte magic[] = new byte[4];\n                in.read(magic);\n                if(!Arrays.equals(magic,new byte[]{'O','g','g','S'})){\n                    in.close();\n                    throw new IOException();\n                }\n                byte header[] = new byte[23];\n                in.read(header);\n                int count = header[22]& 0xFF;\n                while(count-->0){\n                    bytesinpage += in.read();\n                }\n            }\n            int read = toread;\n            if(bytesinpage-toread<0)read = bytesinpage;\n            if(skip != -1)\n                in.skip(read);\n            else\n                in.read(buf, offset, read);\n            offset += read;\n            toread -= read;\n            bytesinpage -= read;\n        }\n        return bytesinpage;\n    }\n\n    private static String getLyricsVorbis(File file) throws Exception{\n        FileInputStream in = new FileInputStream(file);\n        int bytesinpage = 0;\n        byte buffer[] = new byte[7];\n        bytesinpage = readOgg(buffer,in,bytesinpage,-1);\n        if(!Arrays.equals(buffer, new byte[]{1,'v','o','r','b','i','s'})){\n            in.close();\n            return null;\n        }\n        bytesinpage = readOgg(null,in,bytesinpage, 23);\n        bytesinpage = readOgg(buffer,in,bytesinpage,-1);\n        if(!Arrays.equals(buffer, new byte[]{3,'v','o','r','b','i','s'})){\n            in.close();\n            return null;\n        }\n        byte length[] = new byte[4];\n        bytesinpage = readOgg(length, in, bytesinpage,-1);\n        bytesinpage = readOgg(null, in, bytesinpage, byteArrayToInt(length));\n        bytesinpage = readOgg(length, in, bytesinpage,-1);\n        int count = byteArrayToIntLE(length);\n        while(count-->0){\n            bytesinpage = readOgg(length, in, bytesinpage,-1);\n            int comment_len = byteArrayToIntLE(length);\n            byte lyrics_tag[] = new byte[]{'L','Y','R','I','C','S','='};\n            if(comment_len<=lyrics_tag.length){\n                bytesinpage = readOgg(null, in, bytesinpage, comment_len);\n                continue;\n            }\n            byte comment_probe[] = new byte[lyrics_tag.length];\n            bytesinpage = readOgg(comment_probe, in, bytesinpage,-1);\n            if(Arrays.equals(comment_probe,lyrics_tag)){\n                byte  lyrics[] = new byte[comment_len - lyrics_tag.length];\n                readOgg(lyrics, in, bytesinpage,-1);\n                in.close();\n                return new String(lyrics);\n            }else{\n                bytesinpage = readOgg(null, in, bytesinpage, comment_len - lyrics_tag.length);\n            }\n        }\n        in.close();\n        return null;\n\n    }\n\n\n    private static String getLyricsMP4(File file) throws Exception{\n        FileInputStream in = new FileInputStream(file);\n\n        byte head[] = new byte[4];\n        in.read(head);\n        int len = byteArrayToInt(head);\n        in.read(head);\n        if (!Arrays.equals(head, new byte[]{'f','t','y','p'})){\n            in.close();\n            return null;\n        }\n        in.skip(len - 8);\n        final byte path[][] = new byte[][]{{'m','o','o','v'},{'u','d','t','a'},{'m','e','t','a'},{'i','l','s','t'},{(byte) '©','l','y','r'},{'d','a','t','a'}};\n        int atom_size = Integer.MAX_VALUE;\n        outter:\n        for(byte[] atom: path){\n            while(in.available()>0){\n                byte buffer[] = new byte[4];\n                in.read(buffer);\n                len = byteArrayToInt(buffer);\n                if(len==0)continue;\n                in.read(buffer);\n                if(len>atom_size){\n                    in.close();\n                    return null;\n                }\n                if (Arrays.equals(buffer, atom)){\n                    atom_size = len - 8;\n                    //Found Atom search next atom\n                    continue outter;\n                }else{\n                    //Skip Atom\n                    in.skip(len - 8);\n                    atom_size-=len;\n                }\n            }\n            in.close();\n            return null;\n        }\n        in.skip(8);\n        byte buffer[] = new byte[atom_size-8];\n        in.read(buffer);\n        in.close();\n        return new String(buffer);\n    }\n\n\n    private static String getLyricsID3(File file) throws Exception{\n        FileInputStream in = new FileInputStream(file);\n        byte buffer[] = new byte[4];\n        in.read(buffer, 0, 3);\n        if (!Arrays.equals(buffer, new byte[] { 'I', 'D', '3', 0 })){\n            in.close();\n            return null;\n        }\n\n        in.read(buffer, 0, 3);\n        boolean ext = (buffer[2] & (byte) 0b0100000) != 0;\n        in.read(buffer);\n        int len = buffer[3] & 0x7F | (buffer[2] & 0x7F) << 7 | (buffer[1] & 0x7F) << 14 | (buffer[0] & 0x7F) << 21;\n        if (ext) {\n            in.read(buffer); len-=4;\n            int ext_len = byteArrayToInt(buffer);\n            in.skip(ext_len); len -= ext_len;\n\n        }\n        while (len > 0) {\n            byte tag_name[] = new byte[4];\n            in.read(tag_name); len-=4;\n            if(tag_name[0]==0)break;\n            in.read(buffer); len -=4;\n            int tag_len = byteArrayToInt(buffer);\n            in.read(buffer,0,2); len-=2;\n            if(Arrays.equals(tag_name, new byte[] { 'U', 'S', 'L', 'T' })){\n                byte head[] = new byte[4];\n                in.read(head); len -= 4; tag_len -= 4;\n                while(in.read()!=0){\n                    len--;\n                    tag_len--;\n                }\n                if(head[0]==1)in.read();\n                byte tag_value[] = new byte[tag_len];\n                in.read(tag_value); len -= tag_len;\n                in.close();\n                Charset charset = null;\n                switch (head[0]){\n                    case 0: charset = Charset.forName(\"ISO-8859-1\");\n                        break;\n                    case 1: charset = Charset.forName(\"UTF-16\");\n                        break;\n                    case 2: charset = Charset.forName(\"UTF-16BE\");\n                        break;\n                    case 3: charset = Charset.forName(\"UTF-8\");\n                        break;\n                    default:\n                        return null;\n                }\n                return new String(tag_value,charset);\n\n            }else{\n                in.skip(tag_len); len -= tag_len;\n            }\n\n        }\n        in.close();\n        return null;\n\n    }\n\n    private static int byteArrayToInt(byte[] b) {\n        return b[3] & 0xFF | (b[2] & 0xFF) << 8 | (b[1] & 0xFF) << 16 | (b[0] & 0xFF) << 24;\n    }\n\n    private static int byteArrayToIntLE(byte[] b) {\n        return b[0] & 0xFF | (b[1] & 0xFF) << 8 | (b[2] & 0xFF) << 16 | (b[3] & 0xFF) << 24;\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/utils/LyricsLoader.java",
    "content": "package com.naman14.timber.utils;\n\nimport android.content.Context;\n\nimport com.squareup.okhttp.Cache;\nimport com.squareup.okhttp.OkHttpClient;\n\nimport java.io.BufferedReader;\nimport java.io.IOException;\nimport java.io.InputStreamReader;\nimport java.lang.reflect.Type;\nimport java.util.concurrent.TimeUnit;\n\nimport retrofit.Callback;\nimport retrofit.RequestInterceptor;\nimport retrofit.RestAdapter;\nimport retrofit.client.OkClient;\nimport retrofit.converter.ConversionException;\nimport retrofit.converter.Converter;\nimport retrofit.http.GET;\nimport retrofit.http.Headers;\nimport retrofit.http.Query;\nimport retrofit.mime.TypedInput;\nimport retrofit.mime.TypedOutput;\n\n/**\n * Created by Christoph Walcher on 03.12.16.\n */\n\npublic class LyricsLoader {\n    private static LyricsLoader instance = null;\n    private static final String BASE_API_URL = \"https://makeitpersonal.co\";\n    private static final long CACHE_SIZE = 1024 * 1024;\n    private LyricsRestService service;\n\n    public static LyricsLoader getInstance(Context con) {\n        if(instance==null)instance = new LyricsLoader(con);\n        return instance;\n    }\n\n    private LyricsLoader(Context con){\n        final OkHttpClient okHttpClient = new OkHttpClient();\n\n        okHttpClient.setCache(new Cache(con.getApplicationContext().getCacheDir(),\n                CACHE_SIZE));\n        okHttpClient.setConnectTimeout(20, TimeUnit.SECONDS);\n\n        RequestInterceptor interceptor = new RequestInterceptor() {\n            @Override\n            public void intercept(RequestFacade request) {\n                //7-days cache\n                request.addHeader(\"Cache-Control\", String.format(\"max-age=%d,max-stale=%d\", Integer.valueOf(60 * 60 * 24 * 7), Integer.valueOf(31536000)));\n            }\n        };\n\n        RestAdapter.Builder builder = new RestAdapter.Builder()\n                .setEndpoint(BASE_API_URL)\n                .setRequestInterceptor(interceptor)\n                .setConverter(new Converter() {\n                    @Override\n                    public Object fromBody(TypedInput arg0, Type arg1)\n                            throws ConversionException {\n\n                        try {\n                            BufferedReader br = null;\n                            StringBuilder sb = new StringBuilder();\n\n                            String line;\n\n                            br = new BufferedReader(new InputStreamReader(arg0.in()));\n                            while ((line = br.readLine()) != null) {\n                                sb.append(line);\n                                sb.append('\\n');\n                            }\n                            return sb.toString();\n\n                        } catch (IOException e) {\n                            e.printStackTrace();\n                            return null;\n                        }\n                    }\n\n                    @Override\n                    public TypedOutput toBody(Object arg0) {\n                        return null;\n                    }\n                })\n                .setClient(new OkClient(okHttpClient));\n\n         service = builder\n                .build()\n                .create(LyricsRestService.class);\n    }\n\n    public void getLyrics(String artist, String title, Callback<String> callback){\n        service.getLyrics(artist,title,callback);\n    }\n\n    private interface LyricsRestService {\n        @Headers(\"Cache-Control: public\")\n        @GET(\"/lyrics\")\n        void getLyrics(@Query(\"artist\") String artist, @Query(\"title\") String title, Callback<String> callback);\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/utils/NavigationUtils.java",
    "content": "/*\n * Copyright (C) 2015 Naman Dwivedi\n *\n * Licensed under the GNU General Public License v3\n *\n * This is free software: you can redistribute it and/or modify it\n * under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;\n * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n * See the GNU General Public License for more details.\n */\n\npackage com.naman14.timber.utils;\n\nimport android.annotation.TargetApi;\nimport android.app.Activity;\nimport android.app.ActivityOptions;\nimport android.content.ActivityNotFoundException;\nimport android.content.Context;\nimport android.content.Intent;\nimport androidx.fragment.app.Fragment;\nimport androidx.fragment.app.FragmentTransaction;\nimport androidx.appcompat.app.AppCompatActivity;\n\nimport android.util.Pair;\nimport android.view.View;\nimport android.widget.Toast;\n\nimport com.naman14.timber.R;\nimport com.naman14.timber.activities.MainActivity;\nimport com.naman14.timber.activities.NowPlayingActivity;\nimport com.naman14.timber.activities.PlaylistDetailActivity;\nimport com.naman14.timber.activities.SearchActivity;\nimport com.naman14.timber.activities.SettingsActivity;\nimport com.naman14.timber.fragments.AlbumDetailFragment;\nimport com.naman14.timber.fragments.ArtistDetailFragment;\nimport com.naman14.timber.nowplaying.Timber1;\nimport com.naman14.timber.nowplaying.Timber2;\nimport com.naman14.timber.nowplaying.Timber3;\nimport com.naman14.timber.nowplaying.Timber4;\nimport com.naman14.timber.nowplaying.Timber5;\nimport com.naman14.timber.nowplaying.Timber6;\n\nimport java.util.ArrayList;\n\npublic class NavigationUtils {\n\n    @TargetApi(21)\n    public static void navigateToAlbum(Activity context, long albumID, Pair<View, String> transitionViews) {\n\n        FragmentTransaction transaction = ((AppCompatActivity) context).getSupportFragmentManager().beginTransaction();\n        Fragment fragment;\n\n        transaction.setCustomAnimations(R.anim.activity_fade_in,\n                R.anim.activity_fade_out, R.anim.activity_fade_in, R.anim.activity_fade_out);\n        fragment = AlbumDetailFragment.newInstance(albumID, false, null);\n\n        transaction.hide(((AppCompatActivity) context).getSupportFragmentManager().findFragmentById(R.id.fragment_container));\n        transaction.add(R.id.fragment_container, fragment);\n        transaction.addToBackStack(null).commit();\n\n    }\n\n    @TargetApi(21)\n    public static void navigateToArtist(Activity context, long artistID, Pair<View, String> transitionViews) {\n\n        FragmentTransaction transaction = ((AppCompatActivity) context).getSupportFragmentManager().beginTransaction();\n        Fragment fragment;\n\n        transaction.setCustomAnimations(R.anim.activity_fade_in,\n                R.anim.activity_fade_out, R.anim.activity_fade_in, R.anim.activity_fade_out);\n        fragment = ArtistDetailFragment.newInstance(artistID, false, null);\n\n        transaction.hide(((AppCompatActivity) context).getSupportFragmentManager().findFragmentById(R.id.fragment_container));\n        transaction.add(R.id.fragment_container, fragment);\n        transaction.addToBackStack(null).commit();\n\n    }\n\n    public static void goToArtist(Context context, long artistId) {\n        Intent intent = new Intent(context, MainActivity.class);\n        intent.setAction(Constants.NAVIGATE_ARTIST);\n        intent.putExtra(Constants.ARTIST_ID, artistId);\n        context.startActivity(intent);\n    }\n\n    public static void goToAlbum(Context context, long albumId) {\n        Intent intent = new Intent(context, MainActivity.class);\n        intent.setAction(Constants.NAVIGATE_ALBUM);\n        intent.putExtra(Constants.ALBUM_ID, albumId);\n        context.startActivity(intent);\n    }\n\n    public static void goToLyrics(Context context) {\n        Intent intent = new Intent(context, MainActivity.class);\n        intent.setAction(Constants.NAVIGATE_LYRICS);\n        context.startActivity(intent);\n    }\n\n    public static void navigateToNowplaying(Activity context, boolean withAnimations) {\n\n        final Intent intent = new Intent(context, NowPlayingActivity.class);\n        context.startActivity(intent);\n    }\n\n    public static Intent getNowPlayingIntent(Context context) {\n\n        final Intent intent = new Intent(context, MainActivity.class);\n        intent.setAction(Constants.NAVIGATE_NOWPLAYING);\n        return intent;\n    }\n\n    public static void navigateToSettings(Activity context) {\n        final Intent intent = new Intent(context, SettingsActivity.class);\n        intent.setAction(Constants.NAVIGATE_SETTINGS);\n        context.startActivity(intent);\n    }\n\n    public static void navigateToSearch(Activity context) {\n        final Intent intent = new Intent(context, SearchActivity.class);\n        intent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);\n        intent.setAction(Constants.NAVIGATE_SEARCH);\n        context.startActivity(intent);\n    }\n\n\n    @TargetApi(21)\n    public static void navigateToPlaylistDetail(Activity context, String action, long firstAlbumID, String playlistName, int foregroundcolor, long playlistID, ArrayList<Pair> transitionViews) {\n        final Intent intent = new Intent(context, PlaylistDetailActivity.class);\n        intent.setAction(action);\n        intent.putExtra(Constants.PLAYLIST_ID, playlistID);\n        intent.putExtra(Constants.PLAYLIST_FOREGROUND_COLOR, foregroundcolor);\n        intent.putExtra(Constants.ALBUM_ID, firstAlbumID);\n        intent.putExtra(Constants.PLAYLIST_NAME, playlistName);\n        intent.putExtra(Constants.ACTIVITY_TRANSITION, transitionViews != null);\n\n        if (transitionViews != null && TimberUtils.isLollipop()) {\n            ActivityOptions options = ActivityOptions.makeSceneTransitionAnimation(context, transitionViews.get(0), transitionViews.get(1), transitionViews.get(2));\n            context.startActivityForResult(intent, Constants.ACTION_DELETE_PLAYLIST, options.toBundle());\n        } else {\n            context.startActivityForResult(intent, Constants.ACTION_DELETE_PLAYLIST);\n        }\n    }\n\n    public static void navigateToEqualizer(Activity context) {\n        try {\n            // The google MusicFX apps need to be started using startActivityForResult\n            context.startActivityForResult(TimberUtils.createEffectsIntent(), 666);\n        } catch (final ActivityNotFoundException notFound) {\n            Toast.makeText(context, \"Equalizer not found\", Toast.LENGTH_SHORT).show();\n        }\n    }\n\n    public static Intent getNavigateToStyleSelectorIntent(Activity context, String what) {\n        final Intent intent = new Intent(context, SettingsActivity.class);\n        intent.setAction(Constants.SETTINGS_STYLE_SELECTOR);\n        intent.putExtra(Constants.SETTINGS_STYLE_SELECTOR_WHAT, what);\n        return intent;\n    }\n\n    public static Fragment getFragmentForNowplayingID(String fragmentID) {\n        switch (fragmentID) {\n            case Constants.TIMBER1:\n                return new Timber1();\n            case Constants.TIMBER2:\n                return new Timber2();\n            case Constants.TIMBER3:\n                return new Timber3();\n            case Constants.TIMBER4:\n                return new Timber4();\n            case Constants.TIMBER5:\n                return new Timber5();\n            case Constants.TIMBER6:\n                return new Timber6();\n            default:\n                return new Timber1();\n        }\n\n    }\n\n    public static int getIntForCurrentNowplaying(String nowPlaying) {\n        switch (nowPlaying) {\n            case Constants.TIMBER1:\n                return 0;\n            case Constants.TIMBER2:\n                return 1;\n            case Constants.TIMBER3:\n                return 2;\n            case Constants.TIMBER4:\n                return 3;\n            case Constants.TIMBER5:\n                return 4;\n            case Constants.TIMBER6:\n                return 5;\n            default:\n                return 2;\n        }\n\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/utils/PreferencesUtility.java",
    "content": "/*\n * Copyright (C) 2015 Naman Dwivedi\n *\n * Licensed under the GNU General Public License v3\n *\n * This is free software: you can redistribute it and/or modify it\n * under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;\n * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n * See the GNU General Public License for more details.\n */\n\npackage com.naman14.timber.utils;\n\nimport android.content.Context;\nimport android.content.Intent;\nimport android.content.SharedPreferences;\nimport android.content.SharedPreferences.OnSharedPreferenceChangeListener;\nimport android.net.ConnectivityManager;\nimport android.net.NetworkInfo;\nimport android.os.Bundle;\nimport android.os.Environment;\nimport android.preference.PreferenceManager;\n\nimport com.naman14.timber.MusicPlayer;\nimport com.naman14.timber.MusicService;\n\npublic final class PreferencesUtility {\n\n    public static final String ARTIST_SORT_ORDER = \"artist_sort_order\";\n    public static final String ARTIST_SONG_SORT_ORDER = \"artist_song_sort_order\";\n    public static final String ARTIST_ALBUM_SORT_ORDER = \"artist_album_sort_order\";\n    public static final String ALBUM_SORT_ORDER = \"album_sort_order\";\n    public static final String ALBUM_SONG_SORT_ORDER = \"album_song_sort_order\";\n    public static final String SONG_SORT_ORDER = \"song_sort_order\";\n    private static final String NOW_PLAYING_SELECTOR = \"now_paying_selector\";\n    private static final String TOGGLE_ANIMATIONS = \"toggle_animations\";\n    private static final String TOGGLE_SYSTEM_ANIMATIONS = \"toggle_system_animations\";\n    private static final String TOGGLE_ARTIST_GRID = \"toggle_artist_grid\";\n    private static final String TOGGLE_ALBUM_GRID = \"toggle_album_grid\";\n    private static final String TOGGLE_PLAYLIST_VIEW = \"toggle_playlist_view\";\n    private static final String TOGGLE_SHOW_AUTO_PLAYLIST = \"toggle_show_auto_playlist\";\n    private static final String LAST_FOLDER = \"last_folder\";\n\n    private static final String TOGGLE_HEADPHONE_PAUSE = \"toggle_headphone_pause\";\n    private static final String THEME_PREFERNCE = \"theme_preference\";\n    private static final String START_PAGE_INDEX = \"start_page_index\";\n    private static final String START_PAGE_PREFERENCE_LASTOPENED = \"start_page_preference_latopened\";\n    private static final String NOW_PLAYNG_THEME_VALUE = \"now_playing_theme_value\";\n    private static final String TOGGLE_XPOSED_TRACKSELECTOR = \"toggle_xposed_trackselector\";\n    public static final String LAST_ADDED_CUTOFF = \"last_added_cutoff\";\n    public static final String GESTURES = \"gestures\";\n\n    public static final String FULL_UNLOCKED = \"full_version_unlocked\";\n\n    private static final String SHOW_LOCKSCREEN_ALBUMART = \"show_albumart_lockscreen\";\n    private static final String ARTIST_ALBUM_IMAGE = \"artist_album_image\";\n    private static final String ARTIST_ALBUM_IMAGE_MOBILE = \"artist_album_image_mobile\";\n    private static final String ALWAYS_LOAD_ALBUM_IMAGES_LASTFM = \"always_load_album_images_lastfm\";\n\n    private static PreferencesUtility sInstance;\n\n    private static SharedPreferences mPreferences;\n    private static Context context;\n    private ConnectivityManager connManager = null;\n\n    public PreferencesUtility(final Context context) {\n        this.context = context;\n        mPreferences = PreferenceManager.getDefaultSharedPreferences(context);\n    }\n\n    public static final PreferencesUtility getInstance(final Context context) {\n        if (sInstance == null) {\n            sInstance = new PreferencesUtility(context.getApplicationContext());\n        }\n        return sInstance;\n    }\n\n\n    public void setOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) {\n        mPreferences.registerOnSharedPreferenceChangeListener(listener);\n    }\n\n    public boolean isArtistsInGrid() {\n        return mPreferences.getBoolean(TOGGLE_ARTIST_GRID, true);\n    }\n\n    public void setArtistsInGrid(final boolean b) {\n        final SharedPreferences.Editor editor = mPreferences.edit();\n        editor.putBoolean(TOGGLE_ARTIST_GRID, b);\n        editor.apply();\n    }\n\n    public boolean isAlbumsInGrid() {\n        return mPreferences.getBoolean(TOGGLE_ALBUM_GRID, true);\n    }\n\n    public void setAlbumsInGrid(final boolean b) {\n        final SharedPreferences.Editor editor = mPreferences.edit();\n        editor.putBoolean(TOGGLE_ALBUM_GRID, b);\n        editor.apply();\n    }\n\n    public boolean pauseEnabledOnDetach() {\n        return mPreferences.getBoolean(TOGGLE_HEADPHONE_PAUSE, true);\n    }\n\n    public String getTheme() {\n        return mPreferences.getString(THEME_PREFERNCE, \"light\");\n    }\n\n    public int getStartPageIndex() {\n        return mPreferences.getInt(START_PAGE_INDEX, 0);\n    }\n\n    public void setStartPageIndex(final int index) {\n        final SharedPreferences.Editor editor = mPreferences.edit();\n        editor.putInt(START_PAGE_INDEX, index);\n        editor.apply();\n    }\n\n    public void setLastOpenedAsStartPagePreference(boolean preference) {\n        final SharedPreferences.Editor editor = mPreferences.edit();\n        editor.putBoolean(START_PAGE_PREFERENCE_LASTOPENED, preference);\n        editor.apply();\n    }\n\n    public boolean lastOpenedIsStartPagePreference() {\n        return mPreferences.getBoolean(START_PAGE_PREFERENCE_LASTOPENED, true);\n    }\n\n    private void setSortOrder(final String key, final String value) {\n        final SharedPreferences.Editor editor = mPreferences.edit();\n        editor.putString(key, value);\n        editor.apply();\n    }\n\n    public final String getArtistSortOrder() {\n        return mPreferences.getString(ARTIST_SORT_ORDER, SortOrder.ArtistSortOrder.ARTIST_A_Z);\n    }\n\n    public void setArtistSortOrder(final String value) {\n        setSortOrder(ARTIST_SORT_ORDER, value);\n    }\n\n    public final String getArtistSongSortOrder() {\n        return mPreferences.getString(ARTIST_SONG_SORT_ORDER,\n                SortOrder.ArtistSongSortOrder.SONG_A_Z);\n    }\n\n    public void setArtistSongSortOrder(final String value) {\n        setSortOrder(ARTIST_SONG_SORT_ORDER, value);\n    }\n\n    public final String getArtistAlbumSortOrder() {\n        return mPreferences.getString(ARTIST_ALBUM_SORT_ORDER,\n                SortOrder.ArtistAlbumSortOrder.ALBUM_A_Z);\n    }\n\n    public void setArtistAlbumSortOrder(final String value) {\n        setSortOrder(ARTIST_ALBUM_SORT_ORDER, value);\n    }\n\n    public final String getAlbumSortOrder() {\n        return mPreferences.getString(ALBUM_SORT_ORDER, SortOrder.AlbumSortOrder.ALBUM_A_Z);\n    }\n\n    public void setAlbumSortOrder(final String value) {\n        setSortOrder(ALBUM_SORT_ORDER, value);\n    }\n\n    public final String getAlbumSongSortOrder() {\n        return mPreferences.getString(ALBUM_SONG_SORT_ORDER,\n                SortOrder.AlbumSongSortOrder.SONG_TRACK_LIST);\n    }\n\n    public void setAlbumSongSortOrder(final String value) {\n        setSortOrder(ALBUM_SONG_SORT_ORDER, value);\n    }\n\n    public final String getSongSortOrder() {\n        return mPreferences.getString(SONG_SORT_ORDER, SortOrder.SongSortOrder.SONG_A_Z);\n    }\n\n    public void setSongSortOrder(final String value) {\n        setSortOrder(SONG_SORT_ORDER, value);\n    }\n\n    public final boolean didNowplayingThemeChanged() {\n        return mPreferences.getBoolean(NOW_PLAYNG_THEME_VALUE, false);\n    }\n\n    public void setNowPlayingThemeChanged(final boolean value) {\n        final SharedPreferences.Editor editor = mPreferences.edit();\n        editor.putBoolean(NOW_PLAYNG_THEME_VALUE, value);\n        editor.apply();\n    }\n\n    public boolean getXPosedTrackselectorEnabled() {\n        return mPreferences.getBoolean(TOGGLE_XPOSED_TRACKSELECTOR, false);\n    }\n\n    public int getPlaylistView() {\n        return mPreferences.getInt(TOGGLE_PLAYLIST_VIEW ,0);\n    }\n\n    public void setPlaylistView(final int i) {\n        final SharedPreferences.Editor editor = mPreferences.edit();\n        editor.putInt(TOGGLE_PLAYLIST_VIEW, i);\n        editor.apply();\n    }\n\n    public boolean showAutoPlaylist() {\n        return mPreferences.getBoolean(TOGGLE_SHOW_AUTO_PLAYLIST, true);\n    }\n\n    public void setToggleShowAutoPlaylist(final boolean b) {\n        final SharedPreferences.Editor editor = mPreferences.edit();\n        editor.putBoolean(TOGGLE_SHOW_AUTO_PLAYLIST, b);\n        editor.apply();\n    }\n\n    /** @parm lastAddedMillis timestamp in millis used as a cutoff for last added playlist */\n    public void setLastAddedCutoff(long lastAddedMillis) {\n        mPreferences.edit().putLong(LAST_ADDED_CUTOFF, lastAddedMillis).apply();\n    }\n\n    public long getLastAddedCutoff() {\n        return mPreferences.getLong(LAST_ADDED_CUTOFF, 0L);\n    }\n\n    public boolean isGesturesEnabled() {\n        return mPreferences.getBoolean(GESTURES, true);\n    }\n\n    public void storeLastFolder(String path) {\n        SharedPreferences.Editor editor = mPreferences.edit();\n        editor.putString(LAST_FOLDER, path);\n        editor.apply();\n    }\n\n    public String getLastFolder() {\n        return mPreferences.getString(LAST_FOLDER, Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC).getPath());\n    }\n\n    public boolean fullUnlocked() {\n        return mPreferences.getBoolean(FULL_UNLOCKED, false);\n    }\n\n    public void setFullUnlocked(final boolean b) {\n        final SharedPreferences.Editor editor = mPreferences.edit();\n        editor.putBoolean(FULL_UNLOCKED, b);\n        editor.apply();\n    }\n\n    public boolean getSetAlbumartLockscreen() {\n        return mPreferences.getBoolean(SHOW_LOCKSCREEN_ALBUMART, true);\n    }\n\n    public void updateService(Bundle extras) {\n        if(!MusicPlayer.isPlaybackServiceConnected())return;\n        final Intent intent = new Intent(context, MusicService.class);\n        intent.setAction(MusicService.UPDATE_PREFERENCES);\n        intent.putExtras(extras);\n        context.startService(intent);\n    }\n\n    public boolean loadArtistAndAlbumImages() {\n        if (mPreferences.getBoolean(ARTIST_ALBUM_IMAGE, true)) {\n            if (!mPreferences.getBoolean(ARTIST_ALBUM_IMAGE_MOBILE, true)) {\n                if (connManager == null) connManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);\n                NetworkInfo ni = connManager.getActiveNetworkInfo();\n                return ni != null && ni.getType() == ConnectivityManager.TYPE_WIFI;\n            }\n            return true;\n        }\n        return false;\n    }\n\n    public boolean alwaysLoadAlbumImagesFromLastfm() {\n        return mPreferences.getBoolean(ALWAYS_LOAD_ALBUM_IMAGES_LASTFM, false);\n    }\n}\n\n"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/utils/SlideTrackSwitcher.java",
    "content": "package com.naman14.timber.utils;\n\nimport androidx.annotation.NonNull;\nimport android.view.GestureDetector;\nimport android.view.MotionEvent;\nimport android.view.View;\n\nimport com.naman14.timber.MusicPlayer;\n\n/**\n * Created by nv95 on 02.11.16.\n */\n\npublic class SlideTrackSwitcher implements View.OnTouchListener {\n\n    private static final int SWIPE_THRESHOLD = 200;\n    private static final int SWIPE_VELOCITY_THRESHOLD = 100;\n\n    private GestureDetector mDetector;\n    private View mView;\n\n\n    public SlideTrackSwitcher() {\n    }\n\n    public void attach(@NonNull View v) {\n        mView = v;\n        mDetector = new GestureDetector(v.getContext(), new SwipeListener());\n        v.setOnTouchListener(this);\n    }\n\n    @Override\n    public boolean onTouch(View v, MotionEvent event) {\n        return mDetector.onTouchEvent(event);\n    }\n\n    private class SwipeListener extends GestureDetector.SimpleOnGestureListener {\n\n        @Override\n        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {\n            boolean result = false;\n            try {\n                float diffY = e2.getY() - e1.getY();\n                float diffX = e2.getX() - e1.getX();\n                if (Math.abs(diffX) > Math.abs(diffY)) {\n                    if (Math.abs(diffX) > SWIPE_THRESHOLD && Math.abs(velocityX) > SWIPE_VELOCITY_THRESHOLD) {\n                        if (diffX > 0) {\n                            onSwipeRight();\n                        } else {\n                            onSwipeLeft();\n                        }\n                    }\n                    result = true;\n                }\n                else if (Math.abs(diffY) > SWIPE_THRESHOLD && Math.abs(velocityY) > SWIPE_VELOCITY_THRESHOLD) {\n                    if (diffY > 0) {\n                        onSwipeBottom();\n                    } else {\n                        onSwipeTop();\n                    }\n                }\n                result = true;\n\n            } catch (Exception exception) {\n                exception.printStackTrace();\n            }\n            return result;\n        }\n\n        @Override\n        public boolean onDown(MotionEvent e) {\n            return true;\n        }\n\n        @Override\n        public boolean onDoubleTap(MotionEvent e) {\n            MusicPlayer.playOrPause();\n            return true;\n        }\n\n        @Override\n        public boolean onSingleTapConfirmed(MotionEvent e) {\n            onClick();\n            return super.onSingleTapConfirmed(e);\n        }\n    }\n\n    public void onSwipeRight() {\n        MusicPlayer.previous(mView.getContext(), true);\n    }\n\n    public void onSwipeLeft() {\n        MusicPlayer.next();\n    }\n\n    public void onSwipeTop() {\n    }\n\n    public void onSwipeBottom() {\n    }\n\n    public void onClick() {\n\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/utils/SortOrder.java",
    "content": "/*\n * Copyright (C) 2012 Andrew Neal\n * Copyright (C) 2014 The CyanogenMod Project\n * Licensed under the Apache License, Version 2.0\n * (the \"License\"); you may not use this file except in compliance with the\n * License. You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law\n * or agreed to in writing, software distributed under the License is\n * distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n * KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\npackage com.naman14.timber.utils;\n\nimport android.provider.MediaStore;\n\n/**\n * Holds all of the sort orders for each list type.\n *\n * @author Andrew Neal (andrewdneal@gmail.com)\n */\npublic final class SortOrder {\n\n    /**\n     * This class is never instantiated\n     */\n    public SortOrder() {\n    }\n\n    /**\n     * Artist sort order entries.\n     */\n    public interface ArtistSortOrder {\n        /* Artist sort order A-Z */\n        String ARTIST_A_Z = MediaStore.Audio.Artists.DEFAULT_SORT_ORDER;\n\n        /* Artist sort order Z-A */\n        String ARTIST_Z_A = ARTIST_A_Z + \" DESC\";\n\n        /* Artist sort order number of songs */\n        String ARTIST_NUMBER_OF_SONGS = MediaStore.Audio.Artists.NUMBER_OF_TRACKS\n                + \" DESC\";\n\n        /* Artist sort order number of albums */\n        String ARTIST_NUMBER_OF_ALBUMS = MediaStore.Audio.Artists.NUMBER_OF_ALBUMS\n                + \" DESC\";\n    }\n\n    /**\n     * Album sort order entries.\n     */\n    public interface AlbumSortOrder {\n        /* Album sort order A-Z */\n        String ALBUM_A_Z = MediaStore.Audio.Albums.DEFAULT_SORT_ORDER;\n\n        /* Album sort order Z-A */\n        String ALBUM_Z_A = ALBUM_A_Z + \" DESC\";\n\n        /* Album sort order songs */\n        String ALBUM_NUMBER_OF_SONGS = MediaStore.Audio.Albums.NUMBER_OF_SONGS\n                + \" DESC\";\n\n        /* Album sort order artist */\n        String ALBUM_ARTIST = MediaStore.Audio.Albums.ARTIST;\n\n        /* Album sort order year */\n        String ALBUM_YEAR = MediaStore.Audio.Albums.FIRST_YEAR + \" DESC\";\n\n    }\n\n    /**\n     * Song sort order entries.\n     */\n    public interface SongSortOrder {\n        /* Song sort order A-Z */\n        String SONG_A_Z = MediaStore.Audio.Media.DEFAULT_SORT_ORDER;\n\n        /* Song sort order Z-A */\n        String SONG_Z_A = SONG_A_Z + \" DESC\";\n\n        /* Song sort order artist */\n        String SONG_ARTIST = MediaStore.Audio.Media.ARTIST;\n\n        /* Song sort order album */\n        String SONG_ALBUM = MediaStore.Audio.Media.ALBUM;\n\n        /* Song sort order year */\n        String SONG_YEAR = MediaStore.Audio.Media.YEAR + \" DESC\";\n\n        /* Song sort order duration */\n        String SONG_DURATION = MediaStore.Audio.Media.DURATION + \" DESC\";\n\n        /* Song sort order date */\n        String SONG_DATE = MediaStore.Audio.Media.DATE_ADDED + \" DESC\";\n\n        /* Song sort order filename */\n        String SONG_FILENAME = MediaStore.Audio.Media.DATA;\n    }\n\n    /**\n     * Album song sort order entries.\n     */\n    public interface AlbumSongSortOrder {\n        /* Album song sort order A-Z */\n        String SONG_A_Z = MediaStore.Audio.Media.DEFAULT_SORT_ORDER;\n\n        /* Album song sort order Z-A */\n        String SONG_Z_A = SONG_A_Z + \" DESC\";\n\n        /* Album song sort order track list */\n        String SONG_TRACK_LIST = MediaStore.Audio.Media.TRACK + \", \"\n                + MediaStore.Audio.Media.DEFAULT_SORT_ORDER;\n\n        /* Album song sort order duration */\n        String SONG_DURATION = SongSortOrder.SONG_DURATION;\n\n        /* Album Song sort order year */\n        String SONG_YEAR = MediaStore.Audio.Media.YEAR + \" DESC\";\n\n        /* Album song sort order filename */\n        String SONG_FILENAME = SongSortOrder.SONG_FILENAME;\n    }\n\n    /**\n     * Artist song sort order entries.\n     */\n    public interface ArtistSongSortOrder {\n        /* Artist song sort order A-Z */\n        String SONG_A_Z = MediaStore.Audio.Media.DEFAULT_SORT_ORDER;\n\n        /* Artist song sort order Z-A */\n        String SONG_Z_A = SONG_A_Z + \" DESC\";\n\n        /* Artist song sort order album */\n        String SONG_ALBUM = MediaStore.Audio.Media.ALBUM;\n\n        /* Artist song sort order year */\n        String SONG_YEAR = MediaStore.Audio.Media.YEAR + \" DESC\";\n\n        /* Artist song sort order duration */\n        String SONG_DURATION = MediaStore.Audio.Media.DURATION + \" DESC\";\n\n        /* Artist song sort order date */\n        String SONG_DATE = MediaStore.Audio.Media.DATE_ADDED + \" DESC\";\n\n        /* Artist song sort order filename */\n        String SONG_FILENAME = SongSortOrder.SONG_FILENAME;\n    }\n\n    /**\n     * Artist album sort order entries.\n     */\n    public interface ArtistAlbumSortOrder {\n        /* Artist album sort order A-Z */\n        String ALBUM_A_Z = MediaStore.Audio.Albums.DEFAULT_SORT_ORDER;\n\n        /* Artist album sort order Z-A */\n        String ALBUM_Z_A = ALBUM_A_Z + \" DESC\";\n\n        /* Artist album sort order songs */\n        String ALBUM_NUMBER_OF_SONGS = MediaStore.Audio.Artists.Albums.NUMBER_OF_SONGS\n                + \" DESC\";\n\n        /* Artist album sort order year */\n        String ALBUM_YEAR = MediaStore.Audio.Artists.Albums.FIRST_YEAR\n                + \" DESC\";\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/utils/TimberUtils.java",
    "content": "/*\n * Copyright (C) 2015 Naman Dwivedi\n *\n * Licensed under the GNU General Public License v3\n *\n * This is free software: you can redistribute it and/or modify it\n * under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.\n *\n * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;\n * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n * See the GNU General Public License for more details.\n */\n\npackage com.naman14.timber.utils;\n\nimport android.app.Activity;\nimport android.content.ContentResolver;\nimport android.content.ContentUris;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.content.pm.PackageManager;\nimport android.database.Cursor;\nimport android.graphics.Color;\nimport android.media.MediaMetadataRetriever;\nimport android.media.audiofx.AudioEffect;\nimport android.net.Uri;\nimport android.os.Build;\nimport android.provider.BaseColumns;\nimport android.provider.MediaStore;\nimport androidx.annotation.NonNull;\n\nimport android.util.Log;\nimport android.util.TypedValue;\nimport android.widget.Toast;\n\nimport com.afollestad.materialdialogs.DialogAction;\nimport com.afollestad.materialdialogs.MaterialDialog;\nimport com.naman14.timber.MusicPlayer;\nimport com.naman14.timber.R;\nimport com.naman14.timber.adapters.BaseQueueAdapter;\nimport com.naman14.timber.adapters.BaseSongAdapter;\nimport com.naman14.timber.provider.RecentStore;\nimport com.naman14.timber.provider.SongPlayCount;\n\nimport java.io.File;\nimport java.net.InetAddress;\nimport java.net.NetworkInterface;\nimport java.util.Collections;\nimport java.util.List;\n\npublic class TimberUtils {\n\n    public static final String MUSIC_ONLY_SELECTION = MediaStore.Audio.AudioColumns.IS_MUSIC + \"=1\"\n            + \" AND \" + MediaStore.Audio.AudioColumns.TITLE + \" != ''\";\n\n    public static boolean isOreo() {\n        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O;\n    }\n\n    public static boolean isMarshmallow() {\n        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M;\n    }\n\n    public static boolean isLollipop() {\n        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP;\n    }\n\n\n    public static boolean isJellyBeanMR2() {\n        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2;\n    }\n\n    public static boolean isJellyBean() {\n        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN;\n    }\n\n    public static boolean isJellyBeanMR1() {\n        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1;\n    }\n\n    public static Uri getAlbumArtUri(long albumId) {\n        return ContentUris.withAppendedId(Uri.parse(\"content://media/external/audio/albumart\"), albumId);\n    }\n    public static String getAlbumArtForFile(String filePath) {\n        MediaMetadataRetriever mmr = new MediaMetadataRetriever();\n        mmr.setDataSource(filePath);\n\n        return mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ALBUM);\n    }\n\n    public static final String makeCombinedString(final Context context, final String first,\n                                                  final String second) {\n        final String formatter = context.getResources().getString(R.string.combine_two_strings);\n        return String.format(formatter, first, second);\n    }\n\n    public static final String makeLabel(final Context context, final int pluralInt,\n                                         final int number) {\n        return context.getResources().getQuantityString(pluralInt, number, number);\n    }\n\n    public static final String makeShortTimeString(final Context context, long secs) {\n        long hours, mins;\n\n        hours = secs / 3600;\n        secs %= 3600;\n        mins = secs / 60;\n        secs %= 60;\n\n        final String durationFormat = context.getResources().getString(\n                hours == 0 ? R.string.durationformatshort : R.string.durationformatlong);\n        return String.format(durationFormat, hours, mins, secs);\n    }\n\n    public static int getActionBarHeight(Context context) {\n        int mActionBarHeight;\n        TypedValue mTypedValue = new TypedValue();\n\n        context.getTheme().resolveAttribute(R.attr.actionBarSize, mTypedValue, true);\n\n        mActionBarHeight = TypedValue.complexToDimensionPixelSize(mTypedValue.data, context.getResources().getDisplayMetrics());\n\n        return mActionBarHeight;\n    }\n\n    public static final int getSongCountForPlaylist(final Context context, final long playlistId) {\n        Cursor c = context.getContentResolver().query(\n                MediaStore.Audio.Playlists.Members.getContentUri(\"external\", playlistId),\n                new String[]{BaseColumns._ID}, MUSIC_ONLY_SELECTION, null, null);\n\n        if (c != null) {\n            int count = 0;\n            if (c.moveToFirst()) {\n                count = c.getCount();\n            }\n            c.close();\n            c = null;\n            return count;\n        }\n\n        return 0;\n    }\n\n    public static boolean hasEffectsPanel(final Activity activity) {\n        final PackageManager packageManager = activity.getPackageManager();\n        return packageManager.resolveActivity(createEffectsIntent(),\n                PackageManager.MATCH_DEFAULT_ONLY) != null;\n    }\n\n    public static Intent createEffectsIntent() {\n        final Intent effects = new Intent(AudioEffect.ACTION_DISPLAY_AUDIO_EFFECT_CONTROL_PANEL);\n        effects.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, MusicPlayer.getAudioSessionId());\n        return effects;\n    }\n\n    public static int getBlackWhiteColor(int color) {\n        double darkness = 1 - (0.299 * Color.red(color) + 0.587 * Color.green(color) + 0.114 * Color.blue(color)) / 255;\n        if (darkness >= 0.5) {\n            return Color.WHITE;\n        } else return Color.BLACK;\n    }\n\n    public enum IdType {\n        NA(0),\n        Artist(1),\n        Album(2),\n        Playlist(3);\n\n        public final int mId;\n\n        IdType(final int id) {\n            mId = id;\n        }\n\n        public static IdType getTypeById(int id) {\n            for (IdType type : values()) {\n                if (type.mId == id) {\n                    return type;\n                }\n            }\n\n            throw new IllegalArgumentException(\"Unrecognized id: \" + id);\n        }\n    }\n\n    public enum PlaylistType {\n        LastAdded(-1, R.string.playlist_last_added),\n        RecentlyPlayed(-2, R.string.playlist_recently_played),\n        TopTracks(-3, R.string.playlist_top_tracks);\n\n        public long mId;\n        public int mTitleId;\n\n        PlaylistType(long id, int titleId) {\n            mId = id;\n            mTitleId = titleId;\n        }\n\n        public static PlaylistType getTypeById(long id) {\n            for (PlaylistType type : PlaylistType.values()) {\n                if (type.mId == id) {\n                    return type;\n                }\n            }\n\n            return null;\n        }\n    }\n\n    public static void removeFromPlaylist(final Context context, final long id,\n                                          final long playlistId) {\n        final Uri uri = MediaStore.Audio.Playlists.Members.getContentUri(\"external\", playlistId);\n        final ContentResolver resolver = context.getContentResolver();\n        resolver.delete(uri, MediaStore.Audio.Playlists.Members.AUDIO_ID + \" = ? \", new String[]{\n                Long.toString(id)\n        });\n    }\n\n    public static void clearTopTracks(Context context) {\n        SongPlayCount.getInstance(context).deleteAll();\n    }\n\n    public static void clearRecent(Context context) {\n        RecentStore.getInstance(context).deleteAll();\n    }\n\n    public static void clearLastAdded(Context context) {\n        PreferencesUtility.getInstance(context)\n                .setLastAddedCutoff(System.currentTimeMillis());\n    }\n\n    public static void showDeleteDialog(final Context context, final String name, final long[] list, final BaseSongAdapter adapter, final int pos) {\n\n        new MaterialDialog.Builder(context)\n                .title(\"Delete song?\")\n                .content(\"Are you sure you want to delete \" + name + \" ?\")\n                .positiveText(\"Delete\")\n                .negativeText(\"Cancel\")\n                .onPositive(new MaterialDialog.SingleButtonCallback() {\n                    @Override\n                    public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) {\n                        TimberUtils.deleteTracks(context, list);\n                        adapter.removeSongAt(pos);\n                        adapter.notifyItemRemoved(pos);\n                        adapter.notifyItemRangeChanged(pos, adapter.getItemCount());\n                    }\n                })\n                .onNegative(new MaterialDialog.SingleButtonCallback() {\n                    @Override\n                    public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) {\n                        dialog.dismiss();\n                    }\n                })\n                .show();\n    }\n\n    public static void showDeleteDialog(final Context context, final String name, final long[] list, final BaseQueueAdapter qAdapter, final int pos) {\n\n        new MaterialDialog.Builder(context)\n                .title(\"Delete song?\")\n                .content(\"Are you sure you want to delete \" + name + \" ?\")\n                .positiveText(\"Delete\")\n                .negativeText(\"Cancel\")\n                .onPositive(new MaterialDialog.SingleButtonCallback() {\n                    @Override\n                    public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) {\n                        TimberUtils.deleteTracks(context, list);\n                        qAdapter.removeSongAt(pos);\n                        qAdapter.notifyItemRemoved(pos);\n                        qAdapter.notifyItemRangeChanged(pos, qAdapter.getItemCount());\n                    }\n                })\n                .onNegative(new MaterialDialog.SingleButtonCallback() {\n                    @Override\n                    public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) {\n                        dialog.dismiss();\n                    }\n                })\n                .show();\n    }\n\n\n    public static void deleteTracks(final Context context, final long[] list) {\n        final String[] projection = new String[]{\n                BaseColumns._ID, MediaStore.MediaColumns.DATA, MediaStore.Audio.AudioColumns.ALBUM_ID\n        };\n        final StringBuilder selection = new StringBuilder();\n        selection.append(BaseColumns._ID + \" IN (\");\n        for (int i = 0; i < list.length; i++) {\n            selection.append(list[i]);\n            if (i < list.length - 1) {\n                selection.append(\",\");\n            }\n        }\n        selection.append(\")\");\n        final Cursor c = context.getContentResolver().query(\n                MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, projection, selection.toString(),\n                null, null);\n        if (c != null) {\n            // Step 1: Remove selected tracks from the current playlist, as well\n            // as from the album art cache\n            c.moveToFirst();\n            while (!c.isAfterLast()) {\n                // Remove from current playlist\n                final long id = c.getLong(0);\n                MusicPlayer.removeTrack(id);\n                // Remove the track from the play count\n                SongPlayCount.getInstance(context).removeItem(id);\n                // Remove any items in the recents database\n                RecentStore.getInstance(context).removeItem(id);\n                c.moveToNext();\n            }\n\n            // Step 2: Remove selected tracks from the database\n            context.getContentResolver().delete(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,\n                    selection.toString(), null);\n\n            // Step 3: Remove files from card\n            c.moveToFirst();\n            while (!c.isAfterLast()) {\n                final String name = c.getString(1);\n                final File f = new File(name);\n                try { // File.delete can throw a security exception\n                    if (!f.delete()) {\n                        // I'm not sure if we'd ever get here (deletion would\n                        // have to fail, but no exception thrown)\n                        Log.e(\"MusicUtils\", \"Failed to delete file \" + name);\n                    }\n                    c.moveToNext();\n                } catch (final SecurityException ex) {\n                    c.moveToNext();\n                }\n            }\n            c.close();\n        }\n\n        final String message = makeLabel(context, R.plurals.NNNtracksdeleted, list.length);\n\n        Toast.makeText(context, message, Toast.LENGTH_SHORT).show();\n        context.getContentResolver().notifyChange(Uri.parse(\"content://media\"), null);\n        MusicPlayer.refresh();\n    }\n\n    public static void shareTrack(final Context context, long id) {\n\n        try {\n            Intent share = new Intent(Intent.ACTION_SEND);\n            share.setType(\"audio/*\");\n            share.putExtra(Intent.EXTRA_STREAM, getSongUri(context, id));\n            context.startActivity(Intent.createChooser(share, \"Share\"));\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n    }\n\n    public static Uri getSongUri(Context context, long id) {\n        final String[] projection = new String[]{\n                BaseColumns._ID, MediaStore.MediaColumns.DATA, MediaStore.Audio.AudioColumns.ALBUM_ID\n        };\n        final StringBuilder selection = new StringBuilder();\n        selection.append(BaseColumns._ID + \" IN (\");\n        selection.append(id);\n        selection.append(\")\");\n        final Cursor c = context.getContentResolver().query(\n                MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, projection, selection.toString(),\n                null, null);\n\n        if (c == null) {\n            return null;\n        }\n        c.moveToFirst();\n\n\n        try {\n\n            Uri uri = Uri.parse(c.getString(1));\n            c.close();\n\n            return uri;\n        } catch (Exception e) {\n            e.printStackTrace();\n            return null;\n        }\n    }\n\n    public static String getIPAddress(boolean useIPv4) {\n        try {\n            List<NetworkInterface> interfaces = Collections.list(NetworkInterface.getNetworkInterfaces());\n            for (NetworkInterface intf : interfaces) {\n                List<InetAddress> addrs = Collections.list(intf.getInetAddresses());\n                for (InetAddress addr : addrs) {\n                    if (!addr.isLoopbackAddress()) {\n                        String sAddr = addr.getHostAddress();\n                        //boolean isIPv4 = InetAddressUtils.isIPv4Address(sAddr);\n                        boolean isIPv4 = sAddr.indexOf(':')<0;\n\n                        if (useIPv4) {\n                            if (isIPv4)\n                                return sAddr;\n                        } else {\n                            if (!isIPv4) {\n                                int delim = sAddr.indexOf('%'); // drop ip6 zone suffix\n                                return delim<0 ? sAddr.toUpperCase() : sAddr.substring(0, delim).toUpperCase();\n                            }\n                        }\n                    }\n                }\n            }\n        } catch (Exception ex) { }\n        return \"\";\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/widgets/BaseRecyclerView.java",
    "content": "package com.naman14.timber.widgets;\n\nimport android.content.Context;\n\nimport androidx.recyclerview.widget.RecyclerView;\nimport android.util.AttributeSet;\nimport android.view.View;\nimport android.widget.TextView;\n\nimport com.afollestad.appthemeengine.Config;\nimport com.naman14.timber.utils.Helpers;\n\nimport net.steamcrafted.materialiconlib.MaterialDrawableBuilder;\n\npublic class BaseRecyclerView extends RecyclerView {\n\n    private View emptyView;\n\n    private AdapterDataObserver emptyObserver = new AdapterDataObserver() {\n\n        @Override\n        public void onChanged() {\n            Adapter<?> adapter =  getAdapter();\n            if(adapter != null && emptyView != null) {\n                if(adapter.getItemCount() == 0) {\n                    emptyView.setVisibility(View.VISIBLE);\n                    BaseRecyclerView.this.setVisibility(View.GONE);\n                }\n                else {\n                    emptyView.setVisibility(View.GONE);\n                    BaseRecyclerView.this.setVisibility(View.VISIBLE);\n                }\n            }\n\n        }\n    };\n\n    public BaseRecyclerView(Context context) {\n        super(context);\n    }\n\n    public BaseRecyclerView(Context context, AttributeSet attrs) {\n        super(context, attrs);\n    }\n\n    public BaseRecyclerView(Context context, AttributeSet attrs, int defStyle) {\n        super(context, attrs, defStyle);\n    }\n\n    @Override\n    public void setAdapter(Adapter adapter) {\n        super.setAdapter(adapter);\n\n        if(adapter != null) {\n            adapter.registerAdapterDataObserver(emptyObserver);\n        }\n\n        emptyObserver.onChanged();\n    }\n\n    public void setEmptyView(Context context, View emptyView, String text) {\n        this.emptyView = emptyView;\n        ((TextView) emptyView).setText(text);\n\n        MaterialDrawableBuilder builder = MaterialDrawableBuilder.with(context)\n                .setIcon(MaterialDrawableBuilder.IconValue.MUSIC_NOTE)\n                .setColor(Config.textColorPrimary(context, Helpers.getATEKey(context)))\n                .setSizeDp(30);\n\n        ((TextView) emptyView).setCompoundDrawables(null, builder.build(), null, null);\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/widgets/BubbleTextGetter.java",
    "content": "package com.naman14.timber.widgets;\n\npublic interface BubbleTextGetter {\n    String getTextToShowInBubble(int pos);\n}"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/widgets/CircleImageView.java",
    "content": "package com.naman14.timber.widgets;\n\nimport android.content.Context;\nimport android.content.res.TypedArray;\nimport android.graphics.Bitmap;\nimport android.graphics.BitmapShader;\nimport android.graphics.Canvas;\nimport android.graphics.Color;\nimport android.graphics.ColorFilter;\nimport android.graphics.Matrix;\nimport android.graphics.Paint;\nimport android.graphics.RectF;\nimport android.graphics.Shader;\nimport android.graphics.drawable.BitmapDrawable;\nimport android.graphics.drawable.ColorDrawable;\nimport android.graphics.drawable.Drawable;\nimport android.net.Uri;\nimport androidx.annotation.ColorRes;\nimport androidx.annotation.DrawableRes;\nimport androidx.core.content.ContextCompat;\nimport android.util.AttributeSet;\nimport android.widget.ImageView;\n\nimport com.naman14.timber.R;\n\npublic class CircleImageView extends ImageView {\n\n    private static final ScaleType SCALE_TYPE = ScaleType.CENTER_CROP;\n\n    private static final Bitmap.Config BITMAP_CONFIG = Bitmap.Config.ARGB_8888;\n    private static final int COLORDRAWABLE_DIMENSION = 2;\n\n    private static final int DEFAULT_BORDER_WIDTH = 0;\n    private static final int DEFAULT_BORDER_COLOR = Color.BLACK;\n    private static final boolean DEFAULT_BORDER_OVERLAY = false;\n\n    private final RectF mDrawableRect = new RectF();\n    private final RectF mBorderRect = new RectF();\n\n    private final Matrix mShaderMatrix = new Matrix();\n    private final Paint mBitmapPaint = new Paint();\n    private final Paint mBorderPaint = new Paint();\n\n    private int mBorderColor = DEFAULT_BORDER_COLOR;\n    private int mBorderWidth = DEFAULT_BORDER_WIDTH;\n\n    private Bitmap mBitmap;\n    private BitmapShader mBitmapShader;\n    private int mBitmapWidth;\n    private int mBitmapHeight;\n\n    private float mDrawableRadius;\n    private float mBorderRadius;\n\n    private ColorFilter mColorFilter;\n\n    private boolean mReady;\n    private boolean mSetupPending;\n    private boolean mBorderOverlay;\n\n    public CircleImageView(Context context) {\n        super(context);\n\n        init();\n    }\n\n    public CircleImageView(Context context, AttributeSet attrs) {\n        this(context, attrs, 0);\n    }\n\n    public CircleImageView(Context context, AttributeSet attrs, int defStyle) {\n        super(context, attrs, defStyle);\n\n        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CircleImageView, defStyle, 0);\n\n        mBorderWidth = a.getDimensionPixelSize(R.styleable.CircleImageView_border_width, DEFAULT_BORDER_WIDTH);\n        mBorderColor = a.getColor(R.styleable.CircleImageView_border_color, DEFAULT_BORDER_COLOR);\n        mBorderOverlay = a.getBoolean(R.styleable.CircleImageView_border_overlay, DEFAULT_BORDER_OVERLAY);\n\n        a.recycle();\n\n        init();\n    }\n\n    private void init() {\n        super.setScaleType(SCALE_TYPE);\n        mReady = true;\n\n        if (mSetupPending) {\n            setup();\n            mSetupPending = false;\n        }\n    }\n\n    @Override\n    public ScaleType getScaleType() {\n        return SCALE_TYPE;\n    }\n\n    @Override\n    public void setScaleType(ScaleType scaleType) {\n        if (scaleType != SCALE_TYPE) {\n            throw new IllegalArgumentException(String.format(\"ScaleType %s not supported.\", scaleType));\n        }\n    }\n\n    @Override\n    public void setAdjustViewBounds(boolean adjustViewBounds) {\n        if (adjustViewBounds) {\n            throw new IllegalArgumentException(\"adjustViewBounds not supported.\");\n        }\n    }\n\n    @Override\n    protected void onDraw(Canvas canvas) {\n        if (getDrawable() == null) {\n            return;\n        }\n\n        canvas.drawCircle(getWidth() / 2, getHeight() / 2, mDrawableRadius, mBitmapPaint);\n        if (mBorderWidth != 0) {\n            canvas.drawCircle(getWidth() / 2, getHeight() / 2, mBorderRadius, mBorderPaint);\n        }\n    }\n\n    @Override\n    protected void onSizeChanged(int w, int h, int oldw, int oldh) {\n        super.onSizeChanged(w, h, oldw, oldh);\n        setup();\n    }\n\n    public int getBorderColor() {\n        return mBorderColor;\n    }\n\n    public void setBorderColor(int borderColor) {\n        if (borderColor == mBorderColor) {\n            return;\n        }\n\n        mBorderColor = borderColor;\n        mBorderPaint.setColor(mBorderColor);\n        invalidate();\n    }\n\n    public void setBorderColorResource(@ColorRes int borderColorRes) {\n        setBorderColor(ContextCompat.getColor(getContext(), borderColorRes));\n    }\n\n    public int getBorderWidth() {\n        return mBorderWidth;\n    }\n\n    public void setBorderWidth(int borderWidth) {\n        if (borderWidth == mBorderWidth) {\n            return;\n        }\n\n        mBorderWidth = borderWidth;\n        setup();\n    }\n\n    public boolean isBorderOverlay() {\n        return mBorderOverlay;\n    }\n\n    public void setBorderOverlay(boolean borderOverlay) {\n        if (borderOverlay == mBorderOverlay) {\n            return;\n        }\n\n        mBorderOverlay = borderOverlay;\n        setup();\n    }\n\n    @Override\n    public void setImageBitmap(Bitmap bm) {\n        super.setImageBitmap(bm);\n        mBitmap = bm;\n        setup();\n    }\n\n    @Override\n    public void setImageDrawable(Drawable drawable) {\n        super.setImageDrawable(drawable);\n        mBitmap = getBitmapFromDrawable(drawable);\n        setup();\n    }\n\n    @Override\n    public void setImageResource(@DrawableRes int resId) {\n        super.setImageResource(resId);\n        mBitmap = getBitmapFromDrawable(getDrawable());\n        setup();\n    }\n\n    @Override\n    public void setImageURI(Uri uri) {\n        super.setImageURI(uri);\n        mBitmap = getBitmapFromDrawable(getDrawable());\n        setup();\n    }\n\n    @Override\n    public void setColorFilter(ColorFilter cf) {\n        if (cf == mColorFilter) {\n            return;\n        }\n\n        mColorFilter = cf;\n        mBitmapPaint.setColorFilter(mColorFilter);\n        invalidate();\n    }\n\n    private Bitmap getBitmapFromDrawable(Drawable drawable) {\n        if (drawable == null) {\n            return null;\n        }\n\n        if (drawable instanceof BitmapDrawable) {\n            return ((BitmapDrawable) drawable).getBitmap();\n        }\n\n        try {\n            Bitmap bitmap;\n\n            if (drawable instanceof ColorDrawable) {\n                bitmap = Bitmap.createBitmap(COLORDRAWABLE_DIMENSION, COLORDRAWABLE_DIMENSION, BITMAP_CONFIG);\n            } else {\n                bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), BITMAP_CONFIG);\n            }\n\n            Canvas canvas = new Canvas(bitmap);\n            drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());\n            drawable.draw(canvas);\n            return bitmap;\n        } catch (OutOfMemoryError e) {\n            return null;\n        }\n    }\n\n    private void setup() {\n        if (!mReady) {\n            mSetupPending = true;\n            return;\n        }\n\n        if (mBitmap == null) {\n            return;\n        }\n\n        mBitmapShader = new BitmapShader(mBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);\n\n        mBitmapPaint.setAntiAlias(true);\n        mBitmapPaint.setShader(mBitmapShader);\n\n        mBorderPaint.setStyle(Paint.Style.STROKE);\n        mBorderPaint.setAntiAlias(true);\n        mBorderPaint.setColor(mBorderColor);\n        mBorderPaint.setStrokeWidth(mBorderWidth);\n\n        mBitmapHeight = mBitmap.getHeight();\n        mBitmapWidth = mBitmap.getWidth();\n\n        mBorderRect.set(0, 0, getWidth(), getHeight());\n        mBorderRadius = Math.min((mBorderRect.height() - mBorderWidth) / 2, (mBorderRect.width() - mBorderWidth) / 2);\n\n        mDrawableRect.set(mBorderRect);\n        if (!mBorderOverlay) {\n            mDrawableRect.inset(mBorderWidth, mBorderWidth);\n        }\n        mDrawableRadius = Math.min(mDrawableRect.height() / 2, mDrawableRect.width() / 2);\n\n        updateShaderMatrix();\n        invalidate();\n    }\n\n    private void updateShaderMatrix() {\n        float scale;\n        float dx = 0;\n        float dy = 0;\n\n        mShaderMatrix.set(null);\n\n        if (mBitmapWidth * mDrawableRect.height() > mDrawableRect.width() * mBitmapHeight) {\n            scale = mDrawableRect.height() / (float) mBitmapHeight;\n            dx = (mDrawableRect.width() - mBitmapWidth * scale) * 0.5f;\n        } else {\n            scale = mDrawableRect.width() / (float) mBitmapWidth;\n            dy = (mDrawableRect.height() - mBitmapHeight * scale) * 0.5f;\n        }\n\n        mShaderMatrix.setScale(scale, scale);\n        mShaderMatrix.postTranslate((int) (dx + 0.5f) + mDrawableRect.left, (int) (dy + 0.5f) + mDrawableRect.top);\n\n        mBitmapShader.setLocalMatrix(mShaderMatrix);\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/widgets/CircularSeekBar.java",
    "content": "package com.naman14.timber.widgets;\n\nimport android.content.Context;\nimport android.content.res.TypedArray;\nimport android.graphics.BlurMaskFilter;\nimport android.graphics.Canvas;\nimport android.graphics.Color;\nimport android.graphics.Paint;\nimport android.graphics.Path;\nimport android.graphics.PathMeasure;\nimport android.graphics.RectF;\nimport android.os.Bundle;\nimport android.os.Parcelable;\nimport android.util.AttributeSet;\nimport android.view.MotionEvent;\nimport android.view.View;\n\nimport com.naman14.timber.R;\n\npublic class CircularSeekBar extends View {\n\n    // Default values\n    private static final float DEFAULT_CIRCLE_X_RADIUS = 30f;\n    private static final float DEFAULT_CIRCLE_Y_RADIUS = 30f;\n    private static final float DEFAULT_POINTER_RADIUS = 7f;\n    private static final float DEFAULT_POINTER_HALO_WIDTH = 6f;\n    private static final float DEFAULT_POINTER_HALO_BORDER_WIDTH = 2f;\n    private static final float DEFAULT_CIRCLE_STROKE_WIDTH = 5f;\n    private static final float DEFAULT_START_ANGLE = 270f; // Geometric (clockwise, relative to 3 o'clock)\n    private static final float DEFAULT_END_ANGLE = 270f; // Geometric (clockwise, relative to 3 o'clock)\n    private static final int DEFAULT_MAX = 100;\n    private static final int DEFAULT_PROGRESS = 0;\n    private static final int DEFAULT_CIRCLE_COLOR = Color.DKGRAY;\n    private static final int DEFAULT_CIRCLE_PROGRESS_COLOR = Color.argb(235, 74, 138, 255);\n    private static final int DEFAULT_POINTER_COLOR = Color.argb(235, 74, 138, 255);\n    private static final int DEFAULT_POINTER_HALO_COLOR = Color.argb(135, 74, 138, 255);\n    private static final int DEFAULT_POINTER_HALO_COLOR_ONTOUCH = Color.argb(135, 74, 138, 255);\n    private static final int DEFAULT_CIRCLE_FILL_COLOR = Color.TRANSPARENT;\n    private static final int DEFAULT_POINTER_ALPHA = 135;\n    private static final int DEFAULT_POINTER_ALPHA_ONTOUCH = 100;\n    private static final boolean DEFAULT_USE_CUSTOM_RADII = false;\n    private static final boolean DEFAULT_MAINTAIN_EQUAL_CIRCLE = true;\n    private static final boolean DEFAULT_MOVE_OUTSIDE_CIRCLE = false;\n    private static final boolean DEFAULT_LOCK_ENABLED = true;\n    /**\n     * Used to scale the dp units to pixels\n     */\n    private final float DPTOPX_SCALE = getResources().getDisplayMetrics().density;\n    /**\n     * Minimum touch target size in DP. 48dp is the Android design recommendation\n     */\n    private final float MIN_TOUCH_TARGET_DP = 48;\n    /**\n     * {@code Paint} instance used to draw the inactive circle.\n     */\n    private Paint mCirclePaint;\n\n    /**\n     * {@code Paint} instance used to draw the circle fill.\n     */\n    private Paint mCircleFillPaint;\n\n    /**\n     * {@code Paint} instance used to draw the active circle (represents progress).\n     */\n    private Paint mCircleProgressPaint;\n\n    /**\n     * {@code Paint} instance used to draw the glow from the active circle.\n     */\n    private Paint mCircleProgressGlowPaint;\n\n    /**\n     * {@code Paint} instance used to draw the center of the pointer.\n     * Note: This is broken on 4.0+, as BlurMasks do not work with hardware acceleration.\n     */\n    private Paint mPointerPaint;\n\n    /**\n     * {@code Paint} instance used to draw the halo of the pointer.\n     * Note: The halo is the part that changes transparency.\n     */\n    private Paint mPointerHaloPaint;\n\n    /**\n     * {@code Paint} instance used to draw the border of the pointer, outside of the halo.\n     */\n    private Paint mPointerHaloBorderPaint;\n\n    /**\n     * The width of the circle (in pixels).\n     */\n    private float mCircleStrokeWidth;\n\n    /**\n     * The X radius of the circle (in pixels).\n     */\n    private float mCircleXRadius;\n\n    /**\n     * The Y radius of the circle (in pixels).\n     */\n    private float mCircleYRadius;\n\n    /**\n     * The radius of the pointer (in pixels).\n     */\n    private float mPointerRadius;\n\n    /**\n     * The width of the pointer halo (in pixels).\n     */\n    private float mPointerHaloWidth;\n\n    /**\n     * The width of the pointer halo border (in pixels).\n     */\n    private float mPointerHaloBorderWidth;\n\n    /**\n     * Start angle of the CircularSeekBar.\n     * Note: If mStartAngle and mEndAngle are set to the same angle, 0.1 is subtracted\n     * from the mEndAngle to make the circle function properly.\n     */\n    private float mStartAngle;\n\n    /**\n     * End angle of the CircularSeekBar.\n     * Note: If mStartAngle and mEndAngle are set to the same angle, 0.1 is subtracted\n     * from the mEndAngle to make the circle function properly.\n     */\n    private float mEndAngle;\n\n    /**\n     * {@code RectF} that represents the circle (or ellipse) of the seekbar.\n     */\n    private RectF mCircleRectF = new RectF();\n\n    /**\n     * Holds the color value for {@code mPointerPaint} before the {@code Paint} instance is created.\n     */\n    private int mPointerColor = DEFAULT_POINTER_COLOR;\n\n    /**\n     * Holds the color value for {@code mPointerHaloPaint} before the {@code Paint} instance is created.\n     */\n    private int mPointerHaloColor = DEFAULT_POINTER_HALO_COLOR;\n\n    /**\n     * Holds the color value for {@code mPointerHaloPaint} before the {@code Paint} instance is created.\n     */\n    private int mPointerHaloColorOnTouch = DEFAULT_POINTER_HALO_COLOR_ONTOUCH;\n\n    /**\n     * Holds the color value for {@code mCirclePaint} before the {@code Paint} instance is created.\n     */\n    private int mCircleColor = DEFAULT_CIRCLE_COLOR;\n\n    /**\n     * Holds the color value for {@code mCircleFillPaint} before the {@code Paint} instance is created.\n     */\n    private int mCircleFillColor = DEFAULT_CIRCLE_FILL_COLOR;\n\n    /**\n     * Holds the color value for {@code mCircleProgressPaint} before the {@code Paint} instance is created.\n     */\n    private int mCircleProgressColor = DEFAULT_CIRCLE_PROGRESS_COLOR;\n\n    /**\n     * Holds the alpha value for {@code mPointerHaloPaint}.\n     */\n    private int mPointerAlpha = DEFAULT_POINTER_ALPHA;\n\n    /**\n     * Holds the OnTouch alpha value for {@code mPointerHaloPaint}.\n     */\n    private int mPointerAlphaOnTouch = DEFAULT_POINTER_ALPHA_ONTOUCH;\n\n    /**\n     * Distance (in degrees) that the the circle/semi-circle makes up.\n     * This amount represents the max of the circle in degrees.\n     */\n    private float mTotalCircleDegrees;\n\n    /**\n     * Distance (in degrees) that the current progress makes up in the circle.\n     */\n    private float mProgressDegrees;\n\n    /**\n     * {@code Path} used to draw the circle/semi-circle.\n     */\n    private Path mCirclePath;\n\n    /**\n     * {@code Path} used to draw the progress on the circle.\n     */\n    private Path mCircleProgressPath;\n\n    /**\n     * Max value that this CircularSeekBar is representing.\n     */\n    private int mMax;\n\n    /**\n     * Progress value that this CircularSeekBar is representing.\n     */\n    private int mProgress;\n\n    /**\n     * If true, then the user can specify the X and Y radii.\n     * If false, then the View itself determines the size of the CircularSeekBar.\n     */\n    private boolean mCustomRadii;\n\n    /**\n     * Maintain a perfect circle (equal x and y radius), regardless of view or custom attributes.\n     * The smaller of the two radii will always be used in this case.\n     * The default is to be a circle and not an ellipse, due to the behavior of the ellipse.\n     */\n    private boolean mMaintainEqualCircle;\n\n    /**\n     * Once a user has touched the circle, this determines if moving outside the circle is able\n     * to change the position of the pointer (and in turn, the progress).\n     */\n    private boolean mMoveOutsideCircle;\n\n    /**\n     * Used for enabling/disabling the lock option for easier hitting of the 0 progress mark.\n     */\n    private boolean lockEnabled = true;\n\n    /**\n     * Used for when the user moves beyond the start of the circle when moving counter clockwise.\n     * Makes it easier to hit the 0 progress mark.\n     */\n    private boolean lockAtStart = true;\n\n    /**\n     * Used for when the user moves beyond the end of the circle when moving clockwise.\n     * Makes it easier to hit the 100% (max) progress mark.\n     */\n    private boolean lockAtEnd = false;\n\n    /**\n     * When the user is touching the circle on ACTION_DOWN, this is set to true.\n     * Used when touching the CircularSeekBar.\n     */\n    private boolean mUserIsMovingPointer = false;\n\n    /**\n     * Represents the clockwise distance from {@code mStartAngle} to the touch angle.\n     * Used when touching the CircularSeekBar.\n     */\n    private float cwDistanceFromStart;\n\n    /**\n     * Represents the counter-clockwise distance from {@code mStartAngle} to the touch angle.\n     * Used when touching the CircularSeekBar.\n     */\n    private float ccwDistanceFromStart;\n\n    /**\n     * Represents the clockwise distance from {@code mEndAngle} to the touch angle.\n     * Used when touching the CircularSeekBar.\n     */\n    private float cwDistanceFromEnd;\n\n    /**\n     * Represents the counter-clockwise distance from {@code mEndAngle} to the touch angle.\n     * Used when touching the CircularSeekBar.\n     * Currently unused, but kept just in case.\n     */\n    @SuppressWarnings(\"unused\")\n    private float ccwDistanceFromEnd;\n\n    /**\n     * The previous touch action value for {@code cwDistanceFromStart}.\n     * Used when touching the CircularSeekBar.\n     */\n    private float lastCWDistanceFromStart;\n\n    /**\n     * Represents the clockwise distance from {@code mPointerPosition} to the touch angle.\n     * Used when touching the CircularSeekBar.\n     */\n    private float cwDistanceFromPointer;\n\n    /**\n     * Represents the counter-clockwise distance from {@code mPointerPosition} to the touch angle.\n     * Used when touching the CircularSeekBar.\n     */\n    private float ccwDistanceFromPointer;\n\n    /**\n     * True if the user is moving clockwise around the circle, false if moving counter-clockwise.\n     * Used when touching the CircularSeekBar.\n     */\n    private boolean mIsMovingCW;\n\n    /**\n     * The width of the circle used in the {@code RectF} that is used to draw it.\n     * Based on either the View width or the custom X radius.\n     */\n    private float mCircleWidth;\n\n    /**\n     * The height of the circle used in the {@code RectF} that is used to draw it.\n     * Based on either the View width or the custom Y radius.\n     */\n    private float mCircleHeight;\n\n    /**\n     * Represents the progress mark on the circle, in geometric degrees.\n     * This is not provided by the user; it is calculated;\n     */\n    private float mPointerPosition;\n\n    /**\n     * Pointer position in terms of X and Y coordinates.\n     */\n    private float[] mPointerPositionXY = new float[2];\n\n    /**\n     * Listener.\n     */\n    private OnCircularSeekBarChangeListener mOnCircularSeekBarChangeListener;\n\n    public CircularSeekBar(Context context) {\n        super(context);\n        init(null, 0);\n    }\n\n    public CircularSeekBar(Context context, AttributeSet attrs) {\n        super(context, attrs);\n        init(attrs, 0);\n    }\n\n    public CircularSeekBar(Context context, AttributeSet attrs, int defStyle) {\n        super(context, attrs, defStyle);\n        init(attrs, defStyle);\n    }\n\n    /**\n     * Initialize the CircularSeekBar with the attributes from the XML style.\n     * Uses the defaults defined at the top of this file when an attribute is not specified by the user.\n     *\n     * @param attrArray TypedArray containing the attributes.\n     */\n    private void initAttributes(TypedArray attrArray) {\n        mCircleXRadius = attrArray.getFloat(R.styleable.CircularSeekBar_circle_x_radius, DEFAULT_CIRCLE_X_RADIUS) * DPTOPX_SCALE;\n        mCircleYRadius = attrArray.getFloat(R.styleable.CircularSeekBar_circle_y_radius, DEFAULT_CIRCLE_Y_RADIUS) * DPTOPX_SCALE;\n        mPointerRadius = attrArray.getFloat(R.styleable.CircularSeekBar_pointer_radius, DEFAULT_POINTER_RADIUS) * DPTOPX_SCALE;\n        mPointerHaloWidth = attrArray.getFloat(R.styleable.CircularSeekBar_pointer_halo_width, DEFAULT_POINTER_HALO_WIDTH) * DPTOPX_SCALE;\n        mPointerHaloBorderWidth = attrArray.getFloat(R.styleable.CircularSeekBar_pointer_halo_border_width, DEFAULT_POINTER_HALO_BORDER_WIDTH) * DPTOPX_SCALE;\n        mCircleStrokeWidth = attrArray.getFloat(R.styleable.CircularSeekBar_circle_stroke_width, DEFAULT_CIRCLE_STROKE_WIDTH) * DPTOPX_SCALE;\n\n        String tempColor = attrArray.getString(R.styleable.CircularSeekBar_pointer_color);\n        if (tempColor != null) {\n            try {\n                mPointerColor = Color.parseColor(tempColor);\n            } catch (IllegalArgumentException e) {\n                mPointerColor = DEFAULT_POINTER_COLOR;\n            }\n        }\n\n        tempColor = attrArray.getString(R.styleable.CircularSeekBar_pointer_halo_color);\n        if (tempColor != null) {\n            try {\n                mPointerHaloColor = Color.parseColor(tempColor);\n            } catch (IllegalArgumentException e) {\n                mPointerHaloColor = DEFAULT_POINTER_HALO_COLOR;\n            }\n        }\n\n        tempColor = attrArray.getString(R.styleable.CircularSeekBar_pointer_halo_color_ontouch);\n        if (tempColor != null) {\n            try {\n                mPointerHaloColorOnTouch = Color.parseColor(tempColor);\n            } catch (IllegalArgumentException e) {\n                mPointerHaloColorOnTouch = DEFAULT_POINTER_HALO_COLOR_ONTOUCH;\n            }\n        }\n\n        tempColor = attrArray.getString(R.styleable.CircularSeekBar_circle_color);\n        if (tempColor != null) {\n            try {\n                mCircleColor = Color.parseColor(tempColor);\n            } catch (IllegalArgumentException e) {\n                mCircleColor = DEFAULT_CIRCLE_COLOR;\n            }\n        }\n\n        tempColor = attrArray.getString(R.styleable.CircularSeekBar_circle_progress_color);\n        if (tempColor != null) {\n            try {\n                mCircleProgressColor = Color.parseColor(tempColor);\n            } catch (IllegalArgumentException e) {\n                mCircleProgressColor = DEFAULT_CIRCLE_PROGRESS_COLOR;\n            }\n        }\n\n        tempColor = attrArray.getString(R.styleable.CircularSeekBar_circle_fill);\n        if (tempColor != null) {\n            try {\n                mCircleFillColor = Color.parseColor(tempColor);\n            } catch (IllegalArgumentException e) {\n                mCircleFillColor = DEFAULT_CIRCLE_FILL_COLOR;\n            }\n        }\n\n        mPointerAlpha = Color.alpha(mPointerHaloColor);\n\n        mPointerAlphaOnTouch = attrArray.getInt(R.styleable.CircularSeekBar_pointer_alpha_ontouch, DEFAULT_POINTER_ALPHA_ONTOUCH);\n        if (mPointerAlphaOnTouch > 255 || mPointerAlphaOnTouch < 0) {\n            mPointerAlphaOnTouch = DEFAULT_POINTER_ALPHA_ONTOUCH;\n        }\n\n        mMax = attrArray.getInt(R.styleable.CircularSeekBar_max, DEFAULT_MAX);\n        mProgress = attrArray.getInt(R.styleable.CircularSeekBar_progress, DEFAULT_PROGRESS);\n        mCustomRadii = attrArray.getBoolean(R.styleable.CircularSeekBar_use_custom_radii, DEFAULT_USE_CUSTOM_RADII);\n        mMaintainEqualCircle = attrArray.getBoolean(R.styleable.CircularSeekBar_maintain_equal_circle, DEFAULT_MAINTAIN_EQUAL_CIRCLE);\n        mMoveOutsideCircle = attrArray.getBoolean(R.styleable.CircularSeekBar_move_outside_circle, DEFAULT_MOVE_OUTSIDE_CIRCLE);\n        lockEnabled = attrArray.getBoolean(R.styleable.CircularSeekBar_lock_enabled, DEFAULT_LOCK_ENABLED);\n\n        // Modulo 360 right now to avoid constant conversion\n        mStartAngle = ((360f + (attrArray.getFloat((R.styleable.CircularSeekBar_start_angle), DEFAULT_START_ANGLE) % 360f)) % 360f);\n        mEndAngle = ((360f + (attrArray.getFloat((R.styleable.CircularSeekBar_end_angle), DEFAULT_END_ANGLE) % 360f)) % 360f);\n\n        if (mStartAngle == mEndAngle) {\n            //mStartAngle = mStartAngle + 1f;\n            mEndAngle = mEndAngle - .1f;\n        }\n\n\n    }\n\n    /**\n     * Initializes the {@code Paint} objects with the appropriate styles.\n     */\n    private void initPaints() {\n        mCirclePaint = new Paint();\n        mCirclePaint.setAntiAlias(true);\n        mCirclePaint.setDither(true);\n        mCirclePaint.setColor(mCircleColor);\n        mCirclePaint.setStrokeWidth(mCircleStrokeWidth);\n        mCirclePaint.setStyle(Paint.Style.STROKE);\n        mCirclePaint.setStrokeJoin(Paint.Join.ROUND);\n        mCirclePaint.setStrokeCap(Paint.Cap.ROUND);\n\n        mCircleFillPaint = new Paint();\n        mCircleFillPaint.setAntiAlias(true);\n        mCircleFillPaint.setDither(true);\n        mCircleFillPaint.setColor(mCircleFillColor);\n        mCircleFillPaint.setStyle(Paint.Style.FILL);\n\n        mCircleProgressPaint = new Paint();\n        mCircleProgressPaint.setAntiAlias(true);\n        mCircleProgressPaint.setDither(true);\n        mCircleProgressPaint.setColor(mCircleProgressColor);\n        mCircleProgressPaint.setStrokeWidth(mCircleStrokeWidth);\n        mCircleProgressPaint.setStyle(Paint.Style.STROKE);\n        mCircleProgressPaint.setStrokeJoin(Paint.Join.ROUND);\n        mCircleProgressPaint.setStrokeCap(Paint.Cap.ROUND);\n\n        mCircleProgressGlowPaint = new Paint();\n        mCircleProgressGlowPaint.set(mCircleProgressPaint);\n        mCircleProgressGlowPaint.setMaskFilter(new BlurMaskFilter((5f * DPTOPX_SCALE), BlurMaskFilter.Blur.NORMAL));\n\n        mPointerPaint = new Paint();\n        mPointerPaint.setAntiAlias(true);\n        mPointerPaint.setDither(true);\n        mPointerPaint.setStyle(Paint.Style.FILL);\n        mPointerPaint.setColor(mPointerColor);\n        mPointerPaint.setStrokeWidth(mPointerRadius);\n\n        mPointerHaloPaint = new Paint();\n        mPointerHaloPaint.set(mPointerPaint);\n        mPointerHaloPaint.setColor(mPointerHaloColor);\n        mPointerHaloPaint.setAlpha(mPointerAlpha);\n        mPointerHaloPaint.setStrokeWidth(mPointerRadius + mPointerHaloWidth);\n\n        mPointerHaloBorderPaint = new Paint();\n        mPointerHaloBorderPaint.set(mPointerPaint);\n        mPointerHaloBorderPaint.setStrokeWidth(mPointerHaloBorderWidth);\n        mPointerHaloBorderPaint.setStyle(Paint.Style.STROKE);\n\n    }\n\n    /**\n     * Calculates the total degrees between mStartAngle and mEndAngle, and sets mTotalCircleDegrees\n     * to this value.\n     */\n    private void calculateTotalDegrees() {\n        mTotalCircleDegrees = (360f - (mStartAngle - mEndAngle)) % 360f; // Length of the entire circle/arc\n        if (mTotalCircleDegrees <= 0f) {\n            mTotalCircleDegrees = 360f;\n        }\n    }\n\n    /**\n     * Calculate the degrees that the progress represents. Also called the sweep angle.\n     * Sets mProgressDegrees to that value.\n     */\n    private void calculateProgressDegrees() {\n        mProgressDegrees = mPointerPosition - mStartAngle; // Verified\n        mProgressDegrees = (mProgressDegrees < 0 ? 360f + mProgressDegrees : mProgressDegrees); // Verified\n    }\n\n    /**\n     * Calculate the pointer position (and the end of the progress arc) in degrees.\n     * Sets mPointerPosition to that value.\n     */\n    private void calculatePointerAngle() {\n        float progressPercent = ((float) mProgress / (float) mMax);\n        mPointerPosition = (progressPercent * mTotalCircleDegrees) + mStartAngle;\n        mPointerPosition = mPointerPosition % 360f;\n    }\n\n    private void calculatePointerXYPosition() {\n        PathMeasure pm = new PathMeasure(mCircleProgressPath, false);\n        boolean returnValue = pm.getPosTan(pm.getLength(), mPointerPositionXY, null);\n        if (!returnValue) {\n            pm = new PathMeasure(mCirclePath, false);\n            returnValue = pm.getPosTan(0, mPointerPositionXY, null);\n        }\n    }\n\n    /**\n     * Initialize the {@code Path} objects with the appropriate values.\n     */\n    private void initPaths() {\n        mCirclePath = new Path();\n        mCirclePath.addArc(mCircleRectF, mStartAngle, mTotalCircleDegrees);\n\n        mCircleProgressPath = new Path();\n        mCircleProgressPath.addArc(mCircleRectF, mStartAngle, mProgressDegrees);\n    }\n\n    /**\n     * Initialize the {@code RectF} objects with the appropriate values.\n     */\n    private void initRects() {\n        mCircleRectF.set(-mCircleWidth, -mCircleHeight, mCircleWidth, mCircleHeight);\n    }\n\n    @Override\n    protected void onDraw(Canvas canvas) {\n        super.onDraw(canvas);\n\n        canvas.translate(this.getWidth() / 2, this.getHeight() / 2);\n\n        canvas.drawPath(mCirclePath, mCirclePaint);\n        canvas.drawPath(mCircleProgressPath, mCircleProgressGlowPaint);\n        canvas.drawPath(mCircleProgressPath, mCircleProgressPaint);\n\n        canvas.drawPath(mCirclePath, mCircleFillPaint);\n\n        canvas.drawCircle(mPointerPositionXY[0], mPointerPositionXY[1], mPointerRadius + mPointerHaloWidth, mPointerHaloPaint);\n        canvas.drawCircle(mPointerPositionXY[0], mPointerPositionXY[1], mPointerRadius, mPointerPaint);\n        if (mUserIsMovingPointer) {\n            canvas.drawCircle(mPointerPositionXY[0], mPointerPositionXY[1], mPointerRadius + mPointerHaloWidth + (mPointerHaloBorderWidth / 2f), mPointerHaloBorderPaint);\n        }\n    }\n\n    /**\n     * Get the progress of the CircularSeekBar.\n     *\n     * @return The progress of the CircularSeekBar.\n     */\n    public int getProgress() {\n        int progress = Math.round((float) mMax * mProgressDegrees / mTotalCircleDegrees);\n        return progress;\n    }\n\n    /**\n     * Set the progress of the CircularSeekBar.\n     * If the progress is the same, then any listener will not receive a onProgressChanged event.\n     *\n     * @param progress The progress to set the CircularSeekBar to.\n     */\n    public void setProgress(int progress) {\n        if (mProgress != progress) {\n            mProgress = progress;\n            if (mOnCircularSeekBarChangeListener != null) {\n                mOnCircularSeekBarChangeListener.onProgressChanged(this, progress, false);\n            }\n\n            recalculateAll();\n            invalidate();\n        }\n    }\n\n    private void setProgressBasedOnAngle(float angle) {\n        mPointerPosition = angle;\n        calculateProgressDegrees();\n        mProgress = Math.round((float) mMax * mProgressDegrees / mTotalCircleDegrees);\n    }\n\n    private void recalculateAll() {\n        calculateTotalDegrees();\n        calculatePointerAngle();\n        calculateProgressDegrees();\n\n        initRects();\n\n        initPaths();\n\n        calculatePointerXYPosition();\n    }\n\n    @Override\n    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {\n        int height = getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec);\n        int width = getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec);\n        if (mMaintainEqualCircle) {\n            int min = Math.min(width, height);\n            setMeasuredDimension(min, min);\n        } else {\n            setMeasuredDimension(width, height);\n        }\n\n        // Set the circle width and height based on the view for the moment\n        mCircleHeight = (float) height / 2f - mCircleStrokeWidth - mPointerRadius - (mPointerHaloBorderWidth * 1.5f);\n        mCircleWidth = (float) width / 2f - mCircleStrokeWidth - mPointerRadius - (mPointerHaloBorderWidth * 1.5f);\n\n        // If it is not set to use custom\n        if (mCustomRadii) {\n            // Check to make sure the custom radii are not out of the view. If they are, just use the view values\n            if ((mCircleYRadius - mCircleStrokeWidth - mPointerRadius - mPointerHaloBorderWidth) < mCircleHeight) {\n                mCircleHeight = mCircleYRadius - mCircleStrokeWidth - mPointerRadius - (mPointerHaloBorderWidth * 1.5f);\n            }\n\n            if ((mCircleXRadius - mCircleStrokeWidth - mPointerRadius - mPointerHaloBorderWidth) < mCircleWidth) {\n                mCircleWidth = mCircleXRadius - mCircleStrokeWidth - mPointerRadius - (mPointerHaloBorderWidth * 1.5f);\n            }\n        }\n\n        if (mMaintainEqualCircle) { // Applies regardless of how the values were determined\n            float min = Math.min(mCircleHeight, mCircleWidth);\n            mCircleHeight = min;\n            mCircleWidth = min;\n        }\n\n        recalculateAll();\n    }\n\n    public boolean isLockEnabled() {\n        return lockEnabled;\n    }\n\n    public void setLockEnabled(boolean lockEnabled) {\n        this.lockEnabled = lockEnabled;\n    }\n\n    @Override\n    public boolean onTouchEvent(MotionEvent event) {\n        // Convert coordinates to our internal coordinate system\n        float x = event.getX() - getWidth() / 2;\n        float y = event.getY() - getHeight() / 2;\n\n        // Get the distance from the center of the circle in terms of x and y\n        float distanceX = mCircleRectF.centerX() - x;\n        float distanceY = mCircleRectF.centerY() - y;\n\n        // Get the distance from the center of the circle in terms of a radius\n        float touchEventRadius = (float) Math.sqrt((Math.pow(distanceX, 2) + Math.pow(distanceY, 2)));\n\n        float minimumTouchTarget = MIN_TOUCH_TARGET_DP * DPTOPX_SCALE; // Convert minimum touch target into px\n        float additionalRadius; // Either uses the minimumTouchTarget size or larger if the ring/pointer is larger\n\n        if (mCircleStrokeWidth < minimumTouchTarget) { // If the width is less than the minimumTouchTarget, use the minimumTouchTarget\n            additionalRadius = minimumTouchTarget / 2;\n        } else {\n            additionalRadius = mCircleStrokeWidth / 2; // Otherwise use the width\n        }\n        float outerRadius = Math.max(mCircleHeight, mCircleWidth) + additionalRadius; // Max outer radius of the circle, including the minimumTouchTarget or wheel width\n        float innerRadius = Math.min(mCircleHeight, mCircleWidth) - additionalRadius; // Min inner radius of the circle, including the minimumTouchTarget or wheel width\n\n        if (mPointerRadius < (minimumTouchTarget / 2)) { // If the pointer radius is less than the minimumTouchTarget, use the minimumTouchTarget\n            additionalRadius = minimumTouchTarget / 2;\n        } else {\n            additionalRadius = mPointerRadius; // Otherwise use the radius\n        }\n\n        float touchAngle;\n        touchAngle = (float) ((java.lang.Math.atan2(y, x) / Math.PI * 180) % 360); // Verified\n        touchAngle = (touchAngle < 0 ? 360 + touchAngle : touchAngle); // Verified\n\n        cwDistanceFromStart = touchAngle - mStartAngle; // Verified\n        cwDistanceFromStart = (cwDistanceFromStart < 0 ? 360f + cwDistanceFromStart : cwDistanceFromStart); // Verified\n        ccwDistanceFromStart = 360f - cwDistanceFromStart; // Verified\n\n        cwDistanceFromEnd = touchAngle - mEndAngle; // Verified\n        cwDistanceFromEnd = (cwDistanceFromEnd < 0 ? 360f + cwDistanceFromEnd : cwDistanceFromEnd); // Verified\n        ccwDistanceFromEnd = 360f - cwDistanceFromEnd; // Verified\n\n        switch (event.getAction()) {\n            case MotionEvent.ACTION_DOWN:\n                // These are only used for ACTION_DOWN for handling if the pointer was the part that was touched\n                float pointerRadiusDegrees = (float) ((mPointerRadius * 180) / (Math.PI * Math.max(mCircleHeight, mCircleWidth)));\n                cwDistanceFromPointer = touchAngle - mPointerPosition;\n                cwDistanceFromPointer = (cwDistanceFromPointer < 0 ? 360f + cwDistanceFromPointer : cwDistanceFromPointer);\n                ccwDistanceFromPointer = 360f - cwDistanceFromPointer;\n                // This is for if the first touch is on the actual pointer.\n                if (((touchEventRadius >= innerRadius) && (touchEventRadius <= outerRadius)) && ((cwDistanceFromPointer <= pointerRadiusDegrees) || (ccwDistanceFromPointer <= pointerRadiusDegrees))) {\n                    setProgressBasedOnAngle(mPointerPosition);\n                    lastCWDistanceFromStart = cwDistanceFromStart;\n                    mIsMovingCW = true;\n                    mPointerHaloPaint.setAlpha(mPointerAlphaOnTouch);\n                    mPointerHaloPaint.setColor(mPointerHaloColorOnTouch);\n                    recalculateAll();\n                    invalidate();\n                    if (mOnCircularSeekBarChangeListener != null) {\n                        mOnCircularSeekBarChangeListener.onStartTrackingTouch(this);\n                    }\n                    mUserIsMovingPointer = true;\n                    lockAtEnd = false;\n                    lockAtStart = false;\n                } else if (cwDistanceFromStart > mTotalCircleDegrees) { // If the user is touching outside of the start AND end\n                    mUserIsMovingPointer = false;\n                    return false;\n                } else if ((touchEventRadius >= innerRadius) && (touchEventRadius <= outerRadius)) { // If the user is touching near the circle\n                    setProgressBasedOnAngle(touchAngle);\n                    lastCWDistanceFromStart = cwDistanceFromStart;\n                    mIsMovingCW = true;\n                    mPointerHaloPaint.setAlpha(mPointerAlphaOnTouch);\n                    mPointerHaloPaint.setColor(mPointerHaloColorOnTouch);\n                    recalculateAll();\n                    invalidate();\n                    if (mOnCircularSeekBarChangeListener != null) {\n                        mOnCircularSeekBarChangeListener.onStartTrackingTouch(this);\n                        mOnCircularSeekBarChangeListener.onProgressChanged(this, mProgress, true);\n                    }\n                    mUserIsMovingPointer = true;\n                    lockAtEnd = false;\n                    lockAtStart = false;\n                } else { // If the user is not touching near the circle\n                    mUserIsMovingPointer = false;\n                    return false;\n                }\n                break;\n            case MotionEvent.ACTION_MOVE:\n                if (mUserIsMovingPointer) {\n                    if (lastCWDistanceFromStart < cwDistanceFromStart) {\n                        if ((cwDistanceFromStart - lastCWDistanceFromStart) > 180f && !mIsMovingCW) {\n                            lockAtStart = true;\n                            lockAtEnd = false;\n                        } else {\n                            mIsMovingCW = true;\n                        }\n                    } else {\n                        if ((lastCWDistanceFromStart - cwDistanceFromStart) > 180f && mIsMovingCW) {\n                            lockAtEnd = true;\n                            lockAtStart = false;\n                        } else {\n                            mIsMovingCW = false;\n                        }\n                    }\n\n                    if (lockAtStart && mIsMovingCW) {\n                        lockAtStart = false;\n                    }\n                    if (lockAtEnd && !mIsMovingCW) {\n                        lockAtEnd = false;\n                    }\n                    if (lockAtStart && !mIsMovingCW && (ccwDistanceFromStart > 90)) {\n                        lockAtStart = false;\n                    }\n                    if (lockAtEnd && mIsMovingCW && (cwDistanceFromEnd > 90)) {\n                        lockAtEnd = false;\n                    }\n                    // Fix for passing the end of a semi-circle quickly\n                    if (!lockAtEnd && cwDistanceFromStart > mTotalCircleDegrees && mIsMovingCW && lastCWDistanceFromStart < mTotalCircleDegrees) {\n                        lockAtEnd = true;\n                    }\n\n                    if (lockAtStart && lockEnabled) {\n                        // TODO: Add a check if mProgress is already 0, in which case don't call the listener\n                        mProgress = 0;\n                        recalculateAll();\n                        invalidate();\n                        if (mOnCircularSeekBarChangeListener != null) {\n                            mOnCircularSeekBarChangeListener.onProgressChanged(this, mProgress, true);\n                        }\n\n                    } else if (lockAtEnd && lockEnabled) {\n                        mProgress = mMax;\n                        recalculateAll();\n                        invalidate();\n                        if (mOnCircularSeekBarChangeListener != null) {\n                            mOnCircularSeekBarChangeListener.onProgressChanged(this, mProgress, true);\n                        }\n                    } else if ((mMoveOutsideCircle) || (touchEventRadius <= outerRadius)) {\n                        if (!(cwDistanceFromStart > mTotalCircleDegrees)) {\n                            setProgressBasedOnAngle(touchAngle);\n                        }\n                        recalculateAll();\n                        invalidate();\n                        if (mOnCircularSeekBarChangeListener != null) {\n                            mOnCircularSeekBarChangeListener.onProgressChanged(this, mProgress, true);\n                        }\n                    } else {\n                        break;\n                    }\n\n                    lastCWDistanceFromStart = cwDistanceFromStart;\n                } else {\n                    return false;\n                }\n                break;\n            case MotionEvent.ACTION_UP:\n                mPointerHaloPaint.setAlpha(mPointerAlpha);\n                mPointerHaloPaint.setColor(mPointerHaloColor);\n                if (mUserIsMovingPointer) {\n                    mUserIsMovingPointer = false;\n                    invalidate();\n                    if (mOnCircularSeekBarChangeListener != null) {\n                        mOnCircularSeekBarChangeListener.onStopTrackingTouch(this);\n                    }\n                } else {\n                    return false;\n                }\n                break;\n            case MotionEvent.ACTION_CANCEL: // Used when the parent view intercepts touches for things like scrolling\n                mPointerHaloPaint.setAlpha(mPointerAlpha);\n                mPointerHaloPaint.setColor(mPointerHaloColor);\n                mUserIsMovingPointer = false;\n                invalidate();\n                break;\n        }\n\n        if (event.getAction() == MotionEvent.ACTION_MOVE && getParent() != null) {\n            getParent().requestDisallowInterceptTouchEvent(true);\n        }\n\n        return true;\n    }\n\n    private void init(AttributeSet attrs, int defStyle) {\n        final TypedArray attrArray = getContext().obtainStyledAttributes(attrs, R.styleable.CircularSeekBar, defStyle, 0);\n\n        initAttributes(attrArray);\n\n        attrArray.recycle();\n\n        initPaints();\n    }\n\n    @Override\n    protected Parcelable onSaveInstanceState() {\n        Parcelable superState = super.onSaveInstanceState();\n\n        Bundle state = new Bundle();\n        state.putParcelable(\"PARENT\", superState);\n        state.putInt(\"MAX\", mMax);\n        state.putInt(\"PROGRESS\", mProgress);\n        state.putInt(\"mCircleColor\", mCircleColor);\n        state.putInt(\"mCircleProgressColor\", mCircleProgressColor);\n        state.putInt(\"mPointerColor\", mPointerColor);\n        state.putInt(\"mPointerHaloColor\", mPointerHaloColor);\n        state.putInt(\"mPointerHaloColorOnTouch\", mPointerHaloColorOnTouch);\n        state.putInt(\"mPointerAlpha\", mPointerAlpha);\n        state.putInt(\"mPointerAlphaOnTouch\", mPointerAlphaOnTouch);\n        state.putBoolean(\"lockEnabled\", lockEnabled);\n\n        return state;\n    }\n\n    @Override\n    protected void onRestoreInstanceState(Parcelable state) {\n        Bundle savedState = (Bundle) state;\n\n        Parcelable superState = savedState.getParcelable(\"PARENT\");\n        super.onRestoreInstanceState(superState);\n\n        mMax = savedState.getInt(\"MAX\");\n        mProgress = savedState.getInt(\"PROGRESS\");\n        mCircleColor = savedState.getInt(\"mCircleColor\");\n        mCircleProgressColor = savedState.getInt(\"mCircleProgressColor\");\n        mPointerColor = savedState.getInt(\"mPointerColor\");\n        mPointerHaloColor = savedState.getInt(\"mPointerHaloColor\");\n        mPointerHaloColorOnTouch = savedState.getInt(\"mPointerHaloColorOnTouch\");\n        mPointerAlpha = savedState.getInt(\"mPointerAlpha\");\n        mPointerAlphaOnTouch = savedState.getInt(\"mPointerAlphaOnTouch\");\n        lockEnabled = savedState.getBoolean(\"lockEnabled\");\n\n        initPaints();\n\n        recalculateAll();\n    }\n\n\n    public void setOnSeekBarChangeListener(OnCircularSeekBarChangeListener l) {\n        mOnCircularSeekBarChangeListener = l;\n    }\n\n    /**\n     * Gets the circle color.\n     *\n     * @return An integer color value for the circle\n     */\n    public int getCircleColor() {\n        return mCircleColor;\n    }\n\n    /**\n     * Sets the circle color.\n     *\n     * @param color the color of the circle\n     */\n    public void setCircleColor(int color) {\n        mCircleColor = color;\n        mCirclePaint.setColor(mCircleColor);\n        invalidate();\n    }\n\n    /**\n     * Gets the circle progress color.\n     *\n     * @return An integer color value for the circle progress\n     */\n    public int getCircleProgressColor() {\n        return mCircleProgressColor;\n    }\n\n    /**\n     * Sets the circle progress color.\n     *\n     * @param color the color of the circle progress\n     */\n    public void setCircleProgressColor(int color) {\n        mCircleProgressColor = color;\n        mCircleProgressPaint.setColor(mCircleProgressColor);\n        invalidate();\n    }\n\n    /**\n     * Gets the pointer color.\n     *\n     * @return An integer color value for the pointer\n     */\n    public int getPointerColor() {\n        return mPointerColor;\n    }\n\n    /**\n     * Sets the pointer color.\n     *\n     * @param color the color of the pointer\n     */\n    public void setPointerColor(int color) {\n        mPointerColor = color;\n        mPointerPaint.setColor(mPointerColor);\n        invalidate();\n    }\n\n    /**\n     * Gets the pointer halo color.\n     *\n     * @return An integer color value for the pointer halo\n     */\n    public int getPointerHaloColor() {\n        return mPointerHaloColor;\n    }\n\n    /**\n     * Sets the pointer halo color.\n     *\n     * @param color the color of the pointer halo\n     */\n    public void setPointerHaloColor(int color) {\n        mPointerHaloColor = color;\n        mPointerHaloPaint.setColor(mPointerHaloColor);\n        invalidate();\n    }\n\n    /**\n     * Gets the pointer alpha value.\n     *\n     * @return An integer alpha value for the pointer (0..255)\n     */\n    public int getPointerAlpha() {\n        return mPointerAlpha;\n    }\n\n    /**\n     * Sets the pointer alpha.\n     *\n     * @param alpha the alpha of the pointer\n     */\n    public void setPointerAlpha(int alpha) {\n        if (alpha >= 0 && alpha <= 255) {\n            mPointerAlpha = alpha;\n            mPointerHaloPaint.setAlpha(mPointerAlpha);\n            invalidate();\n        }\n    }\n\n    /**\n     * Gets the pointer alpha value when touched.\n     *\n     * @return An integer alpha value for the pointer (0..255) when touched\n     */\n    public int getPointerAlphaOnTouch() {\n        return mPointerAlphaOnTouch;\n    }\n\n    /**\n     * Sets the pointer alpha when touched.\n     *\n     * @param alpha the alpha of the pointer (0..255) when touched\n     */\n    public void setPointerAlphaOnTouch(int alpha) {\n        if (alpha >= 0 && alpha <= 255) {\n            mPointerAlphaOnTouch = alpha;\n        }\n    }\n\n    /**\n     * Gets the circle fill color.\n     *\n     * @return An integer color value for the circle fill\n     */\n    public int getCircleFillColor() {\n        return mCircleFillColor;\n    }\n\n    /**\n     * Sets the circle fill color.\n     *\n     * @param color the color of the circle fill\n     */\n    public void setCircleFillColor(int color) {\n        mCircleFillColor = color;\n        mCircleFillPaint.setColor(mCircleFillColor);\n        invalidate();\n    }\n\n    /**\n     * Get the current max of the CircularSeekBar.\n     *\n     * @return Synchronized integer value of the max.\n     */\n    public synchronized int getMax() {\n        return mMax;\n    }\n\n    /**\n     * Set the max of the CircularSeekBar.\n     * If the new max is less than the current progress, then the progress will be set to zero.\n     * If the progress is changed as a result, then any listener will receive a onProgressChanged event.\n     *\n     * @param max The new max for the CircularSeekBar.\n     */\n    public void setMax(int max) {\n        if (!(max <= 0)) { // Check to make sure it's greater than zero\n            if (max <= mProgress) {\n                mProgress = 0; // If the new max is less than current progress, set progress to zero\n                if (mOnCircularSeekBarChangeListener != null) {\n                    mOnCircularSeekBarChangeListener.onProgressChanged(this, mProgress, false);\n                }\n            }\n            mMax = max;\n\n            recalculateAll();\n            invalidate();\n        }\n    }\n\n    /**\n     * Listener for the CircularSeekBar. Implements the same methods as the normal OnSeekBarChangeListener.\n     */\n    public interface OnCircularSeekBarChangeListener {\n\n        void onProgressChanged(CircularSeekBar circularSeekBar, int progress, boolean fromUser);\n\n        void onStopTrackingTouch(CircularSeekBar seekBar);\n\n        void onStartTrackingTouch(CircularSeekBar seekBar);\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/widgets/DividerItemDecoration.java",
    "content": "package com.naman14.timber.widgets;\n\nimport android.content.Context;\nimport android.content.res.TypedArray;\nimport android.graphics.Canvas;\nimport android.graphics.Rect;\nimport android.graphics.drawable.Drawable;\nimport androidx.core.content.ContextCompat;\nimport androidx.recyclerview.widget.LinearLayoutManager;\nimport androidx.recyclerview.widget.RecyclerView;\nimport android.view.View;\n\nimport com.naman14.timber.R;\nimport com.naman14.timber.utils.PreferencesUtility;\n\npublic class DividerItemDecoration extends RecyclerView.ItemDecoration {\n\n    public static final int HORIZONTAL_LIST = LinearLayoutManager.HORIZONTAL;\n    public static final int VERTICAL_LIST = LinearLayoutManager.VERTICAL;\n    private static final int[] ATTRS = new int[]{\n            android.R.attr.listDivider\n    };\n    private Drawable mDivider;\n\n    private int mOrientation;\n\n    public DividerItemDecoration(Context context, int orientation) {\n        final TypedArray a = context.obtainStyledAttributes(ATTRS);\n        if (PreferencesUtility.getInstance(context).getTheme().equals(\"light\"))\n            mDivider = ContextCompat.getDrawable(context, R.drawable.item_divider_black);\n        else mDivider = ContextCompat.getDrawable(context, R.drawable.item_divider_white);\n//        mDivider = a.getDrawable(0);\n        a.recycle();\n        setOrientation(orientation);\n    }\n\n    public DividerItemDecoration(Context context, int orientation, int resId) {\n        mDivider = ContextCompat.getDrawable(context, resId);\n        setOrientation(orientation);\n    }\n\n    public void setOrientation(int orientation) {\n        if (orientation != HORIZONTAL_LIST && orientation != VERTICAL_LIST) {\n            throw new IllegalArgumentException(\"invalid orientation\");\n        }\n        mOrientation = orientation;\n    }\n\n    @Override\n    public void onDraw(Canvas c, RecyclerView parent) {\n        if (mOrientation == VERTICAL_LIST) {\n            drawVertical(c, parent);\n        } else {\n            drawHorizontal(c, parent);\n        }\n    }\n\n    public void drawVertical(Canvas c, RecyclerView parent) {\n        final int left = parent.getPaddingLeft();\n        final int right = parent.getWidth() - parent.getPaddingRight();\n\n        final int childCount = parent.getChildCount();\n        for (int i = 0; i < childCount; i++) {\n            final View child = parent.getChildAt(i);\n            final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child\n                    .getLayoutParams();\n            final int top = child.getBottom() + params.bottomMargin;\n            final int bottom = top + mDivider.getIntrinsicHeight();\n            mDivider.setBounds(left, top, right, bottom);\n            mDivider.draw(c);\n        }\n    }\n\n    public void drawHorizontal(Canvas c, RecyclerView parent) {\n        final int top = parent.getPaddingTop();\n        final int bottom = parent.getHeight() - parent.getPaddingBottom();\n\n        final int childCount = parent.getChildCount();\n        for (int i = 0; i < childCount; i++) {\n            final View child = parent.getChildAt(i);\n            final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child\n                    .getLayoutParams();\n            final int left = child.getRight() + params.rightMargin;\n            final int right = left + mDivider.getIntrinsicHeight();\n            mDivider.setBounds(left, top, right, bottom);\n            mDivider.draw(c);\n        }\n    }\n\n    @Override\n    public void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent) {\n        if (mOrientation == VERTICAL_LIST) {\n            outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());\n        } else {\n            outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0);\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/widgets/DragSortRecycler.java",
    "content": "package com.naman14.timber.widgets;\n\nimport android.graphics.Bitmap;\nimport android.graphics.Canvas;\nimport android.graphics.Paint;\nimport android.graphics.Rect;\nimport android.graphics.drawable.BitmapDrawable;\nimport androidx.annotation.Nullable;\nimport androidx.recyclerview.widget.RecyclerView;\nimport android.util.Log;\nimport android.view.MotionEvent;\nimport android.view.View;\n\n\npublic class DragSortRecycler extends RecyclerView.ItemDecoration implements RecyclerView.OnItemTouchListener {\n\n    final String TAG = \"DragSortRecycler\";\n\n    final boolean DEBUG = false;\n    OnItemMovedListener moveInterface;\n    @Nullable\n    OnDragStateChangedListener dragStateChangedListener;\n    Paint bgColor = new Paint();\n    private int dragHandleWidth = 0;\n    private int selectedDragItemPos = -1;\n    private int fingerAnchorY;\n    RecyclerView.OnScrollListener scrollListener = new RecyclerView.OnScrollListener() {\n        @Override\n        public void onScrollStateChanged(RecyclerView recyclerView, int newState) {\n            super.onScrollStateChanged(recyclerView, newState);\n        }\n\n        @Override\n        public void onScrolled(RecyclerView recyclerView, int dx, int dy) {\n            super.onScrolled(recyclerView, dx, dy);\n            debugLog(\"Scrolled: \" + dx + \" \" + dy);\n            fingerAnchorY -= dy;\n        }\n    };\n    private int fingerY;\n    private int fingerOffsetInViewY;\n    private float autoScrollWindow = 0.1f;\n    private float autoScrollSpeed = 0.5f;\n    private BitmapDrawable floatingItem;\n    private Rect floatingItemStatingBounds;\n    private Rect floatingItemBounds;\n    private float floatingItemAlpha = 0.5f;\n    private int floatingItemBgColor = 0;\n    private int viewHandleId = -1;\n    private boolean isDragging;\n\n    private void debugLog(String log) {\n        if (DEBUG)\n            Log.d(TAG, log);\n    }\n\n    public RecyclerView.OnScrollListener getScrollListener() {\n        return scrollListener;\n    }\n\n    /*\n     * Set the item move interface\n     */\n    public void setOnItemMovedListener(OnItemMovedListener swif) {\n        moveInterface = swif;\n    }\n\n    public void setViewHandleId(int id) {\n        viewHandleId = id;\n    }\n\n    public void setLeftDragArea(int w) {\n        dragHandleWidth = w;\n    }\n\n    public void setFloatingAlpha(float a) {\n        floatingItemAlpha = a;\n    }\n\n    public void setFloatingBgColor(int c) {\n        floatingItemBgColor = c;\n    }\n\n    /*\n     Set the window at top and bottom of list, must be between 0 and 0.5\n     For example 0.1 uses the top and bottom 10% of the lists for scrolling\n     */\n    public void setAutoScrollWindow(float w) {\n        autoScrollWindow = w;\n    }\n\n    /*\n    Set the autoscroll speed, default is 0.5\n     */\n    public void setAutoScrollSpeed(float speed) {\n        autoScrollSpeed = speed;\n    }\n\n    @Override\n    public void getItemOffsets(Rect outRect, View view, RecyclerView rv, RecyclerView.State state) {\n        super.getItemOffsets(outRect, view, rv, state);\n\n        debugLog(\"getItemOffsets\");\n\n        debugLog(\"View top = \" + view.getTop());\n        if (selectedDragItemPos != -1) {\n            int itemPos = rv.getChildLayoutPosition(view);\n            debugLog(\"itemPos =\" + itemPos);\n\n            if (!canDragOver(itemPos)) {\n                return;\n            }\n\n            //Movement of finger\n            float totalMovement = fingerY - fingerAnchorY;\n\n            if (itemPos == selectedDragItemPos) {\n                view.setVisibility(View.INVISIBLE);\n            } else {\n                //Make view visible incase invisible\n                view.setVisibility(View.VISIBLE);\n\n                //Find middle of the floatingItem\n                float floatMiddleY = floatingItemBounds.top + floatingItemBounds.height() / 2;\n\n                //Moving down the list\n                //These will auto-animate if the device continually sends touch motion events\n                // if (totalMovment>0)\n                {\n                    if ((itemPos > selectedDragItemPos) && (view.getTop() < floatMiddleY)) {\n                        float amountUp = (floatMiddleY - view.getTop()) / (float) view.getHeight();\n                        //  amountUp *= 0.5f;\n                        if (amountUp > 1)\n                            amountUp = 1;\n\n                        outRect.top = -(int) (floatingItemBounds.height() * amountUp);\n                        outRect.bottom = (int) (floatingItemBounds.height() * amountUp);\n                    }\n\n                }//Moving up the list\n                // else if (totalMovment < 0)\n                {\n                    if ((itemPos < selectedDragItemPos) && (view.getBottom() > floatMiddleY)) {\n                        float amountDown = ((float) view.getBottom() - floatMiddleY) / (float) view.getHeight();\n                        //  amountDown *= 0.5f;\n                        if (amountDown > 1)\n                            amountDown = 1;\n\n                        outRect.top = (int) (floatingItemBounds.height() * amountDown);\n                        outRect.bottom = -(int) (floatingItemBounds.height() * amountDown);\n                    }\n                }\n            }\n        } else {\n            outRect.top = 0;\n            outRect.bottom = 0;\n            //Make view visible incase invisible\n            view.setVisibility(View.VISIBLE);\n        }\n    }\n\n    /**\n     * Find the new position by scanning through the items on\n     * screen and finding the positional relationship.\n     * This *seems* to work, another method would be to use\n     * getItemOffsets, but I think that could miss items?..\n     */\n    private int getNewPostion(RecyclerView rv) {\n        int itemsOnScreen = rv.getLayoutManager().getChildCount();\n\n        float floatMiddleY = floatingItemBounds.top + floatingItemBounds.height() / 2;\n\n        int above = 0;\n        int below = Integer.MAX_VALUE;\n        for (int n = 0; n < itemsOnScreen; n++) //Scan though items on screen, however they may not\n        {                                   // be in order!\n\n            View view = rv.getLayoutManager().getChildAt(n);\n\n            if (view.getVisibility() != View.VISIBLE)\n                continue;\n\n            int itemPos = rv.getChildLayoutPosition(view);\n\n            if (itemPos == selectedDragItemPos) //Don't check against itself!\n                continue;\n\n            float viewMiddleY = view.getTop() + view.getHeight() / 2;\n            if (floatMiddleY > viewMiddleY) //Is above this item\n            {\n                if (itemPos > above)\n                    above = itemPos;\n            } else if (floatMiddleY <= viewMiddleY) //Is below this item\n            {\n                if (itemPos < below)\n                    below = itemPos;\n            }\n        }\n        debugLog(\"above = \" + above + \" below = \" + below);\n\n        if (below != Integer.MAX_VALUE) {\n            if (below < selectedDragItemPos) //Need to count itself\n                below++;\n            return below - 1;\n        } else {\n            if (above < selectedDragItemPos)\n                above++;\n\n            return above;\n        }\n    }\n\n\n    @Override\n    public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {\n        debugLog(\"onInterceptTouchEvent\");\n\n        //if (e.getAction() == MotionEvent.ACTION_DOWN)\n        {\n            View itemView = rv.findChildViewUnder(e.getX(), e.getY());\n\n            if (itemView == null)\n                return false;\n\n            boolean dragging = false;\n\n            if ((dragHandleWidth > 0) && (e.getX() < dragHandleWidth)) {\n                dragging = true;\n            } else if (viewHandleId != -1) {\n                //Find the handle in the list item\n                View handleView = itemView.findViewById(viewHandleId);\n\n                if (handleView == null) {\n                    Log.e(TAG, \"The view ID \" + viewHandleId + \" was not found in the RecycleView item\");\n                    return false;\n                }\n\n                //View should be visible to drag\n                if (handleView.getVisibility() != View.VISIBLE) {\n                    return false;\n                }\n\n                //We need to find the relative position of the handle to the parent view\n                //Then we can work out if the touch is within the handle\n                int[] parentItemPos = new int[2];\n                itemView.getLocationInWindow(parentItemPos);\n\n                int[] handlePos = new int[2];\n                handleView.getLocationInWindow(handlePos);\n\n                int xRel = handlePos[0] - parentItemPos[0];\n                int yRel = handlePos[1] - parentItemPos[1];\n\n                Rect touchBounds = new Rect(itemView.getLeft() + xRel, itemView.getTop() + yRel,\n                        itemView.getLeft() + xRel + handleView.getWidth(),\n                        itemView.getTop() + yRel + handleView.getHeight()\n                );\n\n                if (touchBounds.contains((int) e.getX(), (int) e.getY()))\n                    dragging = true;\n\n                debugLog(\"parentItemPos = \" + parentItemPos[0] + \" \" + parentItemPos[1]);\n                debugLog(\"handlePos = \" + handlePos[0] + \" \" + handlePos[1]);\n            }\n\n\n            if (dragging) {\n                debugLog(\"Started Drag\");\n\n                setIsDragging(true);\n\n                floatingItem = createFloatingBitmap(itemView);\n\n                fingerAnchorY = (int) e.getY();\n                fingerOffsetInViewY = fingerAnchorY - itemView.getTop();\n                fingerY = fingerAnchorY;\n\n                selectedDragItemPos = rv.getChildLayoutPosition(itemView);\n                debugLog(\"selectedDragItemPos = \" + selectedDragItemPos);\n\n                return true;\n            }\n        }\n        return false;\n    }\n\n    @Override\n    public void onRequestDisallowInterceptTouchEvent(boolean b) {\n\n    }\n\n    @Override\n    public void onTouchEvent(RecyclerView rv, MotionEvent e) {\n        debugLog(\"onTouchEvent\");\n\n        if ((e.getAction() == MotionEvent.ACTION_UP) ||\n                (e.getAction() == MotionEvent.ACTION_CANCEL)) {\n            if ((e.getAction() == MotionEvent.ACTION_UP) && selectedDragItemPos != -1) {\n                int newPos = getNewPostion(rv);\n                if (moveInterface != null)\n                    moveInterface.onItemMoved(selectedDragItemPos, newPos);\n            }\n\n            setIsDragging(false);\n            selectedDragItemPos = -1;\n            floatingItem = null;\n            rv.invalidateItemDecorations();\n            return;\n        }\n\n\n        fingerY = (int) e.getY();\n\n        if (floatingItem != null) {\n            floatingItemBounds.top = fingerY - fingerOffsetInViewY;\n\n            if (floatingItemBounds.top < -floatingItemStatingBounds.height() / 2) //Allow half the view out the top\n                floatingItemBounds.top = -floatingItemStatingBounds.height() / 2;\n\n            floatingItemBounds.bottom = floatingItemBounds.top + floatingItemStatingBounds.height();\n\n            floatingItem.setBounds(floatingItemBounds);\n        }\n\n        //Do auto scrolling at end of list\n        float scrollAmount = 0;\n        if (fingerY > (rv.getHeight() * (1 - autoScrollWindow))) {\n            scrollAmount = (fingerY - (rv.getHeight() * (1 - autoScrollWindow)));\n        } else if (fingerY < (rv.getHeight() * autoScrollWindow)) {\n            scrollAmount = (fingerY - (rv.getHeight() * autoScrollWindow));\n        }\n        debugLog(\"Scroll: \" + scrollAmount);\n\n        scrollAmount *= autoScrollSpeed;\n        rv.scrollBy(0, (int) scrollAmount);\n\n        rv.invalidateItemDecorations();// Redraw\n    }\n\n    private void setIsDragging(final boolean dragging) {\n        if (dragging != isDragging) {\n            isDragging = dragging;\n            if (dragStateChangedListener != null) {\n                if (isDragging) {\n                    dragStateChangedListener.onDragStart();\n                } else {\n                    dragStateChangedListener.onDragStop();\n                }\n            }\n        }\n    }\n\n    public void setOnDragStateChangedListener(final OnDragStateChangedListener dragStateChangedListener) {\n        this.dragStateChangedListener = dragStateChangedListener;\n    }\n\n    @Override\n    public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {\n        if (floatingItem != null) {\n            floatingItem.setAlpha((int) (255 * floatingItemAlpha));\n            bgColor.setColor(floatingItemBgColor);\n            c.drawRect(floatingItemBounds, bgColor);\n            floatingItem.draw(c);\n        }\n    }\n\n    /**\n     * @param position\n     * @return True if we can drag the item over this position, False if not.\n     */\n    protected boolean canDragOver(int position) {\n        return true;\n    }\n\n    private BitmapDrawable createFloatingBitmap(View v) {\n        floatingItemStatingBounds = new Rect(v.getLeft(), v.getTop(), v.getRight(), v.getBottom());\n        floatingItemBounds = new Rect(floatingItemStatingBounds);\n\n        Bitmap bitmap = Bitmap.createBitmap(floatingItemStatingBounds.width(),\n                floatingItemStatingBounds.height(), Bitmap.Config.ARGB_8888);\n        Canvas canvas = new Canvas(bitmap);\n        v.draw(canvas);\n\n        BitmapDrawable retDrawable = new BitmapDrawable(v.getResources(), bitmap);\n        retDrawable.setBounds(floatingItemBounds);\n\n        return retDrawable;\n    }\n\n    public interface OnItemMovedListener {\n        void onItemMoved(int from, int to);\n    }\n\n\n    public interface OnDragStateChangedListener {\n        void onDragStart();\n\n        void onDragStop();\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/widgets/FastScroller.java",
    "content": "package com.naman14.timber.widgets;\n\nimport android.animation.Animator;\nimport android.animation.AnimatorListenerAdapter;\nimport android.animation.AnimatorSet;\nimport android.animation.ObjectAnimator;\nimport android.content.Context;\nimport androidx.annotation.NonNull;\nimport androidx.recyclerview.widget.LinearLayoutManager;\nimport androidx.recyclerview.widget.RecyclerView;\nimport android.util.AttributeSet;\nimport android.view.LayoutInflater;\nimport android.view.MotionEvent;\nimport android.view.View;\nimport android.widget.LinearLayout;\nimport android.widget.TextView;\n\nimport com.naman14.timber.R;\n\nimport static androidx.recyclerview.widget.RecyclerView.OnScrollListener;\n\npublic class FastScroller extends LinearLayout {\n    private static final int BUBBLE_ANIMATION_DURATION = 100;\n    private static final int TRACK_SNAP_RANGE = 5;\n    private final ScrollListener scrollListener = new ScrollListener();\n    private TextView bubble;\n    private View handle;\n    private RecyclerView recyclerView;\n    private int height;\n\n    private ObjectAnimator currentAnimator = null;\n\n    public FastScroller(final Context context, final AttributeSet attrs, final int defStyleAttr) {\n        super(context, attrs, defStyleAttr);\n        initialise(context);\n    }\n\n    public FastScroller(final Context context) {\n        super(context);\n        initialise(context);\n    }\n\n    public FastScroller(final Context context, final AttributeSet attrs) {\n        super(context, attrs);\n        initialise(context);\n    }\n\n    private void initialise(Context context) {\n        setOrientation(HORIZONTAL);\n        setClipChildren(false);\n        LayoutInflater inflater = LayoutInflater.from(context);\n        inflater.inflate(R.layout.recyclerview_fastscroller, this, true);\n        bubble = (TextView) findViewById(R.id.fastscroller_bubble);\n        handle = findViewById(R.id.fastscroller_handle);\n        bubble.setVisibility(INVISIBLE);\n    }\n\n    @Override\n    protected void onSizeChanged(int w, int h, int oldw, int oldh) {\n        super.onSizeChanged(w, h, oldw, oldh);\n        height = h;\n    }\n\n    @Override\n    public boolean onTouchEvent(@NonNull MotionEvent event) {\n        final int action = event.getAction();\n        switch (action) {\n            case MotionEvent.ACTION_DOWN:\n                if (event.getX() < handle.getX())\n                    return false;\n                if (currentAnimator != null)\n                    currentAnimator.cancel();\n                if (bubble.getVisibility() == INVISIBLE)\n                    showBubble();\n                handle.setSelected(true);\n            case MotionEvent.ACTION_MOVE:\n                final float y = event.getY();\n                setBubbleAndHandlePosition(y);\n                setRecyclerViewPosition(y);\n                return true;\n            case MotionEvent.ACTION_UP:\n            case MotionEvent.ACTION_CANCEL:\n                handle.setSelected(false);\n                hideBubble();\n                return true;\n        }\n        return super.onTouchEvent(event);\n    }\n\n    public void setRecyclerView(RecyclerView recyclerView) {\n        this.recyclerView = recyclerView;\n        recyclerView.addOnScrollListener(scrollListener);\n    }\n\n    private void setRecyclerViewPosition(float y) {\n        if (recyclerView != null) {\n            int itemCount = recyclerView.getAdapter().getItemCount();\n            float proportion;\n            if (handle.getY() == 0)\n                proportion = 0f;\n            else if (handle.getY() + handle.getHeight() >= height - TRACK_SNAP_RANGE)\n                proportion = 1f;\n            else\n                proportion = y / (float) height;\n            int targetPos = getValueInRange(0, itemCount - 1, (int) (proportion * (float) itemCount));\n            ((LinearLayoutManager) recyclerView.getLayoutManager()).scrollToPositionWithOffset(targetPos, 0);\n//      recyclerView.oPositionWithOffset(targetPos);\n            String bubbleText = ((BubbleTextGetter) recyclerView.getAdapter()).getTextToShowInBubble(targetPos);\n            bubble.setText(bubbleText);\n        }\n    }\n\n    private int getValueInRange(int min, int max, int value) {\n        int minimum = Math.max(min, value);\n        return Math.min(minimum, max);\n    }\n\n    private void setBubbleAndHandlePosition(float y) {\n        int bubbleHeight = bubble.getHeight();\n        int handleHeight = handle.getHeight();\n        handle.setY(getValueInRange(0, height - handleHeight, (int) (y - handleHeight / 2)));\n        bubble.setY(getValueInRange(0, height - bubbleHeight - handleHeight / 2, (int) (y - bubbleHeight)));\n    }\n\n    private void showBubble() {\n        AnimatorSet animatorSet = new AnimatorSet();\n        bubble.setVisibility(VISIBLE);\n        if (currentAnimator != null)\n            currentAnimator.cancel();\n        currentAnimator = ObjectAnimator.ofFloat(bubble, \"alpha\", 0f, 1f).setDuration(BUBBLE_ANIMATION_DURATION);\n        currentAnimator.start();\n    }\n\n    private void hideBubble() {\n        if (currentAnimator != null)\n            currentAnimator.cancel();\n        currentAnimator = ObjectAnimator.ofFloat(bubble, \"alpha\", 1f, 0f).setDuration(BUBBLE_ANIMATION_DURATION);\n        currentAnimator.addListener(new AnimatorListenerAdapter() {\n            @Override\n            public void onAnimationEnd(Animator animation) {\n                super.onAnimationEnd(animation);\n                bubble.setVisibility(INVISIBLE);\n                currentAnimator = null;\n            }\n\n            @Override\n            public void onAnimationCancel(Animator animation) {\n                super.onAnimationCancel(animation);\n                bubble.setVisibility(INVISIBLE);\n                currentAnimator = null;\n            }\n        });\n        currentAnimator.start();\n    }\n\n    private class ScrollListener extends OnScrollListener {\n        @Override\n        public void onScrolled(RecyclerView rv, int dx, int dy) {\n\n            if (handle.isSelected()) {\n                return;\n            }\n            View firstVisibleView = recyclerView.getChildAt(0);\n            int firstVisiblePosition = recyclerView.getChildLayoutPosition(firstVisibleView);\n            int visibleRange = recyclerView.getChildCount();\n            int lastVisiblePosition = firstVisiblePosition + visibleRange;\n            int itemCount = recyclerView.getAdapter().getItemCount();\n            int position;\n            if (firstVisiblePosition == 0)\n                position = 0;\n            else if (lastVisiblePosition == itemCount)\n                position = itemCount;\n            else\n                position = (int) (((float) firstVisiblePosition / (((float) itemCount - (float) visibleRange))) * (float) itemCount);\n            float proportion = (float) position / (float) itemCount;\n            setBubbleAndHandlePosition(height * proportion);\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/widgets/MultiViewPager.java",
    "content": "package com.naman14.timber.widgets;\n\nimport android.content.Context;\nimport android.content.res.TypedArray;\nimport android.graphics.Point;\nimport androidx.viewpager.widget.ViewPager;\nimport android.util.AttributeSet;\nimport android.view.View;\n\nimport com.naman14.timber.R;\n\npublic class MultiViewPager extends ViewPager {\n\n    private final Point size;\n    private final Point maxSize;\n    /**\n     * Maximum size.\n     */\n    private int mMaxWidth = -1;\n    /**\n     * Maximum size.\n     */\n    private int mMaxHeight = -1;\n    /**\n     * Child view inside a page to match the page size against.\n     */\n    private int mMatchWidthChildResId;\n    /**\n     * Internal state to schedule a new measurement pass.\n     */\n    private boolean mNeedsMeasurePage;\n\n    public MultiViewPager(Context context) {\n        super(context);\n        size = new Point();\n        maxSize = new Point();\n    }\n\n    public MultiViewPager(Context context, AttributeSet attrs) {\n        super(context, attrs);\n        init(context, attrs);\n        size = new Point();\n        maxSize = new Point();\n    }\n\n    private static void constrainTo(Point size, Point maxSize) {\n        if (maxSize.x >= 0) {\n            if (size.x > maxSize.x) {\n                size.x = maxSize.x;\n            }\n        }\n        if (maxSize.y >= 0) {\n            if (size.y > maxSize.y) {\n                size.y = maxSize.y;\n            }\n        }\n    }\n\n    private void init(Context context, AttributeSet attrs) {\n        setClipChildren(false);\n        TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.MultiViewPager);\n        setMaxWidth(ta.getDimensionPixelSize(R.styleable.MultiViewPager_android_maxWidth, -1));\n        setMaxHeight(ta.getDimensionPixelSize(R.styleable.MultiViewPager_android_maxHeight, -1));\n        setMatchChildWidth(ta.getResourceId(R.styleable.MultiViewPager_matchChildWidth, 0));\n        ta.recycle();\n    }\n\n    @Override\n    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {\n        size.set(MeasureSpec.getSize(widthMeasureSpec),\n                MeasureSpec.getSize(heightMeasureSpec));\n        if (mMaxWidth >= 0 || mMaxHeight >= 0) {\n            maxSize.set(mMaxWidth, mMaxHeight);\n            constrainTo(size, maxSize);\n            widthMeasureSpec = MeasureSpec.makeMeasureSpec(\n                    size.x,\n                    MeasureSpec.EXACTLY);\n            heightMeasureSpec = MeasureSpec.makeMeasureSpec(\n                    size.y,\n                    MeasureSpec.EXACTLY);\n        }\n        super.onMeasure(widthMeasureSpec, heightMeasureSpec);\n        onMeasurePage(widthMeasureSpec, heightMeasureSpec);\n    }\n\n    protected void onMeasurePage(int widthMeasureSpec, int heightMeasureSpec) {\n        // Only measure if a measurement pass was scheduled\n        if (!mNeedsMeasurePage) {\n            return;\n        }\n        if (mMatchWidthChildResId == 0) {\n            mNeedsMeasurePage = false;\n        } else if (getChildCount() > 0) {\n            View child = getChildAt(0);\n            child.measure(widthMeasureSpec, heightMeasureSpec);\n            int pageWidth = child.getMeasuredWidth();\n            View match = child.findViewById(mMatchWidthChildResId);\n            if (match == null) {\n                throw new NullPointerException(\n                        \"MatchWithChildResId did not find that ID in the first fragment of the ViewPager; \"\n                                + \"is that view defined in the child view's layout? Note that MultiViewPager \"\n                                + \"only measures the child for index 0.\");\n            }\n            int childWidth = match.getMeasuredWidth();\n            // Check that the measurement was successful\n            if (childWidth > 0) {\n                mNeedsMeasurePage = false;\n                int difference = pageWidth - childWidth;\n                setPageMargin(-difference);\n                int offscreen = (int) Math.ceil((float) pageWidth / (float) childWidth) + 1;\n                setOffscreenPageLimit(offscreen);\n                requestLayout();\n            }\n        }\n    }\n\n    @Override\n    protected void onSizeChanged(int w, int h, int oldw, int oldh) {\n        super.onSizeChanged(w, h, oldw, oldh);\n        // Schedule a new measurement pass as the dimensions have changed\n        mNeedsMeasurePage = true;\n    }\n\n    /**\n     * Sets the child view inside a page to match the page size against.\n     *\n     * @param matchChildWidthResId the child id\n     */\n    public void setMatchChildWidth(int matchChildWidthResId) {\n        if (mMatchWidthChildResId != matchChildWidthResId) {\n            mMatchWidthChildResId = matchChildWidthResId;\n            mNeedsMeasurePage = true;\n        }\n    }\n\n    /**\n     * Sets the maximum size.\n     *\n     * @param width in pixels\n     */\n    public void setMaxWidth(int width) {\n        mMaxWidth = width;\n    }\n\n    /**\n     * Sets the maximum size.\n     *\n     * @param height in pixels\n     */\n    public void setMaxHeight(int height) {\n        mMaxHeight = height;\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/widgets/MusicVisualizer.java",
    "content": "package com.naman14.timber.widgets;\n\nimport android.content.Context;\nimport android.graphics.Canvas;\nimport android.graphics.Paint;\nimport android.util.AttributeSet;\nimport android.util.TypedValue;\nimport android.view.View;\n\nimport java.util.Random;\n\n/**\n * a music visualizer sort of animation (with random data)\n */\npublic class MusicVisualizer extends View {\n\n    Random random = new Random();\n\n    Paint paint = new Paint();\n    private Runnable animateView = new Runnable() {\n        @Override\n        public void run() {\n\n            //run every 100 ms\n            postDelayed(this, 120);\n\n            invalidate();\n        }\n    };\n\n    public MusicVisualizer(Context context) {\n        super(context);\n        new MusicVisualizer(context, null);\n    }\n\n    public MusicVisualizer(Context context, AttributeSet attrs) {\n        super(context, attrs);\n\n        //start runnable\n        removeCallbacks(animateView);\n        post(animateView);\n    }\n\n    @Override\n    protected void onDraw(Canvas canvas) {\n        super.onDraw(canvas);\n\n        //set paint style, Style.FILL will fill the color, Style.STROKE will stroke the color\n        paint.setStyle(Paint.Style.FILL);\n\n        canvas.drawRect(getDimensionInPixel(0), getHeight() - (40 + random.nextInt((int) (getHeight() / 1.5f) - 25)), getDimensionInPixel(7), getHeight() - 15, paint);\n        canvas.drawRect(getDimensionInPixel(10), getHeight() - (40 + random.nextInt((int) (getHeight() / 1.5f) - 25)), getDimensionInPixel(17), getHeight() -15, paint);\n        canvas.drawRect(getDimensionInPixel(20), getHeight() - (40 + random.nextInt((int) (getHeight() / 1.5f) - 25)), getDimensionInPixel(27), getHeight() -15, paint);\n    }\n\n    public void setColor(int color) {\n        paint.setColor(color);\n        invalidate();\n    }\n\n    //get all dimensions in dp so that views behaves properly on different screen resolutions\n    private int getDimensionInPixel(int dp) {\n        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, getResources().getDisplayMetrics());\n    }\n\n    @Override\n    protected void onWindowVisibilityChanged(int visibility) {\n        super.onWindowVisibilityChanged(visibility);\n        if (visibility == VISIBLE) {\n            removeCallbacks(animateView);\n            post(animateView);\n        } else if (visibility == GONE) {\n            removeCallbacks(animateView);\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/widgets/PlayPauseButton.java",
    "content": "package com.naman14.timber.widgets;\n\nimport android.animation.ValueAnimator;\nimport android.content.Context;\nimport android.graphics.Canvas;\nimport android.graphics.Color;\nimport android.graphics.Paint;\nimport android.graphics.Path;\nimport android.graphics.drawable.Drawable;\nimport android.os.Parcel;\nimport android.os.Parcelable;\nimport androidx.annotation.NonNull;\nimport android.util.AttributeSet;\nimport android.view.View;\n\npublic class PlayPauseButton extends View {\n\n    /**\n     * √3\n     */\n    private final static double SQRT_3 = Math.sqrt(3);\n\n    /**\n     * Animationの速度の倍率\n     */\n    private final static int SPEED = 1;\n    /**\n     * 座標の基準を\n     * ・中心を(0,0)\n     * ・右上(1,1) 右下(1,ー1) 左上(ー1,1) 左下(ー1,ー1)\n     * の座標時期に変換するためのClass\n     */\n    private final Point mPoint;\n    /**\n     * 描画するPaint\n     */\n    private Paint mPaint;\n    /**\n     * 左半分のPath\n     */\n    private Path mLeftPath;\n    /**\n     * 右半分のPath\n     */\n    private Path mRightPath;\n    /**\n     * 真ん中のAnimator\n     */\n    private ValueAnimator mCenterEdgeAnimator;\n    /**\n     * 一番左側のAnimator\n     */\n    private ValueAnimator mLeftEdgeAnimator;\n    /**\n     * 一番右側のAnimator\n     */\n    private ValueAnimator mRightEdgeAnimator;\n    /**\n     * ボタンの状況\n     */\n    private boolean mPlayed;\n\n    /**\n     * ボタンの色\n     */\n    private int mBackgroundColor = Color.BLACK;\n\n    /**\n     * AnimatorのUpdateListener\n     */\n    private ValueAnimator.AnimatorUpdateListener mAnimatorUpdateListener =\n            new ValueAnimator.AnimatorUpdateListener() {\n                @Override\n                public void onAnimationUpdate(ValueAnimator valueAnimator) {\n                    invalidate();\n                }\n            };\n\n    private OnControlStatusChangeListener mListener;\n\n    /**\n     * コンストラクタ\n     * {@inheritDoc}\n     */\n    public PlayPauseButton(Context context) {\n        this(context, null, 0);\n    }\n\n    /**\n     * コンストラクタ\n     * {@inheritDoc}\n     */\n    public PlayPauseButton(Context context, AttributeSet attrs) {\n        this(context, attrs, 0);\n    }\n\n    /**\n     * コンストラクタ\n     * android:backgroundのデータを取得しボタンの色とする\n     * {@inheritDoc}\n     */\n    public PlayPauseButton(Context context, AttributeSet attrs, int defStyleAttr) {\n        super(context, attrs, defStyleAttr);\n\n        mPoint = new Point();\n        initView();\n    }\n\n    /**\n     * Viewの初期化\n     */\n    private void initView() {\n        setUpPaint();\n        setUpPath();\n        setUpAnimator();\n    }\n\n    /**\n     * Animatorの初期化\n     * それぞれのAnimatorに初期値をセットし､startさせている\n     */\n    private void setUpAnimator() {\n        if (mPlayed) {\n            mCenterEdgeAnimator = ValueAnimator.ofFloat(1.f, 1.f);\n            mLeftEdgeAnimator = ValueAnimator.ofFloat((float) (-0.2f * SQRT_3), (float) (-0.2f * SQRT_3));\n            mRightEdgeAnimator = ValueAnimator.ofFloat(1.f, 1.f);\n        } else {\n            mCenterEdgeAnimator = ValueAnimator.ofFloat(0.5f, 0.5f);\n            mLeftEdgeAnimator = ValueAnimator.ofFloat(0.f, 0.f);\n            mRightEdgeAnimator = ValueAnimator.ofFloat(0.f, 0.f);\n        }\n\n        mCenterEdgeAnimator.start();\n        mLeftEdgeAnimator.start();\n        mRightEdgeAnimator.start();\n    }\n\n    /**\n     * Paintの初期化\n     */\n    private void setUpPaint() {\n        mPaint = new Paint();\n        mPaint.setColor(mBackgroundColor);\n        mPaint.setAntiAlias(true);\n        mPaint.setStyle(Paint.Style.FILL);\n    }\n\n    /**\n     * Pathの初期化\n     */\n    private void setUpPath() {\n        mLeftPath = new Path();\n        mRightPath = new Path();\n    }\n\n    /**\n     * 描画処理\n     * {@inheritDoc}\n     */\n    @Override\n    protected void onDraw(Canvas canvas) {\n        mPoint.setHeight(canvas.getHeight());\n        mPoint.setWidth(canvas.getWidth());\n\n        mLeftPath.reset();\n        mRightPath.reset();\n\n        //左半分Pathの設定\n        mLeftPath.moveTo(mPoint.getX(-0.5 * SQRT_3), mPoint.getY(1.f));\n        mLeftPath.lineTo(mPoint.getY((Float) mLeftEdgeAnimator.getAnimatedValue()) + 0.7f,\n                mPoint.getY((Float) mCenterEdgeAnimator.getAnimatedValue()));\n        mLeftPath.lineTo(mPoint.getY((Float) mLeftEdgeAnimator.getAnimatedValue()) + 0.7f,\n                mPoint.getY(-1 * (Float) mCenterEdgeAnimator.getAnimatedValue()));\n        mLeftPath.lineTo(mPoint.getX(-0.5 * SQRT_3), mPoint.getY(-1.f));\n\n        //右半分Pathの設定\n        mRightPath.moveTo(mPoint.getY(-1 * (Float) mLeftEdgeAnimator.getAnimatedValue()),\n                mPoint.getY((Float) mCenterEdgeAnimator.getAnimatedValue()));\n        mRightPath.lineTo(mPoint.getX(0.5 * SQRT_3),\n                mPoint.getY((Float) mRightEdgeAnimator.getAnimatedValue()));\n        mRightPath.lineTo(mPoint.getX(0.5 * SQRT_3),\n                mPoint.getY(-1 * (Float) mRightEdgeAnimator.getAnimatedValue()));\n        mRightPath.lineTo(mPoint.getY(-1 * (Float) mLeftEdgeAnimator.getAnimatedValue()),\n                mPoint.getY(-1 * (Float) mCenterEdgeAnimator.getAnimatedValue()));\n\n        canvas.drawPath(mLeftPath, mPaint);\n        canvas.drawPath(mRightPath, mPaint);\n    }\n\n    /**\n     * アニメーションを開始する\n     */\n    public void startAnimation() {\n        mCenterEdgeAnimator = ValueAnimator.ofFloat(1.f, 0.5f);\n        mCenterEdgeAnimator.setDuration(100 * SPEED);\n        mCenterEdgeAnimator.addUpdateListener(mAnimatorUpdateListener);\n\n        mLeftEdgeAnimator = ValueAnimator.ofFloat((float) (-0.2 * SQRT_3), 0.f);\n        mLeftEdgeAnimator.setDuration(100 * SPEED);\n        mLeftEdgeAnimator.addUpdateListener(mAnimatorUpdateListener);\n\n        mRightEdgeAnimator = ValueAnimator.ofFloat(1.f, 0.f);\n        mRightEdgeAnimator.setDuration(150 * SPEED);\n        mRightEdgeAnimator.addUpdateListener(mAnimatorUpdateListener);\n\n        if (!mPlayed) {\n            mCenterEdgeAnimator.start();\n            mLeftEdgeAnimator.start();\n            mRightEdgeAnimator.start();\n        } else {\n            mCenterEdgeAnimator.reverse();\n            mLeftEdgeAnimator.reverse();\n            mRightEdgeAnimator.reverse();\n        }\n    }\n\n    public void setOnControlStatusChangeListener(OnControlStatusChangeListener listener) {\n        mListener = listener;\n    }\n\n//  /**\n//   * ACTION_DOWNされたらアニメーションのスタートとか\n//   * {@inheritDoc}\n//   */\n//  @Override public boolean onTouchEvent(@NonNull MotionEvent event) {\n//    switch (event.getAction()) {\n//      case MotionEvent.ACTION_DOWN:\n//        setPlayed(!mPlayed);\n//        startAnimation();\n//        if (mListener != null) {\n//          mListener.onStatusChange(this, mPlayed);\n//        }\n//        break;\n//    }\n//    return false;\n//  }\n\n    /**\n     * 回転状況を {@link PlayPauseButton.SavedState} に保存する\n     * {@inheritDoc}\n     */\n    @Override\n    public Parcelable onSaveInstanceState() {\n        Parcelable superState = super.onSaveInstanceState();\n        SavedState savedState = new SavedState(superState);\n        savedState.played = isPlayed();\n        return savedState;\n    }\n\n    /**\n     * Restoreしてきたやつから {@link PlayPauseButton.SavedState}\n     * を取得し状況をセットする\n     * {@inheritDoc}\n     */\n    @Override\n    public void onRestoreInstanceState(Parcelable state) {\n        SavedState savedState = (SavedState) state;\n        super.onRestoreInstanceState(savedState.getSuperState());\n        setPlayed(savedState.played);\n        setUpAnimator();\n        invalidate();\n    }\n\n    /**\n     * android:background で親の {@link View} が背景をセットするので上書き\n     * {@inheritDoc}\n     */\n    @Override\n    public void setBackground(Drawable background) {\n    }\n\n    /**\n     * {@link PlayPauseButton#mPlayed} を返す\n     *\n     * @return {@link PlayPauseButton#mPlayed}\n     */\n    public boolean isPlayed() {\n        return mPlayed;\n    }\n\n    /**\n     * {@link PlayPauseButton#mPlayed} をセットする\n     *\n     * @param played 状況\n     */\n    public void setPlayed(boolean played) {\n        if (mPlayed != played) {\n            mPlayed = played;\n            invalidate();\n        }\n    }\n\n    /**\n     * {@link PlayPauseButton#mBackgroundColor} をセットして再描画する\n     *\n     * @param color セットする色\n     */\n    public void setColor(int color) {\n        mBackgroundColor = color;\n        mPaint.setColor(mBackgroundColor);\n        invalidate();\n    }\n\n    public interface OnControlStatusChangeListener {\n        void onStatusChange(View view, boolean state);\n    }\n\n    /**\n     * 画面開店時の状態を保存しておくやつ\n     */\n    static class SavedState extends BaseSavedState {\n\n        public static final Parcelable.Creator<SavedState> CREATOR =\n                new Parcelable.Creator<SavedState>() {\n                    public SavedState createFromParcel(Parcel in) {\n                        return new SavedState(in);\n                    }\n\n                    public SavedState[] newArray(int size) {\n                        return new SavedState[size];\n                    }\n                };\n        boolean played;\n\n        SavedState(Parcelable superState) {\n            super(superState);\n        }\n\n        private SavedState(Parcel in) {\n            super(in);\n            played = (Boolean) in.readValue(null);\n        }\n\n        @Override\n        public void writeToParcel(@NonNull Parcel out, int flags) {\n            super.writeToParcel(out, flags);\n            out.writeValue(played);\n        }\n    }\n\n    /**\n     * 座標の基準を\n     * ・中心を(0,0)\n     * ・右上(1,1) 右下(1,ー1) 左上(ー1,1) 左下(ー1,ー1)\n     * の座標時期に変換するためのClass\n     */\n    static class Point {\n\n        private int width;\n\n        private int height;\n\n        public void setWidth(int width) {\n            this.width = width;\n        }\n\n        public void setHeight(int height) {\n            this.height = height;\n        }\n\n        public float getX(float x) {\n            return (width / 2) * (x + 1);\n        }\n\n        public float getY(float y) {\n            return (height / 2) * (y + 1);\n        }\n\n        public float getX(double x) {\n            return getX((float) x);\n        }\n\n        public float getY(double y) {\n            return getY((float) y);\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/widgets/PlayPauseDrawable.java",
    "content": "/**\n * This code was modified by me, Paul Woitaschek. All these changes are licensed under GPLv3. The\n * original source can be found here: {@link https://github.com/alexjlockwood/material-pause-play-\n * animation/blob/master/app/src/main/java/com/alexjlockwood/example/playpauseanimation/\n * PlayPauseView.java}\n * <p/>\n * The original licensing is as follows:\n * <p/>\n * <p/>\n * The MIT License (MIT)\n * <p/>\n * Copyright (c) 2015 Alex Lockwood\n * <p/>\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and\n * associated documentation files (the \"Software\"), to deal in the Software without restriction,\n * including without limitation the rights to use, copy, modify, merge, publish, distribute,\n * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n * <p/>\n * The above copyright notice and this permission notice shall be included in all copies or\n * substantial portions of the Software.\n * <p/>\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING\n * BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\n\npackage com.naman14.timber.widgets;\n\nimport android.animation.Animator;\nimport android.animation.AnimatorListenerAdapter;\nimport android.animation.ObjectAnimator;\nimport android.graphics.Canvas;\nimport android.graphics.Color;\nimport android.graphics.ColorFilter;\nimport android.graphics.Paint;\nimport android.graphics.Path;\nimport android.graphics.PixelFormat;\nimport android.graphics.drawable.Drawable;\nimport androidx.annotation.Nullable;\nimport android.util.Log;\nimport android.util.Property;\nimport android.view.animation.DecelerateInterpolator;\n\n\npublic class PlayPauseDrawable extends Drawable {\n\n\n    private static final String TAG = PlayPauseDrawable.class.getSimpleName();\n    private final Path leftPauseBar = new Path();\n    private final Path rightPauseBar = new Path();\n    private final Paint paint = new Paint();\n    private float progress;\n    private static final Property<PlayPauseDrawable, Float> PROGRESS =\n            new Property<PlayPauseDrawable, Float>(Float.class, \"progress\") {\n                @Override\n                public Float get(PlayPauseDrawable d) {\n                    return d.getProgress();\n                }\n\n                @Override\n                public void set(PlayPauseDrawable d, Float value) {\n                    d.setProgress(value);\n                }\n            };\n    private boolean isPlay;\n    @Nullable\n    private Animator animator;\n\n    public PlayPauseDrawable() {\n        paint.setAntiAlias(true);\n        paint.setStyle(Paint.Style.FILL);\n        paint.setColor(Color.WHITE);\n    }\n\n    /**\n     * Linear interpolate between a and b with parameter t.\n     */\n    private static float interpolate(float a, float b, float t) {\n        return a + (b - a) * t;\n    }\n\n    @Override\n    public void draw(Canvas canvas) {\n        long startDraw = System.currentTimeMillis();\n\n        leftPauseBar.rewind();\n        rightPauseBar.rewind();\n\n        // move to center of canvas\n        canvas.translate(getBounds().left, getBounds().top);\n\n        float pauseBarHeight = 7.0F / 12.0F * ((float) getBounds().height());\n        float pauseBarWidth = pauseBarHeight / 3.0F;\n        float pauseBarDistance = pauseBarHeight / 3.6F;\n\n        // The current distance between the two pause bars.\n        final float barDist = interpolate(pauseBarDistance, 0.0F, progress);\n        // The current width of each pause bar.\n        final float barWidth = interpolate(pauseBarWidth, pauseBarHeight / 1.75F, progress);\n        // The current position of the left pause bar's top left coordinate.\n        final float firstBarTopLeft = interpolate(0.0F, barWidth, progress);\n        // The current position of the right pause bar's top right coordinate.\n        final float secondBarTopRight = interpolate(2.0F * barWidth + barDist, barWidth + barDist, progress);\n\n        // Draw the left pause bar. The left pause bar transforms into the\n        // top half of the play button triangle by animating the position of the\n        // rectangle's top left coordinate and expanding its bottom width.\n        leftPauseBar.moveTo(0.0F, 0.0F);\n        leftPauseBar.lineTo(firstBarTopLeft, -pauseBarHeight);\n        leftPauseBar.lineTo(barWidth, -pauseBarHeight);\n        leftPauseBar.lineTo(barWidth, 0.0F);\n        leftPauseBar.close();\n\n        // Draw the right pause bar. The right pause bar transforms into the\n        // bottom half of the play button triangle by animating the position of the\n        // rectangle's top right coordinate and expanding its bottom width.\n        rightPauseBar.moveTo(barWidth + barDist, 0.0F);\n        rightPauseBar.lineTo(barWidth + barDist, -pauseBarHeight);\n        rightPauseBar.lineTo(secondBarTopRight, -pauseBarHeight);\n        rightPauseBar.lineTo(2.0F * barWidth + barDist, 0.0F);\n        rightPauseBar.close();\n\n        canvas.save();\n\n        // Translate the play button a tiny bit to the right so it looks more centered.\n        canvas.translate(interpolate(0.0F, pauseBarHeight / 8.0F, progress), 0.0F);\n\n        // (1) Pause --> Play: rotate 0 to 90 degrees clockwise.\n        // (2) Play --> Pause: rotate 90 to 180 degrees clockwise.\n        final float rotationProgress = isPlay ? 1.0F - progress : progress;\n        final float startingRotation = isPlay ? 90.0F : 0.0F;\n        canvas.rotate(interpolate(startingRotation, startingRotation + 90.0F, rotationProgress), getBounds().width() / 2.0F, getBounds().height() / 2.0F);\n\n        // Position the pause/play button in the center of the drawable's bounds.\n        canvas.translate(getBounds().width() / 2.0F - ((2.0F * barWidth + barDist) / 2.0F), getBounds().height() / 2.0F + (pauseBarHeight / 2.0F));\n\n        // Draw the two bars that form the animated pause/play button.\n        canvas.drawPath(leftPauseBar, paint);\n        canvas.drawPath(rightPauseBar, paint);\n\n        canvas.restore();\n\n        long timeElapsed = System.currentTimeMillis() - startDraw;\n        if (timeElapsed > 16) {\n            Log.e(TAG, \"Drawing took too long=\" + timeElapsed);\n        }\n    }\n\n    public void transformToPause(boolean animated) {\n        if (isPlay) {\n            if (animated) {\n                toggle();\n            } else {\n                isPlay = false;\n                setProgress(0.0F);\n            }\n        }\n    }\n\n    @Override\n    public void jumpToCurrentState() {\n        Log.v(TAG, \"jumpToCurrentState()\");\n        if (animator != null) {\n            animator.cancel();\n        }\n        setProgress(isPlay ? 1.0F : 0.0F);\n    }\n\n    public void transformToPlay(boolean animated) {\n        if (!isPlay) {\n            if (animated) {\n                toggle();\n            } else {\n                isPlay = true;\n                setProgress(1.0F);\n            }\n        }\n    }\n\n    private void toggle() {\n        if (animator != null) {\n            animator.cancel();\n        }\n\n        animator = ObjectAnimator.ofFloat(this, PROGRESS, isPlay ? 1.0F : 0.0F, isPlay ? 0.0F : 1.0F);\n        animator.addListener(new AnimatorListenerAdapter() {\n            @Override\n            public void onAnimationEnd(Animator animation) {\n                isPlay = !isPlay;\n            }\n        });\n\n        animator.setInterpolator(new DecelerateInterpolator());\n        animator.setDuration(200);\n        animator.start();\n    }\n\n    private float getProgress() {\n        return progress;\n    }\n\n    private void setProgress(float progress) {\n        this.progress = progress;\n        invalidateSelf();\n    }\n\n    @Override\n    public void setAlpha(int alpha) {\n        paint.setAlpha(alpha);\n        invalidateSelf();\n    }\n\n    @Override\n    public void setColorFilter(ColorFilter cf) {\n        paint.setColorFilter(cf);\n        invalidateSelf();\n    }\n\n\n    @Override\n    public int getOpacity() {\n        return PixelFormat.TRANSLUCENT;\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/widgets/PopupImageView.java",
    "content": "package com.naman14.timber.widgets;\n\nimport android.annotation.TargetApi;\nimport android.content.Context;\nimport android.graphics.Color;\nimport android.preference.PreferenceManager;\nimport android.util.AttributeSet;\nimport android.widget.ImageView;\n\nimport com.afollestad.appthemeengine.util.TintHelper;\n\n/**\n * Created by naman on 29/10/16.\n */\npublic class PopupImageView extends ImageView {\n\n    public PopupImageView(Context context) {\n        super(context);\n        tint();\n    }\n\n    public PopupImageView(Context context, AttributeSet attrs) {\n        super(context, attrs);\n        tint();\n    }\n\n    public PopupImageView(Context context, AttributeSet attrs, int defStyleAttr) {\n        super(context, attrs, defStyleAttr);\n        tint();\n    }\n\n    @TargetApi(21)\n    public PopupImageView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {\n        super(context, attrs, defStyleAttr, defStyleRes);\n        tint();\n    }\n\n    private void tint() {\n        if (PreferenceManager.getDefaultSharedPreferences(getContext()).getBoolean(\"dark_theme\", false)) {\n            TintHelper.setTint(this, Color.parseColor(\"#eeeeee\"));\n        } else  TintHelper.setTint(this, Color.parseColor(\"#434343\"));\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/widgets/SquareImageView.java",
    "content": "package com.naman14.timber.widgets;\n\nimport android.content.Context;\nimport android.util.AttributeSet;\nimport android.widget.ImageView;\n\npublic class SquareImageView extends ImageView {\n\n\n    public SquareImageView(Context context) {\n        super(context);\n    }\n\n    public SquareImageView(Context context, AttributeSet attrs) {\n        super(context, attrs);\n    }\n\n    public SquareImageView(Context context, AttributeSet attrs, int defStyle) {\n        super(context, attrs, defStyle);\n    }\n\n    @Override\n    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {\n        super.onMeasure(widthMeasureSpec, heightMeasureSpec);\n        setMeasuredDimension(getMeasuredWidth(), getMeasuredWidth());\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/widgets/TextDrawable.java",
    "content": "package com.naman14.timber.widgets;\n\nimport android.graphics.Canvas;\nimport android.graphics.Color;\nimport android.graphics.ColorFilter;\nimport android.graphics.Paint;\nimport android.graphics.PixelFormat;\nimport android.graphics.Rect;\nimport android.graphics.RectF;\nimport android.graphics.Typeface;\nimport android.graphics.drawable.ShapeDrawable;\nimport android.graphics.drawable.shapes.OvalShape;\nimport android.graphics.drawable.shapes.RectShape;\nimport android.graphics.drawable.shapes.RoundRectShape;\n\n\npublic class TextDrawable extends ShapeDrawable {\n\n    private static final float SHADE_FACTOR = 0.9f;\n    private final Paint textPaint;\n    private final Paint borderPaint;\n    private final String text;\n    private final int color;\n    private final RectShape shape;\n    private final int height;\n    private final int width;\n    private final int fontSize;\n    private final float radius;\n    private final int borderThickness;\n\n    private TextDrawable(Builder builder) {\n        super(builder.shape);\n\n        // shape properties\n        shape = builder.shape;\n        height = builder.height;\n        width = builder.width;\n        radius = builder.radius;\n\n        // text and color\n        text = builder.toUpperCase ? builder.text.toUpperCase() : builder.text;\n        color = builder.color;\n\n        // text paint settings\n        fontSize = builder.fontSize;\n        textPaint = new Paint();\n        textPaint.setColor(builder.textColor);\n        textPaint.setAntiAlias(true);\n        textPaint.setFakeBoldText(builder.isBold);\n        textPaint.setStyle(Paint.Style.FILL);\n        textPaint.setTypeface(builder.font);\n        textPaint.setTextAlign(Paint.Align.CENTER);\n        textPaint.setStrokeWidth(builder.borderThickness);\n\n        // border paint settings\n        borderThickness = builder.borderThickness;\n        borderPaint = new Paint();\n        borderPaint.setColor(getDarkerShade(color));\n        borderPaint.setStyle(Paint.Style.STROKE);\n        borderPaint.setStrokeWidth(borderThickness);\n\n        // drawable paint color\n        Paint paint = getPaint();\n        paint.setColor(color);\n\n    }\n\n    public static IShapeBuilder builder() {\n        return new Builder();\n    }\n\n    private int getDarkerShade(int color) {\n        return Color.rgb((int) (SHADE_FACTOR * Color.red(color)),\n                (int) (SHADE_FACTOR * Color.green(color)),\n                (int) (SHADE_FACTOR * Color.blue(color)));\n    }\n\n    @Override\n    public void draw(Canvas canvas) {\n        super.draw(canvas);\n        Rect r = getBounds();\n\n\n        // draw border\n        if (borderThickness > 0) {\n            drawBorder(canvas);\n        }\n\n        int count = canvas.save();\n        canvas.translate(r.left, r.top);\n\n        // draw text\n        int width = this.width < 0 ? r.width() : this.width;\n        int height = this.height < 0 ? r.height() : this.height;\n        int fontSize = this.fontSize < 0 ? (Math.min(width, height) / 2) : this.fontSize;\n        textPaint.setTextSize(fontSize);\n        canvas.drawText(text, width / 2, height / 2 - ((textPaint.descent() + textPaint.ascent()) / 2), textPaint);\n\n        canvas.restoreToCount(count);\n\n    }\n\n    private void drawBorder(Canvas canvas) {\n        RectF rect = new RectF(getBounds());\n        rect.inset(borderThickness / 2, borderThickness / 2);\n\n        if (shape instanceof OvalShape) {\n            canvas.drawOval(rect, borderPaint);\n        } else if (shape instanceof RoundRectShape) {\n            canvas.drawRoundRect(rect, radius, radius, borderPaint);\n        } else {\n            canvas.drawRect(rect, borderPaint);\n        }\n    }\n\n    @Override\n    public void setAlpha(int alpha) {\n        textPaint.setAlpha(alpha);\n    }\n\n    @Override\n    public void setColorFilter(ColorFilter cf) {\n        textPaint.setColorFilter(cf);\n    }\n\n    @Override\n    public int getOpacity() {\n        return PixelFormat.TRANSLUCENT;\n    }\n\n    @Override\n    public int getIntrinsicWidth() {\n        return width;\n    }\n\n    @Override\n    public int getIntrinsicHeight() {\n        return height;\n    }\n\n    public interface IConfigBuilder {\n        IConfigBuilder width(int width);\n\n        IConfigBuilder height(int height);\n\n        IConfigBuilder textColor(int color);\n\n        IConfigBuilder withBorder(int thickness);\n\n        IConfigBuilder useFont(Typeface font);\n\n        IConfigBuilder fontSize(int size);\n\n        IConfigBuilder bold();\n\n        IConfigBuilder toUpperCase();\n\n        IShapeBuilder endConfig();\n    }\n\n    public interface IBuilder {\n\n        TextDrawable build(String text, int color);\n    }\n\n    public interface IShapeBuilder {\n\n        IConfigBuilder beginConfig();\n\n        IBuilder rect();\n\n        IBuilder round();\n\n        IBuilder roundRect(int radius);\n\n        TextDrawable buildRect(String text, int color);\n\n        TextDrawable buildRoundRect(String text, int color, int radius);\n\n        TextDrawable buildRound(String text, int color);\n    }\n\n    public static class Builder implements IConfigBuilder, IShapeBuilder, IBuilder {\n\n        public int textColor;\n        public float radius;\n        private String text;\n        private int color;\n        private int borderThickness;\n        private int width;\n        private int height;\n        private Typeface font;\n        private RectShape shape;\n        private int fontSize;\n        private boolean isBold;\n        private boolean toUpperCase;\n\n        private Builder() {\n            text = \"\";\n            color = Color.GRAY;\n            textColor = Color.WHITE;\n            borderThickness = 0;\n            width = -1;\n            height = -1;\n            shape = new RectShape();\n            font = Typeface.create(\"sans-serif-light\", Typeface.NORMAL);\n            fontSize = -1;\n            isBold = false;\n            toUpperCase = false;\n        }\n\n        public IConfigBuilder width(int width) {\n            this.width = width;\n            return this;\n        }\n\n        public IConfigBuilder height(int height) {\n            this.height = height;\n            return this;\n        }\n\n        public IConfigBuilder textColor(int color) {\n            this.textColor = color;\n            return this;\n        }\n\n        public IConfigBuilder withBorder(int thickness) {\n            this.borderThickness = thickness;\n            return this;\n        }\n\n        public IConfigBuilder useFont(Typeface font) {\n            this.font = font;\n            return this;\n        }\n\n        public IConfigBuilder fontSize(int size) {\n            this.fontSize = size;\n            return this;\n        }\n\n        public IConfigBuilder bold() {\n            this.isBold = true;\n            return this;\n        }\n\n        public IConfigBuilder toUpperCase() {\n            this.toUpperCase = true;\n            return this;\n        }\n\n        @Override\n        public IConfigBuilder beginConfig() {\n            return this;\n        }\n\n        @Override\n        public IShapeBuilder endConfig() {\n            return this;\n        }\n\n        @Override\n        public IBuilder rect() {\n            this.shape = new RectShape();\n            return this;\n        }\n\n        @Override\n        public IBuilder round() {\n            this.shape = new OvalShape();\n            return this;\n        }\n\n        @Override\n        public IBuilder roundRect(int radius) {\n            this.radius = radius;\n            float[] radii = {radius, radius, radius, radius, radius, radius, radius, radius};\n            this.shape = new RoundRectShape(radii, null, null);\n            return this;\n        }\n\n        @Override\n        public TextDrawable buildRect(String text, int color) {\n            rect();\n            return build(text, color);\n        }\n\n        @Override\n        public TextDrawable buildRoundRect(String text, int color, int radius) {\n            roundRect(radius);\n            return build(text, color);\n        }\n\n        @Override\n        public TextDrawable buildRound(String text, int color) {\n            round();\n            return build(text, color);\n        }\n\n        @Override\n        public TextDrawable build(String text, int color) {\n            this.color = color;\n            this.text = text;\n            return new TextDrawable(this);\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/widgets/ThemedPreferenceCategory.java",
    "content": "package com.naman14.timber.widgets;\n\nimport android.content.Context;\nimport android.preference.PreferenceCategory;\nimport android.util.AttributeSet;\nimport android.view.View;\nimport android.widget.TextView;\n\nimport com.afollestad.appthemeengine.Config;\nimport com.naman14.timber.utils.Helpers;\n\n/**\n * Created by naman on 31/12/15.\n */\npublic class ThemedPreferenceCategory extends PreferenceCategory {\n\n    private Context context;\n\n    public ThemedPreferenceCategory(Context context) {\n        super(context);\n        this.context = context;\n    }\n\n    public ThemedPreferenceCategory(Context context, AttributeSet attrs) {\n        super(context, attrs);\n        this.context = context;\n    }\n\n    public ThemedPreferenceCategory(Context context, AttributeSet attrs,\n                                    int defStyle) {\n        super(context, attrs, defStyle);\n        this.context = context;\n    }\n\n    @Override\n    protected void onBindView(View view) {\n        super.onBindView(view);\n        TextView titleView = (TextView) view.findViewById(android.R.id.title);\n        titleView.setTextColor(Config.accentColor(context, Helpers.getATEKey(context)));\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/widgets/desktop/BaseWidget.java",
    "content": "package com.naman14.timber.widgets.desktop;\n\nimport android.appwidget.AppWidgetManager;\nimport android.appwidget.AppWidgetProvider;\nimport android.content.ComponentName;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.os.Bundle;\nimport androidx.annotation.LayoutRes;\nimport android.widget.RemoteViews;\n\nimport com.naman14.timber.MusicService;\n\n/**\n * Created by nv95 on 02.11.16.\n */\n\npublic abstract class BaseWidget extends AppWidgetProvider {\n\n    protected static final int REQUEST_NEXT = 1;\n    protected static final int REQUEST_PREV = 2;\n    protected static final int REQUEST_PLAYPAUSE = 3;\n\n    @Override\n    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {\n        onUpdate(context, appWidgetManager, appWidgetIds, null);\n    }\n\n    private void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds,Bundle extras){\n        ComponentName serviceName = new ComponentName(context, MusicService.class);\n        RemoteViews remoteViews = new RemoteViews(context.getPackageName(), getLayoutRes());\n        try {\n            onViewsUpdate(context, remoteViews, serviceName, extras);\n            appWidgetManager.updateAppWidget(appWidgetIds, remoteViews);\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n    }\n\n    @Override\n    public void onReceive(Context context, Intent intent) {\n        String action = intent.getAction();\n        if (action != null && action.startsWith(\"com.naman14.timber.\")) {\n            AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);\n            ComponentName thisAppWidget = new ComponentName(context.getPackageName(), this.getClass().getName());\n            int[] appWidgetIds = appWidgetManager.getAppWidgetIds(thisAppWidget);\n            onUpdate(context, appWidgetManager, appWidgetIds, intent.getExtras());\n        } else {\n            super.onReceive(context, intent);\n        }\n    }\n\n    abstract void onViewsUpdate(Context context, RemoteViews remoteViews, ComponentName serviceName, Bundle extras);\n\n    abstract @LayoutRes int getLayoutRes();\n}\n"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/widgets/desktop/SmallWidget.java",
    "content": "package com.naman14.timber.widgets.desktop;\n\nimport android.app.PendingIntent;\nimport android.content.ComponentName;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.graphics.Bitmap;\nimport android.os.Bundle;\nimport android.widget.RemoteViews;\n\nimport com.naman14.timber.MusicService;\nimport com.naman14.timber.R;\nimport com.naman14.timber.utils.NavigationUtils;\nimport com.naman14.timber.utils.TimberUtils;\nimport com.nostra13.universalimageloader.core.ImageLoader;\n\n/**\n * Created by nv95 on 02.11.16.\n */\n\npublic class SmallWidget extends BaseWidget {\n\n    @Override\n    int getLayoutRes() {\n        return R.layout.widget_small;\n    }\n\n    @Override\n    void onViewsUpdate(Context context, RemoteViews remoteViews, ComponentName serviceName, Bundle extras) {\n        remoteViews.setOnClickPendingIntent(R.id.image_next, PendingIntent.getService(\n                context,\n                REQUEST_NEXT,\n                new Intent(context, MusicService.class)\n                        .setAction(MusicService.NEXT_ACTION)\n                        .setComponent(serviceName),\n                0\n        ));\n        remoteViews.setOnClickPendingIntent(R.id.image_playpause, PendingIntent.getService(\n                context,\n                REQUEST_PLAYPAUSE,\n                new Intent(context, MusicService.class)\n                        .setAction(MusicService.TOGGLEPAUSE_ACTION)\n                        .setComponent(serviceName),\n                0\n        ));\n        if (extras != null) {\n            String t = extras.getString(\"track\");\n            if (t != null) {\n                remoteViews.setTextViewText(R.id.textView_title, t);\n            }\n            t = extras.getString(\"artist\");\n            if (t != null) {\n                remoteViews.setTextViewText(R.id.textView_subtitle, t);\n            }\n            remoteViews.setImageViewResource(R.id.image_playpause,\n                    extras.getBoolean(\"playing\") ? R.drawable.ic_pause_white_36dp : R.drawable.ic_play_white_36dp);\n            long albumId = extras.getLong(\"albumid\");\n            if (albumId != -1) {\n                Bitmap artwork = ImageLoader.getInstance().loadImageSync(TimberUtils.getAlbumArtUri(albumId).toString());\n                if (artwork != null) {\n                    remoteViews.setImageViewBitmap(R.id.imageView_cover, artwork);\n                } else {\n                    remoteViews.setImageViewResource(R.id.imageView_cover, R.drawable.ic_empty_music2);\n                }\n            }\n        }\n        remoteViews.setOnClickPendingIntent(R.id.textView_title, PendingIntent.getActivity(\n                context,\n                0,\n                NavigationUtils.getNowPlayingIntent(context),\n                PendingIntent.FLAG_UPDATE_CURRENT\n        ));\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/widgets/desktop/StandardWidget.java",
    "content": "package com.naman14.timber.widgets.desktop;\n\nimport android.app.PendingIntent;\nimport android.content.ComponentName;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.graphics.Bitmap;\nimport android.os.Bundle;\nimport android.text.TextUtils;\nimport android.widget.RemoteViews;\n\nimport com.naman14.timber.MusicService;\nimport com.naman14.timber.R;\nimport com.naman14.timber.utils.NavigationUtils;\nimport com.naman14.timber.utils.TimberUtils;\nimport com.nostra13.universalimageloader.core.ImageLoader;\n\n/**\n * Created by nv95 on 08.07.16.\n */\n\npublic class StandardWidget extends BaseWidget {\n\n    @Override\n    int getLayoutRes() {\n        return R.layout.widget_standard;\n    }\n\n    @Override\n    void onViewsUpdate(Context context, RemoteViews remoteViews, ComponentName serviceName, Bundle extras) {\n        remoteViews.setOnClickPendingIntent(R.id.image_next, PendingIntent.getService(\n                context,\n                REQUEST_NEXT,\n                new Intent(context, MusicService.class)\n                        .setAction(MusicService.NEXT_ACTION)\n                        .setComponent(serviceName),\n                0\n        ));\n        remoteViews.setOnClickPendingIntent(R.id.image_prev, PendingIntent.getService(\n                context,\n                REQUEST_PREV,\n                new Intent(context, MusicService.class)\n                        .setAction(MusicService.PREVIOUS_ACTION)\n                        .setComponent(serviceName),\n                0\n        ));\n        remoteViews.setOnClickPendingIntent(R.id.image_playpause, PendingIntent.getService(\n                context,\n                REQUEST_PLAYPAUSE,\n                new Intent(context, MusicService.class)\n                        .setAction(MusicService.TOGGLEPAUSE_ACTION)\n                        .setComponent(serviceName),\n                0\n        ));\n\n        if (extras != null) {\n            String t = extras.getString(\"track\");\n            if (t != null) {\n                remoteViews.setTextViewText(R.id.textView_title, t);\n            }\n            t = extras.getString(\"artist\");\n            ;\n            if (t != null) {\n                String album = extras.getString(\"album\");\n                ;\n                if (!TextUtils.isEmpty(album)) {\n                    t += \" - \" + album;\n                }\n                remoteViews.setTextViewText(R.id.textView_subtitle, t);\n            }\n            remoteViews.setImageViewResource(R.id.image_playpause,\n                    extras.getBoolean(\"playing\") ? R.drawable.ic_pause_white_36dp : R.drawable.ic_play_white_36dp);\n            long albumId = extras.getLong(\"albumid\");\n            if (albumId != -1) {\n                Bitmap artwork = ImageLoader.getInstance().loadImageSync(TimberUtils.getAlbumArtUri(albumId).toString());\n                if (artwork != null) {\n                    remoteViews.setImageViewBitmap(R.id.imageView_cover, artwork);\n                } else {\n                    remoteViews.setImageViewResource(R.id.imageView_cover, R.drawable.ic_empty_music2);\n                }\n            }\n        }\n        remoteViews.setOnClickPendingIntent(R.id.imageView_cover, PendingIntent.getActivity(\n                context,\n                0,\n                NavigationUtils.getNowPlayingIntent(context),\n                PendingIntent.FLAG_UPDATE_CURRENT\n        ));\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/naman14/timber/widgets/desktop/WhiteWidget.java",
    "content": "package com.naman14.timber.widgets.desktop;\n\nimport com.naman14.timber.R;\n\n/**\n * Created by nv95 on 11.11.16.\n */\n\npublic class WhiteWidget extends StandardWidget {\n\n    @Override\n    int getLayoutRes() {\n        return R.layout.widget_white;\n    }\n}\n"
  },
  {
    "path": "app/src/main/res/anim/activity_fade_in.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<alpha xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:duration=\"200\"\n    android:fromAlpha=\"0.0\"\n    android:interpolator=\"@android:anim/accelerate_interpolator\"\n    android:toAlpha=\"1.0\" />"
  },
  {
    "path": "app/src/main/res/anim/activity_fade_out.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<alpha xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:duration=\"200\"\n    android:fromAlpha=\"1.0\"\n    android:interpolator=\"@android:anim/accelerate_interpolator\"\n    android:toAlpha=\"0.0\" />"
  },
  {
    "path": "app/src/main/res/anim/design_fab_out.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  ~ Copyright (C) 2015 The Android Open Source Project\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n  ~ you may not use this file except in compliance with the License.\n  ~ You may obtain a copy of the License at\n  ~\n  ~      http://www.apache.org/licenses/LICENSE-2.0\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  -->\n\n<set xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <alpha android:fromAlpha=\"1.0\"\n        android:toAlpha=\"0.0\"/>\n\n    <scale android:fromXScale=\"1.0\"\n        android:fromYScale=\"1.0\"\n        android:toXScale=\"0.0\"\n        android:toYScale=\"0.0\"\n        android:pivotX=\"50%\"\n        android:pivotY=\"50%\"/>\n\n</set>\n"
  },
  {
    "path": "app/src/main/res/anim/scale.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<set xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:interpolator=\"@android:anim/accelerate_interpolator\">\n    <scale\n        android:fromXScale=\"0.1\"\n        android:toXScale=\"1\"\n        android:fromYScale=\"0.1\"\n        android:toYScale=\"1.0\"\n        android:duration=\"300\"\n        android:pivotX=\"10%\"\n        android:pivotY=\"10%\"\n        android:startOffset=\"100\" />\n</set>"
  },
  {
    "path": "app/src/main/res/anim/slide_in_from_bottom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  Copyright (C) 2014 The Android Open Source Project\n\n  Licensed under the Apache License, Version 2.0 (the \"License\");\n  you may not use this file except in compliance with the License.\n  You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n  Unless required by applicable law or agreed to in writing, software\n  distributed under the License is distributed on an \"AS IS\" BASIS,\n  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  See the License for the specific language governing permissions and\n  limitations under the License.\n  -->\n<set xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:interpolator=\"@android:interpolator/fast_out_slow_in\">\n    <objectAnimator\n        android:propertyName=\"y\"\n        android:valueType=\"floatType\"\n        android:valueFrom=\"2000\"\n        android:valueTo=\"0\"/>\n</set>"
  },
  {
    "path": "app/src/main/res/anim/slide_in_from_left.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  Copyright (C) 2014 The Android Open Source Project\n\n  Licensed under the Apache License, Version 2.0 (the \"License\");\n  you may not use this file except in compliance with the License.\n  You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n  Unless required by applicable law or agreed to in writing, software\n  distributed under the License is distributed on an \"AS IS\" BASIS,\n  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  See the License for the specific language governing permissions and\n  limitations under the License.\n  -->\n<set xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:interpolator=\"@android:interpolator/fast_out_slow_in\">\n    <objectAnimator\n        android:propertyName=\"x\"\n        android:valueType=\"floatType\"\n        android:valueFrom=\"-2000\"\n        android:valueTo=\"0\"/>\n</set>"
  },
  {
    "path": "app/src/main/res/anim/slide_in_from_right.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  Copyright (C) 2014 The Android Open Source Project\n\n  Licensed under the Apache License, Version 2.0 (the \"License\");\n  you may not use this file except in compliance with the License.\n  You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n  Unless required by applicable law or agreed to in writing, software\n  distributed under the License is distributed on an \"AS IS\" BASIS,\n  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  See the License for the specific language governing permissions and\n  limitations under the License.\n  -->\n<set xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:interpolator=\"@android:interpolator/fast_out_slow_in\">\n    <objectAnimator\n        android:propertyName=\"x\"\n        android:valueType=\"floatType\"\n        android:valueFrom=\"2000\"\n        android:valueTo=\"0\"/>\n</set>"
  },
  {
    "path": "app/src/main/res/anim/slide_out_to_bottom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  Copyright (C) 2014 The Android Open Source Project\n\n  Licensed under the Apache License, Version 2.0 (the \"License\");\n  you may not use this file except in compliance with the License.\n  You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n  Unless required by applicable law or agreed to in writing, software\n  distributed under the License is distributed on an \"AS IS\" BASIS,\n  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  See the License for the specific language governing permissions and\n  limitations under the License.\n  -->\n<set xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:interpolator=\"@android:interpolator/fast_out_slow_in\">\n    <objectAnimator\n        android:propertyName=\"y\"\n        android:valueType=\"floatType\"\n        android:valueFrom=\"0\"\n        android:valueTo=\"2000\"/>\n</set>"
  },
  {
    "path": "app/src/main/res/anim/slide_out_to_left.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  Copyright (C) 2014 The Android Open Source Project\n\n  Licensed under the Apache License, Version 2.0 (the \"License\");\n  you may not use this file except in compliance with the License.\n  You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n  Unless required by applicable law or agreed to in writing, software\n  distributed under the License is distributed on an \"AS IS\" BASIS,\n  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  See the License for the specific language governing permissions and\n  limitations under the License.\n  -->\n<set xmlns:android=\"http://schemas.android.com/apk/res/android\"\n     android:interpolator=\"@android:interpolator/fast_out_slow_in\"\n     android:ordering=\"together\">\n    <objectAnimator\n        android:propertyName=\"x\"\n        android:valueType=\"floatType\"\n        android:valueFrom=\"0\"\n        android:valueTo=\"-2000\"/>\n    <objectAnimator\n        android:propertyName=\"alpha\"\n        android:valueType=\"floatType\"\n        android:valueFrom=\"1\"\n        android:valueTo=\"0\"/>\n</set>\n"
  },
  {
    "path": "app/src/main/res/anim/slide_out_to_right.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  Copyright (C) 2014 The Android Open Source Project\n\n  Licensed under the Apache License, Version 2.0 (the \"License\");\n  you may not use this file except in compliance with the License.\n  You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n  Unless required by applicable law or agreed to in writing, software\n  distributed under the License is distributed on an \"AS IS\" BASIS,\n  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  See the License for the specific language governing permissions and\n  limitations under the License.\n  -->\n<set xmlns:android=\"http://schemas.android.com/apk/res/android\"\n     android:interpolator=\"@android:interpolator/fast_out_slow_in\"\n     android:ordering=\"together\">\n    <objectAnimator\n        android:propertyName=\"x\"\n        android:valueType=\"floatType\"\n        android:valueFrom=\"0\"\n        android:valueTo=\"2000\"/>\n    <objectAnimator\n        android:propertyName=\"alpha\"\n        android:valueType=\"floatType\"\n        android:valueFrom=\"1\"\n        android:valueTo=\"0\"/>\n</set>\n"
  },
  {
    "path": "app/src/main/res/color/state_selector_black.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item android:color=\"#ffffff\" android:state_checked=\"true\" />\n    <item android:color=\"#ffffff\" android:state_checked=\"false\" />\n</selector>"
  },
  {
    "path": "app/src/main/res/color/state_selector_dark.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item android:color=\"#ffffff\" android:state_checked=\"true\" />\n    <item android:color=\"#ffffff\" android:state_checked=\"false\" />\n</selector>"
  },
  {
    "path": "app/src/main/res/color/state_selector_light.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item android:color=\"#673AB7\" android:state_checked=\"true\" />\n    <item android:color=\"#000000\" android:state_checked=\"false\" />\n</selector>"
  },
  {
    "path": "app/src/main/res/drawable/bg_gradient.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape android:shape=\"rectangle\"\n    xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <gradient android:startColor=\"#55000000\"\n        android:centerColor=\"#33000000\"\n        android:endColor=\"@android:color/transparent\" android:angle=\"90.0\" android:type=\"linear\" />\n</shape>"
  },
  {
    "path": "app/src/main/res/drawable/ic_file_music_dark.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportHeight=\"24\"\n    android:viewportWidth=\"24\">\n    <path\n        android:fillColor=\"#000\"\n        android:pathData=\"M13,9H18.5L13,3.5V9M6,2H14L20,8V20A2,2 0 0,1 18,22H6C4.89,22 4,21.1 4,20V4C4,2.89 4.89,2 6,2M9,16A2,2 0 0,0 7,18A2,2 0 0,0 9,20A2,2 0 0,0 11,18V13H14V11H10V16.27C9.71,16.1 9.36,16 9,16Z\" />\n</vector>"
  },
  {
    "path": "app/src/main/res/drawable/ic_folder_open_black_24dp.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        android:width=\"24dp\"\n        android:height=\"24dp\"\n        android:viewportWidth=\"24.0\"\n        android:viewportHeight=\"24.0\">\n    <path\n        android:fillColor=\"#FF000000\"\n        android:pathData=\"M20,6h-8l-2,-2L4,4c-1.1,0 -1.99,0.9 -1.99,2L2,18c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,8c0,-1.1 -0.9,-2 -2,-2zM20,18L4,18L4,8h16v10z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_folder_open_white_24dp.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24.0\"\n    android:viewportHeight=\"24.0\">\n    <path\n        android:fillColor=\"#ffffff\"\n        android:pathData=\"M20,6h-8l-2,-2L4,4c-1.1,0 -1.99,0.9 -1.99,2L2,18c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,8c0,-1.1 -0.9,-2 -2,-2zM20,18L4,18L4,8h16v10z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_folder_parent_dark.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportHeight=\"24\"\n    android:viewportWidth=\"24\">\n    <path\n        android:fillColor=\"#000\"\n        android:pathData=\"M20,6A2,2 0 0,1 22,8V18A2,2 0 0,1 20,20H4A2,2 0 0,1 2,18V6A2,2 0 0,1 4,4H10L12,6H20M10.75,13H14V17H16V13H19.25L15,8.75\" />\n</vector>"
  },
  {
    "path": "app/src/main/res/drawable/ic_menu.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportHeight=\"24.0\"\n    android:viewportWidth=\"24.0\">\n    <path\n        android:fillColor=\"#ffffff\"\n        android:pathData=\"M3,18h18v-2L3,16v2zM3,13h18v-2L3,11v2zM3,6v2h18L21,6L3,6z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_timer_wait.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportHeight=\"24\"\n    android:viewportWidth=\"24\">\n    <path\n        android:fillColor=\"#000\"\n        android:pathData=\"M20,2V4H18V8.41L14.41,12L18,15.59V20H20V22H4V20H6V15.59L9.59,12L6,8.41V4H4V2H20M16,16.41L13,13.41V10.59L16,7.59V4H8V7.59L11,10.59V13.41L8,16.41V17H10L12,15L14,17H16V16.41M12,9L10,7H14L12,9Z\" />\n</vector>"
  },
  {
    "path": "app/src/main/res/drawable/item_divider_black.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:shape=\"rectangle\">\n    <size\n        android:height=\"1dp\" />\n    <solid android:color=\"#08000000\" />\n</shape>"
  },
  {
    "path": "app/src/main/res/drawable/item_divider_white.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:shape=\"rectangle\">\n    <size\n        android:height=\"1dp\" />\n    <solid android:color=\"#22ffffff\" />\n</shape>"
  },
  {
    "path": "app/src/main/res/drawable/progress_drawable.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layer-list xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <item android:id=\"@android:id/background\">\n        <shape>\n            <solid\n                android:color=\"@android:color/transparent\" />\n        </shape>\n    </item>\n\n    <item android:id=\"@android:id/progress\">\n        <clip>\n            <shape>\n                <solid\n                    android:color=\"#FF4081\" />\n            </shape>\n        </clip>\n    </item>\n</layer-list>"
  },
  {
    "path": "app/src/main/res/drawable/progress_drawable_black.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layer-list xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <item android:id=\"@android:id/background\">\n        <shape>\n            <solid\n                android:color=\"@android:color/transparent\" />\n        </shape>\n    </item>\n\n    <item android:id=\"@android:id/progress\">\n        <clip>\n            <shape>\n                <solid\n                    android:color=\"#ffffff\" />\n            </shape>\n        </clip>\n    </item>\n</layer-list>"
  },
  {
    "path": "app/src/main/res/drawable/progress_drawable_dark.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layer-list xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <item android:id=\"@android:id/background\">\n        <shape>\n            <solid\n                android:color=\"@android:color/transparent\" />\n        </shape>\n    </item>\n\n    <item android:id=\"@android:id/progress\">\n        <clip>\n            <shape>\n                <solid\n                    android:color=\"#ffb701\" />\n            </shape>\n        </clip>\n    </item>\n</layer-list>"
  },
  {
    "path": "app/src/main/res/drawable/progress_drawable_withbackground.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layer-list xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <item android:id=\"@android:id/background\">\n        <shape>\n            <solid\n                android:color=\"#33000000\" />\n        </shape>\n    </item>\n\n    <item android:id=\"@android:id/progress\">\n        <clip>\n            <shape>\n                <solid\n                    android:color=\"#FF4081\" />\n            </shape>\n        </clip>\n    </item>\n</layer-list>"
  },
  {
    "path": "app/src/main/res/drawable/progress_drawable_withbackground_black.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layer-list xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <item android:id=\"@android:id/background\">\n        <shape>\n            <solid\n                android:color=\"#33ffffff\" />\n        </shape>\n    </item>\n\n    <item android:id=\"@android:id/progress\">\n        <clip>\n            <shape>\n                <solid\n                    android:color=\"#ffffff\" />\n            </shape>\n        </clip>\n    </item>\n</layer-list>"
  },
  {
    "path": "app/src/main/res/drawable/progress_drawable_withbackground_dark.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layer-list xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <item android:id=\"@android:id/background\">\n        <shape>\n            <solid\n                android:color=\"#33ffffff\" />\n        </shape>\n    </item>\n\n    <item android:id=\"@android:id/progress\">\n        <clip>\n            <shape>\n                <solid\n                    android:color=\"#ffb701\" />\n            </shape>\n        </clip>\n    </item>\n</layer-list>"
  },
  {
    "path": "app/src/main/res/drawable/recyclerview_fastscroller_bubble.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:shape=\"rectangle\">\n\n    <corners\n        android:topLeftRadius=\"44dp\"\n        android:topRightRadius=\"44dp\"\n        android:bottomLeftRadius=\"44dp\"\n        android:bottomRightRadius=\"0px\"/>\n\n    <solid android:color=\"#FF0288D1\"/>\n    <size\n        android:height=\"88dp\"\n        android:width=\"88dp\"/>\n</shape>"
  },
  {
    "path": "app/src/main/res/drawable/recyclerview_fastscroller_handle.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item android:state_selected=\"true\">\n        <shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n            android:shape=\"rectangle\">\n\n            <corners android:radius=\"2dp\"/>\n\n            <solid android:color=\"#FF0288D1\"/>\n\n            <size\n                android:height=\"34dp\"\n                android:width=\"5dp\"/>\n        </shape>\n    </item>\n    <item >\n        <shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n            android:shape=\"rectangle\">\n\n            <corners android:radius=\"2dp\"/>\n\n            <solid android:color=\"#FF737373\"/>\n\n            <size\n                android:height=\"34dp\"\n                android:width=\"5dp\"/>\n        </shape>\n    </item>\n\n</selector>"
  },
  {
    "path": "app/src/main/res/drawable/selectable_item_background.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <item\n        android:state_pressed=\"true\"\n        android:drawable=\"@color/colorSelectableItemWhite\"/>\n    <item\n        android:state_enabled=\"false\"\n        android:drawable=\"@android:color/transparent\"/>\n    <item\n        android:drawable=\"@android:color/transparent\"/>\n\n</selector>"
  },
  {
    "path": "app/src/main/res/drawable-xhdpi/above_shadow.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\">   \n    <gradient\n        android:startColor=\"#20000000\"\n        android:endColor=\"@android:color/transparent\"\n        android:angle=\"90\" >\n    </gradient>\n</shape>"
  },
  {
    "path": "app/src/main/res/drawable-xhdpi/below_shadow.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\">   \n    <gradient\n        android:startColor=\"#20000000\"\n        android:endColor=\"@android:color/transparent\"\n        android:angle=\"270\" >\n    </gradient>\n</shape>"
  },
  {
    "path": "app/src/main/res/layout/activity_donate.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\">\n\n    <LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:orientation=\"vertical\">\n\n        <androidx.appcompat.widget.Toolbar\n            android:id=\"@+id/toolbar\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"?attr/actionBarSize\"\n            android:elevation=\"5dp\" />\n\n        <TextView\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:padding=\"20dp\"\n            android:id=\"@+id/donation_status\"\n            android:textSize=\"16sp\"\n            android:layout_gravity=\"center_horizontal\"\n            android:gravity=\"center_horizontal\"\n            android:text=\"Show your appreciation and support the development of Timber. Purchasing any of the below product also unlocks additional now playing styles.\"/>\n\n\n        <ScrollView\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\">\n\n            <LinearLayout\n                android:id=\"@+id/product_list\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginTop=\"15dp\"\n                android:orientation=\"vertical\" />\n\n        </ScrollView>\n\n    </LinearLayout>\n\n    <ProgressBar\n        android:id=\"@+id/progressBar\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"center\"\n        android:gravity=\"center\" />\n</FrameLayout>"
  },
  {
    "path": "app/src/main/res/layout/activity_main.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.drawerlayout.widget.DrawerLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:id=\"@+id/drawer_layout\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:fitsSystemWindows=\"true\">\n\n    <include layout=\"@layout/include_list_viewpager\" />\n\n    <com.google.android.material.navigation.NavigationView\n        android:id=\"@+id/nav_view\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"match_parent\"\n        android:layout_gravity=\"start\"\n        android:fitsSystemWindows=\"true\"\n        app:menu=\"@menu/drawer_view\" />\n\n</androidx.drawerlayout.widget.DrawerLayout>"
  },
  {
    "path": "app/src/main/res/layout/activity_nowplaying.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:id=\"@+id/container\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\" />"
  },
  {
    "path": "app/src/main/res/layout/activity_playlist_detail.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\">\n\n    <ImageView\n        android:id=\"@+id/blurFrame\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:scaleType=\"centerCrop\"\n        android:transitionName=\"transition_album_art\" />\n\n    <View\n        android:id=\"@+id/foreground\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:transitionName=\"transition_foreground\" />\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:orientation=\"vertical\">\n\n        <androidx.appcompat.widget.Toolbar\n            android:id=\"@+id/toolbar\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"?attr/actionBarSize\"\n            android:layout_gravity=\"top\"\n            android:layout_marginTop=\"@dimen/appBarTopMargin\"\n            android:background=\"@android:color/transparent\"\n            app:theme=\"@style/ThemeOverlay.AppCompat.ActionBar\" />\n\n        <TextView\n            android:id=\"@+id/name\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginLeft=\"60dp\"\n            android:ellipsize=\"end\"\n            android:maxLines=\"2\"\n            android:textColor=\"#ffffff\"\n            android:textSize=\"28sp\"\n            android:layout_marginTop=\"15dp\"\n            android:transitionName=\"transition_playlist_name\" />\n\n\n        <RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n            xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\"\n            android:layout_marginLeft=\"5dp\"\n            android:layout_marginTop=\"30dp\"\n            android:background=\"@android:color/transparent\">\n\n            <androidx.recyclerview.widget.RecyclerView xmlns:android=\"http://schemas.android.com/apk/res/android\"\n                xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n                android:id=\"@+id/recyclerview\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"match_parent\"\n                android:fadeScrollbars=\"true\"\n                app:layout_behavior=\"@string/appbar_scrolling_view_behavior\" />\n\n        </RelativeLayout>\n    </LinearLayout>\n</FrameLayout>"
  },
  {
    "path": "app/src/main/res/layout/activity_search.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:fitsSystemWindows=\"true\"\n    android:orientation=\"vertical\">\n\n    <androidx.appcompat.widget.Toolbar\n        android:id=\"@+id/toolbar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"?attr/actionBarSize\"\n        android:elevation=\"5dp\" />\n\n    <androidx.recyclerview.widget.RecyclerView xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n        android:id=\"@+id/recyclerview\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:fadeScrollbars=\"true\"\n        app:layout_behavior=\"@string/appbar_scrolling_view_behavior\" />\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/activity_settings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:orientation=\"vertical\" android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\">\n\n    <androidx.appcompat.widget.Toolbar\n        android:id=\"@+id/toolbar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"?attr/actionBarSize\"\n        android:layout_gravity=\"top\"\n        android:elevation=\"5dp\"\n        android:theme=\"@style/ThemeOverlay.AppCompat.Dark.ActionBar\"\n        app:popupTheme=\"@style/ThemeOverlay.AppCompat.Light\" />\n\n    <FrameLayout\n        android:id=\"@+id/fragment_container\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:layout_marginTop=\"?attr/actionBarSize\"/>\n\n</FrameLayout>"
  },
  {
    "path": "app/src/main/res/layout/artist_detail_albums_header.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"150dp\"\n    android:paddingBottom=\"20dp\"\n    app:layout_behavior=\"@string/appbar_scrolling_view_behavior\">\n\n<androidx.recyclerview.widget.RecyclerView\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:id=\"@+id/recycler_view_album\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"150dp\"\n    android:layout_gravity=\"top\"\n    android:layout_marginTop=\"10dp\"\n    app:layout_behavior=\"@string/appbar_scrolling_view_behavior\"\n    />\n\n</FrameLayout>"
  },
  {
    "path": "app/src/main/res/layout/bottom_nowplaying_card.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:id=\"@+id/topContainer\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"55dp\"\n    android:layout_gravity=\"top\"\n    android:background=\"?attr/windowBackground\">\n\n    <com.naman14.timber.widgets.SquareImageView\n        android:id=\"@+id/album_art_nowplayingcard\"\n        android:layout_width=\"55dp\"\n        android:layout_height=\"55dp\"\n        android:layout_alignParentLeft=\"true\"\n        android:scaleType=\"centerCrop\"\n        android:src=\"@drawable/ic_empty_music2\" />\n\n    <LinearLayout\n        android:id=\"@+id/content\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_alignParentTop=\"true\"\n        android:layout_toRightOf=\"@id/album_art_nowplayingcard\"\n        android:orientation=\"vertical\">\n\n        <ProgressBar\n            android:id=\"@+id/song_progress_normal\"\n            style=\"@style/Widget.AppCompat.ProgressBar.Horizontal\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"top\"\n            android:maxHeight=\"5dp\"\n            android:progress=\"30\"\n            android:tag=\"tint_accent_color\" />\n\n        <TextView\n            android:id=\"@+id/title\"\n            style=\"@style/TextAppearance.AppCompat.Body1\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginLeft=\"12dp\"\n            android:layout_marginRight=\"42dp\"\n            android:ellipsize=\"end\"\n            android:maxLines=\"1\"\n            android:textSize=\"15sp\" />\n\n        <TextView\n            android:id=\"@+id/artist\"\n            style=\"@style/TextAppearance.AppCompat.Widget.ActionMode.Subtitle\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginLeft=\"12dp\"\n            android:ellipsize=\"end\"\n            android:maxLength=\"20\"\n            android:maxLines=\"1\"\n            android:textSize=\"13sp\" />\n\n    </LinearLayout>\n\n    <FrameLayout\n        android:layout_width=\"50dp\"\n        android:layout_height=\"50dp\"\n        android:layout_alignParentRight=\"true\"\n        android:layout_centerVertical=\"true\"\n        android:layout_marginRight=\"5dp\">\n\n        <com.naman14.timber.widgets.PlayPauseButton\n            android:id=\"@+id/play_pause\"\n            android:layout_width=\"23dp\"\n            android:layout_height=\"23dp\"\n            android:layout_gravity=\"center\" />\n\n        <View\n            android:id=\"@+id/play_pause_wrapper\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\"\n            android:background=\"?attr/selectableItemBackgroundBorderless\" />\n\n    </FrameLayout>\n</RelativeLayout>"
  },
  {
    "path": "app/src/main/res/layout/dialog_lastfm_login.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:orientation=\"vertical\" android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:padding=\"15dp\">\n\n    <com.afollestad.appthemeengine.views.ATEEditText\n        android:layout_marginTop=\"20dp\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:id=\"@+id/lastfm_username\"\n        android:hint=\"Username\"\n        android:inputType=\"textNoSuggestions\"/>\n\n    <com.afollestad.appthemeengine.views.ATEEditText\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:inputType=\"textPassword\"\n        android:ems=\"10\"\n        android:id=\"@+id/lastfm_password\"\n        android:hint=\"Password\"/>\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/empty_view.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<TextView\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:id=\"@+id/list_empty\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:gravity=\"center\"\n    android:layout_gravity=\"center\"\n    android:layout_centerInParent=\"true\"\n    android:drawablePadding=\"15dp\"\n    android:textSize=\"20sp\"\n    android:visibility=\"gone\"/>"
  },
  {
    "path": "app/src/main/res/layout/fragment_album_detail.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n\n<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\">\n\n    <com.google.android.material.appbar.AppBarLayout\n        android:id=\"@+id/app_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:theme=\"@style/ThemeOverlay.AppCompat.Dark.ActionBar\">\n\n        <com.google.android.material.appbar.CollapsingToolbarLayout\n            android:id=\"@+id/collapsing_toolbar\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"370dp\"\n            android:background=\"@android:color/white\"\n            android:theme=\"@style/Theme.AppCompat\"\n            app:contentScrim=\"@color/colorPrimary\"\n            app:expandedTitleTextAppearance=\"@style/TextApperance.Album.Title\"\n            app:layout_scrollFlags=\"scroll|exitUntilCollapsed\"\n            app:statusBarScrim=\"@color/colorPrimaryDark\">\n\n\n            <ImageView\n                android:id=\"@+id/album_art\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"match_parent\"\n                android:fitsSystemWindows=\"true\"\n                android:scaleType=\"centerCrop\"\n                android:src=\"@drawable/ic_empty_music2\"\n                android:transitionName=\"transition_album_art\"\n                app:layout_collapseMode=\"parallax\" />\n\n            <View\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"match_parent\"\n                android:background=\"#22000000\" />\n\n\n            <LinearLayout\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:layout_gravity=\"bottom\"\n                android:background=\"@drawable/bg_gradient\"\n                android:orientation=\"horizontal\"\n                android:visibility=\"gone\">\n\n                <com.naman14.timber.widgets.CircleImageView\n                    android:id=\"@+id/artist_art\"\n                    android:layout_width=\"50dp\"\n                    android:layout_height=\"50dp\"\n                    android:layout_alignParentLeft=\"true\"\n                    android:layout_centerVertical=\"true\"\n                    android:layout_marginBottom=\"10dp\"\n                    android:layout_marginLeft=\"10dp\"\n                    android:visibility=\"gone\" />\n\n                <LinearLayout\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_marginBottom=\"15dp\"\n                    android:layout_marginLeft=\"18dp\"\n                    android:orientation=\"vertical\">\n\n                    <TextView\n                        android:id=\"@+id/album_title\"\n                        android:layout_width=\"match_parent\"\n                        android:layout_height=\"wrap_content\"\n                        android:ellipsize=\"end\"\n                        android:singleLine=\"true\"\n                        android:textColor=\"#ffffff\"\n                        android:textSize=\"25sp\"\n                        android:textStyle=\"bold\" />\n\n                    <TextView\n                        android:id=\"@+id/album_details\"\n                        android:layout_width=\"match_parent\"\n                        android:layout_height=\"wrap_content\"\n                        android:paddingTop=\"3dp\"\n                        android:textColor=\"#ffffff\"\n                        android:textSize=\"16sp\" />\n                </LinearLayout>\n\n            </LinearLayout>\n\n\n            <androidx.appcompat.widget.Toolbar\n                android:id=\"@+id/toolbar\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"?attr/actionBarSize\"\n                android:elevation=\"5dp\"\n                app:layout_collapseMode=\"pin\"\n                app:popupTheme=\"@style/ThemeOverlay.AppCompat.Light\" />\n\n        </com.google.android.material.appbar.CollapsingToolbarLayout>\n    </com.google.android.material.appbar.AppBarLayout>\n\n\n    <androidx.recyclerview.widget.RecyclerView xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        android:id=\"@+id/recyclerview\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:clipToPadding=\"false\"\n        android:paddingTop=\"10dp\"\n        app:layout_behavior=\"@string/appbar_scrolling_view_behavior\" />\n\n    <com.google.android.material.floatingactionbutton.FloatingActionButton\n        android:id=\"@+id/fab\"\n        style=\"@style/FabStyle\"\n        android:tag=\"bg_tint_accent_color\"\n        android:visibility=\"gone\"\n        app:layout_anchor=\"@id/app_bar\"\n        app:layout_anchorGravity=\"bottom|right|end\" />\n\n</androidx.coordinatorlayout.widget.CoordinatorLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/fragment_artist_bio.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.core.widget.NestedScrollView\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    app:layout_behavior=\"@string/appbar_scrolling_view_behavior\">\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:orientation=\"vertical\">\n\n    <androidx.cardview.widget.CardView\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:layout_margin=\"@dimen/card_margin\">\n\n        <TextView\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:id=\"@+id/artist_bio\"\n            android:textSize=\"40sp\"/>\n\n        </androidx.cardview.widget.CardView>\n    <com.naman14.timber.widgets.MultiViewPager\n        android:id=\"@+id/tagspager\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"150dp\"\n        android:layout_marginTop=\"16dp\"\n        android:layout_gravity=\"bottom\"\n        app:matchChildWidth=\"@+id/vg_cover\" />\n\n    </LinearLayout>\n\n</androidx.core.widget.NestedScrollView>"
  },
  {
    "path": "app/src/main/res/layout/fragment_artist_detail.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n\n<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\">\n\n    <com.google.android.material.appbar.AppBarLayout\n        android:id=\"@+id/app_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:theme=\"@style/ThemeOverlay.AppCompat.Dark.ActionBar\">\n\n        <com.google.android.material.appbar.CollapsingToolbarLayout\n            android:id=\"@+id/collapsing_toolbar\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"330dp\"\n            android:background=\"@android:color/white\"\n            android:theme=\"@style/Theme.AppCompat\"\n            app:contentScrim=\"@color/colorPrimary\"\n            app:expandedTitleMarginBottom=\"25dp\"\n            app:expandedTitleMarginStart=\"30dp\"\n            app:expandedTitleTextAppearance=\"@style/TextApperance.Artist.Title\"\n            app:layout_scrollFlags=\"scroll|exitUntilCollapsed\"\n            app:statusBarScrim=\"@color/colorPrimaryDark\">\n\n\n            <ImageView\n                android:id=\"@+id/artist_art\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"match_parent\"\n                android:fitsSystemWindows=\"true\"\n                android:scaleType=\"centerCrop\"\n                android:transitionName=\"transition_artist_image\" />\n\n            <View\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"match_parent\"\n                android:background=\"#33000000\" />\n\n\n            <androidx.appcompat.widget.Toolbar\n                android:id=\"@+id/toolbar\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"?attr/actionBarSize\"\n                app:layout_collapseMode=\"pin\"\n                app:popupTheme=\"@style/ThemeOverlay.AppCompat.Light\">\n\n            </androidx.appcompat.widget.Toolbar>\n\n\n        </com.google.android.material.appbar.CollapsingToolbarLayout>\n\n    </com.google.android.material.appbar.AppBarLayout>\n\n    <FrameLayout\n        android:id=\"@+id/container\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        app:layout_behavior=\"@string/appbar_scrolling_view_behavior\" />\n\n\n</androidx.coordinatorlayout.widget.CoordinatorLayout>\n\n"
  },
  {
    "path": "app/src/main/res/layout/fragment_artist_music.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n    <androidx.recyclerview.widget.RecyclerView\n        xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n        android:id=\"@+id/recycler_view_songs\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        app:layout_behavior=\"@string/appbar_scrolling_view_behavior\"/>\n"
  },
  {
    "path": "app/src/main/res/layout/fragment_cast_mini_controller.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<fragment\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:id=\"@+id/castMiniController\"\n    android:layout_width=\"fill_parent\"\n    android:layout_height=\"wrap_content\"\n    android:layout_alignParentBottom=\"true\"\n    android:layout_gravity=\"bottom\"\n    android:gravity=\"bottom\"\n    android:visibility=\"gone\"\n    class=\"com.google.android.gms.cast.framework.media.widget.MiniControllerFragment\"/>"
  },
  {
    "path": "app/src/main/res/layout/fragment_folders.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\">\n\n    <androidx.appcompat.widget.Toolbar\n        android:id=\"@+id/toolbar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"?attr/actionBarSize\"\n        android:layout_alignParentEnd=\"true\"\n        android:layout_alignParentLeft=\"true\"\n        android:layout_alignParentRight=\"true\"\n        android:layout_alignParentStart=\"true\"\n        android:layout_alignParentTop=\"true\"\n        android:elevation=\"5dp\"\n        android:theme=\"@style/ThemeOverlay.AppCompat.Dark.ActionBar\"\n        app:popupTheme=\"@style/ThemeOverlay.AppCompat.Light\" />\n\n    <androidx.recyclerview.widget.RecyclerView xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n        android:id=\"@+id/recyclerview\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:layout_alignParentBottom=\"true\"\n        android:layout_alignParentEnd=\"true\"\n        android:layout_alignParentLeft=\"true\"\n        android:layout_alignParentRight=\"true\"\n        android:layout_alignParentStart=\"true\"\n        android:layout_below=\"@id/toolbar\"\n        android:fadeScrollbars=\"true\"\n        app:layout_behavior=\"@string/appbar_scrolling_view_behavior\" />\n\n    <com.naman14.timber.widgets.FastScroller\n        android:id=\"@+id/fastscroller\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"match_parent\"\n        android:layout_alignBottom=\"@id/recyclerview\"\n        android:layout_alignParentEnd=\"true\"\n        android:layout_alignParentRight=\"true\"\n        android:layout_alignTop=\"@id/recyclerview\"\n        android:layout_marginBottom=\"4dp\"\n        android:layout_marginTop=\"4dp\"\n        android:visibility=\"invisible\" />\n\n    <ProgressBar\n        android:id=\"@id/progressBar\"\n        style=\"?android:progressBarStyleLarge\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_centerInParent=\"true\"\n        android:paddingTop=\"?attr/actionBarSize\"\n        android:tag=\"tint_accent_color\" />\n\n</RelativeLayout>"
  },
  {
    "path": "app/src/main/res/layout/fragment_lyrics.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\">\n\n    <androidx.appcompat.widget.Toolbar\n        android:id=\"@+id/toolbar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"?attr/actionBarSize\"\n        android:background=\"@android:color/transparent\"\n        android:theme=\"@style/Theme.AppCompat\"\n        app:popupTheme=\"@style/ThemeOverlay.AppCompat.Light\" />\n\n    <ScrollView xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        android:id=\"@+id/lyrics\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\">\n\n        <LinearLayout\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:orientation=\"vertical\">\n\n            <TextView\n                android:id=\"@+id/lyrics_text\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_margin=\"20dp\"\n                android:textAppearance=\"@style/TextAppearance.Song.Lyrics\" />\n\n            <TextView\n                android:id=\"@+id/lyrics_makeitpersonal\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_margin=\"20dp\"\n                android:autoLink=\"web\"\n                android:linksClickable=\"true\"\n                android:text=\"@string/lyrics_makeitpersonal\"\n                android:textSize=\"17sp\" />\n        </LinearLayout>\n    </ScrollView>\n</LinearLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/fragment_main.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:id=\"@+id/main_content\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\">\n\n    <com.google.android.material.appbar.AppBarLayout\n        android:id=\"@+id/appbar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\">\n\n        <androidx.appcompat.widget.Toolbar\n            android:id=\"@+id/toolbar\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"?attr/actionBarSize\"\n            app:layout_scrollFlags=\"scroll|enterAlways\" />\n\n        <com.google.android.material.tabs.TabLayout\n            android:id=\"@+id/tabs\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:tag=\"tab_indicator_accent_color,bg_primary_color\" />\n\n    </com.google.android.material.appbar.AppBarLayout>\n\n    <androidx.viewpager.widget.ViewPager\n        android:id=\"@+id/viewpager\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        app:layout_behavior=\"@string/appbar_scrolling_view_behavior\" />\n\n\n</androidx.coordinatorlayout.widget.CoordinatorLayout>"
  },
  {
    "path": "app/src/main/res/layout/fragment_playback_controls.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:id=\"@+id/root_view\">\n\n    <FrameLayout\n        android:id=\"@+id/quick_controls_frame\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\">\n\n        <ImageView\n            android:id=\"@+id/blurredAlbumart\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\"\n            android:scaleType=\"centerCrop\" />\n\n        <View\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\"\n            android:background=\"#44000000\" />\n\n        <LinearLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\"\n            android:layout_gravity=\"center_horizontal|center_vertical\"\n            android:gravity=\"center_horizontal|center_vertical\"\n            android:orientation=\"vertical\"\n            android:paddingLeft=\"30dp\"\n            android:paddingRight=\"30dp\"\n            android:paddingTop=\"20dp\">\n\n            <TextView\n                android:id=\"@+id/song_title\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:ellipsize=\"end\"\n                android:maxLines=\"2\"\n                android:textColor=\"#ffffff\"\n                android:textSize=\"25sp\" />\n\n            <TextView\n                android:id=\"@+id/song_artist\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:ellipsize=\"end\"\n                android:maxLines=\"1\"\n                android:paddingTop=\"3dp\"\n                android:textColor=\"#ffffff\"\n                android:textSize=\"14sp\" />\n\n            <SeekBar\n                android:id=\"@+id/song_progress\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:layout_gravity=\"center\"\n                android:layout_marginLeft=\"6dp\"\n                android:layout_marginRight=\"6dp\"\n                android:maxHeight=\"2dp\"\n                android:paddingTop=\"18dp\"\n                android:progress=\"30\"\n                android:tag=\"tint_accent_color\" />\n\n\n            <TextView\n                android:id=\"@+id/song_album\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:textColor=\"#ffffff\"\n                android:visibility=\"gone\" />\n\n            <TextView\n                android:id=\"@+id/song_duration\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:textColor=\"#ffffff\"\n                android:visibility=\"gone\" />\n\n            <TextView\n                android:id=\"@+id/song_elapsed_time\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:textColor=\"#ffffff\"\n                android:visibility=\"gone\" />\n\n\n            <LinearLayout\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:layout_gravity=\"center_horizontal|center_vertical\"\n                android:orientation=\"horizontal\"\n                android:paddingTop=\"20dp\">\n\n                <net.steamcrafted.materialiconlib.MaterialIconView\n                    android:id=\"@+id/previous\"\n                    android:layout_width=\"48dp\"\n                    android:layout_height=\"48dp\"\n                    android:layout_weight=\"0.7\"\n                    android:background=\"?attr/selectableItemBackgroundBorderless\"\n                    android:paddingLeft=\"50dp\"\n                    android:scaleType=\"center\"\n                    app:materialIcon=\"skip_previous\"\n                    app:materialIconColor=\"#fff\"\n                    app:materialIconSize=\"35dp\" />\n\n                <FrameLayout\n                    android:layout_width=\"50dp\"\n                    android:layout_height=\"50dp\"\n                    android:layout_alignParentRight=\"true\"\n                    android:layout_centerVertical=\"true\"\n                    android:layout_marginRight=\"5dp\"\n                    android:layout_weight=\"1\">\n\n                    <com.naman14.timber.widgets.PlayPauseButton\n                        android:id=\"@+id/playpause\"\n                        android:layout_width=\"23dp\"\n                        android:layout_height=\"23dp\"\n                        android:layout_gravity=\"center\" />\n\n                    <View\n                        android:id=\"@+id/playpausewrapper\"\n                        android:layout_width=\"match_parent\"\n                        android:layout_height=\"match_parent\"\n                        android:background=\"?attr/selectableItemBackgroundBorderless\" />\n\n                </FrameLayout>\n\n                <net.steamcrafted.materialiconlib.MaterialIconView\n                    android:id=\"@+id/next\"\n                    android:layout_width=\"48dp\"\n                    android:layout_height=\"48dp\"\n                    android:layout_weight=\"0.7\"\n                    android:background=\"?attr/selectableItemBackgroundBorderless\"\n                    android:paddingRight=\"50dp\"\n                    android:scaleType=\"center\"\n                    app:materialIcon=\"skip_next\"\n                    app:materialIconColor=\"#fff\"\n                    app:materialIconSize=\"35dp\" />\n            </LinearLayout>\n\n        </LinearLayout>\n    </FrameLayout>\n\n\n    <include layout=\"@layout/bottom_nowplaying_card\" />\n\n\n</FrameLayout>\n\n\n"
  },
  {
    "path": "app/src/main/res/layout/fragment_playlist.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\">\n\n    <LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:orientation=\"vertical\">\n\n        <androidx.appcompat.widget.Toolbar\n            android:id=\"@+id/toolbar\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"?attr/actionBarSize\"\n            android:elevation=\"5dp\"\n            android:theme=\"@style/ThemeOverlay.AppCompat.Dark.ActionBar\"\n            app:popupTheme=\"@style/ThemeOverlay.AppCompat.Light\" />\n\n\n        <FrameLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\">\n\n            <com.naman14.timber.widgets.MultiViewPager\n                android:id=\"@+id/playlistpager\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"match_parent\"\n                android:layout_gravity=\"bottom\"\n                app:matchChildWidth=\"@+id/vg_cover\" />\n\n            <androidx.recyclerview.widget.RecyclerView xmlns:android=\"http://schemas.android.com/apk/res/android\"\n                xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n                android:id=\"@+id/recyclerview\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"match_parent\"\n                android:fadeScrollbars=\"true\"\n                app:layout_behavior=\"@string/appbar_scrolling_view_behavior\" />\n\n        </FrameLayout>\n\n    </LinearLayout>\n\n</FrameLayout>"
  },
  {
    "path": "app/src/main/res/layout/fragment_playlist_pager.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    >\n\n\n    <FrameLayout\n        android:id=\"@+id/vg_cover\"\n        android:layout_width=\"match_parent\"\n        android:layout_marginLeft=\"40dp\"\n        android:layout_marginRight=\"40dp\"\n        android:layout_height=\"match_parent\"\n        android:layout_centerInParent=\"true\"\n        android:layout_marginTop=\"30dp\"\n        android:layout_marginBottom=\"60dp\">\n\n        <ImageView\n            android:id=\"@+id/playlist_image\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\"\n            android:scaleType=\"centerCrop\"\n            android:layout_marginLeft=\"16dp\"\n            android:layout_marginRight=\"16dp\"\n            android:transitionName=\"transition_album_art\"/>\n\n        <View\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\"\n            android:id=\"@+id/foreground\"\n            android:layout_marginLeft=\"16dp\"\n            android:layout_marginRight=\"16dp\"\n            android:transitionName=\"transition_foreground\"/>\n\n        <LinearLayout\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:orientation=\"vertical\"\n            android:layout_marginBottom=\"20dp\"\n            android:layout_marginLeft=\"30dp\"\n            android:layout_gravity=\"bottom|left\">\n        <TextView\n            android:textSize=\"28sp\"\n            android:maxLines=\"1\"\n            android:ellipsize=\"end\"\n            android:layout_marginRight=\"13dp\"\n            android:textColor=\"#ffffff\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:id=\"@+id/name\"\n            android:transitionName=\"transition_playlist_name\"/>\n            <TextView\n                android:textSize=\"15sp\"\n                android:maxLines=\"1\"\n                android:paddingTop=\"3dp\"\n                android:ellipsize=\"end\"\n                android:textColor=\"#ffffff\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:id=\"@+id/songcount\"\n               />\n            <TextView\n                android:textSize=\"15sp\"\n                android:maxLines=\"1\"\n                android:paddingTop=\"3dp\"\n                android:ellipsize=\"end\"\n                android:textColor=\"#ffffff\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:id=\"@+id/runtime\"\n                />\n\n        </LinearLayout>\n\n        <TextView\n            android:layout_gravity=\"top|right\"\n            android:layout_marginTop=\"15dp\"\n            android:layout_marginRight=\"40dp\"\n            android:textSize=\"35sp\"\n            android:maxLines=\"2\"\n            android:ellipsize=\"end\"\n            android:textColor=\"#ffffff\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:id=\"@+id/number\"/>\n\n        <TextView\n            android:layout_gravity=\"top|left\"\n            android:layout_marginTop=\"15dp\"\n            android:layout_marginLeft=\"40dp\"\n            android:textSize=\"15sp\"\n            android:maxLines=\"2\"\n            android:ellipsize=\"end\"\n            android:textColor=\"#ffffff\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:text=\"Auto\\nPlaylist\"\n            android:visibility=\"gone\"\n            android:id=\"@+id/playlisttype\"/>\n\n\n    </FrameLayout>\n\n</RelativeLayout>"
  },
  {
    "path": "app/src/main/res/layout/fragment_queue.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\">\n\n    <androidx.appcompat.widget.Toolbar\n        android:id=\"@+id/toolbar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"?attr/actionBarSize\"\n        android:elevation=\"5dp\"\n        android:layout_alignParentTop=\"true\"\n        android:theme=\"@style/ThemeOverlay.AppCompat.Dark.ActionBar\"\n        app:popupTheme=\"@style/ThemeOverlay.AppCompat.Light\" />\n\n    <com.naman14.timber.widgets.BaseRecyclerView xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n        android:id=\"@+id/recyclerview\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:layout_below=\"@+id/toolbar\"\n        android:fadeScrollbars=\"true\"\n        app:layout_behavior=\"@string/appbar_scrolling_view_behavior\" />\n\n    <include layout=\"@layout/empty_view\"/>\n\n</RelativeLayout>"
  },
  {
    "path": "app/src/main/res/layout/fragment_recyclerview.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:layout_marginBottom=\"55dp\">\n\n    <com.naman14.timber.widgets.BaseRecyclerView xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n        android:id=\"@+id/recyclerview\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:fadeScrollbars=\"true\"\n        app:layout_behavior=\"@string/appbar_scrolling_view_behavior\" />\n\n    <include layout=\"@layout/empty_view\"/>\n\n    <com.naman14.timber.widgets.FastScroller\n        android:id=\"@+id/fastscroller\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"match_parent\"\n        android:layout_alignBottom=\"@id/recyclerview\"\n        android:layout_alignParentEnd=\"true\"\n        android:layout_alignParentRight=\"true\"\n        android:layout_alignTop=\"@id/recyclerview\" />\n\n</RelativeLayout>"
  },
  {
    "path": "app/src/main/res/layout/fragment_similar_artists.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:orientation=\"vertical\" android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\">\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/fragment_style_selector.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:orientation=\"vertical\" android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\">\n\n    <com.naman14.timber.widgets.MultiViewPager\n        android:id=\"@+id/pager\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:layout_gravity=\"center\"\n        app:matchChildWidth=\"@+id/style_root\" />\n\n</FrameLayout>"
  },
  {
    "path": "app/src/main/res/layout/fragment_style_selector_pager.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\">\n\n\n    <FrameLayout\n        android:id=\"@+id/style_root\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:layout_centerInParent=\"true\"\n        android:layout_marginBottom=\"90dp\"\n        android:layout_marginLeft=\"40dp\"\n        android:layout_marginRight=\"40dp\"\n        android:layout_marginTop=\"15dp\"\n        android:background=\"?attr/selectableItemBackgroundBorderless\">\n\n        <ImageView\n            android:id=\"@+id/style_image\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\"\n            android:layout_marginLeft=\"16dp\"\n            android:layout_marginRight=\"16dp\"\n            android:scaleType=\"fitXY\" />\n\n        <View\n            android:id=\"@+id/foreground\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\"\n            android:layout_marginLeft=\"16dp\"\n            android:layout_marginRight=\"16dp\"\n            android:background=\"@color/black_translucent\"\n            android:visibility=\"gone\"/>\n\n\n        <TextView\n            android:id=\"@+id/style_name\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"top|right\"\n            android:layout_marginRight=\"40dp\"\n            android:layout_marginTop=\"15dp\"\n            android:ellipsize=\"end\"\n            android:maxLines=\"2\"\n            android:textColor=\"#ffffff\"\n            android:textSize=\"35sp\"\n            android:visibility=\"gone\"/>\n\n        <LinearLayout\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:orientation=\"vertical\"\n            android:id=\"@+id/currentStyle\"\n            android:visibility=\"gone\"\n            android:layout_gravity=\"center\">\n\n            <net.steamcrafted.materialiconlib.MaterialIconView\n                android:id=\"@+id/previous\"\n                android:layout_width=\"70dp\"\n                android:layout_height=\"70dp\"\n                android:layout_gravity=\"center\"\n                android:scaleType=\"center\"\n                app:materialIcon=\"check\"\n                app:materialIconColor=\"#fff\"\n                app:materialIconSize=\"55dp\" />\n\n            <TextView\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:text=\"@string/currently_selected\"\n                android:textColor=\"#ffffff\"\n                android:textSize=\"16sp\"\n                android:textStyle=\"italic\"\n                android:visibility=\"gone\"/>\n\n        </LinearLayout>\n\n        <ImageView\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:src=\"@drawable/ic_lock_outline_white_24dp\"\n            android:id=\"@+id/img_lock\"\n            android:layout_gravity=\"center\"\n            android:visibility=\"gone\"/>\n\n    </FrameLayout>\n\n</RelativeLayout>"
  },
  {
    "path": "app/src/main/res/layout/fragment_timber1.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\">\n\n    <androidx.percentlayout.widget.PercentRelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:orientation=\"vertical\">\n\n        <FrameLayout\n            android:id=\"@+id/headerView\"\n            android:layout_width=\"match_parent\"\n            app:layout_heightPercent=\"55%\">\n\n            <ImageView\n                android:id=\"@+id/album_art\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"match_parent\"\n                android:scaleType=\"centerCrop\" />\n\n            <View\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"match_parent\"\n                android:background=\"#66000000\" />\n\n            <LinearLayout\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"match_parent\"\n                android:layout_gravity=\"center_horizontal|center_vertical\"\n                android:gravity=\"center_horizontal|center_vertical\"\n                android:orientation=\"vertical\"\n                android:paddingLeft=\"30dp\"\n                android:paddingRight=\"30dp\"\n                android:paddingTop=\"20dp\">\n\n                <TextView\n                    android:id=\"@+id/song_title\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:ellipsize=\"end\"\n                    android:maxLines=\"2\"\n                    android:textColor=\"#ffffff\"\n                    android:textSize=\"25sp\" />\n\n                <TextView\n                    android:id=\"@+id/song_artist\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:ellipsize=\"end\"\n                    android:maxLines=\"1\"\n                    android:paddingTop=\"3dp\"\n                    android:textColor=\"#ffffff\"\n                    android:textSize=\"14sp\" />\n\n                <SeekBar\n                    android:id=\"@+id/song_progress\"\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_gravity=\"center\"\n                    android:layout_marginLeft=\"6dp\"\n                    android:layout_marginRight=\"6dp\"\n                    android:maxHeight=\"2dp\"\n                    android:paddingTop=\"18dp\"\n                    android:progress=\"30\"\n                    android:tag=\"tint_accent_color\" />\n\n\n                <TextView\n                    android:id=\"@+id/song_album\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:textColor=\"#ffffff\"\n                    android:visibility=\"gone\" />\n\n                <RelativeLayout\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\">\n\n                    <TextView\n                        android:id=\"@+id/song_elapsed_time\"\n                        android:layout_width=\"wrap_content\"\n                        android:layout_height=\"wrap_content\"\n                        android:layout_alignParentLeft=\"true\"\n                        android:paddingLeft=\"25dp\"\n                        android:textColor=\"#ffffff\" />\n\n                    <TextView\n                        android:id=\"@+id/song_duration\"\n                        android:layout_width=\"wrap_content\"\n                        android:layout_height=\"wrap_content\"\n                        android:layout_alignParentRight=\"true\"\n                        android:paddingRight=\"25dp\"\n                        android:textColor=\"#ffffff\" />\n\n                </RelativeLayout>\n\n\n                <LinearLayout\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_gravity=\"center_horizontal|center_vertical\"\n                    android:orientation=\"horizontal\"\n                    android:paddingTop=\"20dp\">\n\n                    <net.steamcrafted.materialiconlib.MaterialIconView\n                        android:id=\"@+id/previous\"\n                        android:layout_width=\"48dp\"\n                        android:layout_height=\"48dp\"\n                        android:layout_weight=\"0.7\"\n                        android:background=\"?attr/selectableItemBackgroundBorderless\"\n                        android:paddingLeft=\"50dp\"\n                        android:scaleType=\"center\"\n                        app:materialIcon=\"skip_previous\"\n                        app:materialIconColor=\"#fff\"\n                        app:materialIconSize=\"35dp\" />\n\n                    <FrameLayout\n                        android:layout_width=\"50dp\"\n                        android:layout_height=\"50dp\"\n                        android:layout_alignParentRight=\"true\"\n                        android:layout_centerVertical=\"true\"\n                        android:layout_marginRight=\"5dp\"\n                        android:layout_weight=\"1\">\n\n                        <com.naman14.timber.widgets.PlayPauseButton\n                            android:id=\"@+id/playpause\"\n                            android:layout_width=\"23dp\"\n                            android:layout_height=\"23dp\"\n                            android:layout_gravity=\"center\" />\n\n                        <View\n                            android:id=\"@+id/playpausewrapper\"\n                            android:layout_width=\"match_parent\"\n                            android:layout_height=\"match_parent\"\n                            android:background=\"?attr/selectableItemBackgroundBorderless\" />\n\n                    </FrameLayout>\n\n                    <net.steamcrafted.materialiconlib.MaterialIconView\n                        android:id=\"@+id/next\"\n                        android:layout_width=\"48dp\"\n                        android:layout_height=\"48dp\"\n                        android:layout_weight=\"0.7\"\n                        android:background=\"?attr/selectableItemBackgroundBorderless\"\n                        android:paddingRight=\"50dp\"\n                        android:scaleType=\"center\"\n                        app:materialIcon=\"skip_next\"\n                        app:materialIconColor=\"#fff\"\n                        app:materialIconSize=\"35dp\" />\n                </LinearLayout>\n\n            </LinearLayout>\n\n            <androidx.appcompat.widget.Toolbar\n                android:id=\"@+id/toolbar\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"?attr/actionBarSize\"\n                android:layout_gravity=\"top\"\n                android:layout_marginTop=\"@dimen/appBarTopMargin\"\n                android:background=\"@android:color/transparent\"\n                android:theme=\"@style/Theme.AppCompat\"\n                app:popupTheme=\"@style/ThemeOverlay.AppCompat.Light\" />\n\n        </FrameLayout>\n\n\n        <androidx.recyclerview.widget.RecyclerView xmlns:android=\"http://schemas.android.com/apk/res/android\"\n            android:id=\"@+id/queue_recyclerview\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\"\n            android:layout_below=\"@id/headerView\"\n            android:clipToPadding=\"false\"\n            android:paddingTop=\"18dp\" />\n\n    </androidx.percentlayout.widget.PercentRelativeLayout>\n\n    <com.google.android.material.floatingactionbutton.FloatingActionButton\n        android:id=\"@+id/shuffle\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_margin=\"16dp\"\n        android:clickable=\"true\"\n        android:scaleType=\"centerInside\"\n        android:tag=\"bg_tint_accent_color\"\n        app:layout_anchor=\"@id/headerView\"\n        app:layout_anchorGravity=\"bottom|right|end\" />\n\n</androidx.coordinatorlayout.widget.CoordinatorLayout>"
  },
  {
    "path": "app/src/main/res/layout/fragment_timber2.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\">\n\n    <androidx.percentlayout.widget.PercentRelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:orientation=\"vertical\">\n\n        <FrameLayout\n            android:id=\"@+id/headerView\"\n            android:layout_width=\"match_parent\"\n            app:layout_heightPercent=\"60%\">\n\n            <ImageView\n                android:id=\"@+id/album_art_blurred\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"match_parent\"\n                android:scaleType=\"centerCrop\" />\n\n            <View\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"match_parent\"\n                android:background=\"#77000000\" />\n\n            <LinearLayout\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"match_parent\"\n                android:layout_gravity=\"center_horizontal\"\n                android:layout_marginTop=\"@dimen/appBarTopMargin\"\n                android:gravity=\"center_horizontal\"\n                android:orientation=\"vertical\"\n                android:paddingTop=\"15dp\">\n\n                <TextView\n                    android:id=\"@+id/song_title\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_marginLeft=\"50dp\"\n                    android:layout_marginRight=\"50dp\"\n                    android:ellipsize=\"end\"\n                    android:scrollHorizontally=\"true\"\n                    android:singleLine=\"true\"\n                    android:textColor=\"#ffffff\"\n                    android:textSize=\"21sp\"\n                    android:visibility=\"gone\"/>\n\n                <TextView\n                    android:id=\"@+id/song_artist\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_marginRight=\"10dp\"\n                    android:ellipsize=\"end\"\n                    android:maxLength=\"30\"\n                    android:maxLines=\"1\"\n                    android:paddingTop=\"3dp\"\n                    android:textColor=\"#ffffff\"\n                    android:textSize=\"13sp\"\n                    android:visibility=\"gone\"/>\n\n                <androidx.percentlayout.widget.PercentRelativeLayout\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"match_parent\"\n                    android:layout_gravity=\"center_horizontal|center_vertical\"\n                    android:layout_marginTop=\"10dp\"\n                    android:orientation=\"horizontal\">\n\n                    <net.steamcrafted.materialiconlib.MaterialIconView\n                        android:id=\"@+id/previous\"\n                        android:layout_width=\"48dp\"\n                        android:layout_height=\"48dp\"\n                        android:layout_alignParentLeft=\"true\"\n                        android:layout_centerVertical=\"true\"\n                        android:layout_marginLeft=\"10dp\"\n                        android:background=\"?attr/selectableItemBackgroundBorderless\"\n                        android:scaleType=\"center\"\n                        app:materialIcon=\"skip_previous\"\n                        app:materialIconColor=\"#fff\"\n                        app:materialIconSize=\"35dp\" />\n\n                    <FrameLayout\n                        android:layout_centerInParent=\"true\"\n                        android:layout_gravity=\"center\"\n                        android:background=\"@android:color/transparent\"\n                        app:layout_heightPercent=\"80%\"\n                        app:layout_widthPercent=\"80%\">\n\n                        <com.naman14.timber.widgets.CircleImageView\n                            android:id=\"@+id/album_art\"\n                            android:layout_width=\"match_parent\"\n                            android:layout_height=\"match_parent\"\n                            android:layout_centerInParent=\"true\"\n                            android:layout_gravity=\"center\"\n                            android:background=\"@android:color/transparent\"\n                            android:scaleType=\"centerCrop\" />\n\n                        <com.naman14.timber.widgets.CircleImageView\n                            android:layout_width=\"match_parent\"\n                            android:layout_height=\"match_parent\"\n                            android:layout_centerInParent=\"true\"\n                            android:layout_gravity=\"center\"\n                            android:scaleType=\"centerCrop\"\n                            android:src=\"@color/black_translucent\" />\n\n                        <LinearLayout\n                            android:layout_width=\"wrap_content\"\n                            android:layout_height=\"wrap_content\"\n                            android:layout_gravity=\"center\"\n                            android:orientation=\"vertical\">\n\n                            <include\n                                layout=\"@layout/song_elapsedtime\"\n                                android:layout_width=\"wrap_content\"\n                                android:layout_height=\"wrap_content\"\n                                android:layout_gravity=\"center\" />\n\n                            <LinearLayout\n                                android:layout_width=\"wrap_content\"\n                                android:layout_height=\"wrap_content\"\n                                android:layout_gravity=\"center\"\n                                android:orientation=\"horizontal\"\n                                android:paddingTop=\"10dp\">\n\n                                <ImageView\n                                    android:id=\"@+id/shuffle\"\n                                    android:layout_width=\"48dp\"\n                                    android:layout_height=\"48dp\"\n                                    android:background=\"?attr/selectableItemBackgroundBorderless\"\n                                    android:scaleType=\"center\" />\n\n                                <ImageView\n                                    android:id=\"@+id/repeat\"\n                                    android:layout_width=\"48dp\"\n                                    android:layout_height=\"48dp\"\n                                    android:background=\"?attr/selectableItemBackgroundBorderless\"\n                                    android:paddingLeft=\"5dp\"\n                                    android:scaleType=\"center\" />\n                            </LinearLayout>\n                        </LinearLayout>\n                    </FrameLayout>\n\n                    <com.naman14.timber.widgets.CircularSeekBar\n                        android:id=\"@+id/song_progress_circular\"\n                        style=\"@style/Widget.AppCompat.ProgressBar.Horizontal\"\n                        android:layout_centerInParent=\"true\"\n                        android:layout_gravity=\"center\"\n                        android:background=\"@android:color/transparent\"\n                        android:maxHeight=\"1dp\"\n                        android:progress=\"30\"\n                        app:circle_color=\"#33000000\"\n                        app:circle_stroke_width=\"2\"\n                        app:layout_heightPercent=\"95%\"\n                        app:layout_widthPercent=\"95%\"\n                        app:pointer_halo_border_width=\"1.5\"\n                        app:pointer_halo_width=\"0.5\" />\n\n\n                    <net.steamcrafted.materialiconlib.MaterialIconView\n                        android:id=\"@+id/next\"\n                        android:layout_width=\"48dp\"\n                        android:layout_height=\"48dp\"\n                        android:layout_alignParentRight=\"true\"\n                        android:layout_centerVertical=\"true\"\n                        android:layout_marginRight=\"10dp\"\n                        android:background=\"?attr/selectableItemBackgroundBorderless\"\n                        android:scaleType=\"center\"\n                        app:materialIcon=\"skip_next\"\n                        app:materialIconColor=\"#fff\"\n                        app:materialIconSize=\"35dp\" />\n                </androidx.percentlayout.widget.PercentRelativeLayout>\n\n            </LinearLayout>\n\n            <androidx.appcompat.widget.Toolbar\n                android:id=\"@+id/toolbar\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"?attr/actionBarSize\"\n                android:layout_gravity=\"top\"\n                android:layout_marginTop=\"@dimen/appBarTopMargin\"\n                android:background=\"@android:color/transparent\"\n                android:theme=\"@style/Theme.AppCompat\"\n                app:popupTheme=\"@style/ThemeOverlay.AppCompat.Light\" />\n\n\n        </FrameLayout>\n\n\n        <androidx.recyclerview.widget.RecyclerView xmlns:android=\"http://schemas.android.com/apk/res/android\"\n            android:id=\"@+id/queue_recyclerview\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\"\n            android:layout_below=\"@id/headerView\"\n            android:clipToPadding=\"false\"\n            android:paddingTop=\"18dp\" />\n\n    </androidx.percentlayout.widget.PercentRelativeLayout>\n\n    <com.google.android.material.floatingactionbutton.FloatingActionButton\n        android:id=\"@+id/playpausefloating\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_margin=\"16dp\"\n        android:clickable=\"true\"\n        android:scaleType=\"centerInside\"\n        android:tag=\"bg_tint_accent_color\"\n        app:layout_anchor=\"@id/headerView\"\n        app:layout_anchorGravity=\"bottom|right|end\" />\n\n</androidx.coordinatorlayout.widget.CoordinatorLayout>"
  },
  {
    "path": "app/src/main/res/layout/fragment_timber3.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\">\n\n    <androidx.percentlayout.widget.PercentRelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:orientation=\"vertical\">\n\n        <FrameLayout\n            android:id=\"@+id/headerView\"\n            android:layout_width=\"match_parent\"\n            app:layout_heightPercent=\"65%\">\n\n            <ImageView\n                android:id=\"@+id/album_art\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"match_parent\"\n                android:scaleType=\"centerCrop\" />\n\n            <View\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"match_parent\"\n                android:background=\"#22000000\" />\n\n            <androidx.appcompat.widget.Toolbar\n                android:id=\"@+id/toolbar\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"?attr/actionBarSize\"\n                android:layout_gravity=\"top\"\n                android:layout_marginTop=\"@dimen/appBarTopMargin\"\n                android:background=\"@android:color/transparent\"\n                android:theme=\"@style/Theme.AppCompat\"\n                app:popupTheme=\"@style/ThemeOverlay.AppCompat.Light\" />\n\n        </FrameLayout>\n\n        <LinearLayout\n            android:id=\"@+id/detailView\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\"\n            android:layout_below=\"@+id/headerView\"\n            android:layout_gravity=\"center_horizontal|center_vertical\"\n            android:gravity=\"center_horizontal|center_vertical\"\n            android:orientation=\"vertical\"\n            android:paddingLeft=\"30dp\"\n            android:paddingRight=\"30dp\">\n\n            <RelativeLayout\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:orientation=\"horizontal\">\n\n                <LinearLayout\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_alignParentLeft=\"true\"\n                    android:layout_gravity=\"left\"\n                    android:orientation=\"vertical\">\n\n                    <TextView\n                        android:id=\"@+id/song_title\"\n                        style=\"@style/TextAppearance.AppCompat.Body1\"\n                        android:layout_width=\"wrap_content\"\n                        android:layout_height=\"wrap_content\"\n                        android:layout_marginRight=\"100dp\"\n                        android:width=\"800dp\"\n                        android:ellipsize=\"marquee\"\n                        android:fadingEdge=\"horizontal\"\n                        android:marqueeRepeatLimit=\"marquee_forever\"\n                        android:scrollHorizontally=\"true\"\n                        android:singleLine=\"true\"\n                        android:textSize=\"16sp\" />\n\n                    <TextView\n                        android:id=\"@+id/song_artist\"\n                        style=\"@style/TextAppearance.AppCompat.Body1\"\n                        android:layout_width=\"wrap_content\"\n                        android:width=\"800dp\"\n                        android:layout_height=\"wrap_content\"\n                        android:ellipsize=\"end\"\n                        android:maxLines=\"1\"\n                        android:paddingTop=\"3dp\"\n\n                        android:textSize=\"14sp\" />\n                </LinearLayout>\n\n                <LinearLayout\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_alignParentRight=\"true\"\n                    android:orientation=\"horizontal\">\n\n                    <ImageView\n                        android:id=\"@+id/shuffle\"\n                        android:layout_width=\"48dp\"\n                        android:layout_height=\"48dp\"\n                        android:background=\"?attr/selectableItemBackgroundBorderless\"\n                        android:scaleType=\"center\" />\n\n                    <ImageView\n                        android:id=\"@+id/repeat\"\n                        android:layout_width=\"48dp\"\n                        android:layout_height=\"48dp\"\n                        android:background=\"?attr/selectableItemBackgroundBorderless\"\n                        android:paddingLeft=\"5dp\"\n                        android:scaleType=\"center\" />\n                </LinearLayout>\n            </RelativeLayout>\n\n            <SeekBar\n                android:id=\"@+id/song_progress\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:layout_gravity=\"center\"\n                android:maxHeight=\"2dp\"\n                android:paddingTop=\"10dp\"\n                android:progress=\"30\"\n                android:tag=\"tint_accent_color\" />\n\n\n            <TextView\n                android:id=\"@+id/song_album\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:visibility=\"gone\" />\n\n            <RelativeLayout\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\">\n\n                <TextView\n                    android:id=\"@+id/song_elapsed_time\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_alignParentLeft=\"true\"\n                    android:paddingLeft=\"25dp\" />\n\n                <TextView\n                    android:id=\"@+id/song_duration\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_alignParentRight=\"true\"\n                    android:paddingRight=\"25dp\" />\n            </RelativeLayout>\n\n\n            <LinearLayout\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:layout_gravity=\"center_horizontal|center_vertical\"\n                android:orientation=\"horizontal\"\n                android:paddingTop=\"5dp\">\n\n                <net.steamcrafted.materialiconlib.MaterialIconView\n                    android:id=\"@+id/previous\"\n                    android:layout_width=\"48dp\"\n                    android:layout_height=\"48dp\"\n                    android:layout_gravity=\"center\"\n                    android:layout_marginLeft=\"30dp\"\n                    android:layout_weight=\"0.6\"\n                    android:background=\"?attr/selectableItemBackgroundBorderless\"\n                    android:scaleType=\"center\"\n                    app:materialIcon=\"skip_previous\"\n                    app:materialIconColor=\"?attr/iconColor\"\n                    app:materialIconSize=\"35dp\" />\n\n                <FrameLayout\n                    android:layout_width=\"75dp\"\n                    android:layout_height=\"75dp\"\n                    android:layout_alignParentRight=\"true\"\n                    android:layout_centerVertical=\"true\"\n                    android:layout_marginRight=\"5dp\"\n                    android:layout_weight=\"1\">\n\n\n                    <com.google.android.material.floatingactionbutton.FloatingActionButton\n                        android:id=\"@+id/playpausefloating\"\n                        android:layout_width=\"55dp\"\n                        android:layout_height=\"55dp\"\n                        android:layout_gravity=\"center\"\n                        android:clickable=\"true\"\n                        android:scaleType=\"fitCenter\"\n                        android:tag=\"bg_tint_accent_color\" />\n                </FrameLayout>\n\n                <net.steamcrafted.materialiconlib.MaterialIconView\n                    android:id=\"@+id/next\"\n                    android:layout_width=\"48dp\"\n                    android:layout_height=\"48dp\"\n                    android:layout_gravity=\"center\"\n                    android:layout_marginRight=\"30dp\"\n                    android:layout_weight=\"0.6\"\n                    android:background=\"?attr/selectableItemBackgroundBorderless\"\n                    android:scaleType=\"center\"\n                    app:materialIcon=\"skip_next\"\n                    app:materialIconColor=\"?attr/iconColor\"\n                    app:materialIconSize=\"35dp\" />\n            </LinearLayout>\n\n        </LinearLayout>\n\n    </androidx.percentlayout.widget.PercentRelativeLayout>\n\n\n</androidx.coordinatorlayout.widget.CoordinatorLayout>"
  },
  {
    "path": "app/src/main/res/layout/fragment_timber4.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\">\n\n    <ImageView\n        android:id=\"@+id/album_art_blurred\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:scaleType=\"centerCrop\" />\n\n    <View\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:background=\"#77000000\" />\n\n    <androidx.percentlayout.widget.PercentRelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:orientation=\"vertical\">\n\n        <FrameLayout\n            android:id=\"@+id/headerView\"\n            android:layout_width=\"match_parent\"\n            app:layout_heightPercent=\"70%\">\n\n\n            <LinearLayout\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"match_parent\"\n                android:layout_gravity=\"center_horizontal\"\n                android:layout_marginTop=\"@dimen/appBarTopMargin\"\n                android:gravity=\"center_horizontal\"\n                android:orientation=\"vertical\"\n                android:paddingTop=\"25dp\">\n\n                <TextView\n                    android:id=\"@+id/song_title\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_marginLeft=\"50dp\"\n                    android:layout_marginRight=\"50dp\"\n                    android:ellipsize=\"marquee\"\n                    android:scrollHorizontally=\"true\"\n                    android:singleLine=\"true\"\n                    android:textColor=\"#ffffff\"\n                    android:textSize=\"23sp\"\n                    android:visibility=\"gone\"/>\n\n                <TextView\n                    android:id=\"@+id/song_artist\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_marginLeft=\"50dp\"\n                    android:layout_marginRight=\"50dp\"\n                    android:ellipsize=\"end\"\n                    android:maxLines=\"1\"\n                    android:paddingTop=\"3dp\"\n                    android:textColor=\"#ffffff\"\n                    android:textSize=\"13sp\"\n                    android:visibility=\"gone\"/>\n\n                <androidx.percentlayout.widget.PercentRelativeLayout\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"match_parent\"\n                    android:layout_gravity=\"center_horizontal|center_vertical\"\n                    android:layout_marginTop=\"10dp\"\n                    android:orientation=\"horizontal\">\n\n                    <FrameLayout\n                        android:layout_centerInParent=\"true\"\n                        android:layout_gravity=\"center\"\n                        android:background=\"@android:color/transparent\"\n                        app:layout_heightPercent=\"80%\"\n                        app:layout_widthPercent=\"80%\">\n\n                        <com.naman14.timber.widgets.CircleImageView\n                            android:id=\"@+id/album_art\"\n                            android:layout_width=\"match_parent\"\n                            android:layout_height=\"match_parent\"\n                            android:layout_centerInParent=\"true\"\n                            android:layout_gravity=\"center\"\n                            android:background=\"@android:color/transparent\"\n                            android:scaleType=\"centerCrop\" />\n\n                        <com.naman14.timber.widgets.CircleImageView\n                            android:layout_width=\"match_parent\"\n                            android:layout_height=\"match_parent\"\n                            android:layout_centerInParent=\"true\"\n                            android:layout_gravity=\"center\"\n                            android:scaleType=\"centerCrop\"\n                            android:src=\"@color/black_translucent\" />\n\n                        <LinearLayout\n                            android:layout_width=\"wrap_content\"\n                            android:layout_height=\"wrap_content\"\n                            android:layout_gravity=\"center\"\n                            android:orientation=\"vertical\">\n\n                            <include\n                                layout=\"@layout/song_elapsedtime\"\n                                android:layout_width=\"wrap_content\"\n                                android:layout_height=\"wrap_content\"\n                                android:layout_gravity=\"center\" />\n\n                            <LinearLayout\n                                android:layout_width=\"wrap_content\"\n                                android:layout_height=\"wrap_content\"\n                                android:layout_gravity=\"center\"\n                                android:orientation=\"horizontal\"\n                                android:paddingTop=\"15dp\">\n\n                                <ImageView\n                                    android:id=\"@+id/shuffle\"\n                                    android:layout_width=\"48dp\"\n                                    android:layout_height=\"48dp\"\n                                    android:background=\"?attr/selectableItemBackgroundBorderless\"\n                                    android:scaleType=\"center\" />\n\n                                <ImageView\n                                    android:id=\"@+id/repeat\"\n                                    android:layout_width=\"48dp\"\n                                    android:layout_height=\"48dp\"\n                                    android:background=\"?attr/selectableItemBackgroundBorderless\"\n                                    android:paddingLeft=\"5dp\"\n                                    android:scaleType=\"center\" />\n                            </LinearLayout>\n                        </LinearLayout>\n\n                    </FrameLayout>\n\n                    <com.naman14.timber.widgets.CircularSeekBar\n                        android:id=\"@+id/song_progress_circular\"\n                        style=\"@style/Widget.AppCompat.ProgressBar.Horizontal\"\n                        android:layout_centerInParent=\"true\"\n                        android:layout_gravity=\"center\"\n                        android:background=\"@android:color/transparent\"\n                        android:maxHeight=\"1dp\"\n                        android:progress=\"30\"\n                        app:circle_color=\"#33000000\"\n                        app:circle_progress_color=\"?attr/accentColor\"\n                        app:circle_stroke_width=\"2\"\n                        app:layout_heightPercent=\"95%\"\n                        app:layout_widthPercent=\"95%\"\n                        app:pointer_color=\"?attr/accentColor\"\n                        app:pointer_halo_border_width=\"1.5\"\n                        app:pointer_halo_color=\"?attr/accentColor\"\n                        app:pointer_halo_width=\"0.5\" />\n\n                </androidx.percentlayout.widget.PercentRelativeLayout>\n            </LinearLayout>\n\n            <androidx.appcompat.widget.Toolbar\n                android:id=\"@+id/toolbar\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"?attr/actionBarSize\"\n                android:layout_gravity=\"top\"\n                android:layout_marginTop=\"@dimen/appBarTopMargin\"\n                android:background=\"@android:color/transparent\"\n                android:theme=\"@style/Theme.AppCompat\"\n                app:popupTheme=\"@style/ThemeOverlay.AppCompat.Light\" />\n\n\n        </FrameLayout>\n\n        <LinearLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_below=\"@id/headerView\"\n            android:layout_gravity=\"center_horizontal|center_vertical\"\n            android:orientation=\"horizontal\"\n            android:paddingTop=\"5dp\">\n\n            <net.steamcrafted.materialiconlib.MaterialIconView\n                android:id=\"@+id/previous\"\n                android:layout_width=\"48dp\"\n                android:layout_height=\"48dp\"\n                android:layout_gravity=\"center\"\n                android:layout_marginLeft=\"30dp\"\n                android:layout_weight=\"0.6\"\n                android:background=\"?attr/selectableItemBackgroundBorderless\"\n                android:scaleType=\"center\"\n                app:materialIcon=\"skip_previous\"\n                app:materialIconColor=\"#ffffff\"\n                app:materialIconSize=\"35dp\" />\n\n            <FrameLayout\n                android:layout_width=\"75dp\"\n                android:layout_height=\"75dp\"\n                android:layout_alignParentRight=\"true\"\n                android:layout_centerVertical=\"true\"\n                android:layout_marginRight=\"5dp\"\n                android:layout_weight=\"1\">\n\n\n                <com.google.android.material.floatingactionbutton.FloatingActionButton\n                    android:id=\"@+id/playpausefloating\"\n                    android:layout_width=\"55dp\"\n                    android:layout_height=\"55dp\"\n                    android:layout_gravity=\"center\"\n                    android:clickable=\"true\"\n                    android:scaleType=\"fitCenter\"\n                    android:tag=\"bg_tint_accent_color\" />\n            </FrameLayout>\n\n            <net.steamcrafted.materialiconlib.MaterialIconView\n                android:id=\"@+id/next\"\n                android:layout_width=\"48dp\"\n                android:layout_height=\"48dp\"\n                android:layout_gravity=\"center\"\n                android:layout_marginRight=\"30dp\"\n                android:layout_weight=\"0.6\"\n                android:background=\"?attr/selectableItemBackgroundBorderless\"\n                android:scaleType=\"center\"\n                app:materialIcon=\"skip_next\"\n                app:materialIconColor=\"#ffffff\"\n                app:materialIconSize=\"35dp\" />\n        </LinearLayout>\n\n        <androidx.recyclerview.widget.RecyclerView xmlns:android=\"http://schemas.android.com/apk/res/android\"\n            android:id=\"@+id/queue_recyclerview_horizontal\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"75dp\"\n            android:layout_alignParentBottom=\"true\" />\n\n    </androidx.percentlayout.widget.PercentRelativeLayout>\n\n\n</androidx.coordinatorlayout.widget.CoordinatorLayout>\n\n"
  },
  {
    "path": "app/src/main/res/layout/fragment_timber5.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\">\n\n    <ImageView\n        android:id=\"@+id/album_art_blurred\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:scaleType=\"centerCrop\" />\n\n    <View\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:background=\"#44000000\" />\n\n    <androidx.percentlayout.widget.PercentRelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:orientation=\"vertical\">\n\n        <FrameLayout\n            android:id=\"@+id/headerView\"\n            android:layout_width=\"match_parent\"\n            app:layout_heightPercent=\"60%\">\n\n\n            <androidx.appcompat.widget.Toolbar\n                android:id=\"@+id/toolbar\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"?attr/actionBarSize\"\n                android:layout_gravity=\"top\"\n                android:layout_marginTop=\"@dimen/appBarTopMargin\"\n                android:background=\"@android:color/transparent\"\n                android:theme=\"@style/Theme.AppCompat\"\n                app:popupTheme=\"@style/ThemeOverlay.AppCompat.Light\" />\n\n\n            <androidx.percentlayout.widget.PercentRelativeLayout\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"match_parent\"\n                android:layout_gravity=\"center_horizontal|center_vertical\"\n                android:gravity=\"center_vertical|center_horizontal\"\n                android:orientation=\"horizontal\">\n\n                <androidx.cardview.widget.CardView\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    app:cardBackgroundColor=\"@android:color/transparent\"\n                    app:layout_heightPercent=\"60%\"\n                    app:layout_widthPercent=\"60%\"\n                    app:cardElevation=\"10dp\"\n                    android:layout_marginTop=\"15dp\">\n\n                    <ImageView\n                        android:id=\"@+id/album_art\"\n                        android:layout_width=\"match_parent\"\n                        android:layout_height=\"match_parent\"\n                        android:adjustViewBounds=\"true\"\n                        android:layout_margin=\"1dp\"\n                        android:scaleType=\"centerCrop\"/>\n\n                </androidx.cardview.widget.CardView>\n            </androidx.percentlayout.widget.PercentRelativeLayout>\n\n\n        </FrameLayout>\n\n        <LinearLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_below=\"@id/headerView\"\n            android:layout_gravity=\"center_horizontal|center_vertical\"\n            android:orientation=\"horizontal\"\n            android:paddingTop=\"5dp\">\n\n\n            <LinearLayout\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"match_parent\"\n                android:layout_gravity=\"left\"\n                android:layout_marginTop=\"@dimen/appBarTopMargin\"\n                android:gravity=\"left\"\n                android:orientation=\"vertical\">\n\n                <LinearLayout\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\">\n                <TextView\n                    android:id=\"@+id/song_title\"\n                    style=\"@style/TextAppearance.AppCompat.Body1\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:ellipsize=\"marquee\"\n                    android:scrollHorizontally=\"true\"\n                    android:marqueeRepeatLimit=\"marquee_forever\"\n                    android:singleLine=\"true\"\n                    android:fadingEdge=\"horizontal\"\n                    android:textColor=\"#ffffff\"\n                    android:textSize=\"22sp\"\n                    android:layout_marginLeft=\"40dp\"\n                    android:layout_marginRight=\"40dp\"/>\n                </LinearLayout>\n\n                <TextView\n                    android:id=\"@+id/song_artist\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:ellipsize=\"end\"\n                    android:maxLines=\"1\"\n                    android:paddingTop=\"3dp\"\n                    android:textColor=\"#ffffff\"\n                    android:textSize=\"11sp\"\n                    android:layout_marginLeft=\"40dp\"\n                    android:layout_marginRight=\"40dp\"/>\n\n                <SeekBar\n                    android:id=\"@+id/song_progress\"\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_gravity=\"center\"\n                    android:maxHeight=\"2dp\"\n                    android:progress=\"30\"\n                    android:layout_marginLeft=\"30dp\"\n                    android:layout_marginRight=\"30dp\"\n                    android:tag=\"tint_accent_color\"\n                    android:layout_marginTop=\"10dp\"/>\n\n\n                <TextView\n                    android:id=\"@+id/song_album\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:textColor=\"#ffffff\"\n                    android:visibility=\"gone\" />\n\n                <RelativeLayout\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_marginLeft=\"40dp\"\n                    android:layout_marginRight=\"40dp\">\n\n                    <TextView\n                        android:id=\"@+id/song_elapsed_time\"\n                        android:layout_width=\"wrap_content\"\n                        android:layout_height=\"wrap_content\"\n                        android:layout_alignParentLeft=\"true\"\n                        android:textColor=\"#ffffff\" />\n\n                    <TextView\n                        android:id=\"@+id/song_duration\"\n                        android:layout_width=\"wrap_content\"\n                        android:layout_height=\"wrap_content\"\n                        android:layout_alignParentRight=\"true\"\n                        android:textColor=\"#ffffff\"/>\n\n                </RelativeLayout>\n\n                <LinearLayout\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_gravity=\"center_horizontal|center_vertical\"\n                    android:orientation=\"horizontal\"\n                    android:paddingTop=\"20dp\"\n                    android:layout_marginLeft=\"40dp\"\n                    android:layout_marginRight=\"40dp\">\n\n                    <ImageView\n                        android:id=\"@+id/shuffle\"\n                        android:layout_width=\"48dp\"\n                        android:layout_height=\"48dp\"\n                        android:layout_weight=\"1\"\n                        android:background=\"?attr/selectableItemBackgroundBorderless\"\n                        android:scaleType=\"center\" />\n\n                    <net.steamcrafted.materialiconlib.MaterialIconView\n                        android:id=\"@+id/previous\"\n                        android:layout_width=\"48dp\"\n                        android:layout_height=\"48dp\"\n                        android:layout_weight=\"1\"\n                        android:background=\"?attr/selectableItemBackgroundBorderless\"\n                        android:scaleType=\"center\"\n                        app:materialIcon=\"skip_previous\"\n                        app:materialIconColor=\"#fff\"\n                        app:materialIconSize=\"35dp\" />\n\n                    <FrameLayout\n                        android:layout_width=\"50dp\"\n                        android:layout_height=\"50dp\"\n                        android:layout_alignParentRight=\"true\"\n                        android:layout_centerVertical=\"true\"\n                        android:layout_marginRight=\"5dp\"\n                        android:layout_weight=\"1\">\n\n                        <com.naman14.timber.widgets.PlayPauseButton\n                            android:id=\"@+id/playpause\"\n                            android:layout_width=\"23dp\"\n                            android:layout_height=\"23dp\"\n                            android:layout_gravity=\"center\" />\n\n                        <View\n                            android:id=\"@+id/playpausewrapper\"\n                            android:layout_width=\"match_parent\"\n                            android:layout_height=\"match_parent\"\n                            android:background=\"?attr/selectableItemBackgroundBorderless\" />\n\n                    </FrameLayout>\n\n                    <net.steamcrafted.materialiconlib.MaterialIconView\n                        android:id=\"@+id/next\"\n                        android:layout_width=\"48dp\"\n                        android:layout_height=\"48dp\"\n                        android:layout_weight=\"1\"\n                        android:background=\"?attr/selectableItemBackgroundBorderless\"\n                        android:scaleType=\"center\"\n                        app:materialIcon=\"skip_next\"\n                        app:materialIconColor=\"#fff\"\n                        app:materialIconSize=\"35dp\" />\n\n                    <ImageView\n                        android:id=\"@+id/repeat\"\n                        android:layout_width=\"48dp\"\n                        android:layout_height=\"48dp\"\n                        android:layout_weight=\"1\"\n                        android:background=\"?attr/selectableItemBackgroundBorderless\"\n                        android:paddingLeft=\"5dp\"\n                        android:scaleType=\"center\" />\n                </LinearLayout>\n\n            </LinearLayout>\n        </LinearLayout>\n\n        <androidx.recyclerview.widget.RecyclerView xmlns:android=\"http://schemas.android.com/apk/res/android\"\n            android:id=\"@+id/queue_recyclerview_horizontal\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"75dp\"\n            android:background=\"@android:color/transparent\"\n            android:layout_alignParentBottom=\"true\"/>\n\n    </androidx.percentlayout.widget.PercentRelativeLayout>\n\n</androidx.coordinatorlayout.widget.CoordinatorLayout>\n\n\n\n"
  },
  {
    "path": "app/src/main/res/layout/fragment_timber6.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"#403f4d\">\n\n    <androidx.percentlayout.widget.PercentRelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:orientation=\"vertical\">\n\n        <FrameLayout\n            android:id=\"@+id/headerView\"\n            android:layout_width=\"match_parent\"\n            app:layout_heightPercent=\"65%\">\n\n            <ImageView\n                android:id=\"@+id/album_art\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"match_parent\"\n                android:scaleType=\"centerCrop\" />\n\n            <androidx.percentlayout.widget.PercentRelativeLayout\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"match_parent\">\n\n                <FrameLayout\n                    android:layout_width=\"match_parent\"\n                    android:layout_alignParentBottom=\"true\"\n                    android:layout_gravity=\"bottom\"\n                    android:background=\"#99000000\"\n                    app:layout_heightPercent=\"20%\">\n\n                    <View\n                        android:layout_width=\"match_parent\"\n                        android:layout_height=\"match_parent\"\n                        android:background=\"#99000000\" />\n\n                    <RelativeLayout\n                        android:layout_width=\"match_parent\"\n                        android:layout_height=\"match_parent\"\n                        android:layout_gravity=\"center\"\n                        android:layout_marginLeft=\"40dp\"\n                        android:layout_marginRight=\"40dp\"\n                        android:gravity=\"center\"\n                        android:orientation=\"horizontal\">\n\n                        <LinearLayout\n                            android:layout_width=\"match_parent\"\n                            android:layout_height=\"wrap_content\"\n                            android:layout_alignParentLeft=\"true\"\n                            android:layout_alignParentStart=\"true\"\n                            android:layout_centerVertical=\"true\"\n                            android:layout_gravity=\"left\"\n                            android:orientation=\"vertical\">\n\n                            <TextView\n                                android:id=\"@+id/song_title\"\n                                style=\"@style/TextAppearance.AppCompat.Body1\"\n                                android:layout_width=\"match_parent\"\n                                android:layout_height=\"wrap_content\"\n                                android:layout_marginRight=\"80dp\"\n                                android:ellipsize=\"end\"\n                                android:fontFamily=\"sans-serif-light\"\n                                android:singleLine=\"true\"\n                                android:textColor=\"@android:color/white\"\n                                android:textSize=\"21sp\" />\n\n                            <TextView\n                                android:id=\"@+id/song_artist\"\n                                style=\"@style/TextAppearance.AppCompat.Body1\"\n                                android:layout_width=\"wrap_content\"\n                                android:layout_height=\"wrap_content\"\n                                android:ellipsize=\"end\"\n                                android:fontFamily=\"sans-serif-light\"\n                                android:maxLines=\"1\"\n                                android:paddingTop=\"3dp\"\n                                android:textColor=\"@android:color/white\"\n                                android:textSize=\"13sp\" />\n                        </LinearLayout>\n\n                        <LinearLayout\n                            android:layout_width=\"wrap_content\"\n                            android:layout_height=\"wrap_content\"\n                            android:layout_alignParentRight=\"true\"\n                            android:layout_centerVertical=\"true\"\n                            android:orientation=\"horizontal\">\n\n                            <ImageView\n                                android:id=\"@+id/shuffle\"\n                                android:layout_width=\"30dp\"\n                                android:layout_height=\"30dp\"\n                                android:background=\"?attr/selectableItemBackgroundBorderless\"\n                                android:layout_marginRight=\"10dp\"\n                                android:scaleType=\"center\" />\n\n                            <ImageView\n                                android:id=\"@+id/repeat\"\n                                android:layout_width=\"30dp\"\n                                android:layout_height=\"30dp\"\n                                android:background=\"?attr/selectableItemBackgroundBorderless\"\n                                android:layout_marginLeft=\"10dp\"\n                                android:scaleType=\"center\" />\n                        </LinearLayout>\n                    </RelativeLayout>\n                </FrameLayout>\n\n\n            </androidx.percentlayout.widget.PercentRelativeLayout>\n\n\n            <androidx.appcompat.widget.Toolbar\n                android:id=\"@+id/toolbar\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"?attr/actionBarSize\"\n                android:layout_gravity=\"top\"\n                android:layout_marginTop=\"@dimen/appBarTopMargin\"\n                android:background=\"@android:color/transparent\"\n                android:theme=\"@style/Theme.AppCompat\"\n                app:popupTheme=\"@style/ThemeOverlay.AppCompat.Light\" />\n\n        </FrameLayout>\n\n        <LinearLayout\n            android:id=\"@+id/detailView\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\"\n            android:layout_below=\"@+id/headerView\"\n            android:layout_gravity=\"center_horizontal|center_vertical\"\n            android:gravity=\"center_horizontal|center_vertical\"\n            android:orientation=\"vertical\"\n            android:paddingLeft=\"30dp\"\n            android:paddingRight=\"30dp\"\n            app:layout_heightPercent=\"20%\">\n\n            <LinearLayout\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:layout_gravity=\"center_horizontal|center_vertical\"\n                android:orientation=\"horizontal\"\n                android:layout_marginBottom=\"10dp\">\n\n                <net.steamcrafted.materialiconlib.MaterialIconView\n                    android:id=\"@+id/previous\"\n                    android:layout_width=\"48dp\"\n                    android:layout_height=\"48dp\"\n                    android:layout_gravity=\"center\"\n                    android:layout_marginLeft=\"30dp\"\n                    android:layout_weight=\"1\"\n                    android:background=\"?attr/selectableItemBackgroundBorderless\"\n                    android:scaleType=\"center\"\n                    app:materialIcon=\"skip_previous\"\n                    app:materialIconColor=\"#fff\"\n                    app:materialIconSize=\"35dp\" />\n\n                <FrameLayout\n                    android:layout_width=\"50dp\"\n                    android:layout_height=\"50dp\"\n                    android:layout_alignParentRight=\"true\"\n                    android:layout_centerVertical=\"true\"\n                    android:layout_marginRight=\"5dp\"\n                    android:layout_weight=\"1\">\n\n                    <com.naman14.timber.widgets.PlayPauseButton\n                        android:id=\"@+id/playpause\"\n                        android:layout_width=\"23dp\"\n                        android:layout_height=\"23dp\"\n                        android:layout_gravity=\"center\" />\n\n                    <View\n                        android:id=\"@+id/playpausewrapper\"\n                        android:layout_width=\"match_parent\"\n                        android:layout_height=\"match_parent\"\n                        android:background=\"?attr/selectableItemBackgroundBorderless\" />\n\n                </FrameLayout>\n\n                <net.steamcrafted.materialiconlib.MaterialIconView\n                    android:id=\"@+id/next\"\n                    android:layout_width=\"48dp\"\n                    android:layout_height=\"48dp\"\n                    android:layout_gravity=\"center\"\n                    android:layout_marginRight=\"30dp\"\n                    android:layout_weight=\"1\"\n                    android:background=\"?attr/selectableItemBackgroundBorderless\"\n                    android:scaleType=\"center\"\n                    app:materialIcon=\"skip_next\"\n                    app:materialIconColor=\"#fff\"\n                    app:materialIconSize=\"35dp\" />\n            </LinearLayout>\n\n            <SeekBar\n                android:id=\"@+id/song_progress\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:layout_gravity=\"center\"\n                android:maxHeight=\"2dp\"\n                android:paddingTop=\"10dp\"\n                android:progress=\"30\" />\n\n\n            <TextView\n                android:id=\"@+id/song_album\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:visibility=\"gone\" />\n\n            <RelativeLayout\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:visibility=\"gone\">\n\n                <TextView\n                    android:id=\"@+id/song_elapsed_time\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_alignParentLeft=\"true\"\n                    android:paddingLeft=\"25dp\" />\n\n                <TextView\n                    android:id=\"@+id/song_duration\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_alignParentRight=\"true\"\n                    android:paddingRight=\"25dp\" />\n            </RelativeLayout>\n\n\n        </LinearLayout>\n\n        <RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n            android:id=\"@+id/nextView\"\n            android:layout_width=\"match_parent\"\n            android:layout_below=\"@+id/detailView\"\n            android:layout_marginLeft=\"40dp\"\n            android:layout_marginRight=\"40dp\"\n            android:orientation=\"horizontal\"\n            android:layout_alignParentBottom=\"true\"\n            app:layout_heightPercent=\"15%\"\n            android:background=\"?attr/selectableItemBackground\">\n\n            <com.naman14.timber.widgets.CircleImageView\n                android:id=\"@+id/album_art_next\"\n                android:layout_width=\"55dp\"\n                android:layout_height=\"55dp\"\n                android:layout_alignParentLeft=\"true\"\n                android:scaleType=\"centerCrop\"\n                android:layout_centerVertical=\"true\"\n                android:background=\"#11000000\" />\n\n            <LinearLayout\n                android:id=\"@+id/content\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:layout_toRightOf=\"@id/album_art_next\"\n                android:orientation=\"vertical\"\n                android:layout_centerVertical=\"true\">\n\n                <TextView\n                    style=\"@style/TextAppearance.AppCompat.Body1\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_marginLeft=\"12dp\"\n                    android:layout_marginRight=\"42dp\"\n                    android:ellipsize=\"end\"\n                    android:fontFamily=\"sans-serif-light\"\n                    android:maxLines=\"1\"\n                    android:text=\"Next Song\"\n                    android:textColor=\"@android:color/white\"\n                    android:textSize=\"11sp\" />\n\n                <TextView\n                    android:id=\"@+id/title_next\"\n                    style=\"@style/TextAppearance.AppCompat.Widget.ActionMode.Subtitle\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_marginLeft=\"12dp\"\n                    android:ellipsize=\"end\"\n                    android:fontFamily=\"sans-serif-light\"\n                    android:singleLine=\"true\"\n                    android:text=\"This is a very long song title to test the length of this text view\"\n                    android:textColor=\"@android:color/white\"\n                    android:textSize=\"17sp\" />\n\n            </LinearLayout>\n        </RelativeLayout>\n\n    </androidx.percentlayout.widget.PercentRelativeLayout>\n\n\n</androidx.coordinatorlayout.widget.CoordinatorLayout>"
  },
  {
    "path": "app/src/main/res/layout/include_list_viewpager.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_height=\"match_parent\"\n    android:layout_width=\"match_parent\"\n    android:id=\"@+id/content_root\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n    <com.naman14.timber.slidinguppanel.SlidingUpPanelLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n        xmlns:sothree=\"http://schemas.android.com/apk/res-auto\"\n        android:id=\"@+id/sliding_layout\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:gravity=\"bottom\"\n        sothree:fadeColor=\"@android:color/transparent\"\n        sothree:panelHeight=\"55dp\"\n        sothree:shadowHeight=\"5dp\">\n\n        <FrameLayout\n            android:id=\"@+id/fragment_container\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\" />\n\n        <FrameLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"250dp\"\n            android:id=\"@+id/quickcontrols_container\">\n\n        </FrameLayout>\n\n    </com.naman14.timber.slidinguppanel.SlidingUpPanelLayout>\n\n\n</FrameLayout>"
  },
  {
    "path": "app/src/main/res/layout/item_album_grid.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"160dp\"\n    android:layout_margin=\"2.0dip\"\n    android:elevation=\"3dp\"\n    android:background=\"?attr/selectableItemBackground\">\n\n    <FrameLayout\n        android:id=\"@+id/content\"\n        android:layout_width=\"fill_parent\"\n        android:layout_height=\"match_parent\">\n\n        <LinearLayout\n            android:layout_width=\"fill_parent\"\n            android:layout_height=\"fill_parent\"\n            android:gravity=\"center\"\n            android:orientation=\"vertical\">\n\n            <com.naman14.timber.widgets.SquareImageView\n                android:id=\"@+id/album_art\"\n                android:layout_width=\"fill_parent\"\n                android:layout_height=\"fill_parent\"\n                android:scaleType=\"centerCrop\"\n                android:transitionName=\"transition_album_art\" />\n\n            <LinearLayout\n                android:id=\"@+id/footer\"\n                android:layout_width=\"fill_parent\"\n                android:layout_height=\"68.0dip\"\n                android:gravity=\"center_vertical\"\n                android:orientation=\"vertical\"\n                android:paddingLeft=\"16.0dip\"\n                android:paddingRight=\"16.0dip\">\n\n                <TextView\n                    android:id=\"@+id/album_title\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:fontFamily=\"sans-serif-medium\"\n                    android:singleLine=\"true\"\n                    android:textAppearance=\"@style/TextAppearance.AppCompat.Subhead\" />\n\n                <TextView\n                    android:id=\"@+id/album_artist\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:fontFamily=\"sans-serif\"\n                    android:singleLine=\"true\"\n                    android:textAppearance=\"@style/TextAppearance.AppCompat.Caption\" />\n            </LinearLayout>\n        </LinearLayout>\n    </FrameLayout>\n\n    <View\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:background=\"?attr/selectableItemBackground\"/>\n\n</FrameLayout>"
  },
  {
    "path": "app/src/main/res/layout/item_album_list.xml",
    "content": "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"fill_parent\"\n    android:layout_height=\"wrap_content\"\n    android:background=\"?attr/selectableItemBackground\"\n    android:orientation=\"vertical\"\n    android:paddingBottom=\"8dp\"\n    android:paddingLeft=\"5dp\"\n    android:paddingTop=\"8dp\">\n\n    <RelativeLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:orientation=\"horizontal\">\n\n        <ImageView\n            android:id=\"@+id/album_art\"\n            android:layout_width=\"50dp\"\n            android:layout_height=\"50dp\"\n            android:layout_alignParentLeft=\"true\"\n            android:padding=\"1dp\"\n            android:transitionName=\"transition_album_art\" />\n\n        <LinearLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginRight=\"10dp\"\n            android:layout_toLeftOf=\"@+id/currentlyPlayingIndicator\"\n            android:layout_toRightOf=\"@+id/album_art\"\n            android:orientation=\"vertical\"\n            android:paddingLeft=\"15dp\"\n            android:paddingTop=\"5dp\">\n\n\n            <TextView\n                android:id=\"@+id/album_title\"\n                style=\"@style/TextAppearance.AppCompat.Body1\"\n                android:layout_width=\"fill_parent\"\n                android:layout_height=\"wrap_content\"\n                android:ellipsize=\"end\"\n                android:singleLine=\"true\"\n                android:textSize=\"16sp\" />\n\n            <TextView\n                android:id=\"@+id/album_artist\"\n                style=\"@style/TextAppearance.AppCompat.Widget.ActionMode.Subtitle\"\n                android:layout_width=\"fill_parent\"\n                android:layout_height=\"wrap_content\"\n                android:ellipsize=\"end\"\n                android:paddingTop=\"3dp\"\n                android:singleLine=\"true\"\n                android:textSize=\"13sp\" />\n\n\n        </LinearLayout>\n\n        <net.steamcrafted.materialiconlib.MaterialIconView\n            android:id=\"@+id/currentlyPlayingIndicator\"\n            android:layout_width=\"48dp\"\n            android:layout_height=\"48dp\"\n            android:layout_alignParentRight=\"true\"\n            android:layout_marginRight=\"20dp\"\n            android:background=\"?attr/selectableItemBackgroundBorderless\"\n            android:scaleType=\"center\"\n            android:visibility=\"invisible\"\n            app:materialIconColor=\"#fff\"\n            app:materialIconSize=\"35dp\" />\n\n\n    </RelativeLayout>\n\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/item_album_search.xml",
    "content": "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"fill_parent\"\n    android:layout_height=\"wrap_content\"\n    android:orientation=\"vertical\"\n    android:paddingLeft=\"5dp\"\n    android:paddingTop=\"8dp\"\n    android:paddingBottom=\"8dp\"\n    android:background=\"?attr/selectableItemBackground\">\n\n    <RelativeLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:orientation=\"horizontal\">\n\n        <ImageView\n            android:layout_width=\"50dp\"\n            android:layout_height=\"50dp\"\n            android:id=\"@+id/albumArt\"\n            android:padding=\"1dp\"\n            android:layout_alignParentLeft=\"true\"\n            android:transitionName=\"transition_album_art\"\n            />\n\n        <LinearLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:orientation=\"vertical\"\n            android:paddingLeft=\"15dp\"\n            android:paddingTop=\"5dp\"\n            android:layout_toRightOf=\"@+id/albumArt\"\n            android:layout_marginRight=\"10dp\">\n\n\n            <TextView\n                android:id=\"@+id/album_title\"\n                android:layout_width=\"fill_parent\"\n                android:layout_height=\"wrap_content\"\n                android:textSize=\"16sp\"\n                android:ellipsize=\"end\"\n                android:singleLine=\"true\"\n                style=\"@style/TextAppearance.AppCompat.Body1\"/>\n\n            <TextView\n                android:id=\"@+id/album_artist\"\n                android:layout_width=\"fill_parent\"\n                android:layout_height=\"wrap_content\"\n                android:paddingTop=\"3dp\"\n                android:textSize=\"13sp\"\n                android:ellipsize=\"end\"\n                android:singleLine=\"true\"\n                style=\"@style/TextAppearance.AppCompat.Widget.ActionMode.Subtitle\"/>\n\n\n\n        </LinearLayout>\n\n\n\n    </RelativeLayout>\n\n\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/item_album_song.xml",
    "content": "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"fill_parent\"\n    android:layout_height=\"wrap_content\"\n    android:background=\"?attr/selectableItemBackground\"\n    android:orientation=\"vertical\"\n    android:paddingBottom=\"8dp\"\n    android:paddingLeft=\"5dp\"\n    android:paddingTop=\"8dp\">\n\n    <RelativeLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:orientation=\"horizontal\">\n\n        <TextView\n            android:id=\"@+id/trackNumber\"\n            style=\"@style/TextAppearance.AppCompat.Large\"\n            android:layout_width=\"50dp\"\n            android:layout_height=\"match_parent\"\n            android:layout_alignParentLeft=\"true\"\n            android:gravity=\"center\"\n            android:maxLength=\"3\"\n            android:padding=\"2dp\"\n            android:paddingLeft=\"15dp\"\n            android:textSize=\"20sp\" />\n\n        <LinearLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginRight=\"10dp\"\n            android:layout_toLeftOf=\"@+id/currentlyPlayingIndicator\"\n            android:layout_toRightOf=\"@+id/trackNumber\"\n            android:orientation=\"vertical\"\n            android:paddingLeft=\"18dp\"\n            android:paddingTop=\"5dp\">\n\n\n            <TextView\n                android:id=\"@+id/song_title\"\n                style=\"@style/TextAppearance.AppCompat.Body1\"\n                android:layout_width=\"fill_parent\"\n                android:layout_height=\"wrap_content\"\n                android:ellipsize=\"end\"\n                android:singleLine=\"true\"\n                android:textSize=\"16sp\" />\n\n            <TextView\n                android:id=\"@+id/song_duration\"\n                style=\"@style/TextAppearance.AppCompat.Widget.ActionMode.Subtitle\"\n                android:layout_width=\"fill_parent\"\n                android:layout_height=\"wrap_content\"\n                android:ellipsize=\"end\"\n                android:paddingTop=\"3dp\"\n                android:singleLine=\"true\"\n                android:textSize=\"12sp\" />\n\n\n        </LinearLayout>\n\n        <net.steamcrafted.materialiconlib.MaterialIconView\n            android:id=\"@+id/currentlyPlayingIndicator\"\n            android:layout_width=\"48dp\"\n            android:layout_height=\"48dp\"\n            android:layout_marginRight=\"10dp\"\n            android:layout_toLeftOf=\"@+id/popup_menu\"\n            android:background=\"?attr/selectableItemBackgroundBorderless\"\n            android:scaleType=\"center\"\n            android:visibility=\"invisible\"\n            app:materialIconColor=\"#fff\"\n            app:materialIconSize=\"35dp\" />\n\n\n        <com.naman14.timber.widgets.PopupImageView\n            android:id=\"@+id/popup_menu\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_alignParentRight=\"true\"\n            android:layout_centerVertical=\"true\"\n            android:layout_marginRight=\"8dp\"\n            android:background=\"?attr/selectableItemBackgroundBorderless\"\n            android:foregroundTint=\"#434343\"\n            android:padding=\"5dp\"\n            android:src=\"@drawable/ic_more_vert_black_24dp\" />\n\n    </RelativeLayout>\n\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/item_artist.xml",
    "content": "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"fill_parent\"\n    android:layout_height=\"wrap_content\"\n    android:orientation=\"vertical\"\n    android:paddingLeft=\"5dp\"\n    android:paddingTop=\"8dp\"\n    android:paddingBottom=\"8dp\"\n    android:background=\"?attr/selectableItemBackground\">\n\n    <RelativeLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:orientation=\"horizontal\">\n\n        <ImageView\n            android:layout_width=\"50dp\"\n            android:layout_height=\"50dp\"\n            android:id=\"@+id/artistImage\"\n            android:padding=\"1dp\"\n            android:layout_alignParentLeft=\"true\"\n            android:transitionName=\"transition_artist_image\"\n            />\n\n        <LinearLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:orientation=\"vertical\"\n            android:paddingLeft=\"15dp\"\n            android:paddingTop=\"5dp\"\n            android:layout_toRightOf=\"@+id/artistImage\"\n            android:layout_toLeftOf=\"@+id/currentlyPlayingIndicator\"\n            android:layout_marginRight=\"10dp\">\n\n\n            <TextView\n                android:id=\"@+id/artist_name\"\n                android:layout_width=\"fill_parent\"\n                android:layout_height=\"wrap_content\"\n                android:textSize=\"16sp\"\n                android:ellipsize=\"end\"\n                android:singleLine=\"true\"\n                style=\"@style/TextAppearance.AppCompat.Body1\"/>\n\n            <TextView\n                android:id=\"@+id/album_song_count\"\n                android:layout_width=\"fill_parent\"\n                android:layout_height=\"wrap_content\"\n                android:paddingTop=\"3dp\"\n                android:textSize=\"13sp\"\n                android:ellipsize=\"end\"\n                android:singleLine=\"true\"\n                style=\"@style/TextAppearance.AppCompat.Widget.ActionMode.Subtitle\"/>\n\n\n\n        </LinearLayout>\n\n        <net.steamcrafted.materialiconlib.MaterialIconView\n            android:id=\"@+id/currentlyPlayingIndicator\"\n            android:layout_width=\"48dp\"\n            android:layout_height=\"48dp\"\n            android:background=\"?attr/selectableItemBackgroundBorderless\"\n            android:layout_marginRight=\"20dp\"\n            android:scaleType=\"center\"\n            app:materialIconColor=\"#fff\"\n            app:materialIconSize=\"35dp\"\n            android:visibility=\"invisible\"\n            android:layout_alignParentRight=\"true\"/>\n\n\n\n    </RelativeLayout>\n\n\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/item_artist_album.xml",
    "content": "<androidx.cardview.widget.CardView\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"104dp\"\n    android:layout_height=\"150dp\"\n    android:foreground=\"?selectableItemBackgroundBorderless\"\n    android:paddingLeft=\"25dp\"\n    app:cardUseCompatPadding=\"true\"\n    android:id=\"@+id/root_view\"\n    style=\"?attr/cardStyle\">\n\n    <RelativeLayout\n        android:layout_width=\"104dp\"\n        android:layout_height=\"match_parent\">\n\n        <ImageView\n            android:id=\"@+id/album_art\"\n            android:layout_width=\"104dp\"\n            android:layout_height=\"104dp\"\n            android:layout_alignParentTop=\"true\"\n            android:scaleType=\"centerCrop\"\n            android:src=\"@drawable/ic_empty_music2\"\n            android:transitionName=\"transition_album_art\" />\n\n        <LinearLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\"\n            android:orientation=\"vertical\"\n            android:gravity=\"center_vertical\"\n            android:layout_below=\"@id/album_art\"\n            android:layout_alignParentLeft=\"true\"\n            android:layout_marginLeft=\"8dp\"\n            >\n\n            <TextView\n                android:id=\"@+id/album_title\"\n                android:layout_width=\"104dp\"\n                android:layout_height=\"wrap_content\"\n                android:layout_gravity=\"top\"\n                android:ellipsize=\"end\"\n                android:singleLine=\"true\"\n                android:textSize=\"14sp\"\n                style=\"@style/TextAppearance.AppCompat.Body1\"/>\n\n            <TextView\n                android:id=\"@+id/album_details\"\n                android:layout_width=\"104dp\"\n                android:ellipsize=\"end\"\n                android:singleLine=\"true\"\n                android:layout_height=\"wrap_content\"\n                android:textSize=\"12sp\"\n                style=\"@style/TextAppearance.AppCompat.Widget.ActionMode.Subtitle\"\n                />\n\n        </LinearLayout>\n\n    </RelativeLayout>\n\n</androidx.cardview.widget.CardView>"
  },
  {
    "path": "app/src/main/res/layout/item_artist_grid.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"160dp\"\n    android:layout_margin=\"2.0dip\"\n    android:elevation=\"3dp\"\n    android:background=\"?attr/selectableItemBackground\">\n\n    <FrameLayout\n        android:id=\"@+id/content\"\n        android:layout_width=\"fill_parent\"\n        android:layout_height=\"match_parent\">\n\n        <LinearLayout\n            android:layout_width=\"fill_parent\"\n            android:layout_height=\"fill_parent\"\n            android:gravity=\"center\"\n            android:orientation=\"vertical\">\n\n            <com.naman14.timber.widgets.SquareImageView\n                android:id=\"@+id/artistImage\"\n                android:layout_width=\"fill_parent\"\n                android:layout_height=\"fill_parent\"\n                android:scaleType=\"centerCrop\"\n                android:src=\"@drawable/ic_empty_music2\"\n                android:transitionName=\"transition_artist_image\" />\n\n            <LinearLayout\n                android:id=\"@+id/footer\"\n                android:layout_width=\"fill_parent\"\n                android:layout_height=\"68.0dip\"\n                android:gravity=\"center_vertical\"\n                android:orientation=\"vertical\"\n                android:paddingLeft=\"16.0dip\"\n                android:paddingRight=\"16.0dip\">\n\n                <TextView\n                    android:id=\"@+id/artist_name\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:fontFamily=\"sans-serif-medium\"\n                    android:singleLine=\"true\"\n                    android:textAppearance=\"@style/TextAppearance.AppCompat.Subhead\" />\n\n                <TextView\n                    android:id=\"@+id/album_song_count\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:fontFamily=\"sans-serif\"\n                    android:singleLine=\"true\"\n                    android:textAppearance=\"@style/TextAppearance.AppCompat.Caption\" />\n            </LinearLayout>\n        </LinearLayout>\n    </FrameLayout>\n\n    <View\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:background=\"?attr/selectableItemBackground\"/>\n\n</FrameLayout>"
  },
  {
    "path": "app/src/main/res/layout/item_artist_song.xml",
    "content": "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"fill_parent\"\n    android:layout_height=\"wrap_content\"\n    android:orientation=\"vertical\"\n    android:paddingLeft=\"5dp\"\n    android:paddingTop=\"8dp\"\n    android:paddingBottom=\"8dp\"\n    android:background=\"?attr/selectableItemBackground\">\n\n    <RelativeLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:orientation=\"horizontal\">\n\n        <ImageView\n            android:layout_width=\"50dp\"\n            android:layout_height=\"50dp\"\n            android:id=\"@+id/albumArt\"\n            android:padding=\"1dp\"\n            android:layout_alignParentLeft=\"true\"\n            />\n\n        <LinearLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:orientation=\"vertical\"\n            android:paddingLeft=\"15dp\"\n            android:paddingTop=\"5dp\"\n            android:layout_toRightOf=\"@+id/albumArt\"\n            android:layout_toLeftOf=\"@+id/currentlyPlayingIndicator\"\n            android:layout_marginRight=\"10dp\">\n\n\n            <TextView\n                android:id=\"@+id/song_title\"\n                android:layout_width=\"fill_parent\"\n                android:layout_height=\"wrap_content\"\n                android:textSize=\"16sp\"\n                android:ellipsize=\"end\"\n                android:singleLine=\"true\"\n                style=\"@style/TextAppearance.AppCompat.Body1\"/>\n\n            <TextView\n                android:id=\"@+id/song_album\"\n                android:layout_width=\"fill_parent\"\n                android:layout_height=\"wrap_content\"\n                android:paddingTop=\"3dp\"\n                android:textSize=\"12sp\"\n                android:ellipsize=\"end\"\n                android:singleLine=\"true\"\n                style=\"@style/TextAppearance.AppCompat.Widget.ActionMode.Subtitle\"/>\n\n\n\n        </LinearLayout>\n\n        <net.steamcrafted.materialiconlib.MaterialIconView\n            android:id=\"@+id/currentlyPlayingIndicator\"\n            android:layout_width=\"48dp\"\n            android:layout_height=\"48dp\"\n            android:background=\"?attr/selectableItemBackgroundBorderless\"\n            android:layout_marginRight=\"10dp\"\n            android:scaleType=\"center\"\n            app:materialIconColor=\"#fff\"\n            app:materialIconSize=\"35dp\"\n            android:visibility=\"invisible\"\n            android:layout_toLeftOf=\"@+id/popup_menu\"/>\n\n        <com.naman14.timber.widgets.PopupImageView\n            android:id=\"@+id/popup_menu\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_alignParentRight=\"true\"\n            android:layout_centerVertical=\"true\"\n            android:layout_marginRight=\"8dp\"\n            android:background=\"?attr/selectableItemBackgroundBorderless\"\n            android:foregroundTint=\"#434343\"\n            android:padding=\"5dp\"\n            android:src=\"@drawable/ic_more_vert_black_24dp\" />\n\n    </RelativeLayout>\n\n\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/item_donate_product.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:orientation=\"vertical\" android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\">\n\n    <TextView\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:id=\"@+id/product_detail\"\n        style=\"@style/TextAppearance.AppCompat.Body1\"\n        android:textSize=\"16sp\"\n        android:padding=\"20dp\"\n        android:layout_marginLeft=\"10dp\"\n        android:layout_alignParentLeft=\"true\"/>\n\n    <Button\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:id=\"@+id/btn_donate\"\n        android:text=\"Purchase\"\n        android:layout_alignParentRight=\"true\"\n        android:layout_marginRight=\"15dp\"\n        />\n\n</RelativeLayout>"
  },
  {
    "path": "app/src/main/res/layout/item_folder_list.xml",
    "content": "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"fill_parent\"\n    android:layout_height=\"wrap_content\"\n    android:background=\"?attr/selectableItemBackground\"\n    android:gravity=\"center_vertical\"\n    android:orientation=\"horizontal\"\n    android:paddingBottom=\"8dp\"\n    android:paddingLeft=\"5dp\"\n    android:paddingTop=\"8dp\">\n\n    <ImageView\n        android:id=\"@+id/album_art\"\n        android:layout_width=\"46dp\"\n        android:layout_height=\"46dp\"\n        android:padding=\"4dp\" />\n\n    <TextView\n        android:id=\"@+id/folder_title\"\n        style=\"@style/TextAppearance.AppCompat.Body1\"\n        android:layout_width=\"fill_parent\"\n        android:layout_height=\"wrap_content\"\n        android:ellipsize=\"end\"\n        android:lines=\"1\"\n        android:paddingLeft=\"6dp\"\n        android:paddingStart=\"6dp\"\n        android:textSize=\"16sp\" />\n\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/item_playing_queue.xml",
    "content": "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"fill_parent\"\n    android:layout_height=\"wrap_content\"\n    android:background=\"?attr/selectableItemBackground\"\n    android:orientation=\"vertical\"\n    android:paddingBottom=\"6dp\"\n    android:paddingTop=\"6dp\">\n\n    <RelativeLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:orientation=\"horizontal\">\n\n        <net.steamcrafted.materialiconlib.MaterialIconView\n            android:id=\"@+id/reorder\"\n            android:layout_width=\"48dp\"\n            android:layout_height=\"48dp\"\n            android:layout_alignParentLeft=\"true\"\n            android:background=\"?attr/selectableItemBackgroundBorderless\"\n            android:scaleType=\"center\"\n            app:materialIcon=\"drag_vertical\"\n            app:materialIconColor=\"?attr/iconColor\"\n            app:materialIconSize=\"30dp\" />\n\n\n        <ImageView\n            android:id=\"@+id/albumArt\"\n            android:layout_width=\"55dp\"\n            android:layout_height=\"55dp\"\n            android:layout_toRightOf=\"@+id/reorder\"\n            android:padding=\"1dp\" />\n\n        <LinearLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginRight=\"10dp\"\n            android:layout_toLeftOf=\"@+id/visualizer\"\n            android:layout_toRightOf=\"@+id/albumArt\"\n            android:orientation=\"vertical\"\n            android:paddingLeft=\"10dp\"\n            android:paddingTop=\"5dp\">\n\n\n            <TextView\n                android:id=\"@+id/song_title\"\n                style=\"@style/TextAppearance.AppCompat.Body1\"\n                android:layout_width=\"fill_parent\"\n                android:layout_height=\"wrap_content\"\n                android:ellipsize=\"end\"\n                android:singleLine=\"true\"\n                android:textSize=\"16sp\" />\n\n            <TextView\n                android:id=\"@+id/song_artist\"\n                style=\"@style/TextAppearance.AppCompat.Widget.ActionMode.Subtitle\"\n                android:layout_width=\"fill_parent\"\n                android:layout_height=\"wrap_content\"\n                android:ellipsize=\"end\"\n                android:paddingTop=\"3dp\"\n                android:singleLine=\"true\"\n                android:textSize=\"13sp\" />\n\n\n        </LinearLayout>\n\n\n        <com.naman14.timber.widgets.MusicVisualizer\n            android:id=\"@+id/visualizer\"\n            android:layout_width=\"30dp\"\n            android:layout_height=\"40dp\"\n            android:layout_centerVertical=\"true\"\n            android:layout_marginBottom=\"10dp\"\n            android:layout_marginRight=\"15dp\"\n            android:layout_toLeftOf=\"@+id/popup_menu\"\n            android:visibility=\"gone\" />\n\n        <com.naman14.timber.widgets.PopupImageView\n            android:id=\"@+id/popup_menu\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_alignParentRight=\"true\"\n            android:layout_centerVertical=\"true\"\n            android:layout_marginRight=\"8dp\"\n            android:background=\"?attr/selectableItemBackgroundBorderless\"\n            android:foregroundTint=\"#434343\"\n            android:padding=\"5dp\"\n            android:src=\"@drawable/ic_more_vert_black_24dp\" />\n\n\n    </RelativeLayout>\n\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/item_song.xml",
    "content": "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"fill_parent\"\n    android:layout_height=\"wrap_content\"\n    android:background=\"?attr/selectableItemBackground\"\n    android:orientation=\"vertical\"\n    android:paddingBottom=\"6dp\"\n    android:paddingLeft=\"8dp\"\n    android:paddingTop=\"6dp\">\n\n    <RelativeLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:orientation=\"horizontal\">\n\n        <ImageView\n            android:id=\"@+id/albumArt\"\n            android:layout_width=\"55dp\"\n            android:layout_height=\"55dp\"\n            android:layout_alignParentLeft=\"true\"\n            android:padding=\"1dp\"\n            android:src=\"@drawable/ic_empty_music2\"/>\n\n        <LinearLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginRight=\"10dp\"\n            android:layout_toLeftOf=\"@+id/visualizer\"\n            android:layout_toRightOf=\"@+id/albumArt\"\n            android:orientation=\"vertical\"\n            android:paddingLeft=\"12dp\"\n            android:paddingTop=\"5dp\">\n\n\n            <TextView\n                android:id=\"@+id/song_title\"\n                style=\"@style/TextAppearance.AppCompat.Body1\"\n                android:layout_width=\"fill_parent\"\n                android:layout_height=\"wrap_content\"\n                android:ellipsize=\"end\"\n                android:singleLine=\"true\"\n                android:textSize=\"16sp\" />\n\n            <TextView\n                android:id=\"@+id/song_artist\"\n                style=\"@style/TextAppearance.AppCompat.Widget.ActionMode.Subtitle\"\n                android:layout_width=\"fill_parent\"\n                android:layout_height=\"wrap_content\"\n                android:ellipsize=\"end\"\n                android:paddingTop=\"3dp\"\n                android:singleLine=\"true\"\n                android:textSize=\"13sp\" />\n\n\n        </LinearLayout>\n\n        <com.naman14.timber.widgets.MusicVisualizer\n            android:id=\"@+id/visualizer\"\n            android:layout_width=\"30dp\"\n            android:layout_height=\"40dp\"\n            android:layout_centerVertical=\"true\"\n            android:layout_marginBottom=\"10dp\"\n            android:layout_marginRight=\"15dp\"\n            android:layout_toLeftOf=\"@+id/popup_menu\"\n            android:visibility=\"gone\" />\n\n\n        <com.naman14.timber.widgets.PopupImageView\n            android:id=\"@+id/popup_menu\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_alignParentRight=\"true\"\n            android:layout_centerVertical=\"true\"\n            android:layout_marginRight=\"12dp\"\n            android:background=\"?attr/selectableItemBackgroundBorderless\"\n            android:padding=\"5dp\"\n            android:src=\"@drawable/ic_more_vert_black_24dp\" />\n\n\n    </RelativeLayout>\n\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/item_song_playlist.xml",
    "content": "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"fill_parent\"\n    android:layout_height=\"wrap_content\"\n    android:background=\"?attr/selectableItemBackground\"\n    android:orientation=\"vertical\"\n    android:paddingBottom=\"6dp\"\n    android:paddingTop=\"6dp\">\n\n    <RelativeLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:orientation=\"horizontal\">\n\n        <net.steamcrafted.materialiconlib.MaterialIconView\n            android:id=\"@+id/reorder\"\n            android:layout_width=\"48dp\"\n            android:layout_height=\"48dp\"\n            android:layout_alignParentLeft=\"true\"\n            android:background=\"?attr/selectableItemBackgroundBorderless\"\n            android:scaleType=\"center\"\n            app:materialIcon=\"drag_vertical\"\n            app:materialIconColor=\"@android:color/white\"\n            app:materialIconSize=\"30dp\" />\n\n        <ImageView\n            android:id=\"@+id/albumArt\"\n            android:layout_width=\"55dp\"\n            android:layout_height=\"55dp\"\n            android:padding=\"1dp\"\n            android:layout_toRightOf=\"@+id/reorder\"/>\n\n        <LinearLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginRight=\"10dp\"\n            android:layout_toLeftOf=\"@+id/visualizer\"\n            android:layout_toRightOf=\"@+id/albumArt\"\n            android:orientation=\"vertical\"\n            android:paddingLeft=\"10dp\"\n            android:paddingTop=\"5dp\">\n\n\n            <TextView\n                android:id=\"@+id/song_title\"\n                android:layout_width=\"fill_parent\"\n                android:layout_height=\"wrap_content\"\n                android:ellipsize=\"end\"\n                android:singleLine=\"true\"\n                android:textColor=\"#ffffff\"\n                android:textSize=\"16sp\" />\n\n            <TextView\n                android:id=\"@+id/song_artist\"\n                android:layout_width=\"fill_parent\"\n                android:layout_height=\"wrap_content\"\n                android:ellipsize=\"end\"\n                android:paddingTop=\"3dp\"\n                android:singleLine=\"true\"\n                android:textColor=\"#99ffffff\"\n                android:textSize=\"13sp\" />\n\n\n        </LinearLayout>\n\n        <com.naman14.timber.widgets.MusicVisualizer\n            android:id=\"@+id/visualizer\"\n            android:layout_width=\"30dp\"\n            android:layout_height=\"40dp\"\n            android:layout_centerVertical=\"true\"\n            android:layout_marginBottom=\"10dp\"\n            android:layout_marginRight=\"15dp\"\n            android:layout_toLeftOf=\"@+id/popup_menu\"\n            android:visibility=\"gone\" />\n\n        <ImageView\n            android:id=\"@+id/popup_menu\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_alignParentRight=\"true\"\n            android:layout_centerVertical=\"true\"\n            android:layout_marginRight=\"8dp\"\n            android:background=\"?attr/selectableItemBackgroundBorderless\"\n            android:foregroundTint=\"#434343\"\n            android:padding=\"5dp\"\n            android:src=\"@drawable/ic_more_vert_white_24dp\"\n            android:theme=\"@style/Theme.AppCompat\" />\n\n\n    </RelativeLayout>\n\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/item_song_sliding_queue.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"65dp\"\n    android:layout_height=\"65dp\"\n    android:layout_marginBottom=\"10dp\"\n    android:background=\"?attr/selectableItemBackground\">\n\n\n    <com.naman14.timber.widgets.SquareImageView\n        android:id=\"@+id/album_art\"\n        android:layout_width=\"55dp\"\n        android:layout_height=\"55dp\"\n        android:scaleType=\"centerCrop\"\n        android:layout_margin=\"10dp\"/>\n\n    <View\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:background=\"?attr/selectableItemBackground\" />\n\n</FrameLayout>"
  },
  {
    "path": "app/src/main/res/layout/item_song_timber1.xml",
    "content": "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"fill_parent\"\n    android:layout_height=\"wrap_content\"\n    android:background=\"?attr/selectableItemBackground\"\n    android:orientation=\"vertical\"\n    android:paddingBottom=\"5dp\"\n    android:paddingLeft=\"10dp\"\n    android:paddingTop=\"5dp\">\n\n    <RelativeLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:orientation=\"horizontal\">\n\n        <ImageView\n            android:id=\"@+id/albumArt\"\n            android:layout_width=\"75dp\"\n            android:layout_height=\"70dp\"\n            android:layout_alignParentLeft=\"true\"\n            android:padding=\"3dp\" />\n\n        <LinearLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginRight=\"10dp\"\n            android:layout_toLeftOf=\"@+id/visualizer\"\n            android:layout_toRightOf=\"@+id/albumArt\"\n            android:orientation=\"vertical\"\n            android:paddingLeft=\"10dp\"\n            android:paddingTop=\"5dp\">\n\n\n            <TextView\n                android:id=\"@+id/song_title\"\n                style=\"@style/TextAppearance.AppCompat.Body1\"\n                android:layout_width=\"fill_parent\"\n                android:layout_height=\"wrap_content\"\n                android:ellipsize=\"end\"\n                android:singleLine=\"true\"\n                android:textSize=\"16sp\" />\n\n            <TextView\n                android:id=\"@+id/song_artist\"\n                style=\"@style/TextAppearance.AppCompat.Body1\"\n                android:layout_width=\"fill_parent\"\n                android:layout_height=\"wrap_content\"\n                android:ellipsize=\"end\"\n                android:paddingTop=\"3dp\"\n                android:singleLine=\"true\"\n                android:textSize=\"13sp\" />\n\n\n        </LinearLayout>\n\n        <com.naman14.timber.widgets.MusicVisualizer\n            android:id=\"@+id/visualizer\"\n            android:layout_width=\"30dp\"\n            android:layout_height=\"40dp\"\n            android:layout_centerVertical=\"true\"\n            android:layout_marginBottom=\"10dp\"\n            android:layout_marginRight=\"15dp\"\n            android:layout_toLeftOf=\"@+id/popup_menu\"\n            android:visibility=\"gone\" />\n\n\n        <com.naman14.timber.widgets.PopupImageView\n            android:id=\"@+id/popup_menu\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_alignParentRight=\"true\"\n            android:layout_centerVertical=\"true\"\n            android:layout_marginRight=\"12dp\"\n            android:background=\"?attr/selectableItemBackgroundBorderless\"\n            android:foregroundTint=\"#434343\"\n            android:padding=\"5dp\"\n            android:src=\"@drawable/ic_more_vert_black_24dp\" />\n\n    </RelativeLayout>\n\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/layout_artist_tag.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\">\n\n    <FrameLayout\n        android:id=\"@+id/vg_cover\"\n        android:layout_width=\"200dp\"\n        android:layout_height=\"match_parent\"\n        android:layout_centerInParent=\"true\" >\n\n        <ImageView\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\"\n            android:layout_marginLeft=\"16dp\"\n            android:layout_marginRight=\"16dp\"\n            android:background=\"@drawable/ic_empty_music2\"\n            android:scaleType=\"centerInside\" />\n    </FrameLayout>\n\n</RelativeLayout>"
  },
  {
    "path": "app/src/main/res/layout/layout_dummy_header_artist.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:orientation=\"vertical\" android:layout_width=\"match_parent\"\n    android:layout_height=\"370dp\">\n\n    <View\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"370dp\"\n        android:background=\"#ffffff\"/>\n\n</FrameLayout>"
  },
  {
    "path": "app/src/main/res/layout/nav_header.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"208dp\"\n    android:background=\"?attr/colorPrimaryDark\"\n    android:theme=\"@style/ThemeOverlay.AppCompat.Dark\"\n    android:orientation=\"vertical\"\n    android:gravity=\"bottom\">\n\n    <ImageView\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:scaleType=\"centerCrop\"\n        android:id=\"@+id/album_art\"/>\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:orientation=\"vertical\"\n        android:layout_gravity=\"bottom\"\n        android:gravity=\"bottom\">\n\n    <TextView\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:id=\"@+id/song_title\"\n        android:textSize=\"18sp\"\n        android:textColor=\"#ffffff\"\n        android:singleLine=\"true\"\n        android:ellipsize=\"end\"\n        android:layout_marginLeft=\"10dp\"\n        android:layout_marginRight=\"10dp\"\n        android:textAppearance=\"@style/TextAppearance.AppCompat.Body1\"/>\n\n    <TextView\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:id=\"@+id/song_artist\"\n        android:paddingTop=\"2dp\"\n        android:textSize=\"15sp\"\n        android:textColor=\"#ffffff\"\n        android:singleLine=\"true\"\n        android:ellipsize=\"end\"\n        android:layout_marginBottom=\"10dp\"\n        android:layout_marginLeft=\"10dp\"\n        android:layout_marginRight=\"10dp\"\n        android:textAppearance=\"@style/TextAppearance.AppCompat.Body1\"/>\n\n    </LinearLayout>\n\n    <View\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:background=\"?attr/selectableItemBackground\"/>\n\n</FrameLayout>"
  },
  {
    "path": "app/src/main/res/layout/recyclerview_fastscroller.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"match_parent\">\n\n    <TextView\n        android:id=\"@+id/fastscroller_bubble\"\n        android:layout_gravity=\"right|end\"\n        android:gravity=\"center\"\n        android:textSize=\"48sp\" tools:text=\"A\"\n        android:layout_width=\"wrap_content\"\n        android:textColor=\"#FFffffff\"\n        android:layout_height=\"wrap_content\"\n        android:background=\"@drawable/recyclerview_fastscroller_bubble\"\n        android:visibility=\"visible\"/>\n\n    <ImageView\n        android:id=\"@+id/fastscroller_handle\"\n        android:layout_width=\"wrap_content\"\n        android:layout_marginRight=\"8dp\"\n        android:layout_marginLeft=\"8dp\"\n        android:layout_height=\"wrap_content\"\n        android:src=\"@drawable/recyclerview_fastscroller_handle\"/>\n\n</merge>"
  },
  {
    "path": "app/src/main/res/layout/search_section_header.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:orientation=\"vertical\" android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\">\n\n    <TextView\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:id=\"@+id/section_header\"\n        android:textSize=\"20sp\"\n        android:layout_marginLeft=\"20dp\"\n        android:layout_marginTop=\"15dp\"\n        android:layout_marginBottom=\"15dp\"\n        style=\"@style/TextAppearance.AppCompat.Body1\"/>\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/song_elapsedtime.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:layout_gravity=\"center\"\n    android:orientation=\"horizontal\">\n\n    <com.naman14.timber.timely.TimelyView\n        android:id=\"@+id/timelyView11\"\n        android:layout_width=\"40dp\"\n        android:layout_height=\"40dp\"\n        android:textSize=\"17sp\"\n        app:text_color=\"#ffffff\" />\n\n    <TextView\n        android:id=\"@+id/hour_colon\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:text=\":\"\n        android:textColor=\"@android:color/white\"\n        android:textSize=\"24sp\" />\n\n    <com.naman14.timber.timely.TimelyView\n        android:id=\"@+id/timelyView12\"\n        android:layout_width=\"40dp\"\n        android:layout_height=\"40dp\"\n        android:textSize=\"17sp\"\n        app:text_color=\"#ffffff\" />\n\n    <com.naman14.timber.timely.TimelyView\n        android:id=\"@+id/timelyView13\"\n        android:layout_width=\"40dp\"\n        android:layout_height=\"40dp\"\n        android:textSize=\"17sp\"\n        app:text_color=\"#ffffff\" />\n\n    <TextView\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:text=\":\"\n        android:textColor=\"@android:color/white\"\n        android:textSize=\"24sp\" />\n\n    <com.naman14.timber.timely.TimelyView\n        android:id=\"@+id/timelyView14\"\n        android:layout_width=\"40dp\"\n        android:layout_height=\"40dp\"\n        android:textSize=\"17sp\"\n        app:text_color=\"#ffffff\" />\n\n    <com.naman14.timber.timely.TimelyView\n        android:id=\"@+id/timelyView15\"\n        android:layout_width=\"40dp\"\n        android:layout_height=\"40dp\"\n        android:textSize=\"17sp\"\n        app:text_color=\"#ffffff\" />\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/widget_small.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@color/black_translucent\"\n    android:padding=\"4dp\">\n\n    <ImageView\n        android:id=\"@+id/imageView_cover\"\n        android:padding=\"4dp\"\n        android:layout_width=\"100dp\"\n        android:layout_height=\"100dp\" />\n\n    <ImageView\n        android:id=\"@+id/image_playpause\"\n        android:layout_margin=\"25dp\"\n        android:layout_width=\"50dp\"\n        android:layout_height=\"50dp\"\n        android:scaleType=\"center\"\n        android:background=\"@drawable/selectable_item_background\"\n        android:src=\"@drawable/ic_play_white_36dp\" />\n\n    <TextView\n        android:id=\"@+id/textView_title\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_alignParentEnd=\"true\"\n        android:layout_alignParentRight=\"true\"\n        android:layout_alignParentTop=\"true\"\n        android:layout_toEndOf=\"@id/imageView_cover\"\n        android:layout_toRightOf=\"@id/imageView_cover\"\n        android:ellipsize=\"end\"\n        android:lines=\"1\"\n        android:text=\"@string/tap_to_start\"\n        android:textColor=\"@android:color/white\"\n        android:textStyle=\"bold\" />\n\n    <TextView\n        android:id=\"@+id/textView_subtitle\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_alignParentEnd=\"true\"\n        android:layout_alignParentRight=\"true\"\n        android:layout_below=\"@id/textView_title\"\n        android:layout_toEndOf=\"@id/imageView_cover\"\n        android:layout_toRightOf=\"@id/imageView_cover\"\n        android:lines=\"1\"\n        android:textColor=\"@android:color/white\" />\n\n    <LinearLayout\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_alignParentBottom=\"true\"\n        android:layout_alignParentEnd=\"true\"\n        android:layout_alignParentRight=\"true\"\n        android:layout_below=\"@id/textView_subtitle\"\n        android:layout_toEndOf=\"@id/imageView_cover\"\n        android:layout_toRightOf=\"@id/imageView_cover\"\n        android:gravity=\"bottom\"\n        tools:ignore=\"Suspicious0dp\">\n\n        <TextView\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"0dp\"\n            android:layout_weight=\"1\" />\n\n\n        <ImageView\n            android:id=\"@+id/image_next\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:background=\"@drawable/selectable_item_background\"\n            android:src=\"@drawable/ic_skip_next_white_36dp\" />\n\n        <TextView\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"0dp\"\n            android:layout_weight=\"1\" />\n    </LinearLayout>\n\n</RelativeLayout>"
  },
  {
    "path": "app/src/main/res/layout/widget_standard.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@color/black_translucent\"\n    android:padding=\"4dp\">\n\n    <ImageView\n        android:id=\"@+id/imageView_cover\"\n        android:padding=\"4dp\"\n        android:layout_width=\"100dp\"\n        android:layout_height=\"100dp\" />\n\n    <TextView\n        android:id=\"@+id/textView_title\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_alignParentEnd=\"true\"\n        android:layout_alignParentRight=\"true\"\n        android:layout_alignParentTop=\"true\"\n        android:layout_toEndOf=\"@id/imageView_cover\"\n        android:layout_toRightOf=\"@id/imageView_cover\"\n        android:lines=\"1\"\n        android:text=\"@string/tap_to_start\"\n        android:textColor=\"@android:color/white\"\n        android:textStyle=\"bold\" />\n\n    <TextView\n        android:id=\"@+id/textView_subtitle\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_alignParentEnd=\"true\"\n        android:layout_alignParentRight=\"true\"\n        android:layout_below=\"@id/textView_title\"\n        android:layout_toEndOf=\"@id/imageView_cover\"\n        android:layout_toRightOf=\"@id/imageView_cover\"\n        android:lines=\"1\"\n        android:textColor=\"@android:color/white\" />\n\n    <LinearLayout\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_alignParentBottom=\"true\"\n        android:layout_alignParentEnd=\"true\"\n        android:layout_alignParentRight=\"true\"\n        android:layout_below=\"@id/textView_subtitle\"\n        android:layout_toEndOf=\"@id/imageView_cover\"\n        android:layout_toRightOf=\"@id/imageView_cover\"\n        android:gravity=\"bottom\"\n        tools:ignore=\"Suspicious0dp\">\n\n        <TextView\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"0dp\"\n            android:layout_weight=\"1\" />\n\n        <ImageView\n            android:id=\"@+id/image_prev\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:background=\"@drawable/selectable_item_background\"\n            android:src=\"@drawable/ic_skip_previous_white_36dp\" />\n\n        <TextView\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"0dp\"\n            android:layout_weight=\"1\" />\n\n        <ImageView\n            android:id=\"@+id/image_playpause\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:background=\"@drawable/selectable_item_background\"\n            android:src=\"@drawable/ic_play_white_36dp\" />\n\n        <TextView\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"0dp\"\n            android:layout_weight=\"1\" />\n\n        <ImageView\n            android:id=\"@+id/image_next\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:background=\"@drawable/selectable_item_background\"\n            android:src=\"@drawable/ic_skip_next_white_36dp\" />\n\n        <TextView\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"0dp\"\n            android:layout_weight=\"1\" />\n    </LinearLayout>\n\n</RelativeLayout>"
  },
  {
    "path": "app/src/main/res/layout/widget_white.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@color/white_translucent\"\n    android:padding=\"4dp\">\n\n    <ImageView\n        android:id=\"@+id/imageView_cover\"\n        android:padding=\"4dp\"\n        android:layout_width=\"100dp\"\n        android:layout_height=\"100dp\" />\n\n    <TextView\n        android:id=\"@+id/textView_title\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_alignParentEnd=\"true\"\n        android:layout_alignParentRight=\"true\"\n        android:layout_alignParentTop=\"true\"\n        android:layout_toEndOf=\"@id/imageView_cover\"\n        android:layout_toRightOf=\"@id/imageView_cover\"\n        android:lines=\"1\"\n        android:text=\"@string/tap_to_start\"\n        android:textColor=\"@android:color/black\"\n        android:textStyle=\"bold\" />\n\n    <TextView\n        android:id=\"@+id/textView_subtitle\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_alignParentEnd=\"true\"\n        android:layout_alignParentRight=\"true\"\n        android:layout_below=\"@id/textView_title\"\n        android:layout_toEndOf=\"@id/imageView_cover\"\n        android:layout_toRightOf=\"@id/imageView_cover\"\n        android:lines=\"1\"\n        android:textColor=\"@android:color/black\" />\n\n    <LinearLayout\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_alignParentBottom=\"true\"\n        android:layout_alignParentEnd=\"true\"\n        android:layout_alignParentRight=\"true\"\n        android:layout_below=\"@id/textView_subtitle\"\n        android:layout_toEndOf=\"@id/imageView_cover\"\n        android:layout_toRightOf=\"@id/imageView_cover\"\n        android:gravity=\"bottom\"\n        tools:ignore=\"Suspicious0dp\">\n\n        <TextView\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"0dp\"\n            android:layout_weight=\"1\" />\n\n        <ImageView\n            android:id=\"@+id/image_prev\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:background=\"@drawable/selectable_item_background\"\n            android:tint=\"@android:color/black\"\n            android:src=\"@drawable/ic_skip_previous_white_36dp\" />\n\n        <TextView\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"0dp\"\n            android:layout_weight=\"1\" />\n\n        <ImageView\n            android:id=\"@+id/image_playpause\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:background=\"@drawable/selectable_item_background\"\n            android:tint=\"@android:color/black\"\n            android:src=\"@drawable/ic_play_white_36dp\" />\n\n        <TextView\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"0dp\"\n            android:layout_weight=\"1\" />\n\n        <ImageView\n            android:id=\"@+id/image_next\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:background=\"@drawable/selectable_item_background\"\n            android:tint=\"@android:color/black\"\n            android:src=\"@drawable/ic_skip_next_white_36dp\" />\n\n        <TextView\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"0dp\"\n            android:layout_weight=\"1\" />\n    </LinearLayout>\n\n</RelativeLayout>"
  },
  {
    "path": "app/src/main/res/menu/album_detail.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\" >\n    <item\n        android:id=\"@+id/menu_go_to_artist\"\n        android:title=\"@string/go_to_artist\" />\n    <item\n        android:id=\"@+id/popup_song_addto_queue\"\n        android:title=\"@string/add_to_queue\" />\n    <item\n        android:id=\"@+id/popup_song_addto_playlist\"\n        android:title=\"@string/add_to_playlist\" />\n    <item\n        android:id=\"@+id/menu_sort_by\"\n        android:showAsAction=\"never\"\n        android:orderInCategory=\"50\"\n        android:title=\"@string/menu_sort_by\">\n        <menu>\n            <item\n                android:id=\"@+id/menu_sort_by_az\"\n                android:title=\"@string/sort_order_entry_az\"/>\n            <item\n                android:id=\"@+id/menu_sort_by_za\"\n                android:title=\"@string/sort_order_entry_za\"/>\n            <item\n                android:id=\"@+id/menu_sort_by_year\"\n                android:title=\"@string/sort_order_entry_year\"/>\n            <item\n                android:id=\"@+id/menu_sort_by_track_number\"\n                android:title=\"@string/sort_order_entry_track_number\"/>\n            <item\n                android:id=\"@+id/menu_sort_by_duration\"\n                android:title=\"@string/sort_order_entry_duration\"/>\n        </menu>\n    </item>\n\n</menu>"
  },
  {
    "path": "app/src/main/res/menu/album_sort_by.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\" >\n\n    <item\n        android:id=\"@+id/menu_sort_by\"\n        android:showAsAction=\"never\"\n        android:orderInCategory=\"50\"\n        android:title=\"@string/menu_sort_by\">\n        <menu>\n            <item\n                android:id=\"@+id/menu_sort_by_az\"\n                android:title=\"@string/sort_order_entry_az\"/>\n            <item\n                android:id=\"@+id/menu_sort_by_za\"\n                android:title=\"@string/sort_order_entry_za\"/>\n            <item\n                android:id=\"@+id/menu_sort_by_year\"\n                android:title=\"@string/sort_order_entry_year\"/>\n            <item\n                android:id=\"@+id/menu_sort_by_artist\"\n                android:title=\"@string/sort_order_entry_artist\"/>\n            <item\n                android:id=\"@+id/menu_sort_by_number_of_songs\"\n                android:title=\"@string/sort_order_entry_number_of_songs\"/>\n        </menu>\n    </item>\n\n</menu>"
  },
  {
    "path": "app/src/main/res/menu/artist_detail.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\" >\n    <item\n        android:id=\"@+id/popup_song_addto_queue\"\n        android:title=\"@string/add_to_queue\" />\n    <item\n        android:id=\"@+id/popup_song_addto_playlist\"\n        android:title=\"@string/add_to_playlist\"/>\n</menu>"
  },
  {
    "path": "app/src/main/res/menu/artist_sort_by.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\" >\n\n    <item\n        android:id=\"@+id/menu_sort_by\"\n        android:showAsAction=\"never\"\n        android:orderInCategory=\"50\"\n        android:title=\"@string/menu_sort_by\">\n        <menu>\n            <item\n                android:id=\"@+id/menu_sort_by_az\"\n                android:title=\"@string/sort_order_entry_az\"/>\n            <item\n                android:id=\"@+id/menu_sort_by_za\"\n                android:title=\"@string/sort_order_entry_za\"/>\n            <item\n                android:id=\"@+id/menu_sort_by_number_of_songs\"\n                android:title=\"@string/sort_order_entry_number_of_songs\"/>\n            <item\n                android:id=\"@+id/menu_sort_by_number_of_albums\"\n                android:title=\"@string/sort_order_entry_number_of_albums\"/>\n        </menu>\n    </item>\n\n</menu>"
  },
  {
    "path": "app/src/main/res/menu/drawer_view.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <group\n        android:id=\"@+id/group1\"\n        android:checkableBehavior=\"single\">\n        <item\n            android:id=\"@+id/nav_library\"\n\n            android:title=\"@string/library\" />\n        <item\n            android:id=\"@+id/nav_playlists\"\n\n            android:title=\"@string/playlists\" />\n        <item\n            android:id=\"@+id/nav_folders\"\n\n            android:title=\"@string/folders\" />\n        <item\n            android:id=\"@+id/nav_queue\"\n\n            android:title=\"@string/playing_queue\" />\n        <item\n            android:id=\"@+id/nav_nowplaying\"\n            android:checkable=\"false\"\n            android:title=\"@string/now_playing\" />\n\n    </group>\n\n    <group\n        android:id=\"@+id/group2\"\n        android:checkableBehavior=\"none\">\n        <item\n\n            android:id=\"@+id/nav_settings\"\n            android:title=\"@string/settings\" />\n\n        <item\n\n            android:id=\"@+id/nav_about\"\n            android:title=\"@string/about\" />\n\n        <item\n            android:id=\"@+id/nav_donate\"\n            android:title=\"Support development\" />\n\n    </group>\n\n</menu>\n"
  },
  {
    "path": "app/src/main/res/menu/menu_cast.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n\n    <item\n        android:id=\"@+id/media_route_menu_item\"\n        android:title=\"@string/cast\"\n        app:actionProviderClass=\"androidx.mediarouter.app.MediaRouteActionProvider\"\n        app:showAsAction=\"always\" />\n\n</menu>"
  },
  {
    "path": "app/src/main/res/menu/menu_expanded_controller.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n    <item\n        android:id=\"@+id/media_route_menu_item\"\n        android:title=\"Cast\"\n        app:actionProviderClass=\"androidx.mediarouter.app.MediaRouteActionProvider\"\n        app:showAsAction=\"always\"/>\n\n</menu>"
  },
  {
    "path": "app/src/main/res/menu/menu_folders.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n    <item\n        android:id=\"@+id/action_storages\"\n        android:title=\"@string/select_storage\"\n        android:orderInCategory=\"99\" app:showAsAction=\"never\" />\n</menu>"
  },
  {
    "path": "app/src/main/res/menu/menu_main.xml",
    "content": "<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\" tools:context=\".MainActivity\">\n\n    <item android:id=\"@+id/action_shuffle\" android:title=\"@string/action_shuffle_all\"\n        android:orderInCategory=\"10\" app:showAsAction=\"never\" />\n\n    <item android:id=\"@+id/action_equalizer\" android:title=\"@string/action_equalizer\"\n        android:orderInCategory=\"100\" app:showAsAction=\"never\" />\n\n    <item android:id=\"@+id/action_settings\" android:title=\"@string/action_settings\"\n        android:orderInCategory=\"101\" app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/action_search\"\n        android:orderInCategory=\"1\"\n        android:icon=\"@drawable/ic_action_search\"\n        app:showAsAction=\"always\"\n        android:title=\"@string/search\"/>\n\n</menu>\n"
  },
  {
    "path": "app/src/main/res/menu/menu_playlist.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n    <item\n        android:id=\"@+id/action_new_playlist\"\n        android:orderInCategory=\"9\"\n        android:title=\"@string/new_playlist\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/action_view_auto_playlists\"\n        android:orderInCategory=\"9\"\n        android:title=\"Hide auto playlists\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_show_as\"\n        android:showAsAction=\"never\"\n        android:orderInCategory=\"40\"\n        android:title=\"@string/menu_show_as\">\n        <menu>\n            <item\n                android:id=\"@+id/menu_show_as_list\"\n                android:title=\"@string/menu_show_as_entry_list\"/>\n            <item\n                android:id=\"@+id/menu_show_as_grid\"\n                android:title=\"@string/menu_show_as_entry_grid\"/>\n            <item\n                android:id=\"@+id/menu_show_as_default\"\n                android:title=\"@string/menu_show_as_entry_default\"/>\n        </menu>\n    </item>\n\n</menu>"
  },
  {
    "path": "app/src/main/res/menu/menu_playlist_detail.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n    <item\n        android:id=\"@+id/action_clear_auto_playlist\"\n        android:orderInCategory=\"9\"\n        android:title=\"Clear\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/action_delete_playlist\"\n        android:orderInCategory=\"9\"\n        android:title=\"Delete playlist\"\n        app:showAsAction=\"never\" />\n\n\n</menu>"
  },
  {
    "path": "app/src/main/res/menu/menu_search.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n    <item\n        android:id=\"@+id/menu_search\"\n        android:icon=\"@drawable/ic_action_search\"\n        android:orderInCategory=\"1\"\n        app:actionViewClass=\"androidx.appcompat.widget.SearchView\"\n        app:showAsAction=\"ifRoom|collapseActionView\"\n        android:title=\"@string/search\"/>\n\n</menu>"
  },
  {
    "path": "app/src/main/res/menu/menu_show_as.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\" >\n\n    <item\n        android:id=\"@+id/menu_show_as\"\n        android:showAsAction=\"never\"\n        android:orderInCategory=\"40\"\n        android:title=\"@string/menu_show_as\">\n        <menu>\n            <item\n                android:id=\"@+id/menu_show_as_list\"\n                android:title=\"@string/menu_show_as_entry_list\"/>\n            <item\n                android:id=\"@+id/menu_show_as_grid\"\n                android:title=\"@string/menu_show_as_entry_grid\"/>\n        </menu>\n    </item>\n\n</menu>"
  },
  {
    "path": "app/src/main/res/menu/now_playing.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item\n        android:id=\"@+id/menu_go_to_album\"\n        android:title=\"@string/go_to_album\" />\n    <item\n        android:id=\"@+id/menu_go_to_artist\"\n        android:title=\"@string/go_to_artist\" />\n    <item\n        android:id=\"@+id/action_lyrics\"\n        android:title=\"@string/menu_show_lyrics\" />\n</menu>"
  },
  {
    "path": "app/src/main/res/menu/popup_playing_queue.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <item\n        android:id=\"@+id/popup_song_play\"\n        android:title=\"@string/play\" />\n    <item\n        android:id=\"@+id/popup_song_remove_queue\"\n        android:title=\"@string/remove_from_queue\"\n        />\n\n    <item\n        android:id=\"@+id/popup_song_addto_playlist\"\n        android:title=\"@string/add_to_playlist\" />\n\n    <item\n        android:id=\"@+id/popup_song_goto_album\"\n        android:title=\"@string/go_to_album\" />\n\n    <item\n        android:id=\"@+id/popup_song_goto_artist\"\n        android:title=\"@string/go_to_artist\" />\n</menu>"
  },
  {
    "path": "app/src/main/res/menu/popup_song.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <item\n        android:id=\"@+id/popup_song_remove_playlist\"\n        android:title=\"Remove from playlist\"\n        android:visible=\"false\"/>\n\n    <item\n        android:id=\"@+id/popup_song_play\"\n        android:title=\"@string/play\" />\n\n    <item\n        android:id=\"@+id/popup_song_play_next\"\n        android:title=\"@string/play_next\" />\n\n    <item\n        android:id=\"@+id/popup_song_addto_queue\"\n        android:title=\"@string/add_to_queue\" />\n\n    <item\n        android:id=\"@+id/popup_song_addto_playlist\"\n        android:title=\"@string/add_to_playlist\" />\n\n    <item\n        android:id=\"@+id/popup_song_goto_album\"\n        android:title=\"@string/go_to_album\" />\n\n    <item\n        android:id=\"@+id/popup_song_goto_artist\"\n    android:title=\"@string/go_to_artist\" />\n\n    <item\n        android:id=\"@+id/popup_song_share\"\n        android:title=\"@string/share\" />\n\n    <item\n        android:id=\"@+id/popup_song_delete\"\n        android:title=\"@string/delete_from_device\" />\n\n</menu>"
  },
  {
    "path": "app/src/main/res/menu/song_sort_by.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\" >\n\n    <item\n        android:id=\"@+id/menu_sort_by\"\n        android:showAsAction=\"never\"\n        android:orderInCategory=\"50\"\n        android:title=\"@string/menu_sort_by\">\n        <menu>\n            <item\n                android:id=\"@+id/menu_sort_by_az\"\n                android:title=\"@string/sort_order_entry_az\"/>\n            <item\n                android:id=\"@+id/menu_sort_by_za\"\n                android:title=\"@string/sort_order_entry_za\"/>\n            <item\n                android:id=\"@+id/menu_sort_by_year\"\n                android:title=\"@string/sort_order_entry_year\"/>\n            <item\n                android:id=\"@+id/menu_sort_by_artist\"\n                android:title=\"@string/sort_order_entry_artist\"/>\n            <item\n                android:id=\"@+id/menu_sort_by_album\"\n                android:title=\"@string/sort_order_entry_album\"/>\n            <item\n                android:id=\"@+id/menu_sort_by_duration\"\n                android:title=\"@string/sort_order_entry_duration\"/>\n        </menu>\n    </item>\n\n</menu>"
  },
  {
    "path": "app/src/main/res/transition/image_transform.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<transitionSet xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <changeTransform />\n    <changeBounds />\n</transitionSet>"
  },
  {
    "path": "app/src/main/res/transition/play_transition.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<transitionSet\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:transitionOrdering=\"together\"\n    android:duration=\"240\">\n\n    <transitionSet   >\n        <transition\n            class=\"com.naman14.timber.transition.PlayTransition\"/>\n        <!--<targets>-->\n            <!--<target android:excludeId=\"@id/icon\" /> />-->\n        <!--</targets>-->\n    </transitionSet>\n\n    <autoTransition/>\n\n</transitionSet>"
  },
  {
    "path": "app/src/main/res/values/arrays.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n    <!--Nothing to be translated here-->\n\n    <string-array name=\"pref_theme_entries\">\n        <item>@string/light</item>\n        <item>@string/dark</item>\n        <item>@string/black</item>\n    </string-array>\n\n    <string-array name=\"pref_theme_values\">\n        <item>light</item>\n        <item>dark</item>\n        <item>black</item>\n    </string-array>\n\n\n    <string-array name=\"pref_startpage_entries\">\n        <item>@string/last_opened</item>\n        <item>@string/songs</item>\n        <item>@string/albums</item>\n        <item>@string/artists</item>\n    </string-array>\n\n    <string-array name=\"pref_startpage_values\">\n        <item>last_opened</item>\n        <item>songs</item>\n        <item>albums</item>\n        <item>artists</item>\n    </string-array>\n\n    <array name=\"cast_expanded_controller_control_buttons\">\n        <item>@id/cast_button_type_empty</item>\n        <item>@id/cast_button_type_rewind_30_seconds</item>\n        <item>@id/cast_button_type_mute_toggle</item>\n        <item>@id/cast_button_type_empty</item>\n    </array>\n</resources>"
  },
  {
    "path": "app/src/main/res/values/attrs.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n    <attr name=\"ate_key\" format=\"string\" />\n\n    <declare-styleable name=\"SlidingUpPanelLayout\">\n        <attr name=\"panelHeight\" format=\"dimension\" />\n        <attr name=\"slidePanelOffset\" format=\"dimension\" />\n        <attr name=\"shadowHeight\" format=\"dimension\" />\n        <attr name=\"paralaxOffset\" format=\"dimension\" />\n        <attr name=\"directOffset\" format=\"boolean\" />\n        <attr name=\"fadeColor\" format=\"color\" />\n        <attr name=\"flingVelocity\" format=\"integer\" />\n        <attr name=\"dragView\" format=\"reference\" />\n        <attr name=\"dragViewClickable\" format=\"boolean\" />\n        <attr name=\"overlay\" format=\"boolean\"/>\n        <attr name=\"anchorPoint\" format=\"float\" />\n        <attr name=\"initialState\" format=\"enum\">\n            <enum name=\"expanded\" value=\"0\" />\n            <enum name=\"collapsed\" value=\"1\" />\n            <enum name=\"anchored\" value=\"2\" />\n            <enum name=\"hidden\" value=\"3\" />\n        </attr>\n    </declare-styleable>\n\n\n    <declare-styleable name=\"CircularImageViewStyle\">\n        <attr name=\"circularImageViewDefault\" format=\"reference\"/>\n    </declare-styleable>\n\n    <declare-styleable name=\"PlayTransition\">\n        <attr name=\"colorCT\" format=\"color\"/>\n    </declare-styleable>\n\n    <declare-styleable name=\"MultiViewPager\">\n        <!-- Child view inside a page to match the page size against. -->\n        <attr name=\"matchChildWidth\" format=\"reference\" />\n        <!-- Maximum size. -->\n        <attr name=\"android:maxWidth\" />\n        <!-- Maximum size. -->\n        <attr name=\"android:maxHeight\" />\n    </declare-styleable>\n\n    <declare-styleable name=\"CircleImageView\">\n        <attr name=\"border_width\" format=\"dimension\" />\n        <attr name=\"border_color\" format=\"color\" />\n        <attr name=\"border_overlay\" format=\"boolean\" />\n    </declare-styleable>\n\n    <declare-styleable name=\"CircularSeekBar\">\n        <attr name=\"progress\" format=\"integer\"></attr>\n        <attr name=\"max\" format=\"integer\"></attr>\n        <attr name=\"move_outside_circle\" format=\"boolean\"></attr>\n        <attr name=\"maintain_equal_circle\" format=\"boolean\"></attr>\n        <attr name=\"use_custom_radii\" format=\"boolean\"></attr>\n        <attr name=\"lock_enabled\" format=\"boolean\"></attr>\n        <attr name=\"circle_x_radius\" format=\"float\" />\n        <attr name=\"circle_y_radius\" format=\"float\" />\n        <attr name=\"circle_stroke_width\" format=\"float\" />\n        <attr name=\"pointer_radius\" format=\"float\" />\n        <attr name=\"pointer_halo_width\" format=\"float\" />\n        <attr name=\"pointer_halo_border_width\" format=\"float\"></attr>\n        <attr name=\"circle_color\" format=\"string\"></attr>\n        <attr name=\"circle_progress_color\" format=\"string\"></attr>\n        <attr name=\"pointer_color\" format=\"string\"></attr>\n        <attr name=\"pointer_halo_color\" format=\"string\"></attr>\n        <attr name=\"pointer_halo_color_ontouch\" format=\"string\"></attr>\n        <attr name=\"pointer_alpha_ontouch\" format=\"integer\"></attr>\n        <attr name=\"start_angle\" format=\"float\"></attr>\n        <attr name=\"end_angle\" format=\"float\"></attr>\n        <attr name=\"circle_fill\" format=\"string\"></attr>\n    </declare-styleable>\n\n    <declare-styleable name=\"TimelyView\">\n        <attr name=\"text_color\" format=\"color\"></attr>\n    </declare-styleable>\n\n\n\n</resources>"
  },
  {
    "path": "app/src/main/res/values/colors.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n    <color name=\"window_background\">#FFF5F5F5</color>\n    <color name=\"window_background_dark\">#212121</color>\n    <color name=\"window_background_black\">#000000</color>\n    <color name=\"bt_accent\">#00B8D4</color>\n\n    <color name=\"colorPrimary\">#673AB7</color>\n    <color name=\"colorPrimaryDark\">#512DA8</color>\n    <color name=\"colorAccent\">#FF4081</color>\n\n    <color name=\"colorPrimaryDarkTheme\">#4a4959</color>\n    <color name=\"colorPrimaryDarkDarkTheme\">#525065</color>\n    <color name=\"colorAccentDarkTheme\">#ffb701</color>\n\n    <color name=\"colorPrimaryBlack\">#000000</color>\n    <color name=\"colorPrimaryDarkBlack\">#000000</color>\n    <color name=\"colorAccentBlack\">#ffffff</color>\n\n    <color name=\"colorSelectableItemWhite\">#77ffffff</color>\n\n    <color name=\"pink_transparent\">#44700d39</color>\n    <color name=\"green_transparent\">#44375a1f</color>\n    <color name=\"blue_transparent\">#44293576</color>\n    <color name=\"red_transparent\">#447f3907</color>\n    <color name=\"purple_transparent\">#44432677</color>\n\n    <color name=\"black_translucent\">#66000000</color>\n    <color name=\"white_translucent\">#c8ffffff</color>\n\n    <color name=\"progressBackgroundColorLight\">#33000000</color>\n    <color name=\"progressBackgroundColorDark\">#33ffffff</color>\n\n    <color name=\"colorPrimaryLightDefault\">#FAFAFA</color>\n    <color name=\"colorAccentLightDefault\">#E91E63</color>\n\n    <color name=\"colorPrimaryDarkDefault\">#37474F</color>\n    <color name=\"colorAccentDarkDefault\">#2196F3</color>\n\n</resources>"
  },
  {
    "path": "app/src/main/res/values/dimens.xml",
    "content": "<resources>\n    <!-- Default screen margins, per the Android Design guidelines. -->\n    <dimen name=\"activity_horizontal_margin\">16dp</dimen>\n    <dimen name=\"activity_vertical_margin\">16dp</dimen>\n\n    <dimen name=\"toolbarElevation\">4dp</dimen>\n    <dimen name=\"appBarTopMargin\">0dp</dimen>\n    <dimen name=\"statusBarHeight\">0dp</dimen>\n\n\n    <dimen name=\"detail_backdrop_height\">256dp</dimen>\n    <dimen name=\"card_margin\">16dp</dimen>\n    <dimen name=\"fab_margin\">16dp</dimen>\n    <dimen name=\"list_item_avatar_size\">40dp</dimen>\n\n    <dimen name=\"grid_item_padding_left\">8.0dip</dimen>\n    <dimen name=\"grid_item_padding_right\">5.0dip</dimen>\n    <dimen name=\"grid_bottom_height\">44dip</dimen>\n\n    <dimen name=\"spacing_card\">10dp</dimen>\n    <dimen name=\"spacing_card_album_grid\">3dp</dimen>\n\n    <item name=\"letter_to_tile_ratio\" type=\"dimen\">53%</item>\n\n    <dimen name=\"header_height\">370dp</dimen>\n    <dimen name=\"min_header_height\">270dp</dimen>\n    <dimen name=\"header_logo_size\">100dp</dimen>\n\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values/plurals.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<resources  xmlns:xliff=\"urn:oasis:names:tc:xliff:document:1.2\">\n\n    <!--Nothing to be translated here-->\n\n    <plurals name=\"Nartists\">\n        <item quantity=\"one\">@string/count_artist</item>\n        <item quantity=\"other\">@string/count_artists</item>\n    </plurals>\n\n\n    <plurals name=\"Nalbums\">\n        <item quantity=\"one\">@string/count_album</item>\n        <item quantity=\"other\">@string/count_albums</item>\n    </plurals>\n\n    <plurals name=\"Nsongs\">\n        <item quantity=\"zero\">@string/count_songs</item>\n        <item quantity=\"one\">@string/count_song</item>\n        <item quantity=\"other\">@string/count_songs</item>\n    </plurals>\n\n    <plurals name=\"NNNtrackstoqueue\">\n        <item quantity=\"one\">@string/number_song_add_queue</item>\n        <item quantity=\"other\">@string/number_songs_add_queue</item>\n    </plurals>\n\n    <plurals name=\"NNNtrackstoplaylist\">\n        <item quantity=\"one\">@string/number_song_add_playlist</item>\n        <item quantity=\"other\">@string/number_songs_add_playlist</item>\n    </plurals>\n\n    <plurals name=\"NNNtracksdeleted\">\n        <item quantity=\"one\"><xliff:g id=\"songs_to_delete\" example=\"1\">%d</xliff:g> song was deleted.</item>\n        <item quantity=\"other\"><xliff:g id=\"songs_to_delete\">%d</xliff:g> songs were deleted.</item>\n    </plurals>\n\n</resources>"
  },
  {
    "path": "app/src/main/res/values/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources xmlns:xliff=\"urn:oasis:names:tc:xliff:document:1.2\">\n\n    <string name=\"app_name\">Timber</string>\n    <string name=\"action_settings\">Settings</string>\n\n    <string name=\"accessibility_play\">Play</string>\n    <string name=\"accessibility_pause\">Pause</string>\n    <string name=\"accessibility_next\">Next</string>\n    <string name=\"accessibility_prev\">Previous</string>\n    <string name=\"accessibility_shuffle\">Shuffle</string>\n    <string name=\"accessibility_shuffle_all\">Shuffle all</string>\n    <string name=\"accessibility_repeat\">Repeat</string>\n    <string name=\"accessibility_repeat_all\">Repeat all</string>\n    <string name=\"accessibility_repeat_one\">Repeat one</string>\n    <string name=\"accessibility_queue\">Open queue</string>\n    <string name=\"accessibility_browse\">Browse music</string>\n\n    <string name=\"menu_sort_by\">Sort by</string>\n    <string name=\"sort_order_entry_az\">A-Z</string>\n    <string name=\"sort_order_entry_za\">Z-A</string>\n    <string name=\"sort_order_entry_artist\">Artist</string>\n    <string name=\"sort_order_entry_album\">Album</string>\n    <string name=\"sort_order_entry_year\">Year</string>\n    <string name=\"sort_order_entry_track_number\">Track Number</string>\n    <string name=\"sort_order_entry_duration\">Duration</string>\n    <string name=\"sort_order_entry_date_added\">Date added</string>\n    <string name=\"sort_order_entry_track_list\">Track list</string>\n    <string name=\"sort_order_entry_number_of_songs\">Number of songs</string>\n    <string name=\"sort_order_entry_number_of_albums\">Number of albums</string>\n    <string name=\"sort_order_entry_filename\">Filename</string>\n\n    <string name=\"error_playing_track\">Error Playing track</string>\n\n    <string name=\"playlist_last_added\">Last Added</string>\n    <string name=\"playlist_recently_played\">Recently Played</string>\n    <string name=\"playlist_top_tracks\">My Top Tracks</string>\n\n    <string name=\"timber_source\">Report any bugs <u> here </u> </string>\n    <string name=\"timber_feature_request\">Specify any feature request in the Google+ community <u> here </u></string>\n\n    <string name=\"menu_show_as\">View As</string>\n    <string name=\"menu_show_as_entry_list\">List</string>\n    <string name=\"menu_show_as_entry_grid\">Grid</string>\n    <string name=\"menu_show_as_entry_default\">Default</string>\n\n    <string name=\"light\">Light</string>\n    <string name=\"dark\">Dark</string>\n    <string name=\"black\">Black</string>\n\n    <string name=\"last_opened\">Last Opened</string>\n\n    <string name=\"songs\">Songs</string>\n    <string name=\"albums\">Albums</string>\n    <string name=\"artists\">Artists</string>\n    <string name=\"library\">Library</string>\n    <string name=\"playlists\">Playlists</string>\n    <string name=\"playing_queue\">Playing Queue</string>\n    <string name=\"now_playing\">Now Playing</string>\n    <string name=\"settings\">Settings</string>\n    <string name=\"about\">About</string>\n    <string name=\"help_and_feedback\">Help and Feedback</string>\n    <string name=\"search\">Search</string>\n    <string name=\"action_shuffle_all\">Shuffle All</string>\n    <string name=\"action_equalizer\">Equalizer</string>\n    <string name=\"search_library\">Search Library</string>\n\n    <string name=\"select_theme\">Select Theme</string>\n    <string name=\"choose_theme_throughout\">Choose a theme to be used throughout the app.</string>\n    <string name=\"choose_start_page\">Choose the start page to be displayed on launch.</string>\n    <string name=\"start_page\">Start page</string>\n    <string name=\"artist_in_grid\">Artists in Grid</string>\n    <string name=\"toggle_artists_grid\">Toggle for displaying artists in a 2x2 grid.</string>\n    <string name=\"toggle_system_animations\">Toggle for system animations throughout the app.</string>\n    <string name=\"system_animations\">System animations</string>\n    <string name=\"toggle_animations\">Toggle for Transition animation throughout the app.</string>\n    <string name=\"animations\">Animations</string>\n    <string name=\"now_playing_selector\">Choose a now playing screen from 4 different styles.</string>\n    <string name=\"personalisation\">Personalisation</string>\n    <string name=\"other\">Other</string>\n    <string name=\"advanced\">Advanced</string>\n    <string name=\"timber_about_opensource\">Timber is a open source Music Player developed by Naman Dwivedi.</string>\n    <string name=\"summary_pause_detach\">Toggle for pausing playback when headphone is detached</string>\n    <string name=\"pause_detach\">Pause on Detach</string>\n    <string name=\"summary_xposed_trackselector\">Requires Xposed and XMediaNotificationTrackSelector Xposed module to be installed. This will provide access to playing queue from the notification bar itself.</string>\n    <string name=\"xposed_trackselector\">Playing queue support in notification</string>\n    <string name=\"currently_selected\">Currently Selected</string>\n\n    <string name=\"dark_theme\">Dark Theme</string>\n    <string name=\"dark_theme_summary\">Apply a dark theme throughout the app</string>\n\n    <string name=\"primary_color\">Primary Color</string>\n    <string name=\"primary_color_summary\">Change the primary theme color</string>\n\n    <string name=\"accent_color\">Accent Color</string>\n    <string name=\"accent_color_summary\">Change the accent theme color</string>\n\n    <string name=\"colored_status_bar\">Colored Status Bar</string>\n    <string name=\"colored_status_bar_summary\">Toggle status bar coloring</string>\n\n    <string name=\"colored_nav_bar\">Colored Navigation Bar</string>\n    <string name=\"colored_nav_bar_summary\">Toggle navigation bar coloring</string>\n\n    <string name=\"albumart_lockscreen\">Lockscreen</string>\n    <string name=\"albumart_lockscreen_summary\">Show album art in lockscreen</string>\n\n    <string name=\"load_artist_album_image\">Load artist/album images</string>\n    <string name=\"load_artist_album_image_summary\">Load artist/album images from lastfm</string>\n\n    <string name=\"load_artist_album_image_mobile\">Load artist/album images on mobile data</string>\n    <string name=\"load_artist_album_image_mobile_summary\">Load artist/album images even if there is no wifi</string>\n\n    <string name=\"gestures\">Gestures</string>\n    <string name=\"switching_tracks_by_gestures\">Switching tracks using gestures</string>\n    <string name=\"widget_standard\">Timber standard widget</string>\n    <string name=\"widget_small\">Timber small widget</string>\n    <string name=\"tap_to_start\">Tap to start Timber</string>\n    <string name=\"widget_white\">Timber light widget</string>\n\n    <string name=\"count_song\"><xliff:g id=\"count\">%d</xliff:g> song</string>\n    <string name=\"count_songs\"><xliff:g id=\"count\">%d</xliff:g> songs</string>\n    <string name=\"count_album\"><xliff:g id=\"count\">%d</xliff:g> album</string>\n    <string name=\"count_albums\"><xliff:g id=\"count\">%d</xliff:g> albums</string>\n    <string name=\"count_artist\"><xliff:g id=\"count\">%d</xliff:g> artist</string>\n    <string name=\"count_artists\"><xliff:g id=\"count\">%d</xliff:g> artists</string>\n\n    <string name=\"number_song_add_queue\"><xliff:g example=\"1\" id=\"number\">%d</xliff:g> song added to the queue.</string>\n    <string name=\"number_songs_add_queue\"><xliff:g example=\"1\" id=\"number\">%d</xliff:g> songs added to the queue.</string>\n    <string name=\"number_song_add_playlist\"><xliff:g example=\"1\" id=\"number\">%d</xliff:g> song added to playlist.</string>\n    <string name=\"number_songs_add_playlist\"><xliff:g example=\"1\" id=\"number\">%d</xliff:g> songs added to playlist.</string>\n\n\n    <!--do not translate these strings-->\n    <string name=\"combine_two_strings\" translatable=\"false\">%1$s | %2$s</string>\n    <string name=\"duration_format\" translatable=\"false\"><xliff:g id=\"hours\">%1$s</xliff:g> <xliff:g id=\"minutes\">%2$s</xliff:g></string>\n    <string name=\"durationformatshort\" translatable=\"false\"><xliff:g id=\"format\">%2$d:%3$02d</xliff:g></string>\n    <string name=\"durationformatlong\" translatable=\"false\"><xliff:g id=\"format\">%1$d:%2$02d:%3$02d</xliff:g></string>\n\n    <string name=\"play\">Play</string>\n    <string name=\"add_to_playlist\">Add to playlist</string>\n    <string name=\"go_to_album\">Go to album</string>\n    <string name=\"go_to_artist\">Go to artist</string>\n    <string name=\"add_to_queue\">Add to queue</string>\n    <string name=\"play_next\">Play next</string>\n    <string name=\"new_playlist\">New playlist</string>\n    <string name=\"share\">Share</string>\n    <string name=\"delete_from_device\">Delete from device</string>\n    <string name=\"remove_from_queue\">Remove from queue</string>\n\n    <string name=\"nothing_found\">Nothing found</string>\n    <string name=\"folders\">Folders</string>\n    <string name=\"select_storage\">Select storage</string>\n\n    <string name=\"lastfm_pref\">Login to LastFM to scrobble</string>\n    <string name=\"lastfm_login_failture\">Failed to Login</string>\n    <string name=\"lastfm_login\">Login to LastFM</string>\n    <string name=\"lastfm_loged_in\">Logged in as %s</string>\n\n    <string name=\"cancel\">Cancel</string>\n    <string name=\"always_load_album_images_lastfm\">Always load album images from Last.fm</string>\n    <string name=\"always_load_album_images_lastfm_summary\">If unchecked, album art will be fetched locally from song file if available</string>\n\n    <string name=\"menu_show_lyrics\">Show lyrics</string>\n    <string name=\"no_lyrics\">No lyrics found.</string>\n    <string name=\"lyrics_makeitpersonal\">Powered by <a href=\"https://makeitpersonal.co/\">makeitpersonal.co</a></string>\n    <string name=\"cast\">Cast</string>\n    <string name=\"now_playing_no_track_selected\">\\\"No current track selected.</string>\n    <string name=\"lyrics_loading\">Loading...</string>\n\n    <string name=\"about_dialog_body\"><![CDATA[\n    <b>Timber</b> is a fully featured open source music player.<br/><br/>\n    Designed and developed by <b>Naman Dwivedi</b>. Check out my\n    <a href=\"https://namand.in\">Website</a> and <a href=\"https://github.com/naman14/Timber\">GitHub</a>\n    <br/><br/>Also checkout <a href=\"https://play.google.com/store/apps/details?id=com.naman14.timberx\">TimberX</a>,\n    material themed music player that works across all form factors.\n    ]]></string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values/styles.xml",
    "content": "<resources>\n\n    <attr format=\"reference\" name=\"cardStyle\"/>\n    <attr format=\"reference\" name=\"accentColor\"/>\n    <attr format=\"reference\" name=\"iconColor\"/>\n    <attr format=\"reference\" name=\"windowBackground\"/>\n    <attr format=\"reference\" name=\"progressBackgroundColor\"/>\n    <attr format=\"reference\" name=\"progressDrawable\"/>\n    <attr format=\"reference\" name=\"progressDrawableWithBackground\" />\n    <attr format=\"reference\" name=\"stateSelector\" />\n\n\n    <style name=\"ThemeBaseLight\" parent=\"Theme.AppCompat.Light.NoActionBar\">\n        <item name=\"colorPrimary\">@color/colorPrimary</item>\n        <item name=\"colorPrimaryDark\">@color/colorPrimaryDark</item>\n        <item name=\"colorAccent\">@color/colorAccent</item>\n        <item name=\"android:windowBackground\">@color/window_background</item>\n        <item name=\"cardStyle\">@style/CardViewStyle.Light</item>\n        <item name=\"accentColor\">@color/colorAccent</item>\n        <item name=\"iconColor\">@android:color/black</item>\n        <item name=\"windowBackground\">@color/window_background</item>\n        <item name=\"stateSelector\">@color/state_selector_light</item>\n        <item name=\"progressBackgroundColor\">@color/progressBackgroundColorLight</item>\n        <item name=\"progressDrawable\">@drawable/progress_drawable</item>\n        <item name=\"progressDrawableWithBackground\">@drawable/progress_drawable_withbackground</item>\n        <item name=\"ate_key\">light_theme</item>\n\n    </style>\n    <style name=\"ThemeBaseDark\" parent=\"Theme.AppCompat.NoActionBar\">\n        <item name=\"colorPrimary\">@color/colorPrimaryDarkTheme</item>\n        <item name=\"colorPrimaryDark\">@color/colorPrimaryDarkDarkTheme</item>\n        <item name=\"colorAccent\">@color/colorAccentDarkTheme</item>\n        <item name=\"android:windowBackground\">@color/window_background_dark</item>\n        <item name=\"cardStyle\">@style/CardViewStyle.Dark</item>\n        <item name=\"accentColor\">@color/colorAccentDarkTheme</item>\n        <item name=\"iconColor\">@android:color/white</item>\n        <item name=\"windowBackground\">@color/window_background_dark</item>\n        <item name=\"stateSelector\">@color/state_selector_dark</item>\n        <item name=\"progressBackgroundColor\">@color/progressBackgroundColorDark</item>\n        <item name=\"progressDrawable\">@drawable/progress_drawable_dark</item>\n        <item name=\"progressDrawableWithBackground\">@drawable/progress_drawable_withbackground_dark</item>\n        <item name=\"ate_key\">dark_theme</item>\n\n    </style>\n    <style name=\"ThemeBaseBlack\" parent=\"Theme.AppCompat.NoActionBar\">\n        <item name=\"colorPrimary\">@color/colorPrimaryBlack</item>\n        <item name=\"colorPrimaryDark\">@color/colorPrimaryDarkBlack</item>\n        <item name=\"colorAccent\">@color/colorAccentBlack</item>\n        <item name=\"android:windowBackground\">@color/window_background_black</item>\n        <item name=\"cardStyle\">@style/CardViewStyle.Black</item>\n        <item name=\"accentColor\">@color/colorAccentBlack</item>\n        <item name=\"iconColor\">@android:color/white</item>\n        <item name=\"windowBackground\">@color/window_background_black</item>\n        <item name=\"stateSelector\">@color/state_selector_black</item>\n        <item name=\"progressBackgroundColor\">@color/progressBackgroundColorDark</item>\n        <item name=\"progressDrawable\">@drawable/progress_drawable_black</item>\n        <item name=\"progressDrawableWithBackground\">@drawable/progress_drawable_withbackground_black</item>\n\n    </style>\n\n    <style name=\"AppThemeLight\" parent=\"ThemeBaseLight\">\n    </style>\n\n    <style name=\"AppThemeNormalLight\" parent=\"ThemeBaseLight\">\n    </style>\n\n    <style name=\"AppTheme.FullScreen.Light\" parent=\"ThemeBaseLight\">\n    </style>\n\n    <style name=\"AppThemeDark\" parent=\"ThemeBaseDark\">\n\n\n    </style>\n\n    <style name=\"AppThemeNormalDark\" parent=\"ThemeBaseDark\">\n\n    </style>\n\n    <style name=\"AppTheme.FullScreen.Dark\" parent=\"ThemeBaseDark\">\n\n    </style>\n    <style name=\"AppThemeBlack\" parent=\"ThemeBaseBlack\">\n\n\n    </style>\n\n    <style name=\"AppThemeNormalBlack\" parent=\"ThemeBaseBlack\">\n\n    </style>\n\n    <style name=\"AppTheme.FullScreen.Black\" parent=\"ThemeBaseBlack\">\n\n    </style>\n\n    <style name=\"DrawerArrowStyle\" parent=\"Widget.AppCompat.DrawerArrowToggle\">\n        <item name=\"spinBars\">true</item>\n        <item name=\"color\">@android:color/white</item>\n    </style>\n\n\n\n    <style name=\"TextApperance.Album.Title\" parent=\"@style/TextAppearance.AppCompat.Large\">\n        <item name=\"android:textSize\">29sp</item>\n    </style>\n\n    <style name=\"TextApperance.Artist.Title\" parent=\"@style/TextAppearance.AppCompat.Large\">\n        <item name=\"android:textSize\">29sp</item>\n    </style>\n\n    <style name=\"TextAppearance.Song.Lyrics\" parent=\"@style/TextAppearance.AppCompat.Medium\">\n    </style>\n\n    <style name=\"TransparentText\" parent=\"@android:style/TextAppearance\">\n        <item name=\"android:textColor\">#00666666</item>\n    </style>\n\n\n    <style name=\"FabStyle\">\n        <item name=\"android:layout_width\">wrap_content</item>\n        <item name=\"android:layout_height\">wrap_content</item>\n        <item name=\"android:layout_margin\">16dp</item>\n        <item name=\"borderWidth\">0dp</item>\n        <item name=\"elevation\">6dp</item>\n        <item name=\"pressedTranslationZ\">12dp</item>\n        <item name=\"rippleColor\">@android:color/white</item>\n    </style>\n\n    <style name=\"CardViewStyle.Light\" parent=\"CardView\">\n        <item name=\"cardBackgroundColor\">@color/window_background</item>\n    </style>\n\n    <style name=\"CardViewStyle.Dark\" parent=\"CardView\">\n        <item name=\"cardBackgroundColor\">@color/window_background_dark</item>\n    </style>\n\n    <style name=\"CardViewStyle.Black\" parent=\"CardView\">\n        <item name=\"cardBackgroundColor\">@color/window_background_black</item>\n    </style>\n\n\n    <style name=\"CustomCastMiniController\" parent=\"CastMiniController\">\n        <item name=\"castShowImageThumbnail\">true</item>\n        <item name=\"castTitleTextAppearance\">@style/TextAppearance.AppCompat.Subhead</item>\n        <item name=\"castSubtitleTextAppearance\">@style/TextAppearance.AppCompat.Caption</item>\n    </style>\n\n    <style name=\"CustomCastExpandedController\" parent=\"CastExpandedController\">\n        <item name=\"castControlButtons\">\n            @array/cast_expanded_controller_control_buttons\n        </item>\n    </style>\n\n    <style name=\"AppTheme.ExpandedCastController\" parent=\"Theme.AppCompat.Light.NoActionBar\">\n        <item name=\"castExpandedControllerStyle\">@style/CustomCastExpandedController</item>\n        <item name=\"castExpandedControllerToolbarStyle\">\n            @style/ThemeOverlay.AppCompat.Dark.ActionBar\n        </item>\n    </style>\n\n\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-da/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n<resources xmlns:xliff=\"urn:oasis:names:tc:xliff:document:1.2\">\r\n\r\n    <string name=\"action_settings\">Indstillinger</string>\r\n\r\n    <string name=\"accessibility_play\">Afspil</string>\r\n    <string name=\"accessibility_pause\">Pause</string>\r\n    <string name=\"accessibility_next\">Nste</string>\r\n    <string name=\"accessibility_prev\">Forrige</string>\r\n    <string name=\"accessibility_shuffle\">Bland</string>\r\n    <string name=\"accessibility_shuffle_all\">Bland alle</string>\r\n    <string name=\"accessibility_repeat\">Gentag</string>\r\n    <string name=\"accessibility_repeat_all\">Gentag alle</string>\r\n    <string name=\"accessibility_repeat_one\">Gentag n</string>\r\n    <string name=\"accessibility_queue\">bn k</string>\r\n    <string name=\"accessibility_browse\">Gennemse musik</string>\r\n\r\n    <string name=\"menu_sort_by\">Sortr</string>\r\n    <string name=\"sort_order_entry_az\">A-Z</string>\r\n    <string name=\"sort_order_entry_za\">Z-A</string>\r\n    <string name=\"sort_order_entry_artist\">Kunstner</string>\r\n    <string name=\"sort_order_entry_album\">Album</string>\r\n    <string name=\"sort_order_entry_year\">r</string>\r\n    <string name=\"sort_order_entry_track_number\">Nummer</string>\r\n    <string name=\"sort_order_entry_duration\">Varighed</string>\r\n    <string name=\"sort_order_entry_date_added\">Dato tilfjet</string>\r\n    <string name=\"sort_order_entry_track_list\">Spilleliste</string>\r\n    <string name=\"sort_order_entry_number_of_songs\">Antal sange</string>\r\n    <string name=\"sort_order_entry_number_of_albums\">Antal albums</string>\r\n    <string name=\"sort_order_entry_filename\">Filnavn</string>\r\n\r\n    <string name=\"error_playing_track\">Fejl under afspilning</string>\r\n\r\n    <string name=\"playlist_last_added\">Senest tilfjet</string>\r\n    <string name=\"playlist_recently_played\">Afspillet for nylig</string>\r\n    <string name=\"playlist_top_tracks\">Mine Top Numre</string>\r\n\r\n    <string name=\"timber_source\">Rapportr eventuelle fejl <u> her </u> </string>\r\n    <string name=\"timber_feature_request\">Bed om nye features via Google+ fllesskabet <u> her </u></string>\r\n\r\n    <string name=\"menu_show_as\">Vis som</string>\r\n    <string name=\"menu_show_as_entry_list\">Liste</string>\r\n    <string name=\"menu_show_as_entry_grid\">Gitter</string>\r\n\r\n    <string name=\"light\">Lyst</string>\r\n    <string name=\"dark\">Mrkt</string>\r\n    <string name=\"black\">Sort</string>\r\n\r\n    <string name=\"last_opened\">Sidst bnet</string>\r\n\r\n    <string name=\"songs\">Sange</string>\r\n    <string name=\"albums\">Albums</string>\r\n    <string name=\"artists\">Kunstnere</string>\r\n    <string name=\"library\">Bibliotek</string>\r\n    <string name=\"playlists\">Afspilningslister</string>\r\n    <string name=\"playing_queue\">Afspilningsk</string>\r\n    <string name=\"now_playing\">Afspilles Nu</string>\r\n    <string name=\"settings\">Indstillinger</string>\r\n    <string name=\"about\">Om</string>\r\n    <string name=\"help_and_feedback\">Hjlp og Feedback</string>\r\n    <string name=\"search\">Sg</string>\r\n    <string name=\"action_shuffle_all\">Bland Alle</string>\r\n    <string name=\"action_equalizer\">Equalizer</string>\r\n    <string name=\"search_library\">Sg i Bibliotek</string>\r\n\r\n    <string name=\"select_theme\">Vlg Tema</string>\r\n    <string name=\"choose_theme_throughout\">Vlg et tema til appen.</string>\r\n    <string name=\"choose_start_page\">Vlg en startside, der vises ved opstart.</string>\r\n    <string name=\"start_page\">Startside</string>\r\n    <string name=\"artist_in_grid\">Kunstnere i Gitter</string>\r\n    <string name=\"toggle_artists_grid\">Vlg for visning af kunstnere i et 2x2 gitter.</string>\r\n    <string name=\"toggle_system_animations\">Vlg for systemanimationer i appen.</string>\r\n    <string name=\"system_animations\">Systemanimationer</string>\r\n    <string name=\"toggle_animations\">Vlg for Overgangsanimation i appen.</string>\r\n    <string name=\"animations\">Animationer</string>\r\n    <string name=\"now_playing_selector\">Vlg en Afspilles Nu skrm blandt 4 udseender.</string>\r\n    <string name=\"personalisation\">Personalisering</string>\r\n    <string name=\"other\">Andet</string>\r\n    <string name=\"advanced\">Avanceret</string>\r\n    <string name=\"timber_about_opensource\">Timber er en open source Musikafspiller udviklet af Naman Dwivedi.</string>\r\n    <string name=\"summary_pause_detach\">Vlg for at pause afspilning nr hovedtelefoner frakobles</string>\r\n    <string name=\"pause_detach\">Pause ved Frakobling</string>\r\n    <string name=\"summary_xposed_trackselector\">Krver Xposed og XMediaNotificationTrackSelector Xposed modul installeret. Dette giver adgang til afspilningsken fra selve notifikationslinjen.</string>\r\n    <string name=\"xposed_trackselector\">Afspilningsk understttelse i notifikation</string>\r\n    <string name=\"currently_selected\">Valgt</string>\r\n\r\n    <string name=\"dark_theme\">Mrkt Tema</string>\r\n    <string name=\"dark_theme_summary\">Anvend et mrkt tema i appen</string>\r\n\r\n    <string name=\"primary_color\">Primr Farve</string>\r\n    <string name=\"primary_color_summary\">ndr den primre temafarve</string>\r\n\r\n    <string name=\"accent_color\">Accent Farve</string>\r\n    <string name=\"accent_color_summary\">ndr accent temafarven</string>\r\n\r\n    <string name=\"colored_status_bar\">Farvet Statuslinje</string>\r\n    <string name=\"colored_status_bar_summary\">Vlg statuslinje farvning</string>\r\n\r\n    <string name=\"colored_nav_bar\">Farvet Navigationslinje</string>\r\n    <string name=\"colored_nav_bar_summary\">Vlg navigationslinje farvning</string>\r\n\r\n    <string name=\"count_song\"><xliff:g id=\"count\">%d</xliff:g> sang</string>\r\n    <string name=\"count_songs\"><xliff:g id=\"count\">%d</xliff:g> sange</string>\r\n    <string name=\"count_album\"><xliff:g id=\"count\">%d</xliff:g> album</string>\r\n    <string name=\"count_albums\"><xliff:g id=\"count\">%d</xliff:g> albums</string>\r\n    <string name=\"count_artist\"><xliff:g id=\"count\">%d</xliff:g> kunstner</string>\r\n    <string name=\"count_artists\"><xliff:g id=\"count\">%d</xliff:g> kunstnere</string>\r\n\r\n    <string name=\"number_song_add_queue\"><xliff:g example=\"1\" id=\"number\">%d</xliff:g> sang tilfjet til ken.</string>\r\n    <string name=\"number_songs_add_queue\"><xliff:g example=\"1\" id=\"number\">%d</xliff:g> sange tilfjet til ken.</string>\r\n    <string name=\"number_song_add_playlist\"><xliff:g example=\"1\" id=\"number\">%d</xliff:g> sang tilfjet til afspilningslisten.</string>\r\n    <string name=\"number_songs_add_playlist\"><xliff:g example=\"1\" id=\"number\">%d</xliff:g> sange tilfjet til afspilningslisten.</string>\r\n\r\n</resources>\r\n"
  },
  {
    "path": "app/src/main/res/values-de/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources xmlns:xliff=\"urn:oasis:names:tc:xliff:document:1.2\">\n\n    <string name=\"action_settings\">Einstellungen</string>\n\n    <string name=\"accessibility_play\">Play</string>\n    <string name=\"accessibility_pause\">Pause</string>\n    <string name=\"accessibility_next\">Nächstes</string>\n    <string name=\"accessibility_prev\">Vorheriges</string>\n    <string name=\"accessibility_shuffle\">Zufall</string>\n    <string name=\"accessibility_shuffle_all\">Alles zufällig abspielen</string>\n    <string name=\"accessibility_repeat\">Wiederholen</string>\n    <string name=\"accessibility_repeat_all\">Alle wiederholen</string>\n    <string name=\"accessibility_repeat_one\">Aktuelles wiederholen</string>\n    <string name=\"accessibility_queue\">Warteschlange öffnen</string>\n    <string name=\"accessibility_browse\">Musik durchsuchen</string>\n\n    <string name=\"menu_sort_by\">Sortieren nach</string>\n    <string name=\"sort_order_entry_az\">A-Z</string>\n    <string name=\"sort_order_entry_za\">Z-A</string>\n    <string name=\"sort_order_entry_artist\">Interpret</string>\n    <string name=\"sort_order_entry_album\">Album</string>\n    <string name=\"sort_order_entry_year\">Jahr</string>\n    <string name=\"sort_order_entry_track_number\">Titelnummer</string>\n    <string name=\"sort_order_entry_duration\">Länge</string>\n    <string name=\"sort_order_entry_date_added\">Hinzufügedatum</string>\n    <string name=\"sort_order_entry_track_list\">Tracklist</string>\n    <string name=\"sort_order_entry_number_of_songs\">Anzahl an Lieder</string>\n    <string name=\"sort_order_entry_number_of_albums\">Anzahl an Alben</string>\n    <string name=\"sort_order_entry_filename\">Dateiname</string>\n\n    <string name=\"error_playing_track\">Fehler bei der Wiedergabe des Songs</string>\n\n    <string name=\"playlist_last_added\">Zuletzt hinzugefügt</string>\n    <string name=\"playlist_recently_played\">Zuletzt gespielt</string>\n    <string name=\"playlist_top_tracks\">Meine Lieblingslieder</string>\n\n    <string name=\"timber_source\">Melden Sie Fehler <u> hier </u> </string>\n    <string name=\"timber_feature_request\">Vorschläge? Posten Sie sie in der <u> Google+ Community </u></string>\n\n    <string name=\"menu_show_as\">Ansicht</string>\n    <string name=\"menu_show_as_entry_list\">Liste</string>\n    <string name=\"menu_show_as_entry_grid\">Raster</string>\n\n    <string name=\"light\">Hell</string>\n    <string name=\"dark\">Dunkel</string>\n    <string name=\"black\">Schwarz</string>\n\n    <string name=\"last_opened\">Zuletzt geöffnet</string>\n\n    <string name=\"songs\">Titel</string>\n    <string name=\"albums\">Alben</string>\n    <string name=\"artists\">Interpreten</string>\n    <string name=\"library\">Bibliothek</string>\n    <string name=\"playlists\">Playlisten</string>\n    <string name=\"playing_queue\">Warteschlange</string>\n    <string name=\"now_playing\">Now Playing</string>\n    <string name=\"settings\">Einstellungen</string>\n    <string name=\"about\">Über</string>\n    <string name=\"help_and_feedback\">Hifle und Feedback</string>\n    <string name=\"search\">Suchen</string>\n    <string name=\"action_shuffle_all\">Alles zufällig abspielen</string>\n    <string name=\"action_equalizer\">Equalizer</string>\n    <string name=\"search_library\">Bibliothek durchsuchen</string>\n\n    <string name=\"select_theme\">Design auswählen</string>\n    <string name=\"choose_theme_throughout\">Wählen Sie ein Design welches die App nutzt.</string>\n    <string name=\"choose_start_page\">Wählen Sie die Startseite die beim starten der App angezeigt wird.</string>\n    <string name=\"start_page\">Startseite</string>\n    <string name=\"artist_in_grid\">Interpreten in Raster</string>\n    <string name=\"toggle_artists_grid\">Interpreten in einem 2x2 Raster anzeigen</string>\n    <string name=\"toggle_system_animations\">System-Animationen aktivieren.</string>\n    <string name=\"system_animations\">System-Animationen</string>\n    <string name=\"toggle_animations\">Übergangs-Animationen aktivieren.</string>\n    <string name=\"animations\">Animationen</string>\n    <string name=\"now_playing_selector\">Wählen Sie einen \"Now-Playing\"-Screen aus 4 verschiedenen Stilen.</string>\n    <string name=\"personalisation\">Personifizierung</string>\n    <string name=\"other\">Anderes</string>\n    <string name=\"timber_about_opensource\">Timber ist ein offener Musik-Player entwickelt von Naman Dwivedi.</string>\n    <string name=\"summary_pause_detach\">Wiedergabe pausieren wenn Kopfhörer entfernt werden.</string>\n    <string name=\"pause_detach\">Pause beim entfernen</string>\n    <string name=\"albumart_lockscreen\">Sperrbildschirm</string>\n    <string name=\"albumart_lockscreen_summary\">Albumcover im Sperrbildschirm anzeigen.</string>\n    <string name=\"load_artist_album_image\">Interpretenbilder</string>\n    <string name=\"load_artist_album_image_summary\">Lade Interpretenbilder aus dem Internet.</string>\n    <string name=\"load_artist_album_image_mobile\">Mobiledaten</string>\n    <string name=\"load_artist_album_image_mobile_summary\">Interpretenbilder auch ohne Wifi laden.</string>\n\n\n    <string name=\"count_song\"><xliff:g id=\"count\">%d</xliff:g> Song</string>\n    <string name=\"count_songs\"><xliff:g id=\"count\">%d</xliff:g> Songs</string>\n    <string name=\"count_album\"><xliff:g id=\"count\">%d</xliff:g> Album</string>\n    <string name=\"count_albums\"><xliff:g id=\"count\">%d</xliff:g> Alben</string>\n    <string name=\"count_artist\"><xliff:g id=\"count\">%d</xliff:g> Interpret</string>\n    <string name=\"count_artists\"><xliff:g id=\"count\">%d</xliff:g> Interpreten</string>\n\n    <string name=\"lastfm_pref\">Bei LastFM einloggen um zuscrobblen</string>\n    <string name=\"lastfm_login_failture\">Fehler beim einloggen</string>\n    <string name=\"lastfm_login\">Bei LastFM einloggen</string>\n    <string name=\"lastfm_loged_in\">Eingeloggt als %s</string>\n\n    <string name=\"cancel\">Abbrechen</string>\n\n    <string name=\"menu_show_lyrics\">Songtext anzeigen</string>\n    <string name=\"no_lyrics\">Songtext nicht gefunden.</string>\n\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-es/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources xmlns:xliff=\"urn:oasis:names:tc:xliff:document:1.2\">\n\n    <string name=\"action_settings\">Ajustes</string>\n\n    <string name=\"accessibility_play\">Reproducir</string>\n    <string name=\"accessibility_pause\">Pausa</string>\n    <string name=\"accessibility_next\">Siguiente</string>\n    <string name=\"accessibility_prev\">Anterior</string>\n    <string name=\"accessibility_shuffle\">Barajar</string>\n    <string name=\"accessibility_shuffle_all\">Barajar Todo</string>\n    <string name=\"accessibility_repeat\">Repetir</string>\n    <string name=\"accessibility_repeat_all\">Repetir Todo</string>\n    <string name=\"accessibility_repeat_one\">Repetir Uno</string>\n    <string name=\"accessibility_queue\">Abrir Cola</string>\n    <string name=\"accessibility_browse\">Buscar Música</string>\n\n    <string name=\"menu_sort_by\">Ordenar Por</string>\n    <string name=\"sort_order_entry_az\">A-Z</string>\n    <string name=\"sort_order_entry_za\">Z-A</string>\n    <string name=\"sort_order_entry_artist\">Artista</string>\n    <string name=\"sort_order_entry_album\">Álbum</string>\n    <string name=\"sort_order_entry_year\">Año</string>\n    <string name=\"sort_order_entry_track_number\">Número de Canción</string>\n    <string name=\"sort_order_entry_duration\">Duración</string>\n    <string name=\"sort_order_entry_date_added\">Fecha Añadido</string>\n    <string name=\"sort_order_entry_track_list\">Lista de Pistas</string>\n    <string name=\"sort_order_entry_number_of_songs\">Número de Caciones</string>\n    <string name=\"sort_order_entry_number_of_albums\">Número de Álbumes</string>\n    <string name=\"sort_order_entry_filename\">Nombre del Archivo</string>\n\n    <string name=\"error_playing_track\">Error al Reproducir la Pista</string>\n\n    <string name=\"playlist_last_added\">Últimos Agregados</string>\n    <string name=\"playlist_recently_played\">Reproducciones Recientes</string>\n    <string name=\"playlist_top_tracks\">Mis Mejores Pistas</string>\n\n    <string name=\"timber_source\">Reportar cualquier problema <u> aquí </u> </string>\n    <string name=\"timber_feature_request\">Especifique cualquier caracteristica nueva en la comnunidad de Google+ <u> aquí </u></string>\n\n    <string name=\"menu_show_as\">Ver Como</string>\n    <string name=\"menu_show_as_entry_list\">Lista</string>\n    <string name=\"menu_show_as_entry_grid\">Cuadrícula</string>\n\n    <string name=\"light\">Claro</string>\n    <string name=\"dark\">Oscuro</string>\n    <string name=\"black\">Negro</string>\n\n    <string name=\"last_opened\">Últimos Abiertos</string>\n\n    <string name=\"songs\">Canciones</string>\n    <string name=\"albums\">Álbumes</string>\n    <string name=\"artists\">Artistas</string>\n    <string name=\"library\">Libreria</string>\n    <string name=\"playlists\">Lista de Reproducción</string>\n    <string name=\"playing_queue\">Cola de Reproducción</string>\n    <string name=\"now_playing\">Reproduciendo Ahora</string>\n    <string name=\"settings\">Ajustes</string>\n    <string name=\"about\">Acerca de</string>\n    <string name=\"help_and_feedback\">Ayuda y Comentarios</string>\n    <string name=\"search\">Buscar</string>\n    <string name=\"action_shuffle_all\">Barajar Todo</string>\n    <string name=\"action_equalizer\">Ecualizador</string>\n    <string name=\"search_library\">Buscar en la Librería</string>\n\n    <string name=\"select_theme\">Seleccionar Tema</string>\n    <string name=\"choose_theme_throughout\">Elija el tema que se utilizará en toda la aplicación.</string>\n    <string name=\"choose_start_page\">Elija la página de inicio que se mostrará en el lanzamiento.</string>\n    <string name=\"start_page\">Pagína de Inicio</string>\n    <string name=\"artist_in_grid\">Artistas en Cuadrícula</string>\n    <string name=\"toggle_artists_grid\">Toque para vizualizar los artistas en un cuadrícula de 2x2.</string>\n    <string name=\"toggle_system_animations\">Tque para usar las animaciones del sistema en toda la aplicación.</string>\n    <string name=\"system_animations\">Animaciones del Sistema</string>\n    <string name=\"toggle_animations\">Toque para usar animaciones de transición en toda la aplicación.</string>\n    <string name=\"animations\">Animaciones</string>\n    <string name=\"now_playing_selector\">Elija una pantalla de reproducción a partir de 4 estilos diferentes.</string>\n    <string name=\"personalisation\">Personalización</string>\n    <string name=\"other\">Otro</string>\n    <string name=\"timber_about_opensource\">Timber es un reproductor de música Open Source desarrollado por Naman Dwivedi.</string>\n    <string name=\"summary_pause_detach\">Tocar para pausar la reproducción cuando se desconecta los auriculares</string>\n    <string name=\"pause_detach\">Pausar al Desconectar</string>\n    <string name=\"currently_selected\">Selección Actual</string>\n\n    <string name=\"dark_theme\">Tema Oscuro</string>\n    <string name=\"dark_theme_summary\">Aplicar el Tema Oscuro en toda la aplicación</string>\n\n    <string name=\"primary_color\">Color Primario</string>\n    <string name=\"primary_color_summary\">Cambiar el color primario del tema</string>\n\n    <string name=\"accent_color\">Color Énfasis</string>\n    <string name=\"accent_color_summary\">Cambiar el color de énfasis del tema</string>\n\n    <string name=\"colored_status_bar\">Color de la Barra de Estado</string>\n    <string name=\"colored_status_bar_summary\">Alternar coloración de la barra de estado</string>\n\n    <string name=\"colored_nav_bar\">Color de la Barra de Navegación</string>\n    <string name=\"colored_nav_bar_summary\">Alternar coloración de la barra de navegación</string>\n\n    <string name=\"count_song\"><xliff:g id=\"count\">%d</xliff:g> canción</string>\n    <string name=\"count_songs\"><xliff:g id=\"count\">%d</xliff:g> canciones</string>\n    <string name=\"count_album\"><xliff:g id=\"count\">%d</xliff:g> álbum</string>\n    <string name=\"count_albums\"><xliff:g id=\"count\">%d</xliff:g> álbumes</string>\n    <string name=\"count_artist\"><xliff:g id=\"count\">%d</xliff:g> artista</string>\n    <string name=\"count_artists\"><xliff:g id=\"count\">%d</xliff:g> artistas</string>\n\n    <string name=\"number_song_add_queue\"><xliff:g example=\"1\" id=\"number\">%d</xliff:g> canción agregada a la cola.</string>\n    <string name=\"number_songs_add_queue\"><xliff:g example=\"1\" id=\"number\">%d</xliff:g> canciones agregada a la cola.</string>\n    <string name=\"number_song_add_playlist\"><xliff:g example=\"1\" id=\"number\">%d</xliff:g> canción agregada a la lista de reproducción.</string>\n    <string name=\"number_songs_add_playlist\"><xliff:g example=\"1\" id=\"number\">%d</xliff:g> canciones agregada a la lista de reproducción.</string>\n\n\n    <!--do not translate these strings-->\n    <string name=\"combine_two_strings\" translatable=\"false\">%1$s | %2$s</string>\n    <string name=\"duration_format\" translatable=\"false\"><xliff:g id=\"hours\">%1$s</xliff:g> <xliff:g id=\"minutes\">%2$s</xliff:g></string>\n    <string name=\"durationformatshort\" translatable=\"false\"><xliff:g id=\"format\">%2$d:%3$02d</xliff:g></string>\n    <string name=\"durationformatlong\" translatable=\"false\"><xliff:g id=\"format\">%1$d:%2$02d:%3$02d</xliff:g></string>\n\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-fr/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources xmlns:xliff=\"urn:oasis:names:tc:xliff:document:1.2\">\n\n    <string name=\"action_settings\">Paramètres</string>\n\n    <string name=\"accessibility_play\">Lecture</string>\n    <string name=\"accessibility_pause\">Pause</string>\n    <string name=\"accessibility_next\">Suivant</string>\n    <string name=\"accessibility_prev\">Précédent</string>\n    <string name=\"accessibility_shuffle\">Aléatoire</string>\n    <string name=\"accessibility_shuffle_all\">Tout aléatoire</string>\n    <string name=\"accessibility_repeat\">Répéter</string>\n    <string name=\"accessibility_repeat_all\">Tout répéter</string>\n    <string name=\"accessibility_repeat_one\">Répéter une chanson</string>\n    <string name=\"accessibility_queue\">Ouvrir la file de lecture</string>\n    <string name=\"accessibility_browse\">Parcourir la musique</string>\n\n    <string name=\"menu_sort_by\">Trier par</string>\n    <string name=\"sort_order_entry_az\">A-Z</string>\n    <string name=\"sort_order_entry_za\">Z-A</string>\n    <string name=\"sort_order_entry_artist\">Artiste</string>\n    <string name=\"sort_order_entry_album\">Album</string>\n    <string name=\"sort_order_entry_year\">Année</string>\n    <string name=\"sort_order_entry_track_number\">Numéro de piste</string>\n    <string name=\"sort_order_entry_duration\">Durée</string>\n    <string name=\"sort_order_entry_date_added\">Date d\\'ajout</string>\n    <string name=\"sort_order_entry_track_list\">Liste des pistes</string>\n    <string name=\"sort_order_entry_number_of_songs\">Nombre de chansons</string>\n    <string name=\"sort_order_entry_number_of_albums\">Nombre d\\'albums</string>\n    <string name=\"sort_order_entry_filename\">Nom de fichier</string>\n\n    <string name=\"error_playing_track\">Erreur lors de la lecture de la piste</string>\n\n    <string name=\"playlist_last_added\">Derniers ajouts</string>\n    <string name=\"playlist_recently_played\">Écoutes récentes</string>\n    <string name=\"playlist_top_tracks\">Mes pistes préférées</string>\n\n    <string name=\"timber_source\">Signalez tout bug <u>ici</u></string>\n    <string name=\"timber_feature_request\">Proposez des fonctionnalités dans la communauté Google+ <u>ici</u></string>\n\n    <string name=\"menu_show_as\">Affichage</string>\n    <string name=\"menu_show_as_entry_list\">Liste</string>\n    <string name=\"menu_show_as_entry_grid\">Grille</string>\n\n    <string name=\"light\">Clair</string>\n    <string name=\"dark\">Sombre</string>\n    <string name=\"black\">Noir</string>\n\n    <string name=\"last_opened\">Derniers ouverts</string>\n\n    <string name=\"songs\">Chansons</string>\n    <string name=\"albums\">Albums</string>\n    <string name=\"artists\">Artistes</string>\n    <string name=\"library\">Bibliothèque</string>\n    <string name=\"playlists\">Listes de lecture</string>\n    <string name=\"playing_queue\">File de lecture</string>\n    <string name=\"now_playing\">En lecture</string>\n    <string name=\"settings\">Paramètres</string>\n    <string name=\"about\">À propos</string>\n    <string name=\"help_and_feedback\">Aide et commentaires</string>\n    <string name=\"search\">Rechercher</string>\n    <string name=\"action_shuffle_all\">Tout aléatoire</string>\n    <string name=\"action_equalizer\">Égaliseur</string>\n    <string name=\"search_library\">Rechercher dans la bibliothèque</string>\n\n    <string name=\"select_theme\">Sélectionner un thème</string>\n    <string name=\"choose_theme_throughout\">Choisir un thème qui sera utilisé dans l\\'interface</string>\n    <string name=\"choose_start_page\">Choisir un écran d\\'accueil qui sera affiché au lancement</string>\n    <string name=\"start_page\">Écran d\\'accueil</string>\n    <string name=\"artist_in_grid\">Artistes dans la grille</string>\n    <string name=\"toggle_artists_grid\">Afficher les artistes dans une grille 2x2</string>\n    <string name=\"toggle_system_animations\">Activer les animations système dans l\\'interface</string>\n    <string name=\"system_animations\">Animations système</string>\n    <string name=\"toggle_animations\">Activer les animations de transition dans l\\'interface</string>\n    <string name=\"animations\">Animations</string>\n    <string name=\"now_playing_selector\">Choisir un écran de lecture à partir de 4 styles différents</string>\n    <string name=\"personalisation\">Personnalisation</string>\n    <string name=\"other\">Autre</string>\n    <string name=\"timber_about_opensource\">Timber est une application open source développée par Naman Dwivedi.</string>\n    <string name=\"summary_pause_detach\">Mettre en pause la lecture lorsque le casque est débranché</string>\n    <string name=\"pause_detach\">Pause au débranchement</string>\n    <string name=\"currently_selected\">Actuellement sélectionné</string>\n    \n    <string name=\"dark_theme\">Thème sombre</string>\n    <string name=\"dark_theme_summary\">Appliquer un thème sombre dans l\\'interface</string>\n\n    <string name=\"primary_color\">Couleur principale</string>\n    <string name=\"primary_color_summary\">Changer la couleur principale du thème</string>\n\n    <string name=\"accent_color\">Couleur secondaire</string>\n    <string name=\"accent_color_summary\">Changer la couleur secondaire du thème</string>\n\n    <string name=\"colored_status_bar\">Barre d\\'état colorée</string>\n    <string name=\"colored_status_bar_summary\">Activer la coloration de la barre d\\'état</string>\n\n    <string name=\"colored_nav_bar\">Barre de navigation colorée</string>\n    <string name=\"colored_nav_bar_summary\">Activer la coloration de la barre de navigation</string>    \n\n    <string name=\"count_song\"><xliff:g id=\"count\">%d</xliff:g> chanson</string>\n    <string name=\"count_songs\"><xliff:g id=\"count\">%d</xliff:g> chansons</string>\n    <string name=\"count_album\"><xliff:g id=\"count\">%d</xliff:g> album</string>\n    <string name=\"count_albums\"><xliff:g id=\"count\">%d</xliff:g> albums</string>\n    <string name=\"count_artist\"><xliff:g id=\"count\">%d</xliff:g> artiste</string>\n    <string name=\"count_artists\"><xliff:g id=\"count\">%d</xliff:g> artistes</string>\n    \n    <string name=\"number_song_add_queue\"><xliff:g example=\"1\" id=\"number\">%d</xliff:g> chanson ajoutée à la file de lecture.</string>\n    <string name=\"number_songs_add_queue\"><xliff:g example=\"1\" id=\"number\">%d</xliff:g> chansons ajoutées à la file de lecture.</string>\n    <string name=\"number_song_add_playlist\"><xliff:g example=\"1\" id=\"number\">%d</xliff:g> chanson ajoutée à la liste de lecture.</string>\n    <string name=\"number_songs_add_playlist\"><xliff:g example=\"1\" id=\"number\">%d</xliff:g> chansons ajoutées à la liste de lecture.</string>\n    \n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-id/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources xmlns:xliff=\"urn:oasis:names:tc:xliff:document:1.2\">\n\n    <string name=\"action_settings\">Pengaturan</string>\n\n    <string name=\"accessibility_play\">Main</string>\n    <string name=\"accessibility_pause\">Jeda</string>\n    <string name=\"accessibility_next\">Berikutnya</string>\n    <string name=\"accessibility_prev\">Sebelumnya</string>\n    <string name=\"accessibility_shuffle\">Acak</string>\n    <string name=\"accessibility_shuffle_all\">Acak semua</string>\n    <string name=\"accessibility_repeat\">Ulangi</string>\n    <string name=\"accessibility_repeat_all\">Ulangi semua</string>\n    <string name=\"accessibility_repeat_one\">Ulangi satu kali</string>\n    <string name=\"accessibility_queue\">Buka antrian</string>\n    <string name=\"accessibility_browse\">Telusuri musik</string>\n\n    <string name=\"menu_sort_by\">Urutkan berdasarkan</string>\n    <string name=\"sort_order_entry_az\">A-Z</string>\n    <string name=\"sort_order_entry_za\">Z-A</string>\n    <string name=\"sort_order_entry_artist\">Artis</string>\n    <string name=\"sort_order_entry_album\">Album</string>\n    <string name=\"sort_order_entry_year\">Tahun</string>\n    <string name=\"sort_order_entry_track_number\">Nomor Lagu</string>\n    <string name=\"sort_order_entry_duration\">Durasi</string>\n    <string name=\"sort_order_entry_date_added\">Tanggal Ditambahkan</string>\n    <string name=\"sort_order_entry_track_list\">Daftar Lagu</string>\n    <string name=\"sort_order_entry_number_of_songs\">Jumlah Lagu</string>\n    <string name=\"sort_order_entry_number_of_albums\">Jumlah Album</string>\n    <string name=\"sort_order_entry_filename\">Nama File</string>\n\n    <string name=\"error_playing_track\">Kesalahan Saat Memutar lagu</string>\n\n    <string name=\"playlist_last_added\">Terakhir Ditambahkan</string>\n    <string name=\"playlist_recently_played\">Baru-baru ini Dimainkan</string>\n    <string name=\"playlist_top_tracks\">Lagu Teratas Saya</string>\n\n    <string name=\"combine_two_strings\" translatable=\"false\">%1$s | %2$s</string>\n\n    <string name=\"duration_format\" translatable=\"false\"><xliff:g id=\"hours\">%1$s</xliff:g> <xliff:g id=\"minutes\">%2$s</xliff:g></string>\n    <string name=\"durationformatshort\" translatable=\"false\"><xliff:g id=\"format\">%2$d:%3$02d</xliff:g></string>\n    <string name=\"durationformatlong\" translatable=\"false\"><xliff:g id=\"format\">%1$d:%2$02d:%3$02d</xliff:g></string>\n    <string name=\"timber_source\">Laporkan bug <u> di sini </u></string>\n    <string name=\"timber_feature_request\">Spesifikasikan permintaan fitur dalam komunitas Google+ <u> di sini </u></string>\n\n    <string name=\"menu_show_as\">Lihat Sebagai</string>\n    <string name=\"menu_show_as_entry_list\">Daftar</string>\n    <string name=\"menu_show_as_entry_grid\">Grid</string>\n\n    <string name=\"light\">Terang</string>\n    <string name=\"dark\">Gelap</string>\n    <string name=\"black\">Hitam</string>\n\n    <string name=\"last_opened\">Terakhir Dibuka</string>\n\n    <string name=\"songs\">Lagu</string>\n    <string name=\"albums\">Album</string>\n    <string name=\"artists\">Artis</string>\n    <string name=\"library\">Pustaka</string>\n    <string name=\"playlists\">Daftar Putar</string>\n    <string name=\"playing_queue\">Antrian Main</string>\n    <string name=\"now_playing\">Sedang Dimainkan</string>\n    <string name=\"settings\">Pengaturan</string>\n    <string name=\"about\">Tentang</string>\n    <string name=\"help_and_feedback\">Bantuan dan Umpan Balik</string>\n    <string name=\"search\">Cari</string>\n    <string name=\"action_shuffle_all\">Acak semua</string>\n    <string name=\"action_equalizer\">Ekualiser</string>\n    <string name=\"search_library\">Cari Pustaka</string>\n    <string name=\"count_song\"><xliff:g id=\"count\">%d</xliff:g> song</string>\n    <string name=\"count_songs\"><xliff:g id=\"count\">%d</xliff:g> songs</string>\n    <string name=\"count_album\"><xliff:g id=\"count\">%d</xliff:g> album</string>\n    <string name=\"count_albums\"><xliff:g id=\"count\">%d</xliff:g> albums</string>\n    <string name=\"count_artist\"><xliff:g id=\"count\">%d</xliff:g> artist</string>\n    <string name=\"count_artists\"><xliff:g id=\"count\">%d</xliff:g> artists</string>\n    <string name=\"select_theme\">Pilih Tema</string>\n    <string name=\"choose_theme_throughout\">Pilih tema yang akan digunakan di seluruh aplikasi.</string>\n    <string name=\"choose_start_page\">Pilih halaman awal yang akan ditampilkan pada saat peluncuran.</string>\n    <string name=\"start_page\">Halaman awal</string>\n    <string name=\"artist_in_grid\">Artis dalam Grid</string>\n    <string name=\"toggle_artists_grid\">Toggle untuk menampilkan artis dalam grid 2x2.</string>\n    <string name=\"toggle_system_animations\">Toggle untuk untuk animasi sistem di seluruh aplikasi.</string>\n    <string name=\"system_animations\">Animasi Sistem</string>\n    <string name=\"toggle_animations\">Toggle untuk animasi transisi di seluruh aplikasi.on throughout the app.</string>\n    <string name=\"animations\">Animasi</string>\n    <string name=\"now_playing_selector\">Pilih layar yang sedang diputar dari 4 gaya yang berbeda.</string>\n    <string name=\"personalisation\">Personalisasi</string>\n    <string name=\"other\">Lainnya</string>\n    <string name=\"timber_about_opensource\">Timber adalah Pemutar Musik dengan Sumber Terbuka yang dikembangkan oleh Naman Dwivedi.</string>\n    <string name=\"summary_pause_detach\">Berhenti memutar ketika headphone dilepas</string>\n    <string name=\"pause_detach\">Jeda saat dilepas</string>\n    <string name=\"currently_selected\">Yang Sedang Dipilih</string>\n\n\n    <string name=\"dark_theme\">Tema Gelap</string>\n    <string name=\"dark_theme_summary\">Terapkan tema gelap di seluruh aplikasi</string>\n\n    <string name=\"primary_color\">Warna Utama</string>\n    <string name=\"primary_color_summary\">Mengubah warna tema utama</string>\n\n    <string name=\"accent_color\">Warna Aksen</string>\n    <string name=\"accent_color_summary\">Mengubah warna aksen tema</string>\n\n    <string name=\"colored_status_bar\">Bilah Status Berwarna</string>\n    <string name=\"colored_status_bar_summary\">Mewarnai bilah status.</string>\n\n    <string name=\"colored_nav_bar\">Bilah Navigasi Berwarna</string>\n    <string name=\"colored_nav_bar_summary\">Mewarnai bilah navigasi</string>\n\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-it/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources xmlns:xliff=\"urn:oasis:names:tc:xliff:document:1.2\">\n    \n    <string name=\"action_settings\">Impostazioni</string>\n\n    <string name=\"accessibility_play\">Play</string>\n    <string name=\"accessibility_pause\">Pausa</string>\n    <string name=\"accessibility_next\">Successivo</string>\n    <string name=\"accessibility_prev\">Precedente</string>\n    <string name=\"accessibility_shuffle\">Mischia</string>\n    <string name=\"accessibility_shuffle_all\">Mischia tutti</string>\n    <string name=\"accessibility_repeat\">Ripeti</string>\n    <string name=\"accessibility_repeat_all\">Ripeti tutti</string>\n    <string name=\"accessibility_repeat_one\">Ripeti uno</string>\n    <string name=\"accessibility_queue\">Apri coda</string>\n    <string name=\"accessibility_browse\">Sfoglia</string>\n\n    <string name=\"menu_sort_by\">Ordina per</string>\n    <string name=\"sort_order_entry_az\">A-Z</string>\n    <string name=\"sort_order_entry_za\">Z-A</string>\n    <string name=\"sort_order_entry_artist\">Artista</string>\n    <string name=\"sort_order_entry_album\">Album</string>\n    <string name=\"sort_order_entry_year\">Anno</string>\n    <string name=\"sort_order_entry_track_number\">Numero traccia</string>\n    <string name=\"sort_order_entry_duration\">Durata</string>\n    <string name=\"sort_order_entry_date_added\">Data aggiunta</string>\n    <string name=\"sort_order_entry_track_list\">#</string>\n    <string name=\"sort_order_entry_number_of_songs\">Numero di canzoni</string>\n    <string name=\"sort_order_entry_number_of_albums\">Numero di album</string>\n    <string name=\"sort_order_entry_filename\">Nome file</string>\n\n    <string name=\"error_playing_track\">Errore nella riproduzione del file</string>\n\n    <string name=\"playlist_last_added\">Ultimi aggiunti</string>\n    <string name=\"playlist_recently_played\">Recentemente ascoltati</string>\n    <string name=\"playlist_top_tracks\">Le mie canzoni più belle</string>\n\n    <string name=\"timber_source\">Invia una segnalazione di bug <u> qui </u> </string>\n    <string name=\"timber_feature_request\">Richiedi qualsiasi nuova funzione nella comunità Google+ <u> qui </u></string>\n\n    <string name=\"menu_show_as\">Visualizza come</string>\n    <string name=\"menu_show_as_entry_list\">Lista</string>\n    <string name=\"menu_show_as_entry_grid\">Griglia</string>\n\n    <string name=\"light\">Chiaro</string>\n    <string name=\"dark\">Scuro</string>\n    <string name=\"black\">Nero</string>\n\n    <string name=\"last_opened\">Ultimo aperto</string>\n\n    <string name=\"songs\">Canzoni</string>\n    <string name=\"albums\">Album</string>\n    <string name=\"artists\">Artisti</string>\n    <string name=\"library\">Libreria</string>\n    <string name=\"playlists\">Playlist</string>\n    <string name=\"playing_queue\">Coda di riproduzione</string>\n    <string name=\"now_playing\">In riproduzione ora</string>\n    <string name=\"settings\">Impostazioni</string>\n    <string name=\"about\">Informazioni</string>\n    <string name=\"help_and_feedback\">Aiuto e feedback</string>\n    <string name=\"search\">Cerca</string>\n    <string name=\"action_shuffle_all\">Brani casuali</string>\n    <string name=\"action_equalizer\">Equalizzatore</string>\n    <string name=\"search_library\">Cerca nella libreria</string>\n\n    <string name=\"select_theme\">Seleziona tema</string>\n    <string name=\"choose_theme_throughout\">Scegli un tema da usare all\\'interno dell\\'app.</string>\n    <string name=\"choose_start_page\">Seleziona la pagina di apertura da mostrare all\\'avvio.</string>\n    <string name=\"start_page\">Pagina di apertura</string>\n    <string name=\"artist_in_grid\">Artisti in griglia</string>\n    <string name=\"toggle_artists_grid\">Abilita per mostrare gli artisti in una griglia 2x2.</string>\n    <string name=\"toggle_system_animations\">Abilita per usare le animazioni di sistema all\\'interno dell\\'app.</string>\n    <string name=\"system_animations\">Animazioni di sistema</string>\n    <string name=\"toggle_animations\">Abilita l\\'animazione di transizione all\\'interno dell\\'app.</string>\n    <string name=\"animations\">Animazioni</string>\n    <string name=\"now_playing_selector\">Scegli la schermata di riproduzione tra 4 stili differenti.</string>\n    <string name=\"personalisation\">Personalizzazione</string>\n    <string name=\"other\">Altro</string>\n    <string name=\"timber_about_opensource\">Timber è un riproduttore musicale open source sviluppato da Naman Dwivedi.</string>\n    <string name=\"summary_pause_detach\">Abilita pausa della riproduzione quando vengono staccate le cuffie</string>\n    <string name=\"pause_detach\">Pausa al distacco</string>\n    <string name=\"currently_selected\">Attualmente selezionato</string>\n\n    <string name=\"count_song\"><xliff:g id=\"count\">%d</xliff:g> canzone</string>\n    <string name=\"count_songs\"><xliff:g id=\"count\">%d</xliff:g> canzoni</string>\n    <string name=\"count_album\"><xliff:g id=\"count\">%d</xliff:g> album</string>\n    <string name=\"count_albums\"><xliff:g id=\"count\">%d</xliff:g> album</string>\n    <string name=\"count_artist\"><xliff:g id=\"count\">%d</xliff:g> artista</string>\n    <string name=\"count_artists\"><xliff:g id=\"count\">%d</xliff:g> artisti</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-ko/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources xmlns:xliff=\"urn:oasis:names:tc:xliff:document:1.2\">\n\n    <string name=\"action_settings\">설정</string>\n\n    <string name=\"accessibility_play\">재생</string>\n    <string name=\"accessibility_pause\">일시 중지</string>\n    <string name=\"accessibility_next\">다음</string>\n    <string name=\"accessibility_prev\">이전</string>\n    <string name=\"accessibility_shuffle\">섞기</string>\n    <string name=\"accessibility_shuffle_all\">전체 섞기</string>\n    <string name=\"accessibility_repeat\">반복</string>\n    <string name=\"accessibility_repeat_all\">전체 반복</string>\n    <string name=\"accessibility_repeat_one\">한 번 반복</string>\n    <string name=\"accessibility_queue\">재생 대기열 열기</string>\n    <string name=\"accessibility_browse\">음악 찾기</string>\n\n    <string name=\"menu_sort_by\">정렬</string>\n    <string name=\"sort_order_entry_az\">A-Z</string>\n    <string name=\"sort_order_entry_za\">Z-A</string>\n    <string name=\"sort_order_entry_artist\">아티스트</string>\n    <string name=\"sort_order_entry_album\">앨범</string>\n    <string name=\"sort_order_entry_year\">연도</string>\n    <string name=\"sort_order_entry_track_number\">트랙 번호</string>\n    <string name=\"sort_order_entry_duration\">길이</string>\n    <string name=\"sort_order_entry_date_added\">추가된 날짜</string>\n    <string name=\"sort_order_entry_track_list\">트랙 목록</string>\n    <string name=\"sort_order_entry_number_of_songs\">음악 수</string>\n    <string name=\"sort_order_entry_number_of_albums\">앨범 수</string>\n    <string name=\"sort_order_entry_filename\">파일명</string>\n\n    <string name=\"error_playing_track\">재생 중인 트랙 오류</string>\n\n    <string name=\"playlist_last_added\">마지막으로 추가된 리스트</string>\n    <string name=\"playlist_recently_played\">최근 재생한 리스트</string>\n    <string name=\"playlist_top_tracks\">상위 트랙</string>\n\n    <string name=\"timber_source\"><u>여기</u>에 버그를 남겨주세요</string>\n    <string name=\"timber_feature_request\"><u>Google+ 커뮤니티</u>에 새로운 기능을 요청하세요</string>\n\n    <string name=\"menu_show_as\">보기 정렬</string>\n    <string name=\"menu_show_as_entry_list\">목록</string>\n    <string name=\"menu_show_as_entry_grid\">그리드</string>\n\n    <string name=\"light\">밝게</string>\n    <string name=\"dark\">어둡게</string>\n    <string name=\"black\">까맣게</string>\n\n    <string name=\"last_opened\">마지막으로 본 화면</string>\n\n    <string name=\"songs\">음악</string>\n    <string name=\"albums\">앨범</string>\n    <string name=\"artists\">아티스트</string>\n    <string name=\"library\">저장소</string>\n    <string name=\"playlists\">재생 목록</string>\n    <string name=\"playing_queue\">재생 중인 대기열</string>\n    <string name=\"now_playing\">재생 중</string>\n    <string name=\"settings\">설정</string>\n    <string name=\"about\">소개</string>\n    <string name=\"help_and_feedback\">도움말과 피드백</string>\n    <string name=\"search\">찾기</string>\n    <string name=\"action_shuffle_all\">전체 섞기</string>\n    <string name=\"action_equalizer\">이퀄라이저</string>\n    <string name=\"search_library\">저장소 찾기</string>\n\n    <string name=\"select_theme\">테마 선택</string>\n    <string name=\"choose_theme_throughout\">전체적인 테마를 고르세요.</string>\n    <string name=\"choose_start_page\">어플 실행 시 보여질 화면을 선택하세요.</string>\n    <string name=\"start_page\">시작 화면</string>\n    <string name=\"artist_in_grid\">그리드로 아티스트 보기</string>\n    <string name=\"toggle_artists_grid\">2x2 그리드로 아티스트 보기를 유지합니다.</string>\n    <string name=\"toggle_system_animations\">시스템 애니메이션을 유지합니다.</string>\n    <string name=\"system_animations\">시스템 애니메이션</string>\n    <string name=\"toggle_animations\">변환 애니메이션을 유지합니다.</string>\n    <string name=\"animations\">애니메이션</string>\n    <string name=\"now_playing_selector\">4가지 다른 스타일 중 재생 중인 화면을 고르세요.</string>\n    <string name=\"personalisation\">개인 설정</string>\n    <string name=\"other\">기타</string>\n    <string name=\"advanced\">고급</string>\n    <string name=\"timber_about_opensource\">팀버(Timber)는 Naman Dwivedi가 개발한 오픈 소스 음악 플레이어입니다.</string>\n    <string name=\"summary_pause_detach\">이어폰이 분리되면 일시 중지합니다.</string>\n    <string name=\"pause_detach\">분리 시 일시 중지</string>\n    <string name=\"summary_xposed_trackselector\">Xposed와 XMediaNotificationTrackSelector Xposed 모듈이 설치되어 있어야 합니다. 이들은 알림바에서 재생 중인 대기열에 접근하기 위해 사용됩니다.</string>\n    <string name=\"xposed_trackselector\">재생 중인 대기열 알림 활성화</string>\n    <string name=\"currently_selected\">현재 선택된 것</string>\n\n    <string name=\"dark_theme\">어두운 테마</string>\n    <string name=\"dark_theme_summary\">어두운 테마를 적용합니다</string>\n\n    <string name=\"primary_color\">기본 색깔</string>\n    <string name=\"primary_color_summary\">기본 테마 색깔을 변경합니다</string>\n\n    <string name=\"accent_color\">강조 색깔</string>\n    <string name=\"accent_color_summary\">강조 테마 색깔을 변경합니다</string>\n\n    <string name=\"colored_status_bar\">색깔 있는 상태바</string>\n    <string name=\"colored_status_bar_summary\">상태바 색깔을 유지합니다</string>\n\n    <string name=\"colored_nav_bar\">색깔 있는 내비게이션바</string>\n    <string name=\"colored_nav_bar_summary\">내비게이션바 색깔을 유지합니다</string>\n\n    <string name=\"gestures\">제스처</string>\n    <string name=\"switching_tracks_by_gestures\">제스처로 트랙을 변경합니다</string>\n    <string name=\"widget_standard\">Timber 표준 위젯</string>\n    <string name=\"widget_small\">Timber 작은 위젯</string>\n    <string name=\"tap_to_start\">탭 하여 Timber를 시작하기</string>\n    <string name=\"widget_white\">Timber 밝은 위젯</string>\n\n    <string name=\"count_song\"><xliff:g id=\"count\">%d</xliff:g> 음악</string>\n    <string name=\"count_songs\"><xliff:g id=\"count\">%d</xliff:g> 음악</string>\n    <string name=\"count_album\"><xliff:g id=\"count\">%d</xliff:g> 앨범</string>\n    <string name=\"count_albums\"><xliff:g id=\"count\">%d</xliff:g> 앨범</string>\n    <string name=\"count_artist\"><xliff:g id=\"count\">%d</xliff:g> 음악가</string>\n    <string name=\"count_artists\"><xliff:g id=\"count\">%d</xliff:g> 음악가</string>\n\n    <string name=\"number_song_add_queue\"><xliff:g example=\"1\" id=\"number\">%d</xliff:g> 음악이 대기열에 추가됐습니다.</string>\n    <string name=\"number_songs_add_queue\"><xliff:g example=\"1\" id=\"number\">%d</xliff:g> 음악이 대기열에 추가됐습니다.</string>\n    <string name=\"number_song_add_playlist\"><xliff:g example=\"1\" id=\"number\">%d</xliff:g> 음악이 재생 목록에 추가됐습니다.</string>\n    <string name=\"number_songs_add_playlist\"><xliff:g example=\"1\" id=\"number\">%d</xliff:g> 음악이 재생 목록에 추가됐습니다.</string>\n\n    <string name=\"play\">재생</string>\n    <string name=\"add_to_playlist\">재생 목록에 추가</string>\n    <string name=\"go_to_album\">앨범으로 이동</string>\n    <string name=\"go_to_artist\">음악가로 이동</string>\n    <string name=\"add_to_queue\">대기열에 추가</string>\n    <string name=\"play_next\">다음곡</string>\n    <string name=\"new_playlist\">새로운 재생 목록</string>\n\n\n    <string name=\"nothing_found\">검색 결과가 없습니다</string>\n    <string name=\"folders\">폴더</string>\n\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-nb-rNO/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources xmlns:xliff=\"urn:oasis:names:tc:xliff:document:1.2\">\n\n    <string name=\"action_settings\">Innstillinger</string>\n\n    <string name=\"accessibility_play\">Spill</string>\n    <string name=\"accessibility_pause\">Pause</string>\n    <string name=\"accessibility_next\">Neste</string>\n    <string name=\"accessibility_prev\">Forrige</string>\n    <string name=\"accessibility_shuffle\">Veksle mellom sanger</string>\n    <string name=\"accessibility_shuffle_all\">Veksle mellom alt</string>\n    <string name=\"accessibility_repeat\">Gjenta</string>\n    <string name=\"accessibility_repeat_all\">Gjenta alle</string>\n    <string name=\"accessibility_repeat_one\">Gjenta én</string>\n    <string name=\"accessibility_queue\">Åpne avspillingskø</string>\n    <string name=\"accessibility_browse\">Utforsk musikk</string>\n\n    <string name=\"menu_sort_by\">Sortering</string>\n    <string name=\"sort_order_entry_az\">A-Å</string>\n    <string name=\"sort_order_entry_za\">Å-A</string>\n    <string name=\"sort_order_entry_artist\">Artist</string>\n    <string name=\"sort_order_entry_album\">Album</string>\n    <string name=\"sort_order_entry_year\">År</string>\n    <string name=\"sort_order_entry_track_number\">Spornummer</string>\n    <string name=\"sort_order_entry_duration\">Varighet</string>\n    <string name=\"sort_order_entry_date_added\">Dato lagt til</string>\n    <string name=\"sort_order_entry_track_list\">Sporliste</string>\n    <string name=\"sort_order_entry_number_of_songs\">Antall spor</string>\n    <string name=\"sort_order_entry_number_of_albums\">Antall album</string>\n    <string name=\"sort_order_entry_filename\">Filnavn</string>\n\n    <string name=\"error_playing_track\">Feil ved avspilling av spor</string>\n\n    <string name=\"playlist_last_added\">Sist lagt til</string>\n    <string name=\"playlist_recently_played\">Nylig spilt</string>\n    <string name=\"playlist_top_tracks\">Mine mest spilte spor</string>\n\n    <string name=\"timber_source\">Innrapporter eventuelle feil<u> her </u> </string>\n    <string name=\"timber_feature_request\">Angi funksjonsforespørsler i Google+brukerfellesskapet<u> her </u></string>\n\n    <string name=\"menu_show_as\">Vis som</string>\n    <string name=\"menu_show_as_entry_list\">Liste</string>\n    <string name=\"menu_show_as_entry_grid\">Rutenett</string>\n    <string name=\"menu_show_as_entry_default\">Forvalg</string>\n\n    <string name=\"light\">Lys</string>\n    <string name=\"dark\">Mørk</string>\n    <string name=\"black\">Svart</string>\n\n    <string name=\"last_opened\">Sist åpnet</string>\n\n    <string name=\"songs\">Spor</string>\n    <string name=\"albums\">Album</string>\n    <string name=\"artists\">Artister</string>\n    <string name=\"library\">Bibliotek</string>\n    <string name=\"playlists\">Spillelister</string>\n    <string name=\"playing_queue\">Avspillingskø</string>\n    <string name=\"now_playing\">Spilles nå</string>\n    <string name=\"settings\">Innstillinger</string>\n    <string name=\"about\">Om</string>\n    <string name=\"help_and_feedback\">Hjelp og tilbakemeldinger</string>\n    <string name=\"search\">Søk</string>\n    <string name=\"action_shuffle_all\">Veksle mellom alle</string>\n    <string name=\"action_equalizer\">Tonekontroll</string>\n    <string name=\"search_library\">Søk i bibliotek</string>\n\n    <string name=\"select_theme\">Velg drakt</string>\n    <string name=\"choose_theme_throughout\">Velg en drakt til bruk i programmet.</string>\n    <string name=\"choose_start_page\">Velg en startside til visning ved oppstart.</string>\n    <string name=\"start_page\">Startside</string>\n    <string name=\"artist_in_grid\">Artister i rutenett</string>\n    <string name=\"toggle_artists_grid\">Veksle for visning av artister i 2x2-rutenett.</string>\n    <string name=\"toggle_system_animations\">Veksle for systemanimasjoner i hele programmet.</string>\n    <string name=\"system_animations\">System-animasjoner</string>\n    <string name=\"toggle_animations\">Veksle for overgangsanimasjon i hele programmet.</string>\n    <string name=\"animations\">Animasjoner</string>\n    <string name=\"now_playing_selector\">Velg en \"Nå spilles\"-skjerm blandt fire forskjellige stilarter.</string>\n    <string name=\"personalisation\">Brukertilpasning</string>\n    <string name=\"other\">Annet</string>\n    <string name=\"advanced\">Avansert</string>\n    <string name=\"timber_about_opensource\">Timber er en fri programvare-musikkavspiller, utviklet av Naman Dwivedi.</string>\n    <string name=\"summary_pause_detach\">Veksle for pausing av musikkavspilling når hodetelefoner kobles fra</string>\n    <string name=\"pause_detach\">Pause ved frakobling</string>\n    <string name=\"summary_xposed_trackselector\">Krever Xposed og XMediaNotificationTrackSelector Xposed-modul for å kunne installeres. Dette gir tilgang til avspillingskø fra merknadsmenyen.</string>\n    <string name=\"xposed_trackselector\">Avspillingskø i merknadsfelt</string>\n    <string name=\"currently_selected\">Valgt</string>\n\n    <string name=\"dark_theme\">Mørk drakt</string>\n    <string name=\"dark_theme_summary\">Ikle programmet en mørk drakt</string>\n\n    <string name=\"primary_color\">Hovedfarge</string>\n    <string name=\"primary_color_summary\">Endre draktens hovedfarge</string>\n\n    <string name=\"accent_color\">Sekundærfarge</string>\n    <string name=\"accent_color_summary\">Endre draktens sekundærfarge</string>\n\n    <string name=\"colored_status_bar\">Farget statusfelt</string>\n    <string name=\"colored_status_bar_summary\">Veksle fargelegging av statusfelt</string>\n\n    <string name=\"colored_nav_bar\">Farget navigationsfelt</string>\n    <string name=\"colored_nav_bar_summary\">Veksle fargelegging av navigasjonsfelt</string>\n\n    <string name=\"gestures\">Strøk</string>\n    <string name=\"switching_tracks_by_gestures\">Bytt spor ved å stryke</string>\n    <string name=\"widget_standard\">Timber forvalgt miniprogram</string>\n    <string name=\"widget_small\">Timber lite miniprogram</string>\n    <string name=\"tap_to_start\">Trykk for å starte Timber</string>\n    <string name=\"widget_white\">Timber lyst miniprogram</string>\n\n    <string name=\"count_song\"><xliff:g id=\"count\">%d</xliff:g> sang</string>\n    <string name=\"count_songs\"><xliff:g id=\"count\">%d</xliff:g> sanger</string>\n    <string name=\"count_album\"><xliff:g id=\"count\">%d</xliff:g> album</string>\n    <string name=\"count_albums\"><xliff:g id=\"count\">%d</xliff:g> album</string>\n    <string name=\"count_artist\"><xliff:g id=\"count\">%d</xliff:g> artist</string>\n    <string name=\"count_artists\"><xliff:g id=\"count\">%d</xliff:g> artister</string>\n\n    <string name=\"number_song_add_queue\"><xliff:g example=\"1\" id=\"number\">%d</xliff:g> sang lagt til i avspillingskø.</string>\n    <string name=\"number_songs_add_queue\"><xliff:g example=\"1\" id=\"number\">%d</xliff:g> songer lagt til i avspillingskø.</string>\n    <string name=\"number_song_add_playlist\"><xliff:g example=\"1\" id=\"number\">%d</xliff:g> sang lagt til i spilleliste.</string>\n    <string name=\"number_songs_add_playlist\"><xliff:g example=\"1\" id=\"number\">%d</xliff:g> sanger lagt til i spilleliste.</string>\n\n    <string name=\"play\">Spill</string>\n    <string name=\"add_to_playlist\">Legg til i spilleliste</string>\n    <string name=\"go_to_album\">Gå til album</string>\n    <string name=\"go_to_artist\">Gå til artist</string>\n    <string name=\"add_to_queue\">Legg til i avspillingskø</string>\n    <string name=\"play_next\">Spill neste</string>\n    <string name=\"new_playlist\">Ny spilleliste</string>\n\n\n    <string name=\"nothing_found\">Ingenting funnet</string>\n    <string name=\"folders\">Mapper</string>\n\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-pt/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources xmlns:xliff=\"urn:oasis:names:tc:xliff:document:1.2\">\n\n    <string name=\"action_settings\">Definições</string>\n\n    <string name=\"accessibility_play\">Reproduzir</string>\n    <string name=\"accessibility_pause\">Pausa</string>\n    <string name=\"accessibility_next\">Seguinte</string>\n    <string name=\"accessibility_prev\">Anterior</string>\n    <string name=\"accessibility_shuffle\">Baralhar</string>\n    <string name=\"accessibility_shuffle_all\">Baralhar tudo</string>\n    <string name=\"accessibility_repeat\">Repetição</string>\n    <string name=\"accessibility_repeat_all\">Repetir todas</string>\n    <string name=\"accessibility_repeat_one\">Repetir uma</string>\n    <string name=\"accessibility_queue\">Abrir fila</string>\n    <string name=\"accessibility_browse\">Explorar músicas</string>\n\n    <string name=\"menu_sort_by\">Ordenação</string>\n    <string name=\"sort_order_entry_az\">A-Z</string>\n    <string name=\"sort_order_entry_za\">Z-A</string>\n    <string name=\"sort_order_entry_artist\">Artista</string>\n    <string name=\"sort_order_entry_album\">Álbum</string>\n    <string name=\"sort_order_entry_year\">Ano</string>\n    <string name=\"sort_order_entry_track_number\">N.º da faixa</string>\n    <string name=\"sort_order_entry_duration\">Duração</string>\n    <string name=\"sort_order_entry_date_added\">Data de adição</string>\n    <string name=\"sort_order_entry_track_list\">Lista de faixas</string>\n    <string name=\"sort_order_entry_number_of_songs\">Número de faixas</string>\n    <string name=\"sort_order_entry_number_of_albums\">Número de álbuns</string>\n    <string name=\"sort_order_entry_filename\">Nome de ficheiro</string>\n\n    <string name=\"error_playing_track\">Erro de reprodução</string>\n\n    <string name=\"playlist_last_added\">Última adição</string>\n    <string name=\"playlist_recently_played\">Reproduções recentes</string>\n    <string name=\"playlist_top_tracks\">Faixas favoritas</string>\n\n    <string name=\"timber_source\">Reporte os erros encontrados <u>aqui</u> </string>\n    <string name=\"timber_feature_request\">Sugira funcionalidades na comunidade Google+ <u>aqui</u></string>\n\n    <string name=\"menu_show_as\">Ver como</string>\n    <string name=\"menu_show_as_entry_list\">Lista</string>\n    <string name=\"menu_show_as_entry_grid\">Grelha</string>\n\n    <string name=\"light\">Claro</string>\n    <string name=\"dark\">Escuro</string>\n    <string name=\"black\">Preto</string>\n\n    <string name=\"last_opened\">Última opção</string>\n\n    <string name=\"songs\">Faixas</string>\n    <string name=\"albums\">Álbums</string>\n    <string name=\"artists\">Artistas</string>\n    <string name=\"library\">Coleção</string>\n    <string name=\"playlists\">Listas de reprodução</string>\n    <string name=\"playing_queue\">Fila de reprodução</string>\n    <string name=\"now_playing\">Em reprodução</string>\n    <string name=\"settings\">Definições</string>\n    <string name=\"about\">Sobre</string>\n    <string name=\"help_and_feedback\">Ajuda e comentários</string>\n    <string name=\"search\">Pesquisa</string>\n    <string name=\"action_shuffle_all\">Baralhar tudo</string>\n    <string name=\"action_equalizer\">Equalizador</string>\n    <string name=\"search_library\">Pesquisar na coleção</string>\n\n    <string name=\"select_theme\">Escolher Tema</string>\n    <string name=\"choose_theme_throughout\">Escolha o tema a utilizar na aplicação.</string>\n    <string name=\"choose_start_page\">Escolha o ecrã a mostrar ao iniciar.</string>\n    <string name=\"start_page\">Ecrã inicial</string>\n    <string name=\"artist_in_grid\">Artistas em grelha</string>\n    <string name=\"toggle_artists_grid\">Exibir artistas em grelha no formato 2x2.</string>\n    <string name=\"toggle_system_animations\">Utilizar animações do sistema na aplicação.</string>\n    <string name=\"system_animations\">Animações do sistema</string>\n    <string name=\"toggle_animations\">Utilizar animações de transição na aplicação.</string>\n    <string name=\"animations\">Animações</string>\n    <string name=\"now_playing_selector\">Escolha o ecrã de reprodução de entre os 4 estilos disponíveis.</string>\n    <string name=\"personalisation\">Personalização</string>\n    <string name=\"other\">Outras</string>\n    <string name=\"timber_about_opensource\">O Timber é um reprodutor de músicas open source, desenvolvido por Naman Dwivedi.</string>\n    <string name=\"summary_pause_detach\">Pausa na reprodução ao remover os auscultadores.</string>\n    <string name=\"pause_detach\">Pausa ao remover</string>\n    <string name=\"currently_selected\">Seleção atual</string>\n\n    <string name=\"dark_theme\">Tema Escuro</string>\n    <string name=\"dark_theme_summary\">Aplicar um tema escuro à aplicação</string>\n\n    <string name=\"primary_color\">Cor Primária</string>\n    <string name=\"primary_color_summary\">Mudar a cor primária do tema</string>\n\n    <string name=\"accent_color\">Cor Secundária</string>\n    <string name=\"accent_color_summary\">Mudar a cor secundária do tema</string>\n\n    <string name=\"colored_status_bar\">Colorir a barra de estado</string>\n    <string name=\"colored_status_bar_summary\">Alternar a coloração da barra de estado</string>\n\n    <string name=\"colored_nav_bar\">Colorir a barra de navegação</string>\n    <string name=\"colored_nav_bar_summary\">Alternar a coloração da barra de navegação</string>\n\n    <string name=\"count_song\"><xliff:g id=\"count\">%d</xliff:g> faixa</string>\n    <string name=\"count_songs\"><xliff:g id=\"count\">%d</xliff:g> faixas</string>\n    <string name=\"count_album\"><xliff:g id=\"count\">%d</xliff:g> álbum</string>\n    <string name=\"count_albums\"><xliff:g id=\"count\">%d</xliff:g> álbuns</string>\n    <string name=\"count_artist\"><xliff:g id=\"count\">%d</xliff:g> artista</string>\n    <string name=\"count_artists\"><xliff:g id=\"count\">%d</xliff:g> artistas</string>\n\n    <string name=\"number_song_add_queue\"><xliff:g example=\"1\" id=\"number\">%d</xliff:g> faixa adicionada à fila.</string>\n    <string name=\"number_songs_add_queue\"><xliff:g example=\"1\" id=\"number\">%d</xliff:g> faixas adicionadas à fila.</string>\n    <string name=\"number_song_add_playlist\"><xliff:g example=\"1\" id=\"number\">%d</xliff:g> faixa adicionada à lista de reprodução.</string>\n    <string name=\"number_songs_add_playlist\"><xliff:g example=\"1\" id=\"number\">%d</xliff:g> faixas adicionadas à lista de reprodução.</string>\n    <string name=\"now_playing_no_track_selected\">Não foi selecionado musíca para tocar.</string>\n    <string name=\"lyrics_loading\">Carregando...</string>\n\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-ru/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources xmlns:xliff=\"urn:oasis:names:tc:xliff:document:1.2\">\n\n    <string name=\"action_settings\">Настройки</string>\n\n    <string name=\"accessibility_play\">Играть</string>\n    <string name=\"accessibility_pause\">Пауза</string>\n    <string name=\"accessibility_next\">Следующий</string>\n    <string name=\"accessibility_prev\">Предыдуший</string>\n    <string name=\"accessibility_shuffle\">Перемешать</string>\n    <string name=\"accessibility_shuffle_all\">Перемешать все</string>\n    <string name=\"accessibility_repeat\">Повторить</string>\n    <string name=\"accessibility_repeat_all\">Повторить все</string>\n    <string name=\"accessibility_repeat_one\">Повторить один</string>\n    <string name=\"accessibility_queue\">Открыть очередь</string>\n    <string name=\"accessibility_browse\">Посмотреть музыку</string>\n\n    <string name=\"menu_sort_by\">Сортировать по</string>\n    <string name=\"sort_order_entry_az\">A-Z</string>\n    <string name=\"sort_order_entry_za\">Z-A</string>\n    <string name=\"sort_order_entry_artist\">Артисту</string>\n    <string name=\"sort_order_entry_album\">Альбому</string>\n    <string name=\"sort_order_entry_year\">Году</string>\n    <string name=\"sort_order_entry_track_number\">Номеру трека</string>\n    <string name=\"sort_order_entry_duration\">Длительности</string>\n    <string name=\"sort_order_entry_date_added\">Дате добавления</string>\n    <string name=\"sort_order_entry_track_list\">Трек-листу</string>\n    <string name=\"sort_order_entry_number_of_songs\">Количеству песен</string>\n    <string name=\"sort_order_entry_number_of_albums\">Количеству альбомов</string>\n    <string name=\"sort_order_entry_filename\">Имени файла</string>\n\n    <string name=\"error_playing_track\">Ошибка воспроизведения трека</string>\n\n    <string name=\"playlist_last_added\">Новые</string>\n    <string name=\"playlist_recently_played\">Недавно играли</string>\n    <string name=\"playlist_top_tracks\">Лучшие треки</string>\n\n    <string name=\"timber_source\">Сообщить о какой-либо ошибки <u> здесь </u> </string>\n    <string name=\"timber_feature_request\">Уточните любые вопросы в нашей Google+ группе <u> здесь </u></string>\n\n    <string name=\"menu_show_as\">Показывать как</string>\n    <string name=\"menu_show_as_entry_list\">Список</string>\n    <string name=\"menu_show_as_entry_grid\">Сетку</string>\n\n    <string name=\"light\">Светлая</string>\n    <string name=\"dark\">Темная</string>\n    <string name=\"black\">Черная</string>\n\n    <string name=\"last_opened\">Последний открытый</string>\n\n    <string name=\"songs\">Песни</string>\n    <string name=\"albums\">Альбомы</string>\n    <string name=\"artists\">Артисты</string>\n    <string name=\"library\">Библиотека</string>\n    <string name=\"playlists\">Плей-листы</string>\n    <string name=\"playing_queue\">Играет в очереди</string>\n    <string name=\"now_playing\">Играет сейчас</string>\n    <string name=\"settings\">Настройки</string>\n    <string name=\"about\">О приложении</string>\n    <string name=\"help_and_feedback\">Помощь и обратная связь</string>\n    <string name=\"search\">Поиск</string>\n    <string name=\"action_shuffle_all\">Перемешать все</string>\n    <string name=\"action_equalizer\">Эквалайзер</string>\n    <string name=\"search_library\">Искать в библиотеке</string>\n\n    <string name=\"select_theme\">Выбрать тему</string>\n    <string name=\"choose_theme_throughout\">Выбрать тему всего приложения.</string>\n    <string name=\"choose_start_page\">Выбрать стартовую страницу, которая будет отображаться при запуске.</string>\n    <string name=\"start_page\">Стартовая страница</string>\n    <string name=\"artist_in_grid\">Артисты в сетке</string>\n    <string name=\"toggle_artists_grid\">Включить отображение артисов в сетке 2x2</string>\n    <string name=\"toggle_system_animations\">Включить системные анимации во всем приложении.</string>\n    <string name=\"system_animations\">Системные анимации</string>\n    <string name=\"toggle_animations\">Включить анимации в приложении.</string>\n    <string name=\"animations\">Анимации</string>\n    <string name=\"now_playing_selector\">Выбрать тему для экрана проигрывания из 4-х различных стилей.</string>\n    <string name=\"personalisation\">Персонализация</string>\n    <string name=\"other\">Прочее</string>\n    <string name=\"timber_about_opensource\">Timber это музыкальный плеер с открытым исходным кодом который разработан Наманом Двиведи.</string>\n    <string name=\"summary_pause_detach\">Включить автоматическую паузу при отсоединении наушников</string>\n    <string name=\"pause_detach\">Пауза при отсоединении</string>\n    <string name=\"currently_selected\">Сейчас выбрано</string>\n\n    <string name=\"dark_theme\">Темная тема</string>\n    <string name=\"dark_theme_summary\">Применить темную тему ко всему приложению</string>\n\n    <string name=\"primary_color\">Основной цвет</string>\n    <string name=\"primary_color_summary\">Изменить основной цвет темы</string>\n\n    <string name=\"accent_color\">Дополнительный цвет</string>\n    <string name=\"accent_color_summary\">Изменить дополнительный цвет темы</string>\n\n    <string name=\"colored_status_bar\">Раскрасить статус бар</string>\n    <string name=\"colored_status_bar_summary\">Включить раскраску статус бара</string>\n\n    <string name=\"colored_nav_bar\">Раскрасить панель навигации</string>\n    <string name=\"colored_nav_bar_summary\">Включить раскраску панели навигации</string>\n\n    <string name=\"gestures\">Жесты</string>\n    <string name=\"switching_tracks_by_gestures\">Переключение дорожек с помощью жестов</string>\n\n    <string name=\"count_song\"><xliff:g id=\"count\">%d</xliff:g> песня</string>\n    <string name=\"count_songs\"><xliff:g id=\"count\">%d</xliff:g> песни</string>\n    <string name=\"count_album\"><xliff:g id=\"count\">%d</xliff:g> альбом</string>\n    <string name=\"count_albums\"><xliff:g id=\"count\">%d</xliff:g> альбомы</string>\n    <string name=\"count_artist\"><xliff:g id=\"count\">%d</xliff:g> артист</string>\n    <string name=\"count_artists\"><xliff:g id=\"count\">%d</xliff:g> артисты</string>\n\n    <string name=\"number_song_add_queue\"><xliff:g example=\"1\" id=\"number\">%d</xliff:g> песня добавлена в очередь.</string>\n    <string name=\"number_songs_add_queue\"><xliff:g example=\"1\" id=\"number\">%d</xliff:g> песни добавлены в очередь.</string>\n    <string name=\"number_song_add_playlist\"><xliff:g example=\"1\" id=\"number\">%d</xliff:g> песня добавлена в плей-лист.</string>\n    <string name=\"number_songs_add_playlist\"><xliff:g example=\"1\" id=\"number\">%d</xliff:g> песни добавлены в плей-лист.</string>\n\n    <string name=\"add_to_playlist\">Добавить в список</string>\n    <string name=\"add_to_queue\">В очередь</string>\n    <string name=\"advanced\">Дополнительно</string>\n    <string name=\"go_to_album\">Альбом</string>\n    <string name=\"go_to_artist\">Исполнитель</string>\n    <string name=\"new_playlist\">Новый плейлист</string>\n    <string name=\"play\">Воспроизвести</string>\n    <string name=\"play_next\">Воспр. следующим</string>\n\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-tr/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources xmlns:xliff=\"urn:oasis:names:tc:xliff:document:1.2\">\n\n    <string name=\"action_settings\">Ayarlar</string>\n\n    <string name=\"accessibility_play\">Oynat</string>\n    <string name=\"accessibility_pause\">Durdur</string>\n    <string name=\"accessibility_next\">İleri</string>\n    <string name=\"accessibility_prev\">Geri</string>\n    <string name=\"accessibility_shuffle\">Karıştır</string>\n    <string name=\"accessibility_shuffle_all\">Hepsini karıştır</string>\n    <string name=\"accessibility_repeat\">Tekrarla</string>\n    <string name=\"accessibility_repeat_all\">Hepsini tekrar et</string>\n    <string name=\"accessibility_repeat_one\">Tekrarla</string>\n    <string name=\"accessibility_queue\">Sırayla oynat</string>\n    <string name=\"accessibility_browse\">Keşfet</string>\n\n    <string name=\"menu_sort_by\">Sırala</string>\n    <string name=\"sort_order_entry_az\">A-Z</string>\n    <string name=\"sort_order_entry_za\">Z-A</string>\n    <string name=\"sort_order_entry_artist\">Sanatçı</string>\n    <string name=\"sort_order_entry_album\">Albüm</string>\n    <string name=\"sort_order_entry_year\">Yıl</string>\n    <string name=\"sort_order_entry_track_number\">Parça numarası</string>\n    <string name=\"sort_order_entry_duration\">Süre</string>\n    <string name=\"sort_order_entry_date_added\">Eklenme tarihi</string>\n    <string name=\"sort_order_entry_track_list\">Parça listesi</string>\n    <string name=\"sort_order_entry_number_of_songs\">Müziklerin sayısı</string>\n    <string name=\"sort_order_entry_number_of_albums\">Albümlerin sayısı</string>\n    <string name=\"sort_order_entry_filename\">Dosya adı</string>\n\n    <string name=\"error_playing_track\">Parça oynatılırken hata oluştu</string>\n\n    <string name=\"playlist_last_added\">Listeye eklendi</string>\n    <string name=\"playlist_recently_played\">En son oynatılanlar</string>\n    <string name=\"playlist_top_tracks\">En çok dinlediklerim</string>\n\n    <string name=\"timber_source\">Hataları <u> buraya </u> bildirin. </string>\n    <string name=\"timber_feature_request\">Herhangi bir özellik eklenmesini istiyorsan Google+ gurubumuza <u> buradan </u> ulaşabilirsin</string>\n\n    <string name=\"menu_show_as\">Göster</string>\n    <string name=\"menu_show_as_entry_list\">Liste</string>\n    <string name=\"menu_show_as_entry_grid\">Izgara</string>\n\n    <string name=\"light\">Light</string>\n    <string name=\"dark\">Dark</string>\n    <string name=\"black\">Black</string>\n\n    <string name=\"last_opened\">En son açılan</string>\n\n    <string name=\"songs\">Şarkılar</string>\n    <string name=\"albums\">Albümler</string>\n    <string name=\"artists\">Sanatçılar</string>\n    <string name=\"library\">Kütüphane</string>\n    <string name=\"playlists\">Oynatma listeleri</string>\n    <string name=\"playing_queue\">Oynatma sırası</string>\n    <string name=\"now_playing\">Şimdi Oynatılıyor</string>\n    <string name=\"settings\">Ayarlar</string>\n    <string name=\"about\">Hakkında</string>\n    <string name=\"help_and_feedback\">Yardım ve Geribildirim</string>\n    <string name=\"search\">Ayarlar</string>\n    <string name=\"action_shuffle_all\">Hepsini karıştır</string>\n    <string name=\"action_equalizer\">Ekolayzer</string>\n    <string name=\"search_library\">Kütüphanede Ara</string>\n\n    <string name=\"select_theme\">Tema seç</string>\n    <string name=\"choose_theme_throughout\">Bir tema seçin</string>\n    <string name=\"choose_start_page\">Açılış sayfası seçin</string>\n    <string name=\"start_page\">Açılış sayfası</string>\n    <string name=\"artist_in_grid\">Sanatçıların Düzeni</string>\n    <string name=\"toggle_artists_grid\">Sanatçılar 2x2 ızgarada gösterilsin</string>\n    <string name=\"toggle_system_animations\">Sistem animasyonları kullanılsın</string>\n    <string name=\"system_animations\">Sistem animasyonları</string>\n    <string name=\"toggle_animations\">Geçiş animasyonları kullanılsın</string>\n    <string name=\"animations\">Animasyonlar</string>\n    <string name=\"now_playing_selector\">Şimdi oynatılıyor ekranını seçiniz</string>\n    <string name=\"personalisation\">Kişiselleştirme</string>\n    <string name=\"other\">Diğer</string>\n    <string name=\"timber_about_opensource\">Timber  Naman Dwivedi tarafından geliştirilen açık kaynaklı Muzik oynatıcısıdır.</string>\n    <string name=\"summary_pause_detach\">Kulaklığı çıkardığımda çalan şarkıyı durdur</string>\n    <string name=\"pause_detach\">Kulaklık</string>\n    <string name=\"currently_selected\">Seçilenler</string>\n\n    <string name=\"dark_theme\">Dark Tema</string>\n    <string name=\"dark_theme_summary\">Dark temayı kullan</string>\n\n    <string name=\"primary_color\">Birincil Renk</string>\n    <string name=\"primary_color_summary\">Birincil rengi değiştir</string>\n\n    <string name=\"accent_color\">Vurgu Rengi</string>\n    <string name=\"accent_color_summary\">Vurgu rengini değiştir</string>\n\n    <string name=\"colored_status_bar\">Renkli Status Bar</string>\n    <string name=\"colored_status_bar_summary\">Toggle status bar coloring</string>\n\n    <string name=\"colored_nav_bar\">Renkli Navigation Bar</string>\n    <string name=\"colored_nav_bar_summary\">Toggle navigation bar coloring</string>\n\n    <string name=\"count_song\"><xliff:g id=\"count\">%d</xliff:g> şarkı</string>\n    <string name=\"count_songs\"><xliff:g id=\"count\">%d</xliff:g> şarkılar</string>\n    <string name=\"count_album\"><xliff:g id=\"count\">%d</xliff:g> albüm</string>\n    <string name=\"count_albums\"><xliff:g id=\"count\">%d</xliff:g> albümler</string>\n    <string name=\"count_artist\"><xliff:g id=\"count\">%d</xliff:g> sanatçı</string>\n    <string name=\"count_artists\"><xliff:g id=\"count\">%d</xliff:g> sanatçılar</string>\n\n    <string name=\"number_song_add_queue\"><xliff:g example=\"1\" id=\"number\">%d</xliff:g> şarkı sıraya eklendi.</string>\n    <string name=\"number_songs_add_queue\"><xliff:g example=\"1\" id=\"number\">%d</xliff:g> şarkılar sıraya eklendi.</string>\n    <string name=\"number_song_add_playlist\"><xliff:g example=\"1\" id=\"number\">%d</xliff:g> şarkı oynatma listesine eklendi.</string>\n    <string name=\"number_songs_add_playlist\"><xliff:g example=\"1\" id=\"number\">%d</xliff:g> şarkılar oynatma listesine eklendi.</string>\n\n\n    <!--do not translate these strings-->\n    <string name=\"combine_two_strings\" translatable=\"false\">%1$s | %2$s</string>\n    <string name=\"duration_format\" translatable=\"false\"><xliff:g id=\"hours\">%1$s</xliff:g> <xliff:g id=\"minutes\">%2$s</xliff:g></string>\n    <string name=\"durationformatshort\" translatable=\"false\"><xliff:g id=\"format\">%2$d:%3$02d</xliff:g></string>\n    <string name=\"durationformatlong\" translatable=\"false\"><xliff:g id=\"format\">%1$d:%2$02d:%3$02d</xliff:g></string>\n\n</resources>\n\n"
  },
  {
    "path": "app/src/main/res/values-v19/styles.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n    <style name=\"AppTheme.FullScreen.Light\" parent=\"ThemeBaseLight\">\n\n        <!--<item name=\"android:windowTranslucentStatus\">true</item>-->\n        <!--<item name=\"android:windowTranslucentNavigation\">false</item>-->\n    </style>\n\n    <style name=\"AppThemeLight\" parent=\"ThemeBaseLight\">\n\n\n    </style>\n\n    <style name=\"AppTheme.FullScreen.Dark\" parent=\"ThemeBaseDark\">\n\n        <!--<item name=\"android:windowTranslucentStatus\">true</item>-->\n        <!--<item name=\"android:windowTranslucentNavigation\">false</item>-->\n    </style>\n\n    <style name=\"AppThemeDark\" parent=\"ThemeBaseDark\">\n\n\n    </style>\n\n\n\n\n    <style name=\"AppTheme.FullScreen.Black\" parent=\"ThemeBaseBlack\">\n\n        <!--<item name=\"android:windowTranslucentStatus\">true</item>-->\n        <!--<item name=\"android:windowTranslucentNavigation\">false</item>-->\n    </style>\n\n    <style name=\"AppThemeBlack\" parent=\"ThemeBaseBlack\">\n\n\n    </style>\n\n\n\n</resources>"
  },
  {
    "path": "app/src/main/res/values-v21/dimens.xml",
    "content": "<resources>\n        <dimen name=\"statusBarHeight\">24dp</dimen>\n        <dimen name=\"appBarTopMargin\">24dp</dimen>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-v21/styles.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n    <style name=\"AppTheme.FullScreen.Light\" parent=\"ThemeBaseLight\">\n\n        <item name=\"android:windowContentTransitions\">true</item>\n        <item name=\"android:windowTranslucentStatus\">true</item>\n        <item name=\"android:windowTranslucentNavigation\">false</item>\n\n    </style>\n\n    <style name=\"AppThemeLight\" parent=\"ThemeBaseLight\">\n\n\n        <item name=\"colorPrimary\">@color/colorPrimary</item>\n        <item name=\"colorPrimaryDark\">@color/colorPrimaryDark</item>\n        <item name=\"colorAccent\">@color/colorAccent</item>\n        <item name=\"android:windowDrawsSystemBarBackgrounds\">true</item>\n        <item name=\"android:statusBarColor\">@android:color/transparent</item>\n    </style>\n\n    <style name=\"AppTheme.FullScreen.Dark\" parent=\"ThemeBaseDark\">\n\n        <item name=\"android:windowContentTransitions\">true</item>\n        <item name=\"android:windowTranslucentStatus\">true</item>\n        <item name=\"android:windowTranslucentNavigation\">false</item>\n\n    </style>\n\n    <style name=\"AppThemeDark\" parent=\"ThemeBaseDark\">\n\n\n        <item name=\"colorPrimary\">@color/colorPrimaryDarkTheme</item>\n        <item name=\"colorPrimaryDark\">@color/colorPrimaryDarkDarkTheme</item>\n        <item name=\"colorAccent\">@color/colorAccentDarkTheme</item>\n        <item name=\"android:windowDrawsSystemBarBackgrounds\">true</item>\n        <item name=\"android:statusBarColor\">@android:color/transparent</item>\n\n    </style>\n\n\n    <style name=\"AppTheme.FullScreen.Black\" parent=\"ThemeBaseBlack\">\n\n        <item name=\"android:windowContentTransitions\">true</item>\n        <item name=\"android:windowTranslucentStatus\">true</item>\n        <item name=\"android:windowTranslucentNavigation\">false</item>\n\n    </style>\n\n    <style name=\"AppThemeBlack\" parent=\"ThemeBaseBlack\">\n\n\n        <item name=\"colorPrimary\">@color/colorPrimaryBlack</item>\n        <item name=\"colorPrimaryDark\">@color/colorPrimaryDarkBlack</item>\n        <item name=\"colorAccent\">@color/colorAccentBlack</item>\n        <item name=\"android:windowDrawsSystemBarBackgrounds\">true</item>\n        <item name=\"android:statusBarColor\">@android:color/transparent</item>\n\n    </style>\n\n\n\n</resources>"
  },
  {
    "path": "app/src/main/res/values-w820dp/dimens.xml",
    "content": "<resources>\n    <!-- Example customization of dimensions originally defined in res/values/dimens.xml\n         (such as screen margins) for screens with more than 820dp of available width. This\n         would include 7\" and 10\" devices in landscape (~960dp and ~1280dp respectively). -->\n    <dimen name=\"activity_horizontal_margin\">64dp</dimen>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-zh/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources xmlns:xliff=\"urn:oasis:names:tc:xliff:document:1.2\">\n\n    <string name=\"app_name\">Timber</string>\n    <string name=\"action_settings\">设置</string>\n\n    <string name=\"accessibility_play\">播放</string>\n    <string name=\"accessibility_pause\">暂停</string>\n    <string name=\"accessibility_next\">下一首</string>\n    <string name=\"accessibility_prev\">上一首</string>\n    <string name=\"accessibility_shuffle\">随机</string>\n    <string name=\"accessibility_shuffle_all\">全部随机</string>\n    <string name=\"accessibility_repeat\">循环</string>\n    <string name=\"accessibility_repeat_all\">全部循环</string>\n    <string name=\"accessibility_repeat_one\">单曲循环</string>\n    <string name=\"accessibility_queue\">打开队列</string>\n    <string name=\"accessibility_browse\">浏览音乐</string>\n\n    <string name=\"menu_sort_by\">排序</string>\n    <string name=\"sort_order_entry_az\">A-Z</string>\n    <string name=\"sort_order_entry_za\">Z-A</string>\n    <string name=\"sort_order_entry_artist\">艺术家</string>\n    <string name=\"sort_order_entry_album\">专辑</string>\n    <string name=\"sort_order_entry_year\">年份</string>\n    <string name=\"sort_order_entry_track_number\">曲目数</string>\n    <string name=\"sort_order_entry_duration\">时长</string>\n    <string name=\"sort_order_entry_date_added\">日期已添加</string>\n    <string name=\"sort_order_entry_track_list\">曲目列表</string>\n    <string name=\"sort_order_entry_number_of_songs\">歌曲数</string>\n    <string name=\"sort_order_entry_number_of_albums\">专辑数</string>\n    <string name=\"sort_order_entry_filename\">文件名</string>\n\n    <string name=\"error_playing_track\">曲目播放错误</string>\n\n    <string name=\"playlist_last_added\">最新添加</string>\n    <string name=\"playlist_recently_played\">最近播放</string>\n    <string name=\"playlist_top_tracks\">我的最佳曲目</string>\n\n    <string name=\"timber_source\">在 <u>这里</u> 报告漏洞</string>\n    <string name=\"timber_feature_request\">在 <u>这里</u> 通过 Google+ 提出特性请求</string>\n\n    <string name=\"menu_show_as\">视图</string>\n    <string name=\"menu_show_as_entry_list\">列表</string>\n    <string name=\"menu_show_as_entry_grid\">网格</string>\n    <string name=\"menu_show_as_entry_default\">默认</string>\n\n    <string name=\"light\">默认主题</string>\n    <string name=\"dark\">似水年华</string>\n    <string name=\"black\">黑胶唱片</string>\n\n    <string name=\"last_opened\">上次打开</string>\n\n    <string name=\"songs\">歌曲</string>\n    <string name=\"albums\">专辑</string>\n    <string name=\"artists\">艺术家</string>\n    <string name=\"library\">库</string>\n    <string name=\"playlists\">播放列表</string>\n    <string name=\"playing_queue\">播放队列</string>\n    <string name=\"now_playing\">正在播放</string>\n    <string name=\"settings\">设置</string>\n    <string name=\"about\">关于</string>\n    <string name=\"help_and_feedback\">帮助和反馈</string>\n    <string name=\"search\">搜索</string>\n    <string name=\"action_shuffle_all\">全部随机</string>\n    <string name=\"action_equalizer\">均衡器</string>\n    <string name=\"search_library\">在库中搜索</string>\n\n    <string name=\"select_theme\">选择主题</string>\n    <string name=\"choose_theme_throughout\">为整个应用选择一个主题</string>\n    <string name=\"choose_start_page\">选择启动时显示的页面</string>\n    <string name=\"start_page\">起始页</string>\n    <string name=\"artist_in_grid\">网格显示艺术家</string>\n    <string name=\"toggle_artists_grid\">在 2x2 网格中显示艺术家</string>\n    <string name=\"toggle_system_animations\">切换整个应用的系统动画</string>\n    <string name=\"system_animations\">系统动画</string>\n    <string name=\"toggle_animations\">切换整个应用的过渡动画</string>\n    <string name=\"animations\">动画</string>\n    <string name=\"now_playing_selector\">从4中不同的播放界面风格中选择一个</string>\n    <string name=\"personalisation\">个性化</string>\n    <string name=\"other\">其他</string>\n    <string name=\"advanced\">高级</string>\n    <string name=\"timber_about_opensource\">Timber 是 Naman Dwivedi 开发的一款开源的音乐播放器</string>\n    <string name=\"summary_pause_detach\">当耳机拔出时暂停播放</string>\n    <string name=\"pause_detach\">拔出时暂停</string>\n    <string name=\"summary_xposed_trackselector\">需要安装 Xposed 和 Xposed 模块 XMediaNotificationTrackSelector。这将在通知中提供播放队列控制。</string>\n    <string name=\"xposed_trackselector\">通知中的播放队列支持</string>\n    <string name=\"currently_selected\">当前选择</string>\n\n    <string name=\"dark_theme\">夜间模式</string>\n    <string name=\"dark_theme_summary\">应用夜间模式到整个应用</string>\n\n    <string name=\"primary_color\">基础色</string>\n    <string name=\"primary_color_summary\">更改基础主题色</string>\n\n    <string name=\"accent_color\">强调色</string>\n    <string name=\"accent_color_summary\">更改强调主题色</string>\n\n    <string name=\"colored_status_bar\">状态栏着色</string>\n    <string name=\"colored_status_bar_summary\">切换状态栏的着色状态</string>\n\n    <string name=\"colored_nav_bar\">导航栏着色</string>\n    <string name=\"colored_nav_bar_summary\">切换导航栏的着色状态</string>\n\n    <string name=\"albumart_lockscreen\">锁屏</string>\n    <string name=\"albumart_lockscreen_summary\">在锁屏界面显示专辑封面</string>\n\n    <string name=\"load_artist_album_image\">加载艺术家图像</string>\n    <string name=\"load_artist_album_image_summary\">从 Last.fm 加载艺术家图像</string>\n\n    <string name=\"load_artist_album_image_mobile\">从设备数据加载艺术家图像</string>\n    <string name=\"load_artist_album_image_mobile_summary\">无 Wi-Fi 时也能加载艺术家图像</string>\n\n    <string name=\"gestures\">手势</string>\n    <string name=\"switching_tracks_by_gestures\">使用手势切换曲目</string>\n    <string name=\"widget_standard\">Timber 标准</string>\n    <string name=\"widget_small\">Timber 小</string>\n    <string name=\"tap_to_start\">单击打开 Timber</string>\n    <string name=\"widget_white\">Timber 亮</string>\n\n    <string name=\"count_song\"><xliff:g id=\"count\">%d</xliff:g> 首歌曲</string>\n    <string name=\"count_songs\"><xliff:g id=\"count\">%d</xliff:g> 首歌曲</string>\n    <string name=\"count_album\"><xliff:g id=\"count\">%d</xliff:g> 张专辑</string>\n    <string name=\"count_albums\"><xliff:g id=\"count\">%d</xliff:g> 张专辑</string>\n    <string name=\"count_artist\"><xliff:g id=\"count\">%d</xliff:g> 位艺术家</string>\n    <string name=\"count_artists\"><xliff:g id=\"count\">%d</xliff:g> 位艺术家</string>\n\n    <string name=\"number_song_add_queue\">已将 <xliff:g example=\"1\" id=\"number\">%d</xliff:g> 首歌曲添加到队列</string>\n    <string name=\"number_songs_add_queue\">已将 <xliff:g example=\"1\" id=\"number\">%d</xliff:g> 首歌曲添加到队列</string>\n    <string name=\"number_song_add_playlist\">已将 <xliff:g example=\"1\" id=\"number\">%d</xliff:g> 首歌曲添加到播放列表</string>\n    <string name=\"number_songs_add_playlist\">已将 <xliff:g example=\"1\" id=\"number\">%d</xliff:g> 首歌曲添加到播放列表</string>\n\n    <string name=\"play\">播放</string>\n    <string name=\"add_to_playlist\">添加到播放列表</string>\n    <string name=\"go_to_album\">转到专辑</string>\n    <string name=\"go_to_artist\">转到艺术家</string>\n    <string name=\"add_to_queue\">添加到队列</string>\n    <string name=\"play_next\">下一首播放</string>\n    <string name=\"new_playlist\">新的播放列表</string>\n\n\n    <string name=\"nothing_found\">没有结果</string>\n    <string name=\"folders\">文件夹</string>\n    <string name=\"select_storage\">选择存储器</string>\n\n    <string name=\"lastfm_pref\">登录 Last.fm 以上传偏好数据</string>\n    <string name=\"lastfm_login_failture\">登录失败</string>\n    <string name=\"lastfm_login\">登录 Last.fm</string>\n    <string name=\"lastfm_loged_in\">已登录为 %s</string>\n\n    <string name=\"cancel\">取消</string>\n\n</resources>\n"
  },
  {
    "path": "app/src/main/res/xml/preferences.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<PreferenceScreen xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n    <com.naman14.timber.widgets.ThemedPreferenceCategory android:title=\"@string/now_playing\">\n\n        <Preference\n            android:key=\"now_playing_selector\"\n            android:summary=\"@string/now_playing_selector\"\n            android:title=\"@string/now_playing\" />\n\n    </com.naman14.timber.widgets.ThemedPreferenceCategory>\n\n    <com.naman14.timber.widgets.ThemedPreferenceCategory android:title=\"@string/personalisation\">\n\n        <com.afollestad.appthemeengine.prefs.ATECheckBoxPreference\n            android:defaultValue=\"false\"\n            android:key=\"dark_theme\"\n            android:persistent=\"true\"\n            android:summary=\"@string/dark_theme_summary\"\n            android:title=\"@string/dark_theme\"\n            android:widgetLayout=\"@layout/ate_preference_checkbox\"\n            app:ateKey_pref_checkBox=\"?ate_key\" />\n\n        <com.afollestad.appthemeengine.prefs.ATEColorPreference\n            android:key=\"primary_color\"\n            android:persistent=\"false\"\n            android:summary=\"@string/primary_color_summary\"\n            android:title=\"@string/primary_color\"\n            app:ateKey_pref_color=\"?ate_key\" />\n\n        <com.afollestad.appthemeengine.prefs.ATEColorPreference\n            android:key=\"accent_color\"\n            android:persistent=\"false\"\n            android:summary=\"@string/accent_color_summary\"\n            android:title=\"@string/accent_color\"\n            app:ateKey_pref_color=\"?ate_key\" />\n\n        <com.afollestad.appthemeengine.prefs.ATECheckBoxPreference\n            android:defaultValue=\"true\"\n            android:key=\"colored_status_bar\"\n            android:persistent=\"false\"\n            android:summary=\"@string/colored_status_bar_summary\"\n            android:title=\"@string/colored_status_bar\"\n            app:ateKey_pref_checkBox=\"?ate_key\" />\n\n        <com.afollestad.appthemeengine.prefs.ATECheckBoxPreference\n            android:defaultValue=\"true\"\n            android:key=\"colored_nav_bar\"\n            android:persistent=\"false\"\n            android:summary=\"@string/colored_nav_bar_summary\"\n            android:title=\"@string/colored_nav_bar\"\n            app:ateKey_pref_checkBox=\"?ate_key\" />\n\n    </com.naman14.timber.widgets.ThemedPreferenceCategory>\n\n    <com.naman14.timber.widgets.ThemedPreferenceCategory android:title=\"@string/other\">\n\n        <com.afollestad.appthemeengine.prefs.ATEListPreference\n            android:defaultValue=\"last_opened\"\n            android:entries=\"@array/pref_startpage_entries\"\n            android:entryValues=\"@array/pref_startpage_values\"\n            android:key=\"start_page_preference\"\n            android:summary=\"@string/choose_start_page\"\n            android:title=\"@string/start_page\"\n            app:ateKey_pref_checkBox=\"?ate_key\" />\n\n        <com.afollestad.appthemeengine.prefs.ATECheckBoxPreference\n            android:defaultValue=\"true\"\n            android:key=\"gestures\"\n            android:summary=\"@string/switching_tracks_by_gestures\"\n            android:title=\"@string/gestures\"\n            app:ateKey_pref_checkBox=\"?ate_key\" />\n\n        <com.afollestad.appthemeengine.prefs.ATECheckBoxPreference\n            android:defaultValue=\"true\"\n            android:key=\"toggle_headphone_pause\"\n            android:summary=\"@string/summary_pause_detach\"\n            android:title=\"@string/pause_detach\"\n            app:ateKey_pref_checkBox=\"?ate_key\" />\n\n        <com.afollestad.appthemeengine.prefs.ATECheckBoxPreference\n            android:defaultValue=\"true\"\n            android:key=\"show_albumart_lockscreen\"\n            android:summary=\"@string/albumart_lockscreen_summary\"\n            android:title=\"@string/albumart_lockscreen\"\n            app:ateKey_pref_checkBox=\"?ate_key\" />\n\n    </com.naman14.timber.widgets.ThemedPreferenceCategory>\n\n    <com.naman14.timber.widgets.ThemedPreferenceCategory android:title=\"LastFM\">\n\n        <Preference\n            android:key=\"lastfm_login\"\n            android:summary=\"@string/lastfm_pref\"\n            android:title=\"Login\" />\n\n        <com.afollestad.appthemeengine.prefs.ATECheckBoxPreference\n            android:defaultValue=\"true\"\n            android:key=\"artist_album_image\"\n            android:summary=\"@string/load_artist_album_image_summary\"\n            android:title=\"@string/load_artist_album_image\"\n            app:ateKey_pref_checkBox=\"?ate_key\" />\n\n        <com.afollestad.appthemeengine.prefs.ATECheckBoxPreference\n            android:dependency=\"artist_album_image\"\n            android:defaultValue=\"true\"\n            android:key=\"artist_album_image_mobile\"\n            android:summary=\"@string/load_artist_album_image_mobile_summary\"\n            android:title=\"@string/load_artist_album_image_mobile\"\n            app:ateKey_pref_checkBox=\"?ate_key\" />\n\n        <com.afollestad.appthemeengine.prefs.ATECheckBoxPreference\n            android:dependency=\"artist_album_image\"\n            android:defaultValue=\"false\"\n            android:key=\"always_load_album_images_lastfm\"\n            android:summary=\"@string/always_load_album_images_lastfm_summary\"\n            android:title=\"@string/always_load_album_images_lastfm\"\n            app:ateKey_pref_checkBox=\"?ate_key\" />\n    </com.naman14.timber.widgets.ThemedPreferenceCategory>\n\n    <com.naman14.timber.widgets.ThemedPreferenceCategory android:title=\"@string/advanced\">\n\n        <com.afollestad.appthemeengine.prefs.ATECheckBoxPreference\n            android:defaultValue=\"false\"\n            android:key=\"toggle_xposed_trackselector\"\n            android:summary=\"@string/summary_xposed_trackselector\"\n            android:title=\"@string/xposed_trackselector\"\n            app:ateKey_pref_checkBox=\"?ate_key\" />\n\n    </com.naman14.timber.widgets.ThemedPreferenceCategory>\n\n    <com.naman14.timber.widgets.ThemedPreferenceCategory android:title=\"Support\">\n\n        <Preference\n            android:key=\"support_development\"\n            android:summary=\"Support the development of Timber and unlock all now playing styles\"\n            android:title=\"Support development\" />\n\n        <Preference\n            android:key=\"restore_purchases\"\n            android:summary=\"Restore previous purchases\"\n            android:title=\"Restore purchases\" />\n\n    </com.naman14.timber.widgets.ThemedPreferenceCategory>\n\n\n</PreferenceScreen>"
  },
  {
    "path": "app/src/main/res/xml/widget_small.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<appwidget-provider xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:minWidth=\"110dp\"\n    android:minHeight=\"40dp\"\n    android:updatePeriodMillis=\"86400000\"\n    android:previewImage=\"@drawable/widget_small\"\n    android:resizeMode=\"horizontal|vertical\"\n    android:initialLayout=\"@layout/widget_small\" />"
  },
  {
    "path": "app/src/main/res/xml/widget_standard.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<appwidget-provider xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:minWidth=\"250dp\"\n    android:minHeight=\"40dp\"\n    android:updatePeriodMillis=\"86400000\"\n    android:previewImage=\"@drawable/widget_standard\"\n    android:resizeMode=\"horizontal|vertical\"\n    android:initialLayout=\"@layout/widget_standard\" />"
  },
  {
    "path": "app/src/main/res/xml/widget_white.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<appwidget-provider xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:minWidth=\"250dp\"\n    android:minHeight=\"40dp\"\n    android:updatePeriodMillis=\"86400000\"\n    android:previewImage=\"@drawable/widget_white\"\n    android:resizeMode=\"horizontal|vertical\"\n    android:initialLayout=\"@layout/widget_white\" />"
  },
  {
    "path": "build.gradle",
    "content": "// Top-level build file where you can add configuration options common to all sub-projects/modules.\n\nbuildscript {\n    repositories {\n        jcenter()\n        google()\n    }\n    dependencies {\n        classpath 'com.android.tools.build:gradle:3.3.1'\n        classpath 'com.google.gms:google-services:4.2.0'\n        classpath 'com.google.firebase:firebase-crashlytics-gradle:2.3.0'\n        // NOTE: Do not place your application dependencies here; they belong\n        // in the individual module build.gradle files\n    }\n}\n\nallprojects {\n    repositories {\n        jcenter()\n        google()\n        maven { url \"https://jitpack.io\" }\n    }\n}\n\next {\n    // Sdk and tools\n    minSdkVersion = 16\n    targetSdkVersion = 28\n    compileSdkVersion = 28\n\n    // App dependencies\n    supportLibraryVersion = '27.0.2'\n    retrofitVersion = '2.3.0'\n    okHttp3Version = '3.9.0'\n}\n\n"
  },
  {
    "path": "gradle/wrapper/gradle-wrapper.properties",
    "content": "distributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-4.10.1-all.zip\n"
  },
  {
    "path": "gradle.properties",
    "content": "# Project-wide Gradle settings.\n\n# IDE (e.g. Android Studio) users:\n# Gradle settings configured through the IDE *will override*\n# any settings specified in this file.\n\n# For more details on how to configure your build environment visit\n# http://www.gradle.org/docs/current/userguide/build_environment.html\n\n# Specifies the JVM arguments used for the daemon process.\n# The setting is particularly useful for tweaking memory settings.\n# Default value: -Xmx10248m -XX:MaxPermSize=256m\n# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8\n\n# When configured, Gradle will run in incubating parallel mode.\n# This option should only be used with decoupled projects. More details, visit\n# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects\n# org.gradle.parallel=true\nandroid.enableJetifier=true\nandroid.useAndroidX=true"
  },
  {
    "path": "gradlew",
    "content": "#!/usr/bin/env bash\n\n##############################################################################\n##\n##  Gradle start up script for UN*X\n##\n##############################################################################\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS=\"\"\n\nAPP_NAME=\"Gradle\"\nAPP_BASE_NAME=`basename \"$0\"`\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=\"maximum\"\n\nwarn ( ) {\n    echo \"$*\"\n}\n\ndie ( ) {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n}\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\ncase \"`uname`\" in\n  CYGWIN* )\n    cygwin=true\n    ;;\n  Darwin* )\n    darwin=true\n    ;;\n  MINGW* )\n    msys=true\n    ;;\nesac\n\n# For Cygwin, ensure paths are in UNIX format before anything is touched.\nif $cygwin ; then\n    [ -n \"$JAVA_HOME\" ] && JAVA_HOME=`cygpath --unix \"$JAVA_HOME\"`\nfi\n\n# Attempt to set APP_HOME\n# Resolve links: $0 may be a link\nPRG=\"$0\"\n# Need this for relative symlinks.\nwhile [ -h \"$PRG\" ] ; do\n    ls=`ls -ld \"$PRG\"`\n    link=`expr \"$ls\" : '.*-> \\(.*\\)$'`\n    if expr \"$link\" : '/.*' > /dev/null; then\n        PRG=\"$link\"\n    else\n        PRG=`dirname \"$PRG\"`\"/$link\"\n    fi\ndone\nSAVED=\"`pwd`\"\ncd \"`dirname \\\"$PRG\\\"`/\" >&-\nAPP_HOME=\"`pwd -P`\"\ncd \"$SAVED\" >&-\n\nCLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n        # IBM's JDK on AIX uses strange locations for the executables\n        JAVACMD=\"$JAVA_HOME/jre/sh/java\"\n    else\n        JAVACMD=\"$JAVA_HOME/bin/java\"\n    fi\n    if [ ! -x \"$JAVACMD\" ] ; then\n        die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nelse\n    JAVACMD=\"java\"\n    which java >/dev/null 2>&1 || die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\nfi\n\n# Increase the maximum file descriptors if we can.\nif [ \"$cygwin\" = \"false\" -a \"$darwin\" = \"false\" ] ; then\n    MAX_FD_LIMIT=`ulimit -H -n`\n    if [ $? -eq 0 ] ; then\n        if [ \"$MAX_FD\" = \"maximum\" -o \"$MAX_FD\" = \"max\" ] ; then\n            MAX_FD=\"$MAX_FD_LIMIT\"\n        fi\n        ulimit -n $MAX_FD\n        if [ $? -ne 0 ] ; then\n            warn \"Could not set maximum file descriptor limit: $MAX_FD\"\n        fi\n    else\n        warn \"Could not query maximum file descriptor limit: $MAX_FD_LIMIT\"\n    fi\nfi\n\n# For Darwin, add options to specify how the application appears in the dock\nif $darwin; then\n    GRADLE_OPTS=\"$GRADLE_OPTS \\\"-Xdock:name=$APP_NAME\\\" \\\"-Xdock:icon=$APP_HOME/media/gradle.icns\\\"\"\nfi\n\n# For Cygwin, switch paths to Windows format before running java\nif $cygwin ; then\n    APP_HOME=`cygpath --path --mixed \"$APP_HOME\"`\n    CLASSPATH=`cygpath --path --mixed \"$CLASSPATH\"`\n\n    # We build the pattern for arguments to be converted via cygpath\n    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`\n    SEP=\"\"\n    for dir in $ROOTDIRSRAW ; do\n        ROOTDIRS=\"$ROOTDIRS$SEP$dir\"\n        SEP=\"|\"\n    done\n    OURCYGPATTERN=\"(^($ROOTDIRS))\"\n    # Add a user-defined pattern to the cygpath arguments\n    if [ \"$GRADLE_CYGPATTERN\" != \"\" ] ; then\n        OURCYGPATTERN=\"$OURCYGPATTERN|($GRADLE_CYGPATTERN)\"\n    fi\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    i=0\n    for arg in \"$@\" ; do\n        CHECK=`echo \"$arg\"|egrep -c \"$OURCYGPATTERN\" -`\n        CHECK2=`echo \"$arg\"|egrep -c \"^-\"`                                 ### Determine if an option\n\n        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition\n            eval `echo args$i`=`cygpath --path --ignore --mixed \"$arg\"`\n        else\n            eval `echo args$i`=\"\\\"$arg\\\"\"\n        fi\n        i=$((i+1))\n    done\n    case $i in\n        (0) set -- ;;\n        (1) set -- \"$args0\" ;;\n        (2) set -- \"$args0\" \"$args1\" ;;\n        (3) set -- \"$args0\" \"$args1\" \"$args2\" ;;\n        (4) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" ;;\n        (5) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" ;;\n        (6) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" ;;\n        (7) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" ;;\n        (8) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" ;;\n        (9) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" \"$args8\" ;;\n    esac\nfi\n\n# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules\nfunction splitJvmOpts() {\n    JVM_OPTS=(\"$@\")\n}\neval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS\nJVM_OPTS[${#JVM_OPTS[*]}]=\"-Dorg.gradle.appname=$APP_BASE_NAME\"\n\nexec \"$JAVACMD\" \"${JVM_OPTS[@]}\" -classpath \"$CLASSPATH\" org.gradle.wrapper.GradleWrapperMain \"$@\"\n"
  },
  {
    "path": "gradlew.bat",
    "content": "@if \"%DEBUG%\" == \"\" @echo off\r\n@rem ##########################################################################\r\n@rem\r\n@rem  Gradle startup script for Windows\r\n@rem\r\n@rem ##########################################################################\r\n\r\n@rem Set local scope for the variables with windows NT shell\r\nif \"%OS%\"==\"Windows_NT\" setlocal\r\n\r\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\r\nset DEFAULT_JVM_OPTS=\r\n\r\nset DIRNAME=%~dp0\r\nif \"%DIRNAME%\" == \"\" set DIRNAME=.\r\nset APP_BASE_NAME=%~n0\r\nset APP_HOME=%DIRNAME%\r\n\r\n@rem Find java.exe\r\nif defined JAVA_HOME goto findJavaFromJavaHome\r\n\r\nset JAVA_EXE=java.exe\r\n%JAVA_EXE% -version >NUL 2>&1\r\nif \"%ERRORLEVEL%\" == \"0\" goto init\r\n\r\necho.\r\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\r\necho.\r\necho Please set the JAVA_HOME variable in your environment to match the\r\necho location of your Java installation.\r\n\r\ngoto fail\r\n\r\n:findJavaFromJavaHome\r\nset JAVA_HOME=%JAVA_HOME:\"=%\r\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\r\n\r\nif exist \"%JAVA_EXE%\" goto init\r\n\r\necho.\r\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%\r\necho.\r\necho Please set the JAVA_HOME variable in your environment to match the\r\necho location of your Java installation.\r\n\r\ngoto fail\r\n\r\n:init\r\n@rem Get command-line arguments, handling Windowz variants\r\n\r\nif not \"%OS%\" == \"Windows_NT\" goto win9xME_args\r\nif \"%@eval[2+2]\" == \"4\" goto 4NT_args\r\n\r\n:win9xME_args\r\n@rem Slurp the command line arguments.\r\nset CMD_LINE_ARGS=\r\nset _SKIP=2\r\n\r\n:win9xME_args_slurp\r\nif \"x%~1\" == \"x\" goto execute\r\n\r\nset CMD_LINE_ARGS=%*\r\ngoto execute\r\n\r\n:4NT_args\r\n@rem Get arguments from the 4NT Shell from JP Software\r\nset CMD_LINE_ARGS=%$\r\n\r\n:execute\r\n@rem Setup the command line\r\n\r\nset CLASSPATH=%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\r\n\r\n@rem Execute Gradle\r\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -classpath \"%CLASSPATH%\" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%\r\n\r\n:end\r\n@rem End local scope for the variables with windows NT shell\r\nif \"%ERRORLEVEL%\"==\"0\" goto mainEnd\r\n\r\n:fail\r\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\r\nrem the _cmd.exe /c_ return code!\r\nif  not \"\" == \"%GRADLE_EXIT_CONSOLE%\" exit 1\r\nexit /b 1\r\n\r\n:mainEnd\r\nif \"%OS%\"==\"Windows_NT\" endlocal\r\n\r\n:omega\r\n"
  },
  {
    "path": "mock/mock-google-services.json",
    "content": "{\n  \"project_info\": {\n    \"project_number\": \"123456789000\",\n    \"firebase_url\": \"https://mockproject-1234.firebaseio.com\",\n    \"project_id\": \"mockproject-1234\",\n    \"storage_bucket\": \"mockproject-1234.appspot.com\"\n  },\n  \"client\": [\n    {\n      \"client_info\": {\n        \"mobilesdk_app_id\": \"1:123456789000:android:f1bf012572b04063\",\n        \"android_client_info\": {\n          \"package_name\": \"naman14.timber\"\n        }\n      },\n      \"oauth_client\": [\n        {\n          \"client_id\": \"123456789000-hjugbg6ud799v4c49dim8ce2usclthar.apps.googleusercontent.com\",\n          \"client_type\": 3\n        }\n      ],\n      \"api_key\": [\n        {\n          \"current_key\": \"AIzbSzCn1N6LWIe6wthYyrgUUSAlUsdqMb-wvTo\"\n        }\n      ],\n      \"services\": {\n        \"analytics_service\": {\n          \"status\": 1\n        },\n        \"appinvite_service\": {\n          \"status\": 1,\n          \"other_platform_oauth_client\": []\n        },\n        \"ads_service\": {\n          \"status\": 2\n        }\n      }\n    }\n  ],\n  \"configuration_version\": \"1\"\n}\n"
  },
  {
    "path": "mock/mock-secret.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  ~ copied to app/src/main/res/values/secret.xml\n  ~\n  ~ replace with your actual cast app id\n  ~\n  -->\n\n<resources>\n    <string name=\"cast_app_id\" translatable=\"false\">123456</string>\n    <string name=\"play_billing_license_key\" translatable=\"false\">123456</string>\n</resources>\n"
  },
  {
    "path": "mock.gradle",
    "content": "// This script must be applied in app/build.gradle for the paths here to work correctly\n\ndef copyMockFilesNeeded() {\n    def srcGoogleServicesFile = file(\"../mock/mock-google-services.json\")\n    def srcCastSecretFile = file(\"../mock/mock-secret.xml\")\n\n    def destGoogleServicesFile = file(\"google-services.json\")\n    def destCastSecretFile = file(\"src/main/res/values/secret.xml\")\n\n    if (!destGoogleServicesFile.exists()) {\n        destGoogleServicesFile.write(srcGoogleServicesFile.text)\n    }\n    if (!destCastSecretFile.exists()) {\n        destCastSecretFile.write(srcCastSecretFile.text)\n    }\n}\n\nafterEvaluate {\n    copyMockFilesNeeded()\n}"
  },
  {
    "path": "settings.gradle",
    "content": "include ':app'\n"
  }
]